diff options
Diffstat (limited to 'nuttx/drivers')
115 files changed, 58867 insertions, 0 deletions
diff --git a/nuttx/drivers/Makefile b/nuttx/drivers/Makefile new file mode 100644 index 000000000..b76a04a8c --- /dev/null +++ b/nuttx/drivers/Makefile @@ -0,0 +1,110 @@ +############################################################################ +# drivers/Makefile +# +# Copyright (C) 2007-2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +-include $(TOPDIR)/Make.defs + +ifeq ($(WINTOOL),y) +INCDIROPT = -w +endif + +DEPPATH = --dep-path . +ASRCS = +CSRCS = +VPATH = . + +# Include support for various drivers. Each Make.defs file will add its +# files to the source file list, add its DEPPATH info, and will add +# the appropriate paths to the VPATH variable + +include analog/Make.defs +include bch/Make.defs +include input/Make.defs +include lcd/Make.defs +include mmcsd/Make.defs +include mtd/Make.defs +include net/Make.defs +include pipes/Make.defs +include pm/Make.defs +include sensors/Make.defs +include serial/Make.defs +include usbdev/Make.defs +include usbhost/Make.defs +include wireless/Make.defs + +ifneq ($(CONFIG_NFILE_DESCRIPTORS),0) + CSRCS += dev_null.c dev_zero.c loop.c can.c +ifneq ($(CONFIG_DISABLE_MOUNTPOINT),y) + CSRCS += ramdisk.c rwbuffer.c +endif +ifeq ($(CONFIG_PWM),y) + CSRCS += pwm.c +endif +endif + +AOBJS = $(ASRCS:.S=$(OBJEXT)) +COBJS = $(CSRCS:.c=$(OBJEXT)) + +SRCS = $(ASRCS) $(CSRCS) +OBJS = $(AOBJS) $(COBJS) + +BIN = libdrivers$(LIBEXT) + +all: $(BIN) + +$(AOBJS): %$(OBJEXT): %.S + $(call ASSEMBLE, $<, $@) + +$(COBJS): %$(OBJEXT): %.c + $(call COMPILE, $<, $@) + +$(BIN): $(OBJS) + @( for obj in $(OBJS) ; do \ + $(call ARCHIVE, $@, $${obj}); \ + done ; ) + +.depend: Makefile $(SRCS) + @$(MKDEP) $(DEPPATH) $(CC) -- $(CFLAGS) -- $(SRCS) >Make.dep + @touch $@ + +depend: .depend + +clean: + @rm -f $(BIN) *~ .*.swp + $(call CLEAN) + +distclean: clean + @rm -f Make.dep .depend + +-include Make.dep diff --git a/nuttx/drivers/README.txt b/nuttx/drivers/README.txt new file mode 100644 index 000000000..e5a1483b2 --- /dev/null +++ b/nuttx/drivers/README.txt @@ -0,0 +1,117 @@ +README +^^^^^^ + +This directory contains various device drivers -- both block and +character drivers as well as other more specialized drivers. + +Contents: + - Files in this directory + - Subdirectories of this directory + - Skeleton files + +Files in this directory +^^^^^^^^^^^^^^^^^^^^^^^ + +can.c + An unfinished CAN driver. + +dev_null.c and dev_zero.c + These files provide the standard /dev/null and /dev/zero devices. + See include/nuttx/fs.h for functions that should be called if you + want to register these devices (devnull_register() and + devzero_register()). + +loop.c + Supports the standard loop device that can be used to export a + file (or character device) as a block device. See losetup() and + loteardown() in include/nuttx/fs.h. + +ramdisk.c + Can be used to set up a block of memory or (read-only) FLASH as + a block driver that can be mounted as a files system. See + include/nuttx/ramdisk.h. + +rwbuffer.c + A facility that can be use by any block driver in-order to add + writing buffering and read-ahead buffering. + +Subdirectories of this directory: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +analog/ + This directory holds implementations of analog device drivers. + This includes drivers for Analog to Digital Conversion (ADC) as + well as drivers for Digital to Analog Conversion (DAC). + See include/nuttx/analog/*.h for registration information. + +bch/ + Contains logic that may be used to convert a block driver into + a character driver. This is the complementary conversion as that + performed by loop.c. See include/nuttx/fs.h for registration + information. + +analog/ + This directory holds implementations of input device drivers. + This includes such things as touchscreen and keypad drivers. + See include/nuttx/input/*.h for registration information. + +lcd/ + Drivers for parallel and serial LCD and OLED type devices. These + drivers support interfaces as defined in include/nuttx/lcd/lcd.h + +mmcsd/ + Support for MMC/SD block drivers. MMC/SD block drivers based on + SPI and SDIO/MCI interfaces are supported. See include/nuttx/mmcsd.h + and include/nuttx/sdio.h for further information. + +mtd/ + Memory Technology Device (MTD) drivers. Some simple drivers for + memory technologies like FLASH, EEPROM, NVRAM, etc. See + include/nuttx/mtd.h + + (Note: This is a simple memory interface and should not be + confused with the "real" MTD developed at infradead.org. This + logic is unrelated; I just used the name MTD because I am not + aware of any other common way to refer to this class of devices). + +net/ + Network interface drivers. See also include/nuttx/net.h + +pipes/ + FIFO and named pipe drivers. Standard interfaces are declared + in include/unistd.h + +pm/ + Power management (PM) driver interfaces. These interfaces are used + to manage power usage of a platform by monitoring driver activity + and by placing drivers into reduce power usage modes when the + drivers are not active. + +sensors/ + Drivers for various sensors + +serial/ + Front-end character drivers for chip-specific UARTs. This provide + some TTY-like functionality and are commonly used (but not required for) + the NuttX system console. See also include/nuttx/serial.h + +usbdev/ + USB device drivers. See also include/nuttx/usb/usbdev.h + +usbhost/ + USB host drivers. See also include/nuttx/usb/usbhost.h + +wireless/ + Drivers for various wireless devices. + +Skeleton Files +^^^^^^^^^^^^^^ + +Skeleton files a "empty" frameworks for NuttX drivers. They are provided to +give you a good starting point if you want to create a new NuttX driver. +The following skeleton files are available: + + drivers/lcd/skeleton.c -- Skeleton LCD driver + drivers/mtd/skeleton.c -- Skeleton memory technology device drivers + drivers/net/skeleton.c -- Skeleton network/Ethernet drivers + drivers/usbhost/usbhost_skeleton.c -- Skeleton USB host class driver diff --git a/nuttx/drivers/analog/Make.defs b/nuttx/drivers/analog/Make.defs new file mode 100644 index 000000000..8c868a84f --- /dev/null +++ b/nuttx/drivers/analog/Make.defs @@ -0,0 +1,81 @@ +############################################################################ +# drivers/analog/Make.defs +# +# Copyright (C) 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Don't build anything if there is no support for analog devices + +# Check for DAC devices + +ifeq ($(CONFIG_DAC),y) + +# Include the common DAC character driver + +CSRCS += dac.c + +# Include DAC device drivers + +ifeq ($(CONFIG_DAC_AD5410),y) + CSRCS += ad5410.c +endif +endif + +# Check for ADC devices + +ifeq ($(CONFIG_ADC),y) + +# Include the common ADC character driver + +CSRCS += adc.c + +# Include ADC device drivers + +ifeq ($(CONFIG_ADC_ADS125X),y) + CSRCS += ads1255.c +endif +endif + +# Add analog driver build support (the nested if-then-else implements an OR) + +ifeq ($(CONFIG_DAC),y) + DEPPATH += --dep-path analog + VPATH += :analog + CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/analog} +else +ifeq ($(CONFIG_ADC),y) + DEPPATH += --dep-path analog + VPATH += :analog + CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/analog} +endif +endif + diff --git a/nuttx/drivers/analog/ad5410.c b/nuttx/drivers/analog/ad5410.c new file mode 100644 index 000000000..65e4adf7d --- /dev/null +++ b/nuttx/drivers/analog/ad5410.c @@ -0,0 +1,206 @@ +/************************************************************************************ + * arch/drivers/analog/ad5410.c + * + * Copyright (C) 2011 Li Zhuoyi. All rights reserved. + * Author: Li Zhuoyi <lzyy.cn@gmail.com> + * History: 0.1 2011-08-05 initial version + * + * This file is a part of NuttX: + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <semaphore.h> +#include <errno.h> +#include <debug.h> + +#include <arch/board/board.h> +#include <nuttx/arch.h> +#include <nuttx/analog/dac.h> +#include <nuttx/spi.h> +#include "lpc17_internal.h" + +#if defined(CONFIG_DAC_AD5410) + +#define AD5410_REG_NOP 0x00 +#define AD5410_REG_WR 0x01 +#define AD5410_REG_RD 0x02 +#define AD5410_REG_CMD 0x55 +#define AD5410_REG_RST 0x56 + +#define AD5410_CMD_REXT (1<<13) +#define AD5410_CMD_OUTEN (1<<12) +#define AD5410_CMD_SRCLK(x) (x<<8) +#define AD5410_CMD_SRSTEP(x) (x<<5) +#define AD5410_CMD_SREN (1<<4) +#define AD5410_CMD_DCEN (1<<3) +#define AD5410_CMD_420MA 0x05 +#define AD5410_CMD_020MA 0x06 +#define AD5410_CMD_024MA 0x07 + +/**************************************************************************** + * ad_private Types + ****************************************************************************/ +struct up_dev_s +{ + int devno; + FAR struct spi_dev_s *spi; /* Cached SPI device reference */ +}; + +/**************************************************************************** + * ad_private Function Prototypes + ****************************************************************************/ + +/* DAC methods */ + +static void dac_reset(FAR struct dac_dev_s *dev); +static int dac_setup(FAR struct dac_dev_s *dev); +static void dac_shutdown(FAR struct dac_dev_s *dev); +static void dac_txint(FAR struct dac_dev_s *dev, bool enable); +static int dac_send(FAR struct dac_dev_s *dev, FAR struct dac_msg_s *msg); +static int dac_ioctl(FAR struct dac_dev_s *dev, int cmd, unsigned long arg); +static int dac_interrupt(int irq, void *context); + +/**************************************************************************** + * ad_private Data + ****************************************************************************/ + +static const struct dac_ops_s g_dacops = +{ + .ao_reset =dac_reset, + .ao_setup = dac_setup, + .ao_shutdown = dac_shutdown, + .ao_txint = dac_txint, + .ao_send = dac_send, + .ao_ioctl = dac_ioctl, +}; + +static struct up_dev_s g_dacpriv; +static struct dac_dev_s g_dacdev = +{ + .ad_ops = &g_dacops, + .ad_priv= &g_dacpriv, +}; + +/**************************************************************************** + * ad_private Functions + ****************************************************************************/ + +/* Reset the DAC device. Called early to initialize the hardware. This +* is called, before ao_setup() and on error conditions. +*/ +static void dac_reset(FAR struct dac_dev_s *dev) +{ + +} + +/* Configure the DAC. This method is called the first time that the DAC +* device is opened. This will occur when the port is first opened. +* This setup includes configuring and attaching DAC interrupts. Interrupts +* are all disabled upon return. +*/ +static int dac_setup(FAR struct dac_dev_s *dev) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)dev->ad_priv; + FAR struct spi_dev_s *spi = priv->spi; + SPI_SELECT(spi, priv->devno, true); + SPI_SEND(spi,AD5410_REG_CMD); + SPI_SEND(spi,(AD5410_CMD_OUTEN|AD5410_CMD_420MA)>>8); + SPI_SEND(spi,AD5410_CMD_OUTEN|AD5410_CMD_420MA); + SPI_SELECT(spi, priv->devno, false); + return OK; +} + +/* Disable the DAC. This method is called when the DAC device is closed. +* This method reverses the operation the setup method. +*/ +static void dac_shutdown(FAR struct dac_dev_s *dev) +{ +} + +/* Call to enable or disable TX interrupts */ +static void dac_txint(FAR struct dac_dev_s *dev, bool enable) +{ +} + +static int dac_send(FAR struct dac_dev_s *dev, FAR struct dac_msg_s *msg) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)dev->ad_priv; + FAR struct spi_dev_s *spi = priv->spi; + SPI_SELECT(spi, priv->devno, true); + SPI_SEND(spi,AD5410_REG_WR); + SPI_SEND(spi,(uint8_t)(msg->am_data>>24)); + SPI_SEND(spi,(uint8_t)(msg->am_data>>16)); + SPI_SELECT(spi, priv->devno, false); + dac_txdone(&g_dacdev); + return 0; +} + +/* All ioctl calls will be routed through this method */ +static int dac_ioctl(FAR struct dac_dev_s *dev, int cmd, unsigned long arg) +{ + dbg("Fix me:Not Implemented\n"); + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_ad5410cinitialize + * + * Description: + * Initialize the selected DAC port + * + * Input Parameter: + * Port number (for hardware that has mutiple DAC interfaces) + * + * Returned Value: + * Valid ad5410 device structure reference on succcess; a NULL on failure + * + ****************************************************************************/ + +FAR struct dac_dev_s *up_ad5410initialize(FAR struct spi_dev_s *spi, unsigned int devno) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)g_dacdev.ad_priv; + priv->spi=spi; + priv->devno=devno; + return &g_dacdev; +} +#endif + diff --git a/nuttx/drivers/analog/adc.c b/nuttx/drivers/analog/adc.c new file mode 100644 index 000000000..ab374f973 --- /dev/null +++ b/nuttx/drivers/analog/adc.c @@ -0,0 +1,420 @@ +/**************************************************************************** + * drivers/analog/adc.c + * + * Copyright (C) 2011 Li Zhuoyi. All rights reserved. + * Author: Li Zhuoyi <lzyy.cn@gmail.com> + * History: 0.1 2011-08-04 initial version + * + * Derived from drivers/can.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <semaphore.h> +#include <fcntl.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/arch.h> +#include <nuttx/analog/adc.h> + +#include <arch/irq.h> + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int adc_open(FAR struct file *filep); +static int adc_close(FAR struct file *filep); +static ssize_t adc_read(FAR struct file *, FAR char *, size_t); +static ssize_t adc_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); +static int adc_ioctl(FAR struct file *filep,int cmd,unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations adc_fops = +{ + adc_open, /* open */ + adc_close, /* close */ + adc_read, /* read */ + adc_write, /* write */ + 0, /* seek */ + adc_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , 0 /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +/************************************************************************************ + * Name: adc_open + * + * Description: + * This function is called whenever the ADC device is opened. + * + ************************************************************************************/ + +static int adc_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adc_dev_s *dev = inode->i_private; + uint8_t tmp; + int ret = OK; + + /* If the port is the middle of closing, wait until the close is finished */ + + if (sem_wait(&dev->ad_closesem) != OK) + { + ret = -errno; + } + else + { + /* Increment the count of references to the device. If this the first + * time that the driver has been opened for this device, then initialize + * the device. + */ + + tmp = dev->ad_ocount + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + } + else + { + /* Check if this is the first time that the driver has been opened. */ + + if (tmp == 1) + { + /* Yes.. perform one time hardware initialization. */ + + irqstate_t flags = irqsave(); + ret = dev->ad_ops->ao_setup(dev); + if (ret == OK) + { + /* Mark the FIFOs empty */ + + dev->ad_recv.af_head = 0; + dev->ad_recv.af_tail = 0; + + /* Finally, Enable the CAN RX interrupt */ + + dev->ad_ops->ao_rxint(dev, true); + + /* Save the new open count on success */ + + dev->ad_ocount = tmp; + } + irqrestore(flags); + } + } + sem_post(&dev->ad_closesem); + } + return ret; +} + +/************************************************************************************ + * Name: adc_close + * + * Description: + * This routine is called when the ADC device is closed. + * It waits for the last remaining data to be sent. + * + ************************************************************************************/ + +static int adc_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adc_dev_s *dev = inode->i_private; + irqstate_t flags; + int ret = OK; + + if (sem_wait(&dev->ad_closesem) != OK) + { + ret = -errno; + } + else + { + /* Decrement the references to the driver. If the reference count will + * decrement to 0, then uninitialize the driver. + */ + + if (dev->ad_ocount > 1) + { + dev->ad_ocount--; + sem_post(&dev->ad_closesem); + } + else + { + /* There are no more references to the port */ + + dev->ad_ocount = 0; + + /* Free the IRQ and disable the ADC device */ + + flags = irqsave(); /* Disable interrupts */ + dev->ad_ops->ao_shutdown(dev); /* Disable the ADC */ + irqrestore(flags); + + sem_post(&dev->ad_closesem); + } + } + return ret; +} + +/**************************************************************************** + * Name: adc_read + ****************************************************************************/ + +static ssize_t adc_read(FAR struct file *filep, FAR char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adc_dev_s *dev = inode->i_private; + size_t nread; + irqstate_t flags; + int ret = 0; + int msglen; + + avdbg("buflen: %d\n", (int)buflen); + + if (buflen % 5 == 0) + msglen = 5; + else if (buflen % 4 == 0) + msglen = 4; + else if (buflen % 3 == 0) + msglen = 3; + else if (buflen % 2 == 0) + msglen = 2; + else if (buflen == 1) + msglen = 1; + else + msglen = 5; + + if (buflen >= msglen) + { + /* Interrupts must be disabled while accessing the ad_recv FIFO */ + + flags = irqsave(); + while (dev->ad_recv.af_head == dev->ad_recv.af_tail) + { + /* The receive FIFO is empty -- was non-blocking mode selected? */ + + if (filep->f_oflags & O_NONBLOCK) + { + ret = -EAGAIN; + goto return_with_irqdisabled; + } + + /* Wait for a message to be received */ + + dev->ad_nrxwaiters++; + ret = sem_wait(&dev->ad_recv.af_sem); + dev->ad_nrxwaiters--; + if (ret < 0) + { + ret = -errno; + goto return_with_irqdisabled; + } + } + + /* The ad_recv FIFO is not empty. Copy all buffered data that will fit + * in the user buffer. + */ + + nread = 0; + do + { + FAR struct adc_msg_s *msg = &dev->ad_recv.af_buffer[dev->ad_recv.af_head]; + + /* Will the next message in the FIFO fit into the user buffer? */ + + if (ret + msglen > buflen) + { + break; + } + + /* Copy the message to the user buffer */ + + if (msglen == 1) + { + /* Only one channel,read highest 8-bits */ + + buffer[nread] = msg->am_data >> 24; + } + else if (msglen == 2) + { + /* Only one channel, read highest 16-bits */ + + *(int16_t *)&buffer[nread] = msg->am_data >> 16; + } + else if (msglen == 3) + { + /* Read channel highest 16-bits */ + + buffer[nread] = msg->am_channel; + *(int16_t *)&buffer[nread + 1] = msg->am_data >> 16; + } + else if (msglen == 4) + { + /* read channel highest 24-bits */ + + *(int32_t *)&buffer[nread] = msg->am_data; + buffer[nread] = msg->am_channel; + } + else + { + /* Read all */ + + *(int32_t *)&buffer[nread + 1] = msg->am_data; + buffer[nread] = msg->am_channel; + } + nread += msglen; + + /* Increment the head of the circular message buffer */ + + if (++dev->ad_recv.af_head >= CONFIG_ADC_FIFOSIZE) + { + dev->ad_recv.af_head = 0; + } + } + while (dev->ad_recv.af_head != dev->ad_recv.af_tail); + + /* All on the messages have bee transferred. Return the number of bytes + * that were read. + */ + + ret = nread; + +return_with_irqdisabled: + irqrestore(flags); + } + + avdbg("Returning: %d\n", ret); + return ret; +} + +/************************************************************************************ + * Name: adc_write + ************************************************************************************/ + +static ssize_t adc_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) +{ + return 0; +} + +/************************************************************************************ + * Name: adc_ioctl + ************************************************************************************/ + +static int adc_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adc_dev_s *dev = inode->i_private; + int ret = OK; + + ret = dev->ad_ops->ao_ioctl(dev, cmd, arg); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int adc_receive(FAR struct adc_dev_s *dev, uint8_t ch, int32_t data) +{ + FAR struct adc_fifo_s *fifo = &dev->ad_recv; + int nexttail; + int err = -ENOMEM; + + /* Check if adding this new message would over-run the drivers ability to enqueue + * read data. + */ + + nexttail = fifo->af_tail + 1; + if (nexttail >= CONFIG_ADC_FIFOSIZE) + { + nexttail = 0; + } + + /* Refuse the new data if the FIFO is full */ + + if (nexttail != fifo->af_head) + { + /* Add the new, decoded CAN message at the tail of the FIFO */ + + fifo->af_buffer[fifo->af_tail].am_channel = ch; + fifo->af_buffer[fifo->af_tail].am_data = data; + + /* Increment the tail of the circular buffer */ + + fifo->af_tail = nexttail; + + if (dev->ad_nrxwaiters > 0) + { + sem_post(&fifo->af_sem); + } + err = OK; + } + return err; +} + +int adc_register(FAR const char *path, FAR struct adc_dev_s *dev) +{ + /* Initialize the ADC device structure */ + + dev->ad_ocount = 0; + + sem_init(&dev->ad_recv.af_sem, 0, 0); + sem_init(&dev->ad_closesem, 0, 1); + + dev->ad_ops->ao_reset(dev); + + return register_driver(path, &adc_fops, 0444, dev); +} + diff --git a/nuttx/drivers/analog/ads1255.c b/nuttx/drivers/analog/ads1255.c new file mode 100644 index 000000000..374decc54 --- /dev/null +++ b/nuttx/drivers/analog/ads1255.c @@ -0,0 +1,335 @@ +/************************************************************************************ + * arch/drivers/analog/ads1255.c + * + * Copyright (C) 2011 Li Zhuoyi. All rights reserved. + * Author: Li Zhuoyi <lzyy.cn@gmail.com> + * History: 0.1 2011-08-05 initial version + * 0.2 2011-08-25 fix bug in g_adcdev (cd_ops -> ad_ops,cd_priv -> ad_priv) + * + * This file is a part of NuttX: + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <semaphore.h> +#include <errno.h> +#include <debug.h> + +#include <arch/board/board.h> +#include <nuttx/arch.h> +#include <nuttx/spi.h> +#include <nuttx/analog/adc.h> + +#if defined(CONFIG_ADC_ADS1255) + +#define ADS125X_BUFON 0x02 +#define ADS125X_BUFOFF 0x00 + +#define ADS125X_PGA1 0x00 +#define ADS125X_PGA2 0x01 +#define ADS125X_PGA4 0x02 +#define ADS125X_PGA8 0x03 +#define ADS125X_PGA16 0x04 +#define ADS125X_PGA32 0x05 +#define ADS125X_PGA64 0x06 + +#define ADS125X_RDATA 0x01 //Read Data +#define ADS125X_RDATAC 0x03 //Read Data Continuously +#define ADS125X_SDATAC 0x0F //Stop Read Data Continuously +#define ADS125X_RREG 0x10 //Read from REG +#define ADS125X_WREG 0x50 //Write to REG +#define ADS125X_SELFCAL 0xF0 //Offset and Gain Self-Calibration +#define ADS125X_SELFOCAL 0xF1 //Offset Self-Calibration +#define ADS125X_SELFGCAL 0xF2 //Gain Self-Calibration +#define ADS125X_SYSOCAL 0xF3 //System Offset Calibration +#define ADS125X_SYSGCAL 0xF4 //System Gain Calibration +#define ADS125X_SYNC 0xFC //Synchronize the A/D Conversion +#define ADS125X_STANDBY 0xFD //Begin Standby Mode +#define ADS125X_RESET 0xFE //Reset to Power-Up Values +#define ADS125X_WAKEUP 0xFF //Completes SYNC and Exits Standby Mode + +#ifndef CONFIG_ADS1255_FREQUENCY +#define CONFIG_ADS1255_FREQUENCY 1000000 +#endif +#ifndef CONFIG_ADS1255_MUX +#define CONFIG_ADS1255_MUX 0x01 +#endif +#ifndef CONFIG_ADS1255_CHMODE +#define CONFIG_ADS1255_CHMODE 0x00 +#endif +#ifndef CONFIG_ADS1255_BUFON +#define CONFIG_ADS1255_BUFON 1 +#endif +#ifndef CONFIG_ADS1255_PGA +#define CONFIG_ADS1255_PGA ADS125X_PGA2 +#endif +#ifndef CONFIG_ADS1255_SPS +#define CONFIG_ADS1255_SPS 50 +#endif + +/**************************************************************************** + * ad_private Types + ****************************************************************************/ + +struct up_dev_s +{ + uint8_t channel; + uint32_t sps; + uint8_t pga; + uint8_t buf; + const uint8_t *mux; + int irq; + int devno; + FAR struct spi_dev_s *spi; /* Cached SPI device reference */ +}; + +/**************************************************************************** + * ad_private Function Prototypes + ****************************************************************************/ + +/* ADC methods */ + +static void adc_reset(FAR struct adc_dev_s *dev); +static int adc_setup(FAR struct adc_dev_s *dev); +static void adc_shutdown(FAR struct adc_dev_s *dev); +static void adc_rxint(FAR struct adc_dev_s *dev, bool enable); +static int adc_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg); +static int adc_interrupt(int irq, void *context); + +/**************************************************************************** + * ad_private Data + ****************************************************************************/ + +static const struct adc_ops_s g_adcops = +{ + .ao_reset = adc_reset, /* ao_reset */ + .ao_setup = adc_setup, /* ao_setup */ + .ao_shutdown = adc_shutdown, /* ao_shutdown */ + .ao_rxint = adc_rxint, /* ao_rxint */ + .ao_ioctl = adc_ioctl /* ao_read */ +}; + +static struct up_dev_s g_adcpriv = +{ + .mux = (const uint8_t []) + { + CONFIG_ADS1255_MUX,0 + }, + .sps = CONFIG_ADS1255_SPS, + .channel = 0, + .irq = CONFIG_ADS1255_IRQ, +}; + +static struct adc_dev_s g_adcdev = +{ + .ad_ops = &g_adcops, + .ad_priv= &g_adcpriv, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t getspsreg(uint16_t sps) +{ + static const unsigned short sps_tab[]= + { + 3,7,12,20,27,40,55,80,300,750,1500,3000,5000,10000,20000,65535, + }; + static const unsigned char sps_reg[]= + { + 0x03,0x13,0x23,0x33,0x43,0x53,0x63,0x72,0x82,0x92,0xa1,0xb0,0xc0,0xd0,0xe0,0xf0, + }; + int i; + for (i=0; i<16; i++) + { + if (sps<sps_tab[i]) + break; + } + return sps_reg[i]; +} + +/**************************************************************************** + * ad_private Functions + ****************************************************************************/ +/* Reset the ADC device. Called early to initialize the hardware. This +* is called, before ao_setup() and on error conditions. +*/ + +static void adc_reset(FAR struct adc_dev_s *dev) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)dev->ad_priv; + FAR struct spi_dev_s *spi = priv->spi; + + SPI_SETMODE(spi, SPIDEV_MODE1); + SPI_SETBITS(spi, 8); + SPI_SETFREQUENCY(spi, CONFIG_ADS1255_FREQUENCY); + usleep(1000); + SPI_SELECT(spi, priv->devno, true); + SPI_SEND(spi,ADS125X_WREG+0x03); //WRITE SPS REG + SPI_SEND(spi,0x00); //count=1 + SPI_SEND(spi,0x63); + SPI_SELECT(spi, priv->devno, false); +} + +/* Configure the ADC. This method is called the first time that the ADC +* device is opened. This will occur when the port is first opened. +* This setup includes configuring and attaching ADC interrupts. Interrupts +* are all disabled upon return. +*/ + +static int adc_setup(FAR struct adc_dev_s *dev) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)dev->ad_priv; + FAR struct spi_dev_s *spi = priv->spi; + int ret = irq_attach(priv->irq, adc_interrupt); + if (ret == OK) + { + SPI_SELECT(spi, priv->devno, true); + SPI_SEND(spi,ADS125X_WREG); //WRITE REG from 0 + SPI_SEND(spi,0x03); //count=4+1 + if (priv->buf) + SPI_SEND(spi,ADS125X_BUFON); //REG0 STATUS BUFFER ON + else + SPI_SEND(spi,ADS125X_BUFOFF); + SPI_SEND(spi,priv->mux[0]); + SPI_SEND(spi,priv->pga); //REG2 ADCON PGA=2 + SPI_SEND(spi,getspsreg(priv->sps)); + usleep(1000); + SPI_SEND(spi,ADS125X_SELFCAL); + SPI_SELECT(spi, priv->devno, false); + up_enable_irq(priv->irq); + } + return ret; +} + +/* Disable the ADC. This method is called when the ADC device is closed. +* This method reverses the operation the setup method. +*/ + +static void adc_shutdown(FAR struct adc_dev_s *dev) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)dev->ad_priv; + up_disable_irq(priv->irq); + irq_detach(priv->irq); +} + +/* Call to enable or disable RX interrupts */ + +static void adc_rxint(FAR struct adc_dev_s *dev, bool enable) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)dev->ad_priv; + if (enable) + up_enable_irq(priv->irq); + else + up_disable_irq(priv->irq); +} + +/* All ioctl calls will be routed through this method */ + +static int adc_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg) +{ + dbg("Fix me:Not Implemented\n"); + return 0; +} + +static int adc_interrupt(int irq, void *context) +{ + uint32_t regval; + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)g_adcdev.ad_priv; + FAR struct spi_dev_s *spi = priv->spi; + unsigned char buf[4]; + unsigned char ch; + + SPI_SELECT(spi, priv->devno, true); + SPI_SEND(spi,ADS125X_RDATA); + up_udelay(10); + buf[3]=SPI_SEND(spi,0xff); + buf[2]=SPI_SEND(spi,0xff); + buf[1]=SPI_SEND(spi,0xff); + buf[0]=0; + + priv->channel++; + ch = priv->mux[priv->channel]; + if ( ch == 0 ) + { + priv->channel=0; + ch = priv->mux[0]; + } + + SPI_SEND(spi,ADS125X_WREG+0x01); + SPI_SEND(spi,0x00); + SPI_SEND(spi,ch); + SPI_SEND(spi,ADS125X_SYNC); + up_udelay(2); + SPI_SEND(spi,ADS125X_WAKEUP); + SPI_SELECT(spi, priv->devno, false); + + adc_receive(&g_adcdev,priv->channel,*(int32_t *)buf); + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_ads1255initialize + * + * Description: + * Initialize the selected adc port + * + * Input Parameter: + * Port number (for hardware that has mutiple adc interfaces) + * + * Returned Value: + * Valid can device structure reference on succcess; a NULL on failure + * + ****************************************************************************/ + +FAR struct adc_dev_s *up_ads1255initialize(FAR struct spi_dev_s *spi, unsigned int devno) +{ + FAR struct up_dev_s *priv = (FAR struct up_dev_s *)g_adcdev.ad_priv; + + /* Driver state data */ + + priv->spi = spi; + priv->devno = devno; + return &g_adcdev; +} +#endif + diff --git a/nuttx/drivers/analog/dac.c b/nuttx/drivers/analog/dac.c new file mode 100644 index 000000000..b283a5bf5 --- /dev/null +++ b/nuttx/drivers/analog/dac.c @@ -0,0 +1,499 @@ +/**************************************************************************** + * drivers/analog/dac.c + * + * Copyright (C) 2011 Li Zhuoyi. All rights reserved. + * Author: Li Zhuoyi <lzyy.cn@gmail.com> + * History: 0.1 2011-08-04 initial version + * + * Derived from drivers/can.c + * + * Copyright (C) 2008-2009Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <semaphore.h> +#include <fcntl.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/arch.h> +#include <nuttx/analog/dac.h> + +#include <arch/irq.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define HALF_SECOND_MSEC 500 +#define HALF_SECOND_USEC 500000L + + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int dac_open(FAR struct file *filep); +static int dac_close(FAR struct file *filep); +static ssize_t dac_read(FAR struct file *, FAR char *, size_t); +static ssize_t dac_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); +static int dac_ioctl(FAR struct file *filep,int cmd,unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations dac_fops = +{ + dac_open, + dac_close, + dac_read, + dac_write, + 0, + dac_ioctl +#ifndef CONFIG_DISABLE_POLL + , 0 +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +/************************************************************************************ + * Name: dac_open + * + * Description: + * This function is called whenever the DAC device is opened. + * + ************************************************************************************/ + +static int dac_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct dac_dev_s *dev = inode->i_private; + uint8_t tmp; + int ret = OK; + + /* If the port is the middle of closing, wait until the close is finished */ + + if (sem_wait(&dev->ad_closesem) != OK) + { + ret = -errno; + } + else + { + /* Increment the count of references to the device. If this the first + * time that the driver has been opened for this device, then initialize + * the device. + */ + + tmp = dev->ad_ocount + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + } + else + { + /* Check if this is the first time that the driver has been opened. */ + + if (tmp == 1) + { + /* Yes.. perform one time hardware initialization. */ + + irqstate_t flags = irqsave(); + ret = dev->ad_ops->ao_setup(dev); + if (ret == OK) + { + /* Mark the FIFOs empty */ + + dev->ad_xmit.af_head = 0; + dev->ad_xmit.af_tail = 0; + + /* Save the new open count on success */ + + dev->ad_ocount = tmp; + } + irqrestore(flags); + } + } + sem_post(&dev->ad_closesem); + } + return ret; +} + +/************************************************************************************ + * Name: dac_close + * + * Description: + * This routine is called when the DAC device is closed. + * It waits for the last remaining data to be sent. + * + ************************************************************************************/ + +static int dac_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct dac_dev_s *dev = inode->i_private; + irqstate_t flags; + int ret = OK; + + if (sem_wait(&dev->ad_closesem) != OK) + { + ret = -errno; + } + else + { + /* Decrement the references to the driver. If the reference count will + * decrement to 0, then uninitialize the driver. + */ + + if (dev->ad_ocount > 1) + { + dev->ad_ocount--; + sem_post(&dev->ad_closesem); + } + else + { + /* There are no more references to the port */ + + dev->ad_ocount = 0; + + /* Now we wait for the transmit FIFO to clear */ + + while (dev->ad_xmit.af_head != dev->ad_xmit.af_tail) + { +#ifndef CONFIG_DISABLE_SIGNALS + usleep(HALF_SECOND_USEC); +#else + up_mdelay(HALF_SECOND_MSEC); +#endif + } + + /* Free the IRQ and disable the DAC device */ + + flags = irqsave(); /* Disable interrupts */ + dev->ad_ops->ao_shutdown(dev); /* Disable the DAC */ + irqrestore(flags); + + sem_post(&dev->ad_closesem); + } + } + return ret; +} + +/**************************************************************************** + * Name: dac_read + ****************************************************************************/ + +static ssize_t dac_read(FAR struct file *filep, FAR char *buffer, size_t buflen) +{ + return 0; +} + +/************************************************************************************ + * Name: dac_xmit + * + * Description: + * Send the message at the head of the ad_xmit FIFO + * + * Assumptions: + * Called with interrupts disabled + * + ************************************************************************************/ + +static int dac_xmit(FAR struct dac_dev_s *dev) +{ + bool enable = false; + int ret = OK; + + /* Check if the xmit FIFO is empty */ + + if (dev->ad_xmit.af_head != dev->ad_xmit.af_tail) + { + /* Send the next message at the head of the FIFO */ + + ret = dev->ad_ops->ao_send(dev, &dev->ad_xmit.af_buffer[dev->ad_xmit.af_head]); + + /* Make sure the TX done interrupts are enabled */ + + enable = (ret == OK ? true : false); + } + dev->ad_ops->ao_txint(dev, enable); + return ret; +} + +/************************************************************************************ + * Name: dac_write + ************************************************************************************/ + +static ssize_t dac_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct dac_dev_s *dev = inode->i_private; + FAR struct dac_fifo_s *fifo = &dev->ad_xmit; + FAR struct dac_msg_s *msg; + bool empty = false; + ssize_t nsent = 0; + irqstate_t flags; + int nexttail; + int msglen; + int ret = 0; + + /* Interrupts must disabled throughout the following */ + + flags = irqsave(); + + /* Check if the TX FIFO was empty when we started. That is a clue that we have + * to kick off a new TX sequence. + */ + + empty = (fifo->af_head == fifo->af_tail); + + /* Add the messages to the FIFO. Ignore any trailing messages that are + * shorter than the minimum. + */ + + if (buflen % 5 ==0 ) + msglen=5; + else if (buflen % 4 ==0 ) + msglen=4; + else if (buflen % 3 ==0 ) + msglen=3; + else if (buflen % 2 ==0 ) + msglen=2; + else if (buflen == 1) + msglen=1; + else + msglen=5; + + while ((buflen - nsent) >= msglen ) + { + /* Check if adding this new message would over-run the drivers ability to enqueue + * xmit data. + */ + + nexttail = fifo->af_tail + 1; + if (nexttail >= CONFIG_DAC_FIFOSIZE) + { + nexttail = 0; + } + + /* If the XMIT fifo becomes full, then wait for space to become available */ + + while (nexttail == fifo->af_head) + { + /* The transmit FIFO is full -- was non-blocking mode selected? */ + + if (filep->f_oflags & O_NONBLOCK) + { + if (nsent == 0) + { + ret = -EAGAIN; + } + else + { + ret = nsent; + } + goto return_with_irqdisabled; + } + + /* If the FIFO was empty when we started, then we will have + * start the XMIT sequence to clear the FIFO. + */ + + if (empty) + { + dac_xmit(dev); + } + + /* Wait for a message to be sent */ + + do + { + ret = sem_wait(&fifo->af_sem); + if (ret < 0 && errno != EINTR) + { + ret = -errno; + goto return_with_irqdisabled; + } + } + while (ret < 0); + + /* Re-check the FIFO state */ + + empty = (fifo->af_head == fifo->af_tail); + } + + /* We get here if there is space at the end of the FIFO. Add the new + * CAN message at the tail of the FIFO. + */ + + if (msglen==5) + { + msg = (FAR struct dac_msg_s *)&buffer[nsent]; + memcpy(&fifo->af_buffer[fifo->af_tail], msg, msglen); + } + else if(msglen == 4) + { + fifo->af_buffer[fifo->af_tail].am_channel=buffer[nsent]; + fifo->af_buffer[fifo->af_tail].am_data=*(uint32_t *)&buffer[nsent]; + fifo->af_buffer[fifo->af_tail].am_data&=0xffffff00; + } + else if(msglen == 3) + { + fifo->af_buffer[fifo->af_tail].am_channel=buffer[nsent]; + fifo->af_buffer[fifo->af_tail].am_data=(*(uint16_t *)&buffer[nsent+1]); + fifo->af_buffer[fifo->af_tail].am_data<<=16; + } + else if(msglen == 2) + { + fifo->af_buffer[fifo->af_tail].am_channel=0; + fifo->af_buffer[fifo->af_tail].am_data=(*(uint16_t *)&buffer[nsent]); + fifo->af_buffer[fifo->af_tail].am_data<<=16; + } + else if(msglen == 1) + { + fifo->af_buffer[fifo->af_tail].am_channel=0; + fifo->af_buffer[fifo->af_tail].am_data=buffer[nsent]; + fifo->af_buffer[fifo->af_tail].am_data<<=24; + } + /* Increment the tail of the circular buffer */ + + fifo->af_tail = nexttail; + + /* Increment the number of bytes that were sent */ + + nsent += msglen; + } + + /* We get here after all messages have been added to the FIFO. Check if + * we need to kick of the XMIT sequence. + */ + + if (empty) + { + dac_xmit(dev); + } + + /* Return the number of bytes that were sent */ + + ret = nsent; + +return_with_irqdisabled: + irqrestore(flags); + return ret; +} + +/************************************************************************************ + * Name: dac_ioctl + ************************************************************************************/ + +static int dac_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct dac_dev_s *dev = inode->i_private; + int ret = OK; + + ret = dev->ad_ops->ao_ioctl(dev, cmd, arg); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/************************************************************************************ + * Name: dac_txdone + * + * Description: + * Called from the DAC interrupt handler at the completion of a send operation. + * + * Return: + * OK on success; a negated errno on failure. + * + ************************************************************************************/ + +int dac_txdone(FAR struct dac_dev_s *dev) +{ + int ret = -ENOENT; + + /* Verify that the xmit FIFO is not empty */ + + if (dev->ad_xmit.af_head != dev->ad_xmit.af_tail) + { + /* Remove the message at the head of the xmit FIFO */ + + if (++dev->ad_xmit.af_head >= CONFIG_DAC_FIFOSIZE) + { + dev->ad_xmit.af_head = 0; + } + + /* Send the next message in the FIFO */ + + ret = dac_xmit(dev); + if (ret == OK) + { + /* Inform any waiting threads that new xmit space is available */ + + ret = sem_post(&dev->ad_xmit.af_sem); + } + } + return ret; +} + +int dac_register(FAR const char *path, FAR struct dac_dev_s *dev) +{ + /* Initialize the DAC device structure */ + + dev->ad_ocount = 0; + + sem_init(&dev->ad_xmit.af_sem, 0, 0); + sem_init(&dev->ad_closesem, 0, 1); + + dev->ad_ops->ao_reset(dev); + + return register_driver(path, &dac_fops, 0555, dev); +} + diff --git a/nuttx/drivers/bch/Make.defs b/nuttx/drivers/bch/Make.defs new file mode 100644 index 000000000..8dc36b8c4 --- /dev/null +++ b/nuttx/drivers/bch/Make.defs @@ -0,0 +1,52 @@ +############################################################################ +# drivers/bch/Make.defs +# +# Copyright (C) 2008, 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +ifneq ($(CONFIG_NFILE_DESCRIPTORS),0) +ifneq ($(CONFIG_DISABLE_MOUNTPOINT),y) + +# Include BCH driver + +CSRCS += bchlib_setup.c bchlib_teardown.c bchlib_read.c bchlib_write.c \ + bchlib_cache.c bchlib_sem.c bchdev_register.c bchdev_unregister.c \ + bchdev_driver.c + +# Include BCH driver build support + +DEPPATH += --dep-path bch +VPATH += :bch +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/bch} + +endif +endif diff --git a/nuttx/drivers/bch/bch_internal.h b/nuttx/drivers/bch/bch_internal.h new file mode 100644 index 000000000..c24eeebbd --- /dev/null +++ b/nuttx/drivers/bch/bch_internal.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * drivers/bch/bch_internal.h + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __FS_BCH_INTERNAL_H +#define __FS_BCH_INTERNAL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <semaphore.h> +#include <nuttx/fs.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define bchlib_semgive(d) sem_post(&(d)->sem) /* To match bchlib_semtake */ +#define MAX_OPENCNT (255) /* Limit of uint8_t */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct bchlib_s +{ + struct inode *inode; /* I-node of the block driver */ + sem_t sem; /* For atomic accesses to this structure */ + size_t nsectors; /* Number of sectors supported by the device */ + size_t sector; /* The current sector in the buffer */ + uint16_t sectsize; /* The size of one sector on the device */ + uint8_t refs; /* Number of references */ + bool dirty; /* Data has been written to the buffer */ + bool readonly; /* true: Only read operations are supported */ + FAR uint8_t *buffer; /* One sector buffer */ +}; + +/**************************************************************************** + * Global Variables + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +EXTERN const struct file_operations bch_fops; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +EXTERN void bchlib_semtake(FAR struct bchlib_s *bch); +EXTERN int bchlib_flushsector(FAR struct bchlib_s *bch); +EXTERN int bchlib_readsector(FAR struct bchlib_s *bch, size_t sector); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __FS_BCH_INTERNAL_H */ diff --git a/nuttx/drivers/bch/bchdev_driver.c b/nuttx/drivers/bch/bchdev_driver.c new file mode 100644 index 000000000..c57a5cd31 --- /dev/null +++ b/nuttx/drivers/bch/bchdev_driver.c @@ -0,0 +1,255 @@ +/**************************************************************************** + * drivers/bch/bchdev_driver.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Compilation Switches + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sched.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/ioctl.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int bch_open(FAR struct file *filp); +static int bch_close(FAR struct file *filp); +static ssize_t bch_read(FAR struct file *, FAR char *, size_t); +static ssize_t bch_write(FAR struct file *, FAR const char *, size_t); +static int bch_ioctl(FAR struct file *filp, int cmd, unsigned long arg); + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct file_operations bch_fops = +{ + bch_open, /* open */ + bch_close, /* close */ + bch_read, /* read */ + bch_write, /* write */ + 0, /* seek */ + bch_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , 0 /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bch_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int bch_open(FAR struct file *filp) +{ + FAR struct inode *inode = filp->f_inode; + FAR struct bchlib_s *bch; + + DEBUGASSERT(inode && inode->i_private); + bch = (FAR struct bchlib_s *)inode->i_private; + + /* Increment the reference count */ + + bchlib_semtake(bch); + if (bch->refs == MAX_OPENCNT) + { + return -EMFILE; + } + else + { + bch->refs++; + } + bchlib_semgive(bch); + + return OK; +} + +/**************************************************************************** + * Name: bch_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int bch_close(FAR struct file *filp) +{ + FAR struct inode *inode = filp->f_inode; + FAR struct bchlib_s *bch; + int ret = OK; + + DEBUGASSERT(inode && inode->i_private); + bch = (FAR struct bchlib_s *)inode->i_private; + + /* Flush any dirty pages remaining in the cache */ + + bchlib_semtake(bch); + (void)bchlib_flushsector(bch); + + /* Decrement the reference count (I don't use bchlib_decref() because I + * want the entire close operation to be atomic wrt other driver operations. + */ + + if (bch->refs == 0) + { + ret = -EIO; + } + else + { + bch->refs--; + } + bchlib_semgive(bch); + + return ret; +} + +/**************************************************************************** + * Name:bch_read + ****************************************************************************/ + +static ssize_t bch_read(FAR struct file *filp, FAR char *buffer, size_t len) +{ + FAR struct inode *inode = filp->f_inode; + FAR struct bchlib_s *bch; + int ret; + + DEBUGASSERT(inode && inode->i_private); + bch = (FAR struct bchlib_s *)inode->i_private; + + bchlib_semtake(bch); + ret = bchlib_read(bch, buffer, filp->f_pos, len); + if (ret > 0) + { + filp->f_pos += len; + } + bchlib_semgive(bch); + return ret; +} + +/**************************************************************************** + * Name:bch_write + ****************************************************************************/ + +static ssize_t bch_write(FAR struct file *filp, FAR const char *buffer, size_t len) +{ + FAR struct inode *inode = filp->f_inode; + FAR struct bchlib_s *bch; + int ret = -EACCES; + + DEBUGASSERT(inode && inode->i_private); + bch = (FAR struct bchlib_s *)inode->i_private; + + if (!bch->readonly) + { + bchlib_semtake(bch); + ret = bchlib_write(bch, buffer, filp->f_pos, len); + if (ret > 0) + { + filp->f_pos += len; + } + bchlib_semgive(bch); + } + + return ret; +} + +/**************************************************************************** + * Name: bch_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int bch_ioctl(FAR struct file *filp, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filp->f_inode; + FAR struct bchlib_s *bch; + int ret = -ENOTTY; + + DEBUGASSERT(inode && inode->i_private); + bch = (FAR struct bchlib_s *)inode->i_private; + + if (cmd == DIOC_GETPRIV) + { + FAR struct bchlib_s **bchr = (FAR struct bchlib_s **)((uintptr_t)arg); + + bchlib_semtake(bch); + if (!bchr && bch->refs < 255) + { + ret = -EINVAL; + } + else + { + bch->refs++; + *bchr = bch; + } + bchlib_semgive(bch); + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ diff --git a/nuttx/drivers/bch/bchdev_register.c b/nuttx/drivers/bch/bchdev_register.c new file mode 100644 index 000000000..5bbedb6b9 --- /dev/null +++ b/nuttx/drivers/bch/bchdev_register.c @@ -0,0 +1,103 @@ +/**************************************************************************** + * drivers/bch/bchdev_register.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bchdev_register + * + * Description: + * Setup so that it exports the block driver referenced by + * 'blkdev' as a character device 'chardev' + * + ****************************************************************************/ + +int bchdev_register(const char *blkdev, const char *chardev, bool readonly) +{ + FAR void *handle; + int ret; + + /* Setup the BCH lib functions */ + + ret = bchlib_setup(blkdev, readonly, &handle); + if (ret < 0) + { + fdbg("bchlib_setup failed: %d\n", -ret); + return ret; + } + + /* Then setup the character device */ + + ret = register_driver(chardev, &bch_fops, 0666, handle); + if (ret < 0) + { + fdbg("register_driver failed: %d\n", -ret); + bchlib_teardown(handle); + handle = NULL; + } + + return ret; +} diff --git a/nuttx/drivers/bch/bchdev_unregister.c b/nuttx/drivers/bch/bchdev_unregister.c new file mode 100644 index 000000000..c54b5b005 --- /dev/null +++ b/nuttx/drivers/bch/bchdev_unregister.c @@ -0,0 +1,158 @@ +/**************************************************************************** + * drivers/bch/bchdev_unregister.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sched.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/ioctl.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bchdev_unregister + * + * Description: + * Undo the setup performed by losetup + * + ****************************************************************************/ + +int bchdev_unregister(const char *chardev) +{ + FAR struct bchlib_s *bch; + int fd; + int ret; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!chardev) + { + return -EINVAL; + } +#endif + + /* Open the character driver associated with chardev */ + + fd = open(chardev, O_RDONLY); + if (fd < 0) + { + dbg("Failed to open %s: %d\n", chardev, errno); + return -errno; + } + + /* Get a reference to the internal data structure. On success, we + * will hold a reference count on the state structure. + */ + + ret = ioctl(fd, DIOC_GETPRIV, (unsigned long)((uintptr_t)&bch)); + (void)close(fd); + + if (ret < 0) + { + dbg("ioctl failed: %d\n", errno); + return -errno; + } + + /* Lock out context switches. If there are no other references + * and no context switches, then we can assume that we can safely + * teardown the driver. + */ + + sched_lock(); + + /* Check if the internal structure is non-busy (we hold one reference). */ + + if (bch->refs > 1) + { + ret = -EBUSY; + goto errout_with_lock; + } + + /* Unregister the driver (this cannot suspend or we lose our non-preemptive + * state!). Once the driver is successfully unregistered, we can assume + * we have exclusive access to the state instance. + */ + + ret = unregister_driver(chardev); + if (ret < 0) + { + goto errout_with_lock; + } + sched_unlock(); + + /* Release the internal structure */ + + bch->refs = 0; + return bchlib_teardown(bch); + +errout_with_lock: + bch->refs--; + sched_unlock(); + return ret; +} diff --git a/nuttx/drivers/bch/bchlib_cache.c b/nuttx/drivers/bch/bchlib_cache.c new file mode 100644 index 000000000..3e2887c94 --- /dev/null +++ b/nuttx/drivers/bch/bchlib_cache.c @@ -0,0 +1,133 @@ +/**************************************************************************** + * drivers/bch/bchlib_cache.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdbool.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/fs.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bchlib_flushsector + * + * Description: + * Flush the current contents of the sector buffer (if dirty) + * + * Assumptions: + * Caller must assume mutual exclusion + * + ****************************************************************************/ + +int bchlib_flushsector(FAR struct bchlib_s *bch) +{ + FAR struct inode *inode; + ssize_t ret = OK; + + if (bch->dirty) + { + inode = bch->inode; + ret = inode->u.i_bops->write(inode, bch->buffer, bch->sector, 1); + if (ret < 0) + { + fdbg("Write failed: %d\n"); + } + bch->dirty = false; + } + return (int)ret; +} + +/**************************************************************************** + * Name: bchlib_readsector + * + * Description: + * Flush the current contents of the sector buffer (if dirty) + * + * Assumptions: + * Caller must assume mutual exclusion + * + ****************************************************************************/ + +int bchlib_readsector(FAR struct bchlib_s *bch, size_t sector) +{ + FAR struct inode *inode; + ssize_t ret = OK; + + if (bch->sector != sector) + { + inode = bch->inode; + + (void)bchlib_flushsector(bch); + bch->sector = (size_t)-1; + + ret = inode->u.i_bops->read(inode, bch->buffer, sector, 1); + if (ret < 0) + { + fdbg("Read failed: %d\n"); + } + bch->sector = sector; + } + return (int)ret; +} + diff --git a/nuttx/drivers/bch/bchlib_read.c b/nuttx/drivers/bch/bchlib_read.c new file mode 100644 index 000000000..e146f02f8 --- /dev/null +++ b/nuttx/drivers/bch/bchlib_read.c @@ -0,0 +1,203 @@ +/**************************************************************************** + * drivers/bch/bchlib_read.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/fs.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bchlib_read + * + * Description: + * Read from the block device set-up by bchlib_setup as if it were a character + * device. + * + ****************************************************************************/ + +ssize_t bchlib_read(FAR void *handle, FAR char *buffer, size_t offset, size_t len) +{ + FAR struct bchlib_s *bch = (FAR struct bchlib_s *)handle; + size_t nsectors; + size_t sector; + uint16_t sectoffset; + size_t nbytes; + size_t bytesread; + int ret; + + /* Get rid of this special case right away */ + + if (len < 1) + { + return 0; + } + + /* Convert the file position into a sector number an offset. */ + + sector = offset / bch->sectsize; + sectoffset = offset - sector * bch->sectsize; + + if (sector >= bch->nsectors) + { + /* Return end-of-file */ + + return 0; + } + + /* Read the initial partial sector */ + + bytesread = 0; + if (sectoffset > 0) + { + /* Read the sector into the sector buffer */ + + bchlib_readsector(bch, sector); + + /* Copy the tail end of the sector to the user buffer */ + + if (sectoffset + len > bch->sectsize) + { + nbytes = bch->sectsize - sectoffset; + } + else + { + nbytes = len; + } + + memcpy(buffer, &bch->buffer[sectoffset], nbytes); + + /* Adjust pointers and counts */ + + sectoffset = 0; + sector++; + + if (sector >= bch->nsectors) + { + return nbytes; + } + + bytesread = nbytes; + buffer += nbytes; + len -= nbytes; + } + + /* Then read all of the full sectors following the partial sector directly + * into the user buffer. + */ + + if (len >= bch->sectsize ) + { + nsectors = len / bch->sectsize; + if (sector + nsectors > bch->nsectors) + { + nsectors = bch->nsectors - sector; + } + + ret = bch->inode->u.i_bops->read(bch->inode, (FAR uint8_t *)buffer, + sector, nsectors); + if (ret < 0) + { + fdbg("Read failed: %d\n"); + return ret; + } + + /* Adjust pointers and counts */ + + sectoffset = 0; + sector += nsectors; + + nbytes = nsectors * bch->sectsize; + bytesread += nbytes; + + if (sector >= bch->nsectors) + { + return bytesread; + } + + buffer += nbytes; + len -= nbytes; + } + + /* Then read any partial final sector */ + + if (len > 0) + { + /* Read the sector into the sector buffer */ + + bchlib_readsector(bch, sector); + + /* Copy the head end of the sector to the user buffer */ + + memcpy(buffer, bch->buffer, len); + + /* Adjust counts */ + + bytesread += len; + } + + return bytesread; +} diff --git a/nuttx/drivers/bch/bchlib_sem.c b/nuttx/drivers/bch/bchlib_sem.c new file mode 100644 index 000000000..b0c67a997 --- /dev/null +++ b/nuttx/drivers/bch/bchlib_sem.c @@ -0,0 +1,84 @@ +/**************************************************************************** + * drivers/bch/bchlib_sem.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bch_semtake + ****************************************************************************/ + +void bchlib_semtake(FAR struct bchlib_s *bch) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(&bch->sem) != 0) + { + /* The only case that an error should occur here is if + * the wait was awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} diff --git a/nuttx/drivers/bch/bchlib_setup.c b/nuttx/drivers/bch/bchlib_setup.c new file mode 100644 index 000000000..b97b63dd2 --- /dev/null +++ b/nuttx/drivers/bch/bchlib_setup.c @@ -0,0 +1,159 @@ +/**************************************************************************** + * drivers/bch/bchlib_setup.c + * + * Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <sys/mount.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bchlib_setup + * + * Description: + * Setup so that the block driver referenced by 'blkdev' can be accessed + * similar to a character device. + * + ****************************************************************************/ + +int bchlib_setup(const char *blkdev, bool readonly, FAR void **handle) +{ + FAR struct bchlib_s *bch; + struct geometry geo; + int ret; + + DEBUGASSERT(blkdev); + + /* Allocate the BCH state structure */ + + bch = (FAR struct bchlib_s*)kzalloc(sizeof(struct bchlib_s)); + if (!bch) + { + fdbg("Failed to allocate BCH structure\n"); + return -ENOMEM; + } + + /* Open the block driver */ + + ret = open_blockdriver(blkdev, readonly ? MS_RDONLY : 0, &bch->inode); + if (ret < 0) + { + fdbg("Failed to open driver %s: %d\n", blkdev, -ret); + goto errout_with_bch; + } + + DEBUGASSERT(bch->inode && bch->inode->u.i_bops && bch->inode->u.i_bops->geometry); + + ret = bch->inode->u.i_bops->geometry(bch->inode, &geo); + if (ret < 0) + { + fdbg("geometry failed: %d\n", -ret); + goto errout_with_bch; + } + + if (!geo.geo_available) + { + fdbg("geometry failed: %d\n", -ret); + ret = -ENODEV; + goto errout_with_bch; + } + + if (!readonly && (!bch->inode->u.i_bops->write || !geo.geo_writeenabled)) + { + fdbg("write access not supported\n"); + ret = -EACCES; + goto errout_with_bch; + } + + /* Save the geometry info and complete initialization of the structure */ + + sem_init(&bch->sem, 0, 1); + bch->nsectors = geo.geo_nsectors; + bch->sectsize = geo.geo_sectorsize; + bch->sector = (size_t)-1; + bch->readonly = readonly; + + /* Allocate the sector I/O buffer */ + + bch->buffer = (FAR uint8_t *)kmalloc(bch->sectsize); + if (!bch->buffer) + { + fdbg("Failed to allocate sector buffer\n"); + ret = -ENOMEM; + goto errout_with_bch; + } + + *handle = bch; + return OK; + +errout_with_bch: + kfree(bch); + return ret; +} diff --git a/nuttx/drivers/bch/bchlib_teardown.c b/nuttx/drivers/bch/bchlib_teardown.c new file mode 100644 index 000000000..e2084125c --- /dev/null +++ b/nuttx/drivers/bch/bchlib_teardown.c @@ -0,0 +1,113 @@ +/**************************************************************************** + * drivers/bch/bchlib_teardown.c + * + * Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdlib.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bchlib_teardown + * + * Description: + * Setup so that the block driver referenced by 'blkdev' can be accessed + * similar to a character device. + * + ****************************************************************************/ + +int bchlib_teardown(FAR void *handle) +{ + FAR struct bchlib_s *bch = (FAR struct bchlib_s *)handle; + + DEBUGASSERT(handle); + + /* Check that there are not outstanding reference counts on the object */ + + if (bch->refs > 0) + { + return -EBUSY; + } + + /* Flush any pending data to the block driver */ + + bchlib_flushsector(bch); + + /* Close the block driver */ + + (void)close_blockdriver(bch->inode); + + /* Free the BCH state structure */ + + if (bch->buffer) + { + kfree(bch->buffer); + } + + sem_destroy(&bch->sem); + kfree(bch); + return OK; +} + diff --git a/nuttx/drivers/bch/bchlib_write.c b/nuttx/drivers/bch/bchlib_write.c new file mode 100644 index 000000000..956f1619f --- /dev/null +++ b/nuttx/drivers/bch/bchlib_write.c @@ -0,0 +1,216 @@ +/**************************************************************************** + * drivers/bch/bchlib_write.c + * + * Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/fs.h> + +#include "bch_internal.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bchlib_write + * + * Description: + * Write to the block device set-up by bchlib_setup as if it were a character + * device. + * + ****************************************************************************/ + +ssize_t bchlib_write(FAR void *handle, FAR const char *buffer, size_t offset, size_t len) +{ + FAR struct bchlib_s *bch = (FAR struct bchlib_s *)handle; + size_t nsectors; + size_t sector; + uint16_t sectoffset; + size_t nbytes; + size_t byteswritten; + int ret; + + /* Get rid of this special case right away */ + + if (len < 1) + { + return 0; + } + + /* Convert the file position into a sector number an offset. */ + + sector = offset / bch->sectsize; + sectoffset = offset - sector * bch->sectsize; + + if (sector >= bch->nsectors) + { + return -EFBIG; + } + + /* Write the initial partial sector */ + + byteswritten = 0; + if (sectoffset > 0) + { + /* Read the full sector into the sector buffer */ + + bchlib_readsector(bch, sector); + + /* Copy the tail end of the sector from the user buffer */ + + if (sectoffset + len > bch->sectsize) + { + nbytes = bch->sectsize - sectoffset; + } + else + { + nbytes = len; + } + + memcpy(&bch->buffer[sectoffset], buffer, nbytes); + bch->dirty = true; + + /* Adjust pointers and counts */ + + sectoffset = 0; + sector++; + + if (sector >= bch->nsectors) + { + return nbytes; + } + + byteswritten = nbytes; + buffer += nbytes; + len -= nbytes; + } + + /* Then write all of the full sectors following the partial sector + * directly from the user buffer. + */ + + if (len >= bch->sectsize ) + { + nsectors = len / bch->sectsize; + if (sector + nsectors > bch->nsectors) + { + nsectors = bch->nsectors - sector; + } + + /* Write the contiguous sectors */ + + ret = bch->inode->u.i_bops->write(bch->inode, (FAR uint8_t *)buffer, + sector, nsectors); + if (ret < 0) + { + fdbg("Write failed: %d\n", ret); + return ret; + } + + /* Adjust pointers and counts */ + + sectoffset = 0; + sector += nsectors; + + nbytes = nsectors * bch->sectsize; + byteswritten += nbytes; + + if (sector >= bch->nsectors) + { + return byteswritten; + } + + buffer += nbytes; + len -= nbytes; + } + + /* Then write any partial final sector */ + + if (len > 0) + { + /* Read the sector into the sector buffer */ + + bchlib_readsector(bch, sector); + + /* Copy the head end of the sector from the user buffer */ + + memcpy(bch->buffer, buffer, len); + bch->dirty = true; + + /* Adjust counts */ + + byteswritten += len; + } + + /* Finally, flush any cached writes to the device as well */ + + ret = bchlib_flushsector(bch); + if (ret < 0) + { + fdbg("Flush failed: %d\n", ret); + return ret; + } + + return byteswritten; +} + diff --git a/nuttx/drivers/can.c b/nuttx/drivers/can.c new file mode 100644 index 000000000..fc70678a7 --- /dev/null +++ b/nuttx/drivers/can.c @@ -0,0 +1,770 @@ +/**************************************************************************** + * drivers/can.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Compilation Switches + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <semaphore.h> +#include <fcntl.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/arch.h> +#include <nuttx/can.h> + +#include <arch/irq.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define HALF_SECOND_MSEC 500 +#define HALF_SECOND_USEC 500000L + +/**************************************************************************** + * Private Type Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int can_open(FAR struct file *filep); +static int can_close(FAR struct file *filep); +static ssize_t can_read(FAR struct file *filep, FAR char *buffer, size_t buflen); +static int can_xmit(FAR struct can_dev_s *dev); +static ssize_t can_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); +static inline ssize_t can_rtrread(FAR struct can_dev_s *dev, FAR struct canioctl_rtr_s *rtr); +static int can_ioctl(FAR struct file *filep, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_canops = +{ + can_open, /* open */ + can_close, /* close */ + can_read, /* read */ + can_write, /* write */ + 0, /* seek */ + can_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , 0 /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/************************************************************************************ + * Name: can_open + * + * Description: + * This function is called whenever the CAN device is opened. + * + ************************************************************************************/ + +static int can_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct can_dev_s *dev = inode->i_private; + uint8_t tmp; + int ret = OK; + + /* If the port is the middle of closing, wait until the close is finished */ + + if (sem_wait(&dev->cd_closesem) != OK) + { + ret = -errno; + } + else + { + /* Increment the count of references to the device. If this the first + * time that the driver has been opened for this device, then initialize + * the device. + */ + + tmp = dev->cd_ocount + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + } + else + { + /* Check if this is the first time that the driver has been opened. */ + + if (tmp == 1) + { + /* Yes.. perform one time hardware initialization. */ + + irqstate_t flags = irqsave(); + ret = dev_setup(dev); + if (ret == OK) + { + /* Mark the FIFOs empty */ + + dev->cd_xmit.cf_head = 0; + dev->cd_xmit.cf_tail = 0; + dev->cd_recv.cf_head = 0; + dev->cd_recv.cf_tail = 0; + + /* Finally, Enable the CAN RX interrupt */ + + dev_rxint(dev, true); + + /* Save the new open count on success */ + + dev->cd_ocount = tmp; + } + irqrestore(flags); + } + } + sem_post(&dev->cd_closesem); + } + return ret; +} + +/************************************************************************************ + * Name: can_close + * + * Description: + * This routine is called when the CAN device is closed. + * It waits for the last remaining data to be sent. + * + ************************************************************************************/ + +static int can_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct can_dev_s *dev = inode->i_private; + irqstate_t flags; + int ret = OK; + + if (sem_wait(&dev->cd_closesem) != OK) + { + ret = -errno; + } + else + { + /* Decrement the references to the driver. If the reference count will + * decrement to 0, then uninitialize the driver. + */ + + if (dev->cd_ocount > 1) + { + dev->cd_ocount--; + sem_post(&dev->cd_closesem); + } + else + { + /* There are no more references to the port */ + + dev->cd_ocount = 0; + + /* Stop accepting input */ + + dev_rxint(dev, false); + + /* Now we wait for the transmit FIFO to clear */ + + while (dev->cd_xmit.cf_head != dev->cd_xmit.cf_tail) + { +#ifndef CONFIG_DISABLE_SIGNALS + usleep(HALF_SECOND_USEC); +#else + up_mdelay(HALF_SECOND_MSEC); +#endif + } + + /* And wait for the TX hardware FIFO to drain */ + + while (!dev_txempty(dev)) + { +#ifndef CONFIG_DISABLE_SIGNALS + usleep(HALF_SECOND_USEC); +#else + up_mdelay(HALF_SECOND_MSEC); +#endif + } + + /* Free the IRQ and disable the CAN device */ + + flags = irqsave(); /* Disable interrupts */ + dev_shutdown(dev); /* Disable the CAN */ + irqrestore(flags); + + sem_post(&dev->cd_closesem); + } + } + return ret; +} + +/************************************************************************************ + * Name: can_read + * + * Description: + * Read standard CAN messages + * + ************************************************************************************/ + +static ssize_t can_read(FAR struct file *filep, FAR char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct can_dev_s *dev = inode->i_private; + size_t nread; + irqstate_t flags; + int ret = 0; + + /* The caller must provide enough memory to catch the smallest possible message + * This is not a system error condition, but we won't permit it, Hence we return 0. + */ + + if (buflen >= CAN_MSGLEN(0)) + { + /* Interrupts must be disabled while accessing the cd_recv FIFO */ + + flags = irqsave(); + while (dev->cd_recv.cf_head == dev->cd_recv.cf_tail) + { + /* The receive FIFO is empty -- was non-blocking mode selected? */ + + if (filep->f_oflags & O_NONBLOCK) + { + ret = -EAGAIN; + goto return_with_irqdisabled; + } + + /* Wait for a message to be received */ + + ret = sem_wait(&dev->cd_recv.cf_sem); + if (ret < 0) + { + ret = -errno; + goto return_with_irqdisabled; + } + } + + /* The cd_recv FIFO is not empty. Copy all buffered data that will fit + * in the user buffer. + */ + + nread = 0; + do + { + /* Will the next message in the FIFO fit into the user buffer? */ + + FAR struct can_msg_s *msg = &dev->cd_recv.cf_buffer[dev->cd_recv.cf_head]; + int msglen = CAN_MSGLEN(msg->cm_hdr); + + if (ret + msglen > buflen) + { + break; + } + + /* Copy the message to the user buffer */ + + memcpy(&buffer[nread], msg, msglen); + nread += msglen; + + /* Increment the head of the circular message buffer */ + + if (++dev->cd_recv.cf_head >= CONFIG_CAN_FIFOSIZE) + { + dev->cd_recv.cf_head = 0; + } + } + while (dev->cd_recv.cf_head != dev->cd_recv.cf_tail); + + /* All on the messages have bee transferred. Return the number of bytes + * that were read. + */ + + ret = nread; + +return_with_irqdisabled: + irqrestore(flags); + } + return ret; +} + +/************************************************************************************ + * Name: can_xmit + * + * Description: + * Send the message at the head of the cd_xmit FIFO + * + * Assumptions: + * Called with interrupts disabled + * + ************************************************************************************/ + +static int can_xmit(FAR struct can_dev_s *dev) +{ + bool enable = false; + int ret = OK; + + /* Check if the xmit FIFO is empty */ + + if (dev->cd_xmit.cf_head != dev->cd_xmit.cf_tail) + { + /* Send the next message at the head of the FIFO */ + + ret = dev_send(dev, &dev->cd_xmit.cf_buffer[dev->cd_xmit.cf_head]); + + /* Make sure the TX done interrupts are enabled */ + + enable = (ret == OK ? true : false); + } + dev_txint(dev, enable); + return ret; +} + +/************************************************************************************ + * Name: can_write + ************************************************************************************/ + +static ssize_t can_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct can_dev_s *dev = inode->i_private; + FAR struct can_fifo_s *fifo = &dev->cd_xmit; + FAR struct can_msg_s *msg; + bool empty = false; + ssize_t nsent = 0; + irqstate_t flags; + int nexttail; + int msglen; + int ret = 0; + + /* Interrupts must disabled throughout the following */ + + flags = irqsave(); + + /* Check if the TX FIFO was empty when we started. That is a clue that we have + * to kick off a new TX sequence. + */ + + empty = (fifo->cf_head == fifo->cf_tail); + + /* Add the messages to the FIFO. Ignore any trailing messages that are + * shorter than the minimum. + */ + + while ((buflen - nsent) >= CAN_MSGLEN(0)) + { + /* Check if adding this new message would over-run the drivers ability to enqueue + * xmit data. + */ + + nexttail = fifo->cf_tail + 1; + if (nexttail >= CONFIG_CAN_FIFOSIZE) + { + nexttail = 0; + } + + /* If the XMIT fifo becomes full, then wait for space to become available */ + + while (nexttail == fifo->cf_head) + { + /* The transmit FIFO is full -- was non-blocking mode selected? */ + + if (filep->f_oflags & O_NONBLOCK) + { + if (nsent == 0) + { + ret = -EAGAIN; + } + else + { + ret = nsent; + } + goto return_with_irqdisabled; + } + + /* If the FIFO was empty when we started, then we will have + * start the XMIT sequence to clear the FIFO. + */ + + if (empty) + { + can_xmit(dev); + } + + /* Wait for a message to be sent */ + + do + { + DEBUGASSERT(dev->cd_ntxwaiters < 255); + dev->cd_ntxwaiters++; + ret = sem_wait(&fifo->cf_sem); + dev->cd_ntxwaiters--; + + if (ret < 0 && errno != EINTR) + { + ret = -errno; + goto return_with_irqdisabled; + } + } + while (ret < 0); + + /* Re-check the FIFO state */ + + empty = (fifo->cf_head == fifo->cf_tail); + } + + /* We get here if there is space at the end of the FIFO. Add the new + * CAN message at the tail of the FIFO. + */ + + msg = (FAR struct can_msg_s *)&buffer[nsent]; + msglen = CAN_MSGLEN(msg->cm_hdr); + memcpy(&fifo->cf_buffer[fifo->cf_tail], msg, msglen); + + /* Increment the tail of the circular buffer */ + + fifo->cf_tail = nexttail; + + /* Increment the number of bytes that were sent */ + + nsent += msglen; + } + + /* We get here after all messages have been added to the FIFO. Check if + * we need to kick of the XMIT sequence. + */ + + if (empty) + { + can_xmit(dev); + } + + /* Return the number of bytes that were sent */ + + ret = nsent; + +return_with_irqdisabled: + irqrestore(flags); + return ret; +} + +/************************************************************************************ + * Name: can_rtrread + * + * Description: + * Read RTR messages. The RTR message is a special message -- it is an outgoing + * message that says "Please re-transmit the message with the same identifier as + * this message. So the RTR read is really a send-wait-receive operation. + * + ************************************************************************************/ + +static inline ssize_t can_rtrread(FAR struct can_dev_s *dev, FAR struct canioctl_rtr_s *rtr) +{ + FAR struct can_rtrwait_s *wait = NULL; + irqstate_t flags; + int i; + int ret = -ENOMEM; + + /* Disable interrupts through this operation */ + + flags = irqsave(); + + /* Find an avaiable slot in the pending RTR list */ + + for (i = 0; i < CONFIG_CAN_NPENDINGRTR; i++) + { + FAR struct can_rtrwait_s *tmp = &dev->cd_rtr[i]; + if (!rtr->ci_msg) + { + tmp->cr_id = rtr->ci_id; + tmp->cr_msg = rtr->ci_msg; + dev->cd_npendrtr++; + wait = tmp; + break; + } + } + + if (wait) + { + /* Send the remote transmission request */ + + ret = dev_remoterequest(dev, wait->cr_id); + if (ret == OK) + { + /* Then wait for the response */ + + ret = sem_wait(&wait->cr_sem); + } + } + irqrestore(flags); + return ret; +} + +/************************************************************************************ + * Name: can_ioctl + ************************************************************************************/ + +static int can_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct can_dev_s *dev = inode->i_private; + int ret = OK; + + /* Handle built-in ioctl commands */ + + switch (cmd) + { + /* CANIOCTL_RTR: Send the remote transmission request and wait for the response. + * Argument is a reference to struct canioctl_rtr_s (casting to uintptr_t first + * eliminates complaints on some architectures where the sizeof long is different + * from the size of a pointer). + */ + + case CANIOCTL_RTR: + ret = can_rtrread(dev, (struct canioctl_rtr_s*)((uintptr_t)arg)); + break; + + /* Not a "built-in" ioctl command.. perhaps it is unique to this device driver */ + + default: + ret = dev_ioctl(dev, cmd, arg); + break; + } + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/************************************************************************************ + * Name: can_register + * + * Description: + * Register serial console and serial ports. + * + ************************************************************************************/ + +int can_register(FAR const char *path, FAR struct can_dev_s *dev) +{ + int i; + + /* Initialize the CAN device structure */ + + dev->cd_ocount = 0; + + sem_init(&dev->cd_xmit.cf_sem, 0, 0); + sem_init(&dev->cd_recv.cf_sem, 0, 0); + sem_init(&dev->cd_closesem, 0, 1); + + for (i = 0; i < CONFIG_CAN_NPENDINGRTR; i++) + { + sem_init(&dev->cd_rtr[i].cr_sem, 0, 0); + dev->cd_rtr[i].cr_msg = NULL; + dev->cd_npendrtr--; + } + + /* Initialize/reset the CAN hardware */ + + dev_reset(dev); + + /* Register the CAN device */ + + dbg("Registering %s\n", path); + return register_driver(path, &g_canops, 0666, dev); +} + +/************************************************************************************ + * Name: can_receive + * + * Description: + * Called from the CAN interrupt handler when new read data is available + * + * Parameters: + * dev - CAN driver state structure + * hdr - CAN message header + * data - CAN message data (if DLC > 0) + * + * Assumptions: + * CAN interrupts are disabled. + * + ************************************************************************************/ + +int can_receive(FAR struct can_dev_s *dev, uint16_t hdr, FAR uint8_t *data) +{ + FAR struct can_fifo_s *fifo = &dev->cd_recv; + FAR uint8_t *dest; + int nexttail; + int err = -ENOMEM; + int i; + + /* Check if adding this new message would over-run the drivers ability to enqueue + * read data. + */ + + nexttail = fifo->cf_tail + 1; + if (nexttail >= CONFIG_CAN_FIFOSIZE) + { + nexttail = 0; + } + + /* First, check if this response matches any RTR response that we may be waiting for */ + + if (dev->cd_npendrtr > 0) + { + /* There are pending RTR requests -- search the lists of requests + * and see any any matches this new message. + */ + + for (i = 0; i < CONFIG_CAN_NPENDINGRTR; i++) + { + FAR struct can_rtrwait_s *rtr = &dev->cd_rtr[i]; + FAR struct can_msg_s *msg = rtr->cr_msg; + + /* Check if the entry is valid and if the ID matches. A valid entry has + * a non-NULL receiving address + */ + + if (msg && CAN_ID(hdr) == rtr->cr_id) + { + /* We have the response... copy the data to the user's buffer */ + + msg->cm_hdr = hdr; + for (i = 0, dest = msg->cm_data; i < CAN_DLC(hdr); i++) + { + *dest++ = *data++; + } + + /* Mark the entry unused */ + + rtr->cr_msg = NULL; + + /* And restart the waiting thread */ + + sem_post(&rtr->cr_sem); + } + } + } + + /* Refuse the new data if the FIFO is full */ + + if (nexttail != fifo->cf_head) + { + /* Add the new, decoded CAN message at the tail of the FIFO */ + + fifo->cf_buffer[fifo->cf_tail].cm_hdr = hdr; + for (i = 0, dest = fifo->cf_buffer[fifo->cf_tail].cm_data; i < CAN_DLC(hdr); i++) + { + *dest++ = *data++; + } + + /* Increment the tail of the circular buffer */ + + fifo->cf_tail = nexttail; + + /* The increment the counting semaphore. The maximum value should be + * CONFIG_CAN_FIFOSIZE -- one possible count for each allocated message buffer. + */ + + sem_post(&fifo->cf_sem); + err = OK; + } + return err; +} + +/************************************************************************************ + * Name: can_txdone + * + * Description: + * Called from the CAN interrupt handler at the completion of a send operation. + * + * Parameters: + * dev - The specifi CAN device + * hdr - The 16-bit CAN header + * data - An array contain the CAN data. + * + * Return: + * OK on success; a negated errno on failure. + * + ************************************************************************************/ + +int can_txdone(FAR struct can_dev_s *dev) +{ + int ret = -ENOENT; + + /* Verify that the xmit FIFO is not empty */ + + if (dev->cd_xmit.cf_head != dev->cd_xmit.cf_tail) + { + /* Remove the message at the head of the xmit FIFO */ + + if (++dev->cd_xmit.cf_head >= CONFIG_CAN_FIFOSIZE) + { + dev->cd_xmit.cf_head = 0; + } + + /* Send the next message in the FIFO */ + + ret = can_xmit(dev); + + /* Are there any threads waiting for space in the TX FIFO? */ + + if (ret == OK && dev->cd_ntxwaiters > 0) + { + /* Yes.. Inform them that new xmit space is available */ + + ret = sem_post(&dev->cd_xmit.cf_sem); + } + } + return ret; +} + diff --git a/nuttx/drivers/dev_null.c b/nuttx/drivers/dev_null.c new file mode 100644 index 000000000..f473f1b87 --- /dev/null +++ b/nuttx/drivers/dev_null.c @@ -0,0 +1,134 @@ +/**************************************************************************** + * drivers/dev_null.c + * + * Copyright (C) 2007, 2008 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdbool.h> +#include <string.h> +#include <poll.h> +#include <errno.h> +#include <nuttx/fs.h> + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static ssize_t devnull_read(FAR struct file *, FAR char *, size_t); +static ssize_t devnull_write(FAR struct file *, FAR const char *, size_t); +#ifndef CONFIG_DISABLE_POLL +static int devnull_poll(FAR struct file *filp, FAR struct pollfd *fds, + bool setup); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations devnull_fops = +{ + 0, /* open */ + 0, /* close */ + devnull_read, /* read */ + devnull_write, /* write */ + 0, /* seek */ + 0 /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , devnull_poll /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devnull_read + ****************************************************************************/ + +static ssize_t devnull_read(FAR struct file *filp, FAR char *buffer, size_t len) +{ + return 0; /* Return EOF */ +} + +/**************************************************************************** + * Name: devnull_write + ****************************************************************************/ + +static ssize_t devnull_write(FAR struct file *filp, FAR const char *buffer, size_t len) +{ + return len; /* Say that everything was written */ +} + +/**************************************************************************** + * Name: devnull_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int devnull_poll(FAR struct file *filp, FAR struct pollfd *fds, + bool setup) +{ + if (setup) + { + fds->revents |= (fds->events & (POLLIN|POLLOUT)); + if (fds->revents != 0) + { + sem_post(fds->sem); + } + } + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devnull_register + * + * Description: + * Register /dev/null + * + ****************************************************************************/ + +void devnull_register(void) +{ + (void)register_driver("/dev/null", &devnull_fops, 0666, NULL); +} diff --git a/nuttx/drivers/dev_zero.c b/nuttx/drivers/dev_zero.c new file mode 100644 index 000000000..c4b453798 --- /dev/null +++ b/nuttx/drivers/dev_zero.c @@ -0,0 +1,127 @@ +/**************************************************************************** + * drivers/dev_null.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdbool.h> +#include <string.h> +#include <poll.h> +#include <errno.h> +#include <nuttx/fs.h> + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static ssize_t devzero_read(FAR struct file *, FAR char *, size_t); +static ssize_t devzero_write(FAR struct file *, FAR const char *, size_t); +#ifndef CONFIG_DISABLE_POLL +static int devzero_poll(FAR struct file *filp, FAR struct pollfd *fds, + bool setup); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations devzero_fops = +{ + 0, /* open */ + 0, /* close */ + devzero_read, /* read */ + devzero_write, /* write */ + 0, /* seek */ + 0 /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , devzero_poll /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devzero_read + ****************************************************************************/ + +static ssize_t devzero_read(FAR struct file *filp, FAR char *buffer, size_t len) +{ + memset(buffer, 0, len); + return len; +} + +/**************************************************************************** + * Name: devzero_write + ****************************************************************************/ + +static ssize_t devzero_write(FAR struct file *filp, FAR const char *buffer, size_t len) +{ + return len; +} + +/**************************************************************************** + * Name: devzero_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int devzero_poll(FAR struct file *filp, FAR struct pollfd *fds, + bool setup) +{ + if (setup) + { + fds->revents |= (fds->events & (POLLIN|POLLOUT)); + if (fds->revents != 0) + { + sem_post(fds->sem); + } + } + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void devzero_register(void) +{ + (void)register_driver("/dev/zero", &devzero_fops, 0666, NULL); +} diff --git a/nuttx/drivers/input/Make.defs b/nuttx/drivers/input/Make.defs new file mode 100644 index 000000000..e32dc4549 --- /dev/null +++ b/nuttx/drivers/input/Make.defs @@ -0,0 +1,56 @@ +############################################################################ +# drivers/input/Make.defs +# +# Copyright (C) 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Don't build anything if there is no NX support for input devices + +ifeq ($(CONFIG_INPUT),y) + +# Include the selected touchscreen drivers + +ifeq ($(CONFIG_INPUT_TSC2007),y) + CSRCS += tsc2007.c +endif + +ifeq ($(CONFIG_INPUT_ADS7843E),y) + CSRCS += ads7843e.c +endif + +# Include input device driver build support + +DEPPATH += --dep-path input +VPATH += :input +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/input} +endif + diff --git a/nuttx/drivers/input/ads7843e.c b/nuttx/drivers/input/ads7843e.c new file mode 100644 index 000000000..52d871dd5 --- /dev/null +++ b/nuttx/drivers/input/ads7843e.c @@ -0,0 +1,1171 @@ +/**************************************************************************** + * drivers/input/ads7843e.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Authors: Gregory Nutt <gnutt@nuttx.org> + * Diego Sanchez <dsanchez@nx-engineering.com> + * + * References: + * "Touch Screen Controller, ADS7843," Burr-Brown Products from Texas + * Instruments, SBAS090B, September 2000, Revised May 2002" + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> + +#include <stdbool.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <semaphore.h> +#include <poll.h> +#include <wdog.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/fs.h> +#include <nuttx/spi.h> +#include <nuttx/wqueue.h> + +#include <nuttx/input/touchscreen.h> +#include <nuttx/input/ads7843e.h> + +#include "ads7843e.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +/* Low-level SPI helpers */ + +static inline void ads7843e_configspi(FAR struct spi_dev_s *spi); +#ifdef CONFIG_SPI_OWNBUS +static inline void ads7843e_select(FAR struct spi_dev_s *spi); +static inline void ads7843e_deselect(FAR struct spi_dev_s *spi); +#else +static void ads7843e_select(FAR struct spi_dev_s *spi); +static void ads7843e_deselect(FAR struct spi_dev_s *spi); +#endif + +static inline void ads7843e_waitbusy(FAR struct ads7843e_dev_s *priv); +static uint16_t ads7843e_sendcmd(FAR struct ads7843e_dev_s *priv, uint8_t cmd); + +/* Interrupts and data sampling */ + +static void ads7843e_notify(FAR struct ads7843e_dev_s *priv); +static int ads7843e_sample(FAR struct ads7843e_dev_s *priv, + FAR struct ads7843e_sample_s *sample); +static int ads7843e_waitsample(FAR struct ads7843e_dev_s *priv, + FAR struct ads7843e_sample_s *sample); +static void ads7843e_worker(FAR void *arg); +static int ads7843e_interrupt(int irq, FAR void *context); + +/* Character driver methods */ + +static int ads7843e_open(FAR struct file *filep); +static int ads7843e_close(FAR struct file *filep); +static ssize_t ads7843e_read(FAR struct file *filep, FAR char *buffer, size_t len); +static int ads7843e_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +#ifndef CONFIG_DISABLE_POLL +static int ads7843e_poll(FAR struct file *filep, struct pollfd *fds, bool setup); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This the the vtable that supports the character driver interface */ + +static const struct file_operations ads7843e_fops = +{ + ads7843e_open, /* open */ + ads7843e_close, /* close */ + ads7843e_read, /* read */ + 0, /* write */ + 0, /* seek */ + ads7843e_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , ads7843e_poll /* poll */ +#endif +}; + +/* If only a single ADS7843E device is supported, then the driver state + * structure may as well be pre-allocated. + */ + +#ifndef CONFIG_ADS7843E_MULTIPLE +static struct ads7843e_dev_s g_ads7843e; + +/* Otherwise, we will need to maintain allocated driver instances in a list */ + +#else +static struct ads7843e_dev_s *g_ads7843elist; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: ads7843e_select + * + * Description: + * Select the SPI, locking and re-configuring if necessary. This function + * must be called before initiating any sequence of SPI operations. If we + * are sharing the SPI bus with other devices (CONFIG_SPI_OWNBUS undefined) + * then we need to lock and configure the SPI bus for each transfer. + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void ads7843e_select(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just select the chip */ + + SPI_SELECT(spi, SPIDEV_TOUCHSCREEN, true); +} +#else +static void ads7843e_select(FAR struct spi_dev_s *spi) +{ + /* Select ADS7843 chip (locking the SPI bus in case there are multiple + * devices competing for the SPI bus + */ + + SPI_LOCK(spi, true); + SPI_SELECT(spi, SPIDEV_TOUCHSCREEN, true); + + /* Now make sure that the SPI bus is configured for the ADS7843 (it + * might have gotten configured for a different device while unlocked) + */ + + SPI_SETMODE(spi, CONFIG_ADS7843E_SPIMODE); + SPI_SETBITS(spi, 8); + SPI_SETFREQUENCY(spi, CONFIG_ADS7843E_FREQUENCY); +} +#endif + +/**************************************************************************** + * Function: ads7843e_deselect + * + * Description: + * De-select the SPI, unlocking as necessary. This function must be + * after completing a sequence of SPI operations. If we are sharing the SPI + * bus with other devices (CONFIG_SPI_OWNBUS undefined) then we need to + * un-lock the SPI bus for each transfer, possibly losing the current + * configuration. + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void ads7843e_deselect(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just de-select the chip */ + + SPI_SELECT(spi, SPIDEV_TOUCHSCREEN, false); +} +#else +static void ads7843e_deselect(FAR struct spi_dev_s *spi) +{ + /* De-select ADS7843 chip and relinquish the SPI bus. */ + + SPI_SELECT(spi, SPIDEV_TOUCHSCREEN, false); + SPI_LOCK(spi, false); +} +#endif + +/**************************************************************************** + * Function: ads7843e_configspi + * + * Description: + * Configure the SPI for use with the ADS7843E. This function should be + * called once during touchscreen initialization to configure the SPI + * bus. Note that if CONFIG_SPI_OWNBUS is not defined, then this function + * does nothing. + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static inline void ads7843e_configspi(FAR struct spi_dev_s *spi) +{ + idbg("Mode: %d Bits: 8 Frequency: %d\n", + CONFIG_ADS7843E_SPIMODE, CONFIG_ADS7843E_FREQUENCY); + + /* Configure SPI for the ADS7843. But only if we own the SPI bus. Otherwise, don't + * bother because it might change. + */ + +#ifdef CONFIG_SPI_OWNBUS + SPI_SELECT(spi, SPIDEV_TOUCHSCREEN, true); + SPI_SETMODE(spi, CONFIG_ADS7843E_SPIMODE); + SPI_SETBITS(spi, 8); + SPI_SETFREQUENCY(spi, CONFIG_ADS7843E_FREQUENCY); + SPI_SELECT(spi, SPIDEV_TOUCHSCREEN, false); +#endif +} + +/**************************************************************************** + * Name: ads7843e_waitbusy + ****************************************************************************/ + +static inline void ads7843e_waitbusy(FAR struct ads7843e_dev_s *priv) +{ + while (priv->config->busy(priv->config)); +} + +/**************************************************************************** + * Name: ads7843e_sendcmd + ****************************************************************************/ + +static uint16_t ads7843e_sendcmd(FAR struct ads7843e_dev_s *priv, uint8_t cmd) +{ + uint8_t buffer[2]; + uint16_t result; + + /* Select the ADS7843E */ + + ads7843e_select(priv->spi); + + /* Send the command */ + + (void)SPI_SEND(priv->spi, cmd); + ads7843e_waitbusy(priv); + + /* Read the data */ + + SPI_RECVBLOCK(priv->spi, buffer, 2); + ads7843e_deselect(priv->spi); + + result = ((uint16_t)buffer[0] << 8) | (uint16_t)buffer[1]; + result = result >> 4; + + ivdbg("cmd:%02x response:%04x\n", cmd, result); + return result; +} + +/**************************************************************************** + * Name: ads7843e_notify + ****************************************************************************/ + +static void ads7843e_notify(FAR struct ads7843e_dev_s *priv) +{ +#ifndef CONFIG_DISABLE_POLL + int i; +#endif + + /* If there are threads waiting for read data, then signal one of them + * that the read data is available. + */ + + if (priv->nwaiters > 0) + { + /* After posting this semaphore, we need to exit because the ADS7843E + * is no longer available. + */ + + sem_post(&priv->waitsem); + } + + /* If there are threads waiting on poll() for ADS7843E data to become available, + * then wake them up now. NOTE: we wake up all waiting threads because we + * do not know that they are going to do. If they all try to read the data, + * then some make end up blocking after all. + */ + +#ifndef CONFIG_DISABLE_POLL + for (i = 0; i < CONFIG_ADS7843E_NPOLLWAITERS; i++) + { + struct pollfd *fds = priv->fds[i]; + if (fds) + { + fds->revents |= POLLIN; + ivdbg("Report events: %02x\n", fds->revents); + sem_post(fds->sem); + } + } +#endif +} + +/**************************************************************************** + * Name: ads7843e_sample + ****************************************************************************/ + +static int ads7843e_sample(FAR struct ads7843e_dev_s *priv, + FAR struct ads7843e_sample_s *sample) +{ + irqstate_t flags; + int ret = -EAGAIN; + + /* Interrupts me be disabled when this is called to (1) prevent posting + * of semaphores from interrupt handlers, and (2) to prevent sampled data + * from changing until it has been reported. + */ + + flags = irqsave(); + + /* Is there new ADS7843E sample data available? */ + + if (priv->penchange) + { + /* Yes.. the state has changed in some way. Return a copy of the + * sampled data. + */ + + memcpy(sample, &priv->sample, sizeof(struct ads7843e_sample_s )); + + /* Now manage state transitions */ + + if (sample->contact == CONTACT_UP) + { + /* Next.. no contact. Increment the ID so that next contact ID will be unique */ + + priv->sample.contact = CONTACT_NONE; + priv->id++; + } + else if (sample->contact == CONTACT_DOWN) + { + /* First report -- next report will be a movement */ + + priv->sample.contact = CONTACT_MOVE; + } + + priv->penchange = false; + ret = OK; + } + + irqrestore(flags); + return ret; +} + +/**************************************************************************** + * Name: ads7843e_waitsample + ****************************************************************************/ + +static int ads7843e_waitsample(FAR struct ads7843e_dev_s *priv, + FAR struct ads7843e_sample_s *sample) +{ + irqstate_t flags; + int ret; + + /* Interrupts me be disabled when this is called to (1) prevent posting + * of semaphores from interrupt handlers, and (2) to prevent sampled data + * from changing until it has been reported. + * + * In addition, we will also disable pre-emption to prevent other threads + * from getting control while we muck with the semaphores. + */ + + sched_lock(); + flags = irqsave(); + + /* Now release the semaphore that manages mutually exclusive access to + * the device structure. This may cause other tasks to become ready to + * run, but they cannot run yet because pre-emption is disabled. + */ + + sem_post(&priv->devsem); + + /* Try to get the a sample... if we cannot, then wait on the semaphore + * that is posted when new sample data is available. + */ + + while (ads7843e_sample(priv, sample) < 0) + { + /* Wait for a change in the ADS7843E state */ + + ivdbg("Waiting..\n"); + priv->nwaiters++; + ret = sem_wait(&priv->waitsem); + priv->nwaiters--; + + if (ret < 0) + { + /* If we are awakened by a signal, then we need to return + * the failure now. + */ + + idbg("sem_wait: %d\n", errno); + DEBUGASSERT(errno == EINTR); + ret = -EINTR; + goto errout; + } + } + + ivdbg("Sampled\n"); + + /* Re-acquire the the semaphore that manages mutually exclusive access to + * the device structure. We may have to wait here. But we have our sample. + * Interrupts and pre-emption will be re-enabled while we wait. + */ + + ret = sem_wait(&priv->devsem); + +errout: + /* Then re-enable interrupts. We might get interrupt here and there + * could be a new sample. But no new threads will run because we still + * have pre-emption disabled. + */ + + irqrestore(flags); + + /* Restore pre-emption. We might get suspended here but that is okay + * because we already have our sample. Note: this means that if there + * were two threads reading from the ADS7843E for some reason, the data + * might be read out of order. + */ + + sched_unlock(); + return ret; +} + +/**************************************************************************** + * Name: ads7843e_schedule + ****************************************************************************/ + +static int ads7843e_schedule(FAR struct ads7843e_dev_s *priv) +{ + FAR struct ads7843e_config_s *config; + int ret; + + /* Get a pointer the callbacks for convenience (and so the code is not so + * ugly). + */ + + config = priv->config; + DEBUGASSERT(config != NULL); + + /* Disable further interrupts. ADS7843E interrupts will be re-enabled + * after the worker thread executes. + */ + + config->enable(config, false); + + /* Disable the watchdog timer. It will be re-enabled in the worker thread + * while the pen remains down. + */ + + wd_cancel(priv->wdog); + + /* Transfer processing to the worker thread. Since ADS7843E interrupts are + * disabled while the work is pending, no special action should be required + * to protected the work queue. + */ + + DEBUGASSERT(priv->work.worker == NULL); + ret = work_queue(&priv->work, ads7843e_worker, priv, 0); + if (ret != 0) + { + illdbg("Failed to queue work: %d\n", ret); + } + + return OK; +} + +/**************************************************************************** + * Name: ads7843e_wdog + ****************************************************************************/ + +static void ads7843e_wdog(int argc, uint32_t arg1, ...) +{ + FAR struct ads7843e_dev_s *priv = (FAR struct ads7843e_dev_s *)((uintptr_t)arg1); + (void)ads7843e_schedule(priv); +} + +/**************************************************************************** + * Name: ads7843e_worker + ****************************************************************************/ + +static void ads7843e_worker(FAR void *arg) +{ + FAR struct ads7843e_dev_s *priv = (FAR struct ads7843e_dev_s *)arg; + FAR struct ads7843e_config_s *config; + bool pendown; + + ASSERT(priv != NULL); + + /* Get a pointer the callbacks for convenience (and so the code is not so + * ugly). + */ + + config = priv->config; + DEBUGASSERT(config != NULL); + + /* Disable the watchdog timer */ + + wd_cancel(priv->wdog); + + /* Check for pen up or down by reading the PENIRQ GPIO. */ + + pendown = config->pendown(config); + + /* Handle the change from pen down to pen up */ + + if (!pendown) + { + /* Ignore the interrupt if the pen was already up (CONTACT_NONE == pen up and + * already reported. CONTACT_UP == pen up, but not reported) + */ + + if (priv->sample.contact == CONTACT_NONE) + { + goto errout; + } + + /* The pen is up. NOTE: We know from a previous test, that this is a + * loss of contact condition. This will be changed to CONTACT_NONE + * after the loss of contact is sampled. + */ + + priv->sample.contact = CONTACT_UP; + } + else + { + /* Handle all pen down events. First, sample positional values. */ + + priv->sample.x = ads7843e_sendcmd(priv, ADS7843_CMD_XPOSITION); + priv->sample.y = ads7843e_sendcmd(priv, ADS7843_CMD_YPOSITION); + (void)ads7843e_sendcmd(priv, ADS7843_CMD_ENABPINIRQ); + + /* If this is the first (acknowledged) pend down report, then report + * this as the first contact. If contact == CONTACT_DOWN, it will be + * set to set to CONTACT_MOVE after the contact is first sampled. + */ + + if (priv->sample.contact != CONTACT_MOVE) + { + /* First contact */ + + priv->sample.contact = CONTACT_DOWN; + } + + /* Continue to sample the position while the pen is down */ + + wd_start(priv->wdog, ADS7843E_WDOG_DELAY, ads7843e_wdog, 1, (uint32_t)priv); + } + + /* Indicate the availability of new sample data for this ID */ + + priv->sample.id = priv->id; + priv->penchange = true; + + /* Notify any waiters that new ADS7843E data is available */ + + ads7843e_notify(priv); + + /* Exit, re-enabling ADS7843E interrupts */ + +errout: + config->enable(config, true); +} + +/**************************************************************************** + * Name: ads7843e_interrupt + ****************************************************************************/ + +static int ads7843e_interrupt(int irq, FAR void *context) +{ + FAR struct ads7843e_dev_s *priv; + FAR struct ads7843e_config_s *config; + int ret; + + /* Which ADS7843E device caused the interrupt? */ + +#ifndef CONFIG_ADS7843E_MULTIPLE + priv = &g_ads7843e; +#else + for (priv = g_ads7843elist; + priv && priv->configs->irq != irq; + priv = priv->flink); + + ASSERT(priv != NULL); +#endif + + /* Get a pointer the callbacks for convenience (and so the code is not so + * ugly). + */ + + config = priv->config; + DEBUGASSERT(config != NULL); + + /* Schedule sampling to occur on the worker thread */ + + ret = ads7843e_schedule(priv); + + /* Clear any pending interrupts and return success */ + + config->clear(config); + return ret; +} + +/**************************************************************************** + * Name: ads7843e_open + ****************************************************************************/ + +static int ads7843e_open(FAR struct file *filep) +{ +#ifdef CONFIG_ADS7843E_REFCNT + FAR struct inode *inode; + FAR struct ads7843e_dev_s *priv; + uint8_t tmp; + int ret; + + ivdbg("Opening\n"); + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct ads7843e_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Increment the reference count */ + + tmp = priv->crefs + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + goto errout_with_sem; + } + + /* When the reference increments to 1, this is the first open event + * on the driver.. and an opportunity to do any one-time initialization. + */ + + /* Save the new open count on success */ + + priv->crefs = tmp; + +errout_with_sem: + sem_post(&priv->devsem); + return ret; +#else + ivdbg("Opening\n"); + return OK; +#endif +} + +/**************************************************************************** + * Name: ads7843e_close + ****************************************************************************/ + +static int ads7843e_close(FAR struct file *filep) +{ +#ifdef CONFIG_ADS7843E_REFCNT + FAR struct inode *inode; + FAR struct ads7843e_dev_s *priv; + int ret; + + ivdbg("Closing\n"); + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct ads7843e_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Decrement the reference count unless it would decrement a negative + * value. When the count decrements to zero, there are no further + * open references to the driver. + */ + + if (priv->crefs >= 1) + { + priv->crefs--; + } + + sem_post(&priv->devsem); +#endif + ivdbg("Closing\n"); + return OK; +} + +/**************************************************************************** + * Name: ads7843e_read + ****************************************************************************/ + +static ssize_t ads7843e_read(FAR struct file *filep, FAR char *buffer, size_t len) +{ + FAR struct inode *inode; + FAR struct ads7843e_dev_s *priv; + FAR struct touch_sample_s *report; + struct ads7843e_sample_s sample; + int ret; + + ivdbg("buffer:%p len:%d\n", buffer, len); + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct ads7843e_dev_s *)inode->i_private; + + /* Verify that the caller has provided a buffer large enough to receive + * the touch data. + */ + + if (len < SIZEOF_TOUCH_SAMPLE_S(1)) + { + /* We could provide logic to break up a touch report into segments and + * handle smaller reads... but why? + */ + + idbg("Unsupported read size: %d\n", len); + return -ENOSYS; + } + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + idbg("sem_wait: %d\n", errno); + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Try to read sample data. */ + + ret = ads7843e_sample(priv, &sample); + if (ret < 0) + { + /* Sample data is not available now. We would ave to wait to get + * receive sample data. If the user has specified the O_NONBLOCK + * option, then just return an error. + */ + + ivdbg("Sample data is not available\n"); + if (filep->f_oflags & O_NONBLOCK) + { + ret = -EAGAIN; + goto errout; + } + + /* Wait for sample data */ + + ret = ads7843e_waitsample(priv, &sample); + if (ret < 0) + { + /* We might have been awakened by a signal */ + + idbg("ads7843e_waitsample: %d\n", ret); + goto errout; + } + } + + /* In any event, we now have sampled ADS7843E data that we can report + * to the caller. + */ + + report = (FAR struct touch_sample_s *)buffer; + memset(report, 0, SIZEOF_TOUCH_SAMPLE_S(1)); + report->npoints = 1; + report->point[0].id = priv->id; + report->point[0].x = sample.x; + report->point[0].y = sample.y; + + /* Report the appropriate flags */ + + if (sample.contact == CONTACT_UP) + { + /* Pen is now up */ + + report->point[0].flags = TOUCH_UP | TOUCH_ID_VALID; + } + else if (sample.contact == CONTACT_DOWN) + { + /* First contact */ + + report->point[0].flags = TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID; + } + else /* if (sample->contact == CONTACT_MOVE) */ + { + /* Movement of the same contact */ + + report->point[0].flags = TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID; + } + + ivdbg(" id: %d\n", report->point[0].id); + ivdbg(" flags: %02x\n", report->point[0].flags); + ivdbg(" x: %d\n", report->point[0].x); + ivdbg(" y: %d\n", report->point[0].y); + + ret = SIZEOF_TOUCH_SAMPLE_S(1); + +errout: + sem_post(&priv->devsem); + ivdbg("Returning: %d\n", ret); + return ret; +} + +/**************************************************************************** + * Name:ads7843e_ioctl + ****************************************************************************/ + +static int ads7843e_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode; + FAR struct ads7843e_dev_s *priv; + int ret; + + ivdbg("cmd: %d arg: %ld\n", cmd, arg); + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct ads7843e_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Process the IOCTL by command */ + + switch (cmd) + { + case TSIOC_SETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ + { + FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); + DEBUGASSERT(priv->config != NULL && ptr != NULL); + priv->config->frequency = SPI_SETFREQUENCY(priv->spi, *ptr); + } + break; + + case TSIOC_GETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ + { + FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); + DEBUGASSERT(priv->config != NULL && ptr != NULL); + *ptr = priv->config->frequency; + } + break; + + default: + ret = -ENOTTY; + break; + } + + sem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name: ads7843e_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int ads7843e_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode; + FAR struct ads7843e_dev_s *priv; + pollevent_t eventset; + int ndx; + int ret = OK; + int i; + + ivdbg("setup: %d\n", (int)setup); + DEBUGASSERT(filep && fds); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct ads7843e_dev_s *)inode->i_private; + + /* Are we setting up the poll? Or tearing it down? */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + if (setup) + { + /* Ignore waits that do not include POLLIN */ + + if ((fds->events & POLLIN) == 0) + { + ret = -EDEADLK; + goto errout; + } + + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference + */ + + for (i = 0; i < CONFIG_ADS7843E_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!priv->fds[i]) + { + /* Bind the poll structure and this slot */ + + priv->fds[i] = fds; + fds->priv = &priv->fds[i]; + break; + } + } + + if (i >= CONFIG_ADS7843E_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto errout; + } + + /* Should we immediately notify on any of the requested events? */ + + if (priv->penchange) + { + ads7843e_notify(priv); + } + } + else if (fds->priv) + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + DEBUGASSERT(slot != NULL); + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +errout: + sem_post(&priv->devsem); + return ret; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ads7843e_register + * + * Description: + * Configure the ADS7843E to use the provided SPI device instance. This + * will register the driver as /dev/inputN where N is the minor device + * number + * + * Input Parameters: + * dev - An SPI driver instance + * config - Persistent board configuration data + * minor - The input device minor number + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int ads7843e_register(FAR struct spi_dev_s *dev, + FAR struct ads7843e_config_s *config, int minor) +{ + FAR struct ads7843e_dev_s *priv; + char devname[DEV_NAMELEN]; +#ifdef CONFIG_ADS7843E_MULTIPLE + irqstate_t flags; +#endif + int ret; + + ivdbg("dev: %p minor: %d\n", dev, minor); + + /* Debug-only sanity checks */ + + DEBUGASSERT(dev != NULL && config != NULL && minor >= 0 && minor < 100); + + /* Create and initialize a ADS7843E device driver instance */ + +#ifndef CONFIG_ADS7843E_MULTIPLE + priv = &g_ads7843e; +#else + priv = (FAR struct ads7843e_dev_s *)kmalloc(sizeof(struct ads7843e_dev_s)); + if (!priv) + { + idbg("kmalloc(%d) failed\n", sizeof(struct ads7843e_dev_s)); + return -ENOMEM; + } +#endif + + /* Initialize the ADS7843E device driver instance */ + + memset(priv, 0, sizeof(struct ads7843e_dev_s)); + priv->spi = dev; /* Save the SPI device handle */ + priv->config = config; /* Save the board configuration */ + priv->wdog = wd_create(); /* Create a watchdog timer */ + sem_init(&priv->devsem, 0, 1); /* Initialize device structure semaphore */ + sem_init(&priv->waitsem, 0, 0); /* Initialize pen event wait semaphore */ + + /* Make sure that interrupts are disabled */ + + config->clear(config); + config->enable(config, false); + + /* Attach the interrupt handler */ + + ret = config->attach(config, ads7843e_interrupt); + if (ret < 0) + { + idbg("Failed to attach interrupt\n"); + goto errout_with_priv; + } + + /* Configure the SPI interface */ + + ads7843e_configspi(dev); + + /* Enable the PEN IRQ */ + + ads7843e_sendcmd(priv, ADS7843_CMD_ENABPINIRQ); + + /* Register the device as an input device */ + + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, minor); + ivdbg("Registering %s\n", devname); + + ret = register_driver(devname, &ads7843e_fops, 0666, priv); + if (ret < 0) + { + idbg("register_driver() failed: %d\n", ret); + goto errout_with_priv; + } + + /* If multiple ADS7843E devices are supported, then we will need to add + * this new instance to a list of device instances so that it can be + * found by the interrupt handler based on the recieved IRQ number. + */ + +#ifdef CONFIG_ADS7843E_MULTIPLE + priv->flink = g_ads7843elist; + g_ads7843elist = priv; + irqrestore(flags); +#endif + + /* Schedule work to perform the initial sampling and to set the data + * availability conditions. + */ + + ret = work_queue(&priv->work, ads7843e_worker, priv, 0); + if (ret != 0) + { + idbg("Failed to queue work: %d\n", ret); + goto errout_with_priv; + } + + /* And return success (?) */ + + return OK; + +errout_with_priv: + sem_destroy(&priv->devsem); +#ifdef CONFIG_ADS7843E_MULTIPLE + kfree(priv); +#endif + return ret; +} diff --git a/nuttx/drivers/input/ads7843e.h b/nuttx/drivers/input/ads7843e.h new file mode 100644 index 000000000..7a534099e --- /dev/null +++ b/nuttx/drivers/input/ads7843e.h @@ -0,0 +1,170 @@ +/******************************************************************************************** + * drivers/input/ads7843e.h + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * References: + * "Touch Screen Controller, ADS7843," Burr-Brown Products from Texas + * Instruments, SBAS090B, September 2000, Revised May 2002" + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ********************************************************************************************/ + +#ifndef __DRIVERS_INPUT_ADS7843E_H +#define __DRIVERS_INPUT_ADS7843E_H + +/******************************************************************************************** + * Included Files + ********************************************************************************************/ + +#include <nuttx/config.h> + +#include <stdint.h> +#include <semaphore.h> +#include <poll.h> +#include <wdog.h> +#include <nuttx/wqueue.h> + +#include <nuttx/spi.h> +#include <nuttx/clock.h> +#include <nuttx/input/ads7843e.h> + +/******************************************************************************************** + * Pre-Processor Definitions + ********************************************************************************************/ +/* Configuration ****************************************************************************/ +/* Reference counting is partially implemented, but not needed in the current design. */ + +#undef CONFIG_ADS7843E_REFCNT + +/* ADS7843E Interfaces *********************************************************************/ +/* ADS7843E command bit settings */ + +#define ADS7843E_CMD_PD0 (1 << 0) /* PD0 */ +#define ADS7843E_CMD_PD1 (1 << 1) /* PD1 */ +#define ADS7843E_CMD_DFR (1 << 2) /* SER/DFR */ +#define ADS7843E_CMD_EIGHT_BITS_MOD (1 << 3) /* Mode */ +#define ADS7843E_CMD_START (1 << 7) /* Start Bit */ +#define ADS7843E_CMD_SWITCH_SHIFT 4 /* Address setting */ + +/* ADS7843E Commands */ + +#define ADS7843_CMD_YPOSITION \ + ((1 << ADS7843E_CMD_SWITCH_SHIFT)|ADS7843E_CMD_START|ADS7843E_CMD_PD0|ADS7843E_CMD_PD1) +#define ADS7843_CMD_XPOSITION \ + ((5 << ADS7843E_CMD_SWITCH_SHIFT)|ADS7843E_CMD_START|ADS7843E_CMD_PD0|ADS7843E_CMD_PD1) +#define ADS7843_CMD_ENABPINIRQ \ + ((1 << ADS7843E_CMD_SWITCH_SHIFT)|ADS7843E_CMD_START) + +/* Driver support **************************************************************************/ +/* This format is used to construct the /dev/input[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/input%d" +#define DEV_NAMELEN 16 + +/* Poll the pen position while the pen is down at this rate (50MS): */ + +#define ADS7843E_WDOG_DELAY ((50 + (MSEC_PER_TICK-1))/ MSEC_PER_TICK) + +/******************************************************************************************** + * Public Types + ********************************************************************************************/ + +/* This describes the state of one contact */ + +enum ads7843e_contact_3 +{ + CONTACT_NONE = 0, /* No contact */ + CONTACT_DOWN, /* First contact */ + CONTACT_MOVE, /* Same contact, possibly different position */ + CONTACT_UP, /* Contact lost */ +}; + +/* This structure describes the results of one ADS7843E sample */ + +struct ads7843e_sample_s +{ + uint8_t id; /* Sampled touch point ID */ + uint8_t contact; /* Contact state (see enum ads7843e_contact_e) */ + uint16_t x; /* Measured X position */ + uint16_t y; /* Measured Y position */ +}; + +/* This structure describes the state of one ADS7843E driver instance */ + +struct ads7843e_dev_s +{ +#ifdef CONFIG_ADS7843E_MULTIPLE + FAR struct ads7843e_dev_s *flink; /* Supports a singly linked list of drivers */ +#endif +#ifdef CONFIG_ADS7843E_REFCNT + uint8_t crefs; /* Number of times the device has been opened */ +#endif + uint8_t nwaiters; /* Number of threads waiting for ADS7843E data */ + uint8_t id; /* Current touch point ID */ + volatile bool penchange; /* An unreported event is buffered */ + sem_t devsem; /* Manages exclusive access to this structure */ + sem_t waitsem; /* Used to wait for the availability of data */ + + FAR struct ads7843e_config_s *config; /* Board configuration data */ + FAR struct spi_dev_s *spi; /* Saved SPI driver instance */ + struct work_s work; /* Supports the interrupt handling "bottom half" */ + struct ads7843e_sample_s sample; /* Last sampled touch point data */ + WDOG_ID wdog; /* Poll the position while the pen is down */ + + /* The following is a list if poll structures of threads waiting for + * driver events. The 'struct pollfd' reference for each open is also + * retained in the f_priv field of the 'struct file'. + */ + +#ifndef CONFIG_DISABLE_POLL + struct pollfd *fds[CONFIG_ADS7843E_NPOLLWAITERS]; +#endif +}; + +/******************************************************************************************** + * Public Function Prototypes + ********************************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __DRIVERS_INPUT_ADS7843E_H */ diff --git a/nuttx/drivers/input/tsc2007.c b/nuttx/drivers/input/tsc2007.c new file mode 100644 index 000000000..3afe926fe --- /dev/null +++ b/nuttx/drivers/input/tsc2007.c @@ -0,0 +1,1310 @@ +/**************************************************************************** + * drivers/input/tsc2007.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * References: + * "1.2V to 3.6V, 12-Bit, Nanopower, 4-Wire Micro TOUCH SCREEN CONTROLLER + * with I2C Interface," SBAS405A March 2007, Revised, March 2009, Texas + * Instruments Incorporated + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/* The TSC2007 is an analog interface circuit for a human interface touch + * screen device. All peripheral functions are controlled through the command + * byte and onboard state machines. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> + +#include <stdbool.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <semaphore.h> +#include <poll.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/fs.h> +#include <nuttx/i2c.h> +#include <nuttx/wqueue.h> + +#include <nuttx/input/touchscreen.h> +#include <nuttx/input/tsc2007.h> + +#include "tsc2007.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ +/* Reference counting is partially implemented, but not needed in the + * current design. + */ + +#undef CONFIG_TSC2007_REFCNT + +/* I don't think that it is necessary to activate the converters before + * making meaurements. However, I will keep this functionality enabled + * until I have a change to prove that that activation is unnecessary. + */ + +#undef CONFIG_TSC2007_ACTIVATE +#define CONFIG_TSC2007_ACTIVATE 1 + +/* Driver support ***********************************************************/ +/* This format is used to construct the /dev/input[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/input%d" +#define DEV_NAMELEN 16 + +/* Commands *****************************************************************/ + +#define TSC2007_SETUP (TSC2007_CMD_FUNC_SETUP) +#ifdef CONFIG_TSC2007_8BIT +# define TSC2007_ACTIVATE_Y (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YON) +# define TSC2007_MEASURE_Y (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YPOS) +# define TSC2007_ACTIVATE_X (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XON) +# define TSC2007_MEASURE_X (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XPOS) +# define TSC2007_ACTIVATE_Z (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YXON) +# define TSC2007_MEASURE_Z1 (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z1POS) +# define TSC2007_MEASURE_Z2 (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z2POS) +# define TSC2007_ENABLE_PENIRQ (TSC2007_CMD_8BIT | TSC2007_CMD_PWRDN_IRQEN) +#else +# define TSC2007_ACTIVATE_Y (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YON) +# define TSC2007_MEASURE_Y (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YPOS) +# define TSC2007_ACTIVATE_X (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XON) +# define TSC2007_MEASURE_X (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XPOS) +# define TSC2007_ACTIVATE_Z (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YXON) +# define TSC2007_MEASURE_Z1 (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z1POS) +# define TSC2007_MEASURE_Z2 (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z2POS) +# define TSC2007_ENABLE_PENIRQ (TSC2007_CMD_12BIT | TSC2007_CMD_PWRDN_IRQEN) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This describes the state of one contact */ + +enum tsc2007_contact_3 +{ + CONTACT_NONE = 0, /* No contact */ + CONTACT_DOWN, /* First contact */ + CONTACT_MOVE, /* Same contact, possibly different position */ + CONTACT_UP, /* Contact lost */ +}; + +/* This structure describes the results of one TSC2007 sample */ + +struct tsc2007_sample_s +{ + uint8_t id; /* Sampled touch point ID */ + uint8_t contact; /* Contact state (see enum tsc2007_contact_e) */ + uint16_t x; /* Measured X position */ + uint16_t y; /* Measured Y position */ + uint16_t pressure; /* Calculated pressure */ +}; + +/* This structure describes the state of one TSC2007 driver instance */ + +struct tsc2007_dev_s +{ +#ifdef CONFIG_TSC2007_MULTIPLE + FAR struct tsc2007_dev_s *flink; /* Supports a singly linked list of drivers */ +#endif +#ifdef CONFIG_TSC2007_REFCNT + uint8_t crefs; /* Number of times the device has been opened */ +#endif + uint8_t nwaiters; /* Number of threads waiting for TSC2007 data */ + uint8_t id; /* Current touch point ID */ + volatile bool penchange; /* An unreported event is buffered */ + sem_t devsem; /* Manages exclusive access to this structure */ + sem_t waitsem; /* Used to wait for the availability of data */ + + FAR struct tsc2007_config_s *config; /* Board configuration data */ + FAR struct i2c_dev_s *i2c; /* Saved I2C driver instance */ + struct work_s work; /* Supports the interrupt handling "bottom half" */ + struct tsc2007_sample_s sample; /* Last sampled touch point data */ + + /* The following is a list if poll structures of threads waiting for + * driver events. The 'struct pollfd' reference for each open is also + * retained in the f_priv field of the 'struct file'. + */ + +#ifndef CONFIG_DISABLE_POLL + struct pollfd *fds[CONFIG_TSC2007_NPOLLWAITERS]; +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void tsc2007_notify(FAR struct tsc2007_dev_s *priv); +static int tsc2007_sample(FAR struct tsc2007_dev_s *priv, + FAR struct tsc2007_sample_s *sample); +static int tsc2007_waitsample(FAR struct tsc2007_dev_s *priv, + FAR struct tsc2007_sample_s *sample); +#ifdef CONFIG_TSC2007_ACTIVATE +static int tsc2007_activate(FAR struct tsc2007_dev_s *priv, uint8_t cmd); +#endif +static int tsc2007_transfer(FAR struct tsc2007_dev_s *priv, uint8_t cmd); +static void tsc2007_worker(FAR void *arg); +static int tsc2007_interrupt(int irq, FAR void *context); + +/* Character driver methods */ + +static int tsc2007_open(FAR struct file *filep); +static int tsc2007_close(FAR struct file *filep); +static ssize_t tsc2007_read(FAR struct file *filep, FAR char *buffer, size_t len); +static int tsc2007_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +#ifndef CONFIG_DISABLE_POLL +static int tsc2007_poll(FAR struct file *filep, struct pollfd *fds, bool setup); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This the the vtable that supports the character driver interface */ + +static const struct file_operations tsc2007_fops = +{ + tsc2007_open, /* open */ + tsc2007_close, /* close */ + tsc2007_read, /* read */ + 0, /* write */ + 0, /* seek */ + tsc2007_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , tsc2007_poll /* poll */ +#endif +}; + +/* If only a single TSC2007 device is supported, then the driver state + * structure may as well be pre-allocated. + */ + +#ifndef CONFIG_TSC2007_MULTIPLE +static struct tsc2007_dev_s g_tsc2007; + +/* Otherwise, we will need to maintain allocated driver instances in a list */ + +#else +static struct tsc2007_dev_s *g_tsc2007list; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tsc2007_notify + ****************************************************************************/ + +static void tsc2007_notify(FAR struct tsc2007_dev_s *priv) +{ +#ifndef CONFIG_DISABLE_POLL + int i; +#endif + + /* If there are threads waiting for read data, then signal one of them + * that the read data is available. + */ + + if (priv->nwaiters > 0) + { + /* After posting this semaphore, we need to exit because the TSC2007 + * is no longer avaialable. + */ + + sem_post(&priv->waitsem); + } + + /* If there are threads waiting on poll() for TSC2007 data to become availabe, + * then wake them up now. NOTE: we wake up all waiting threads because we + * do not know that they are going to do. If they all try to read the data, + * then some make end up blocking after all. + */ + +#ifndef CONFIG_DISABLE_POLL + for (i = 0; i < CONFIG_TSC2007_NPOLLWAITERS; i++) + { + struct pollfd *fds = priv->fds[i]; + if (fds) + { + fds->revents |= POLLIN; + ivdbg("Report events: %02x\n", fds->revents); + sem_post(fds->sem); + } + } +#endif +} + +/**************************************************************************** + * Name: tsc2007_sample + ****************************************************************************/ + +static int tsc2007_sample(FAR struct tsc2007_dev_s *priv, + FAR struct tsc2007_sample_s *sample) +{ + irqstate_t flags; + int ret = -EAGAIN; + + /* Interrupts me be disabled when this is called to (1) prevent posting + * of semphores from interrupt handlers, and (2) to prevent sampled data + * from changing until it has been reported. + */ + + flags = irqsave(); + + /* Is there new TSC2007 sample data available? */ + + if (priv->penchange) + { + /* Yes.. the state has changed in some way. Return a copy of the + * sampled data. + */ + + memcpy(sample, &priv->sample, sizeof(struct tsc2007_sample_s )); + + /* Now manage state transitions */ + + if (sample->contact == CONTACT_UP) + { + /* Next.. no contract. Increment the ID so that next contact ID will be unique */ + + priv->sample.contact = CONTACT_NONE; + priv->id++; + } + else if (sample->contact == CONTACT_DOWN) + { + /* First report -- next report will be a movement */ + + priv->sample.contact = CONTACT_MOVE; + } + + priv->penchange = false; + ret = OK; + } + + irqrestore(flags); + return ret; +} + +/**************************************************************************** + * Name: tsc2007_waitsample + ****************************************************************************/ + +static int tsc2007_waitsample(FAR struct tsc2007_dev_s *priv, + FAR struct tsc2007_sample_s *sample) +{ + irqstate_t flags; + int ret; + + /* Interrupts me be disabled when this is called to (1) prevent posting + * of semphores from interrupt handlers, and (2) to prevent sampled data + * from changing until it has been reported. + * + * In addition, we will also disable pre-emption to prevent other threads + * from getting control while we muck with the semaphores. + */ + + sched_lock(); + flags = irqsave(); + + /* Now release the semaphore that manages mutually exclusive access to + * the device structure. This may cause other tasks to become ready to + * run, but they cannot run yet because pre-emption is disabled. + */ + + sem_post(&priv->devsem); + + /* Try to get the a sample... if we cannot, then wait on the semaphore + * that is posted when new sample data is availble. + */ + + while (tsc2007_sample(priv, sample) < 0) + { + /* Wait for a change in the TSC2007 state */ + + priv->nwaiters++; + ret = sem_wait(&priv->waitsem); + priv->nwaiters--; + + if (ret < 0) + { + /* If we are awakened by a signal, then we need to return + * the failure now. + */ + + DEBUGASSERT(errno == EINTR); + ret = -EINTR; + goto errout; + } + } + + /* Re-acquire the the semaphore that manages mutually exclusive access to + * the device structure. We may have to wait here. But we have our sample. + * Interrupts and pre-emption will be re-enabled while we wait. + */ + + ret = sem_wait(&priv->devsem); + +errout: + /* Then re-enable interrupts. We might get interrupt here and there + * could be a new sample. But no new threads will run because we still + * have pre-emption disabled. + */ + + irqrestore(flags); + + /* Restore pre-emption. We might get suspended here but that is okay + * because we already have our sample. Note: this means that if there + * were two threads reading from the TSC2007 for some reason, the data + * might be read out of order. + */ + + sched_unlock(); + return ret; +} + +/**************************************************************************** + * Name: tsc2007_activate + ****************************************************************************/ + +#ifdef CONFIG_TSC2007_ACTIVATE +static int tsc2007_activate(FAR struct tsc2007_dev_s *priv, uint8_t cmd) +{ + struct i2c_msg_s msg; + uint8_t data; + int ret; + + /* Send the setup command (with no ACK) followed by the A/D converter + * activation command (ACKed). + */ + + data = TSC2007_SETUP; + + msg.addr = priv->config->address; /* 7-bit address */ + msg.flags = 0; /* Write transaction, beginning with START */ + msg.buffer = &data; /* Transfer from this address */ + msg.length = 1; /* Send one byte following the address */ + + /* Ignore errors from the setup command (because it is not ACKed) */ + + (void)I2C_TRANSFER(priv->i2c, &msg, 1); + + /* Now activate the A/D converter */ + + data = cmd; + + msg.addr = priv->config->address; /* 7-bit address */ + msg.flags = 0; /* Write transaction, beginning with START */ + msg.buffer = &data; /* Transfer from this address */ + msg.length = 1; /* Send one byte following the address */ + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret < 0) + { + idbg("I2C_TRANSFER failed: %d\n", ret); + } + return ret; +} +#else +# define tsc2007_activate(p,c) +#endif + +/**************************************************************************** + * Name: tsc2007_transfer + ****************************************************************************/ + +static int tsc2007_transfer(FAR struct tsc2007_dev_s *priv, uint8_t cmd) +{ + struct i2c_msg_s msg; + uint8_t data12[2]; + int ret; + + /* "A conversion/write cycle begins when the master issues the address + * byte containing the slave address of the TSC2007, with the eighth bit + * equal to a 0 (R/W = 0)... Once the eighth bit has been received... + * the TSC2007 issues an acknowledge. + * + * "When the master receives the acknowledge bit from the TSC2007, the + * master writes the command byte to the slave... After the command byte + * is received by the slave, the slave issues another acknowledge bit. + * The master then ends the write cycle by issuing a repeated START or a + * STOP condition... + */ + + msg.addr = priv->config->address; /* 7-bit address */ + msg.flags = 0; /* Write transaction, beginning with START */ + msg.buffer = &cmd; /* Transfer from this address */ + msg.length = 1; /* Send one byte following the address */ + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret < 0) + { + idbg("I2C_TRANSFER failed: %d\n", ret); + return ret; + } + + /* "The input multiplexer channel for the A/D converter is selected when + * bits C3 through C0 are clocked in. If the selected channel is an X-,Y-, + * or Z-position measurement, the appropriate drivers turn on once the + * acquisition period begins. + * + * "... the input sample acquisition period starts on the falling edge of + * SCL when the C0 bit of the command byte has been latched, and ends + * when a STOP or repeated START condition has been issued. A/D conversion + * starts immediately after the acquisition period... + * + * "For best performance, the I2C bus should remain in an idle state while + * an A/D conversion is taking place. ... The master should wait for at + * least 10ms before attempting to read data from the TSC2007... + */ + + usleep(10*1000); + + /* "Data access begins with the master issuing a START condition followed + * by the address byte ... with R/W = 1. + * + * "When the eighth bit has been received and the address matches, the + * slave issues an acknowledge. The first byte of serial data then follows + * (D11-D4, MSB first). + * + * "After the first byte has been sent by the slave, it releases the SDA line + * for the master to issue an acknowledge. The slave responds with the + * second byte of serial data upon receiving the acknowledge from the master + * (D3-D0, followed by four 0 bits). The second byte is followed by a NOT + * acknowledge bit (ACK = 1) from the master to indicate that the last + * data byte has been received... + */ + + msg.addr = priv->config->address; /* 7-bit address */ + msg.flags = I2C_M_READ; /* Read transaction, beginning with START */ + msg.buffer = data12; /* Transfer to this address */ + msg.length = 2; /* Read two bytes following the address */ + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret < 0) + { + idbg("I2C_TRANSFER failed: %d\n", ret); + return ret; + } + + /* Get the MS 8 bits from the first byte and the remaining LS 4 bits from + * the second byte. The valid range of data is then from 0 to 4095 with + * the LSB unit corresponding to Vref/4096. + */ + + ret = (unsigned int)data12[0] << 4 | (unsigned int)data12[1] >> 4; + ivdbg("data: 0x%04x\n", ret); + return ret; +} + +/**************************************************************************** + * Name: tsc2007_worker + ****************************************************************************/ + +static void tsc2007_worker(FAR void *arg) +{ + FAR struct tsc2007_dev_s *priv = (FAR struct tsc2007_dev_s *)arg; + FAR struct tsc2007_config_s *config; /* Convenience pointer */ + bool pendown; /* true: pend is down */ + uint16_t x; /* X position */ + uint16_t y; /* Y position */ + uint16_t z1; /* Z1 position */ + uint16_t z2; /* Z2 position */ + uint32_t pressure; /* Measured pressure */ + + ASSERT(priv != NULL); + + /* Get a pointer the callbacks for convenience (and so the code is not so + * ugly). + */ + + config = priv->config; + DEBUGASSERT(config != NULL); + + /* Check for pen up or down by reading the PENIRQ GPIO. */ + + pendown = config->pendown(config); + + /* Handle the change from pen down to pen up */ + + if (!pendown) + { + /* Ignore the interrupt if the pen was already down (CONTACT_NONE == pen up and + * already reported. CONTACT_UP == pen up, but not reported) + */ + + if (priv->sample.contact == CONTACT_NONE) + { + goto errout; + } + } + else + { + /* Handle all pen down events. First, sample X, Y, Z1, and Z2 values. + * + * "A resistive touch screen operates by applying a voltage across a + * resistor network and measuring the change in resistance at a given + * point on the matrix where the screen is touched by an input (stylus, + * pen, or finger). The change in the resistance ratio marks the location + * on the touch screen. + * + * "The 4-wire touch screen panel works by applying a voltage across the + * vertical or horizontal resistive network. The A/D converter converts + * the voltage measured at the point where the panel is touched. A measurement + * of the Y position of the pointing device is made by connecting the X+ + * input to a data converter chip, turning on the Y+ and Y– drivers, and + * digitizing the voltage seen at the X+ input ..." + * + * "... it is recommended that whenever the host writes to the TSC2007, the + * master processor masks the interrupt associated to PENIRQ. This masking + * prevents false triggering of interrupts when the PENIRQ line is disabled + * in the cases previously listed." + */ + + (void)tsc2007_activate(priv, TSC2007_ACTIVATE_X); + y = tsc2007_transfer(priv, TSC2007_MEASURE_Y); + + + /* "Voltage is then applied to the other axis, and the A/D converter + * converts the voltage representing the X position on the screen. This + * process provides the X and Y coordinates to the associated processor." + */ + + (void)tsc2007_activate(priv, TSC2007_ACTIVATE_Y); + x = tsc2007_transfer(priv, TSC2007_MEASURE_X); + + /* "... To determine pen or finger touch, the pressure of the touch must be + * determined. ... There are several different ways of performing this + * measurement. The TSC2007 supports two methods. The first method requires + * knowing the X-plate resistance, the measurement of the X-position, and two + * additional cross panel measurements (Z2 and Z1) of the touch screen." + * + * Rtouch = Rxplate * (X / 4096)* (Z2/Z1 - 1) + * + * "The second method requires knowing both the X-plate and Y-plate + * resistance, measurement of X-position and Y-position, and Z1 ..." + * + * Rtouch = Rxplate * (X / 4096) * (4096/Z1 - 1) - Ryplate * (1 - Y/4096) + * + * Read Z1 and Z2 values. + */ + + (void)tsc2007_activate(priv, TSC2007_ACTIVATE_Z); + z1 = tsc2007_transfer(priv, TSC2007_MEASURE_Z1); + (void)tsc2007_activate(priv, TSC2007_ACTIVATE_Z); + z2 = tsc2007_transfer(priv, TSC2007_MEASURE_Z2); + + /* Power down ADC and enable PENIRQ */ + + (void)tsc2007_transfer(priv, TSC2007_ENABLE_PENIRQ); + + /* Now calculate the pressure using the first method, reduced to: + * + * Rtouch = X * Rxplate *(Z2 - Z1) * / Z1 / 4096 + */ + + if (z1 == 0) + { + idbg("Z1 zero\n"); + pressure = 0; + } + else + { + pressure = (x * config->rxplate * (z2 - z1)) / z1; + pressure = (pressure + 2048) >> 12; + + ivdbg("Position: (%d,%4d) pressure: %u z1/2: (%d,%d)\n", + x, y, pressure, z1, z2); + + /* Ignore out of range caculcations */ + + if (pressure > 0x0fff) + { + idbg("Dropped out-of-range pressure: %d\n", pressure); + pressure = 0; + } + } + + /* Save the measurements */ + + priv->sample.x = x; + priv->sample.y = y; + priv->sample.pressure = pressure; + } + + /* Note the availability of new measurements */ + + if (pendown) + { + /* If this is the first (acknowledged) pend down report, then report + * this as the first contact. If contact == CONTACT_DOWN, it will be + * set to set to CONTACT_MOVE after the contact is first sampled. + */ + + if (priv->sample.contact != CONTACT_MOVE) + { + /* First contact */ + + priv->sample.contact = CONTACT_DOWN; + } + } + else /* if (priv->sample.contact != CONTACT_NONE) */ + { + /* The pen is up. NOTE: We know from a previous test, that this is a + * loss of contact condition. This will be changed to CONTACT_NONE + * after the loss of contact is sampled. + */ + + priv->sample.contact = CONTACT_UP; + } + + /* Indicate the availability of new sample data for this ID */ + + priv->sample.id = priv->id; + priv->penchange = true; + + /* Notify any waiters that nes TSC2007 data is available */ + + tsc2007_notify(priv); + + /* Exit, re-enabling TSC2007 interrupts */ + +errout: + config->enable(config, true); +} + +/**************************************************************************** + * Name: tsc2007_interrupt + ****************************************************************************/ + +static int tsc2007_interrupt(int irq, FAR void *context) +{ + FAR struct tsc2007_dev_s *priv; + FAR struct tsc2007_config_s *config; + int ret; + + /* Which TSC2007 device caused the interrupt? */ + +#ifndef CONFIG_TSC2007_MULTIPLE + priv = &g_tsc2007; +#else + for (priv = g_tsc2007list; + priv && priv->configs->irq != irq; + priv = priv->flink); + + ASSERT(priv != NULL); +#endif + + /* Get a pointer the callbacks for convenience (and so the code is not so + * ugly). + */ + + config = priv->config; + DEBUGASSERT(config != NULL); + + /* Disable further interrupts */ + + config->enable(config, false); + + /* Transfer processing to the worker thread. Since TSC2007 interrupts are + * disabled while the work is pending, no special action should be required + * to protected the work queue. + */ + + DEBUGASSERT(priv->work.worker == NULL); + ret = work_queue(&priv->work, tsc2007_worker, priv, 0); + if (ret != 0) + { + illdbg("Failed to queue work: %d\n", ret); + } + + /* Clear any pending interrupts and return success */ + + config->clear(config); + return OK; +} + +/**************************************************************************** + * Name: tsc2007_open + ****************************************************************************/ + +static int tsc2007_open(FAR struct file *filep) +{ +#ifdef CONFIG_TSC2007_REFCNT + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + uint8_t tmp; + int ret; + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Increment the reference count */ + + tmp = priv->crefs + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + goto errout_with_sem; + } + + /* When the reference increments to 1, this is the first open event + * on the driver.. and an opportunity to do any one-time initialization. + */ + + /* Save the new open count on success */ + + priv->crefs = tmp; + +errout_with_sem: + sem_post(&priv->devsem); + return ret; +#else + return OK; +#endif +} + +/**************************************************************************** + * Name: tsc2007_close + ****************************************************************************/ + +static int tsc2007_close(FAR struct file *filep) +{ +#ifdef CONFIG_TSC2007_REFCNT + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + int ret; + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Decrement the reference count unless it would decrement a negative + * value. When the count decrements to zero, there are no further + * open references to the driver. + */ + + if (priv->crefs >= 1) + { + priv->crefs--; + } + + sem_post(&priv->devsem); +#endif + return OK; +} + +/**************************************************************************** + * Name: tsc2007_read + ****************************************************************************/ + +static ssize_t tsc2007_read(FAR struct file *filep, FAR char *buffer, size_t len) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + FAR struct touch_sample_s *report; + struct tsc2007_sample_s sample; + int ret; + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Verify that the caller has provided a buffer large enough to receive + * the touch data. + */ + + if (len < SIZEOF_TOUCH_SAMPLE_S(1)) + { + /* We could provide logic to break up a touch report into segments and + * handle smaller reads... but why? + */ + + return -ENOSYS; + } + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Try to read sample data. */ + + ret = tsc2007_sample(priv, &sample); + if (ret < 0) + { + /* Sample data is not available now. We would ave to wait to get + * receive sample data. If the user has specified the O_NONBLOCK + * option, then just return an error. + */ + + if (filep->f_oflags & O_NONBLOCK) + { + ret = -EAGAIN; + goto errout; + } + + /* Wait for sample data */ + + ret = tsc2007_waitsample(priv, &sample); + if (ret < 0) + { + /* We might have been awakened by a signal */ + + goto errout; + } + } + + /* In any event, we now have sampled TSC2007 data that we can report + * to the caller. + */ + + report = (FAR struct touch_sample_s *)buffer; + memset(report, 0, SIZEOF_TOUCH_SAMPLE_S(1)); + report->npoints = 1; + report->point[0].id = priv->id; + report->point[0].x = sample.x; + report->point[0].y = sample.y; + report->point[0].pressure = sample.pressure; + + /* Report the appropriate flags */ + + if (sample.contact == CONTACT_UP) + { + /* Pen is now up */ + + report->point[0].flags = TOUCH_UP | TOUCH_ID_VALID; + } + else + { + if (sample.contact == CONTACT_DOWN) + { + /* First contact */ + + report->point[0].flags = TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID; + } + else /* if (sample->contact == CONTACT_MOVE) */ + { + /* Movement of the same contact */ + + report->point[0].flags = TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID; + } + + /* A pressure measurement of zero means that pressure is not available */ + + if (report->point[0].pressure != 0) + { + report->point[0].flags |= TOUCH_PRESSURE_VALID; + } + } + + ret = SIZEOF_TOUCH_SAMPLE_S(1); + +errout: + sem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name:tsc2007_ioctl + ****************************************************************************/ + +static int tsc2007_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + int ret; + + ivdbg("cmd: %d arg: %ld\n", cmd, arg); + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Process the IOCTL by command */ + + switch (cmd) + { + case TSIOC_SETCALIB: /* arg: Pointer to int calibration value */ + { + FAR int *ptr = (FAR int *)((uintptr_t)arg); + DEBUGASSERT(priv->config != NULL && ptr != NULL); + priv->config->rxplate = *ptr; + } + break; + + case TSIOC_GETCALIB: /* arg: Pointer to int calibration value */ + { + FAR int *ptr = (FAR int *)((uintptr_t)arg); + DEBUGASSERT(priv->config != NULL && ptr != NULL); + *ptr = priv->config->rxplate; + } + break; + + case TSIOC_SETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ + { + FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); + DEBUGASSERT(priv->config != NULL && ptr != NULL); + priv->config->frequency = I2C_SETFREQUENCY(priv->i2c, *ptr); + } + break; + + case TSIOC_GETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ + { + FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); + DEBUGASSERT(priv->config != NULL && ptr != NULL); + *ptr = priv->config->frequency; + } + break; + + default: + ret = -ENOTTY; + break; + } + + sem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name: tsc2007_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int tsc2007_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + int ret; + int i; + + ivdbg("setup: %d\n", (int)setup); + DEBUGASSERT(filep && fds); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Are we setting up the poll? Or tearing it down? */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + if (setup) + { + /* Ignore waits that do not include POLLIN */ + + if ((fds->events & POLLIN) == 0) + { + idbg("Missing POLLIN: revents: %08x\n", fds->revents); + ret = -EDEADLK; + goto errout; + } + + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference + */ + + for (i = 0; i < CONFIG_TSC2007_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!priv->fds[i]) + { + /* Bind the poll structure and this slot */ + + priv->fds[i] = fds; + fds->priv = &priv->fds[i]; + break; + } + } + + if (i >= CONFIG_TSC2007_NPOLLWAITERS) + { + idbg("No availabled slot found: %d\n", i); + fds->priv = NULL; + ret = -EBUSY; + goto errout; + } + + /* Should we immediately notify on any of the requested events? */ + + if (priv->penchange) + { + tsc2007_notify(priv); + } + } + else if (fds->priv) + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + DEBUGASSERT(slot != NULL); + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +errout: + sem_post(&priv->devsem); + return ret; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tsc2007_register + * + * Description: + * Configure the TSC2007 to use the provided I2C device instance. This + * will register the driver as /dev/inputN where N is the minor device + * number + * + * Input Parameters: + * dev - An I2C driver instance + * config - Persistant board configuration data + * minor - The input device minor number + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int tsc2007_register(FAR struct i2c_dev_s *dev, + FAR struct tsc2007_config_s *config, int minor) +{ + FAR struct tsc2007_dev_s *priv; + char devname[DEV_NAMELEN]; +#ifdef CONFIG_TSC2007_MULTIPLE + irqstate_t flags; +#endif + int ret; + + ivdbg("dev: %p minor: %d\n", dev, minor); + + /* Debug-only sanity checks */ + + DEBUGASSERT(dev != NULL && config != NULL && minor >= 0 && minor < 100); + DEBUGASSERT((config->address & 0xfc) == 0x48); + DEBUGASSERT(config->attach != NULL && config->enable != NULL && + config->clear != NULL && config->pendown != NULL); + + /* Create and initialize a TSC2007 device driver instance */ + +#ifndef CONFIG_TSC2007_MULTIPLE + priv = &g_tsc2007; +#else + priv = (FAR struct tsc2007_dev_s *)kmalloc(sizeof(struct tsc2007_dev_s)); + if (!priv) + { + idbg("kmalloc(%d) failed\n", sizeof(struct tsc2007_dev_s)); + return -ENOMEM; + } +#endif + + /* Initialize the TSC2007 device driver instance */ + + memset(priv, 0, sizeof(struct tsc2007_dev_s)); + priv->i2c = dev; /* Save the I2C device handle */ + priv->config = config; /* Save the board configuration */ + sem_init(&priv->devsem, 0, 1); /* Initialize device structure semaphore */ + sem_init(&priv->waitsem, 0, 0); /* Initialize pen event wait semaphore */ + + /* Set the I2C frequency (saving the actual frequency) */ + + config->frequency = I2C_SETFREQUENCY(dev, config->frequency); + + /* Set the I2C address and address size */ + + ret = I2C_SETADDRESS(dev, config->address, 7); + if (ret < 0) + { + idbg("I2C_SETADDRESS failed: %d\n", ret); + goto errout_with_priv; + } + + /* Make sure that interrupts are disabled */ + + config->clear(config); + config->enable(config, false); + + /* Attach the interrupt handler */ + + ret = config->attach(config, tsc2007_interrupt); + if (ret < 0) + { + idbg("Failed to attach interrupt\n"); + goto errout_with_priv; + } + + /* Power down the ADC and enable PENIRQ. This is the normal state while + * waiting for a touch event. + */ + + ret = tsc2007_transfer(priv, TSC2007_ENABLE_PENIRQ); + if (ret < 0) + { + idbg("tsc2007_transfer failed: %d\n", ret); + goto errout_with_priv; + } + + /* Register the device as an input device */ + + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, minor); + ivdbg("Registering %s\n", devname); + + ret = register_driver(devname, &tsc2007_fops, 0666, priv); + if (ret < 0) + { + idbg("register_driver() failed: %d\n", ret); + goto errout_with_priv; + } + + /* If multiple TSC2007 devices are supported, then we will need to add + * this new instance to a list of device instances so that it can be + * found by the interrupt handler based on the recieved IRQ number. + */ + +#ifdef CONFIG_TSC2007_MULTIPLE + flags = irqsave(); + priv->flink = g_tsc2007list; + g_tsc2007list = priv; + irqrestore(flags); +#endif + + /* Schedule work to perform the initial sampling and to set the data + * availability conditions. + */ + + ret = work_queue(&priv->work, tsc2007_worker, priv, 0); + if (ret != 0) + { + idbg("Failed to queue work: %d\n", ret); + goto errout_with_priv; + } + + /* And return success (?) */ + + return OK; + +errout_with_priv: + sem_destroy(&priv->devsem); +#ifdef CONFIG_TSC2007_MULTIPLE + kfree(priv); +#endif + return ret; +} diff --git a/nuttx/drivers/input/tsc2007.h b/nuttx/drivers/input/tsc2007.h new file mode 100644 index 000000000..052ce76d2 --- /dev/null +++ b/nuttx/drivers/input/tsc2007.h @@ -0,0 +1,120 @@ +/******************************************************************************************** + * drivers/input/tsc2007.h + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * References: + * "1.2V to 3.6V, 12-Bit, Nanopower, 4-Wire Micro TOUCH SCREEN CONTROLLER + * with I2C Interface," SBAS405A March 2007, Revised, March 2009, Texas + * Instruments Incorporated + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ********************************************************************************************/ + +/* The TSC2007 is an analog interface circuit for a human interface touch screen device. + * All peripheral functions are controlled through the command byte and onboard state + * machines. + */ + +#ifndef __DRIVERS_INPUT_TSC2007_H +#define __DRIVERS_INPUT_TSC2007_H + +/******************************************************************************************** + * Included Files + ********************************************************************************************/ + +/******************************************************************************************** + * Pre-Processor Definitions + ********************************************************************************************/ + +/* TSC2007 Address */ + +#define TSC2007_ADDRESS_MASK (0xf8) /* Bits 3-7: Invariant part of TSC2007 address */ +#define TSC2007_ADDRESS (0x90) /* Bits 3-7: Always set at '10010' */ +#define TSC2007_A1 (1 << 2) /* Bit 2: A1 */ +#define TSC2007_A0 (1 << 1) /* Bit 1: A1 */ +#define TSC2007_READ (1 << 0) /* Bit0=1: Selects read operation */ +#define TSC2007_WRITE (0) /* Bit0=0: Selects write operation */ + +/* TSC2007 Command Byte */ + +#define TSC2007_CMD_FUNC_SHIFT (4) /* Bits 4-7: Converter function select bits */ +#define TSC2007_CMD_FUNC_MASK (15 << TSC2007_CMD_FUNC_SHIFT) +# define TSC2007_CMD_FUNC_TEMP0 (0 << TSC2007_CMD_FUNC_SHIFT) /* Measure TEMP0 */ +# define TSC2007_CMD_FUNC_AUX (2 << TSC2007_CMD_FUNC_SHIFT) /* Measure AUX */ +# define TSC2007_CMD_FUNC_TEMP1 (4 << TSC2007_CMD_FUNC_SHIFT) /* Measure TEMP1 */ +# define TSC2007_CMD_FUNC_XON (8 << TSC2007_CMD_FUNC_SHIFT) /* Activate X-drivers */ +# define TSC2007_CMD_FUNC_YON (9 << TSC2007_CMD_FUNC_SHIFT) /* Activate Y-drivers */ +# define TSC2007_CMD_FUNC_YXON (10 << TSC2007_CMD_FUNC_SHIFT) /* Activate Y+, X-drivers */ +# define TSC2007_CMD_FUNC_SETUP (11 << TSC2007_CMD_FUNC_SHIFT) /* Setup command */ +# define TSC2007_CMD_FUNC_XPOS (12 << TSC2007_CMD_FUNC_SHIFT) /* Measure X position */ +# define TSC2007_CMD_FUNC_YPOS (13 << TSC2007_CMD_FUNC_SHIFT) /* Measure Y position */ +# define TSC2007_CMD_FUNC_Z1POS (14 << TSC2007_CMD_FUNC_SHIFT) /* Measure Z1 position */ +# define TSC2007_CMD_FUNC_Z2POS (15 << TSC2007_CMD_FUNC_SHIFT) /* Measure Z2 positionn */ +#define TSC2007_CMD_PWRDN_SHIFT (2) /* Bits 2-3: Power-down bits */ +#define TSC2007_CMD_PWRDN_MASK (3 << TSC2007_CMD_PWRDN_SHIFT) +# define TSC2007_CMD_PWRDN_IRQEN (0 << TSC2007_CMD_PWRDN_SHIFT) /* 00: Power down between cycles; PENIRQ enabled */ +# define TSC2007_CMD_ADCON_IRQDIS (1 << TSC2007_CMD_PWRDN_SHIFT) /* 01: A/D converter on; PENIRQ disabled */ +# define TSC2007_CMD_ADCOFF_IRQEN (2 << TSC2007_CMD_PWRDN_SHIFT) /* 10: A/D converter off; PENIRQ enabled. */ + /* 11: A/D converter on; PENIRQ disabled. */ +#define TSC2007_CMD_12BIT (0) /* Bit 1: 0=12-bit */ +#define TSC2007_CMD_8BIT (1 << 1) /* Bit 1: 1=8-bit */ + /* Bit 0: Don't care */ + +/* TSC2007 Setup Command */ + +#define TSC2007_SETUP_CMD TSC2007_CMD_FUNC_SETUP /* Bits 4-7: Setup command */ + /* Bits 2-3: Must be zero */ +#define TSC2007_CMD_USEMAV (0) /* Bit 1: 0: Use the onboard MAV filter (default) */ +#define TSC2007_CMD_BYPASSMAV (1 << 1) /* Bit 1: 1: Bypass the onboard MAV filter */ +#define TSC2007_CMD_PU_50KOHM (0) /* Bit 0: 0: RIRQ = 50kOhm (default). */ +#define TSC2007_CMD_PU_90KOHM (1 << 1) /* Bit 0: 1: 1: RIRQ = 90kOhm */ + +/******************************************************************************************** + * Public Types + ********************************************************************************************/ + +/******************************************************************************************** + * Public Function Prototypes + ********************************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __DRIVERS_INPUT_TSC2007_H */ diff --git a/nuttx/drivers/lcd/Make.defs b/nuttx/drivers/lcd/Make.defs new file mode 100644 index 000000000..596cce7d5 --- /dev/null +++ b/nuttx/drivers/lcd/Make.defs @@ -0,0 +1,60 @@ +############################################################################ +# drivers/lcd/Make.defs +# +# Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Don't build anything if there is no NX support for LCD drivers + +ifeq ($(CONFIG_NX_LCDDRIVER),y) + +# Include LCD drivers + +ifeq ($(CONFIG_LCD_P14201),y) + CSRCS += p14201.c +endif + +ifeq ($(CONFIG_LCD_NOKIA6100),y) + CSRCS += nokia6100.c +endif + +ifeq ($(CONFIG_LCD_UG9664HSWAG01),y) + CSRCS += ug-9664hswag01.c +endif + +# Include LCD driver build support + +DEPPATH += --dep-path lcd +VPATH += :lcd +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/lcd} +endif + diff --git a/nuttx/drivers/lcd/nokia6100.c b/nuttx/drivers/lcd/nokia6100.c new file mode 100644 index 000000000..d450e05db --- /dev/null +++ b/nuttx/drivers/lcd/nokia6100.c @@ -0,0 +1,1230 @@ +/************************************************************************************** + * drivers/lcd/nokia6100.c + * Nokia 6100 LCD Display Driver + * + * Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * References: + * "Nokia 6100 LCD Display Driver," Revision 1, James P. Lynch ("Nokia 6100 LCD + * Display Driver.pdf") + * "S1D15G0D08B000," Seiko Epson Corportation, 2002. + * "Data Sheet, PCF8833 STN RGB 132x132x3 driver," Phillips, 2003 Feb 14. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************/ + +/************************************************************************************** + * Included Files + **************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/spi.h> +#include <nuttx/lcd/lcd.h> +#include <nuttx/lcd/nokia6100.h> + +#ifdef CONFIG_NOKIA6100_PCF8833 +# include "pcf8833.h" +#endif +#ifdef CONFIG_NOKIA6100_S1D15G10 +# include "s1d15g10.h" +#endif + +/************************************************************************************** + * Pre-processor Definitions + **************************************************************************************/ + +/* Configuration **********************************************************************/ +/* Verify that all configuration requirements have been met */ + +/* Nokia 6100 Configuration Settings: + * + * CONFIG_NOKIA6100_SPIMODE - Controls the SPI mode + * CONFIG_NOKIA6100_FREQUENCY - Define to use a different bus frequency + * CONFIG_NOKIA6100_NINTERFACES - Specifies the number of physical Nokia 6100 devices that + * will be supported. + * CONFIG_NOKIA6100_BPP - Device supports 8, 12, and 16 bits per pixel. + * CONFIG_NOKIA6100_S1D15G10 - Selects the Epson S1D15G10 display controller + * CONFIG_NOKIA6100_PCF8833 - Selects the Phillips PCF8833 display controller + * CONFIG_NOKIA6100_BLINIT - Initial backlight setting + * + * The following may need to be tuned for your hardware: + * CONFIG_NOKIA6100_INVERT - Display inversion, 0 or 1, Default: 1 + * CONFIG_NOKIA6100_MY - Display row direction, 0 or 1, Default: 0 + * CONFIG_NOKIA6100_MX - Display column direction, 0 or 1, Default: 1 + * CONFIG_NOKIA6100_V - Display address direction, 0 or 1, Default: 0 + * CONFIG_NOKIA6100_ML - Display scan direction, 0 or 1, Default: 0 + * CONFIG_NOKIA6100_RGBORD - Display RGB order, 0 or 1, Default: 0 + * + * Required LCD driver settings: + * CONFIG_LCD_NOKIA6100 - Enable Nokia 6100 support + * CONFIG_LCD_MAXCONTRAST - must be 63 with the Epson controller and 127 with + * the Phillips controller. + * CONFIG_LCD_MAXPOWER - Maximum value of backlight setting. The backlight control is + * managed outside of the 6100 driver so this value has no meaning to the driver. + */ + +/* Mode 0,0 should be use. However, somtimes you need to tinker with these things. */ + +#ifndef CONFIG_NOKIA6100_SPIMODE +# define CONFIG_NOKIA6100_SPIMODE SPIDEV_MODE0 +#endif + +/* Default frequency: 1Mhz */ + +#ifndef CONFIG_NOKIA6100_FREQUENCY +# define CONFIG_NOKIA6100_FREQUENCY 1000000 +#endif + +/* CONFIG_NOKIA6100_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_NOKIA6100_NINTERFACES +# define CONFIG_NOKIA6100_NINTERFACES 1 +#endif + +#if CONFIG_NOKIA6100_NINTERFACES != 1 +# error "This implementation supports only a single LCD device" +#endif + +/* Only support for 8 and 12 BPP currently implemented */ + +#if !defined(CONFIG_NOKIA6100_BPP) +# warning "Assuming 8BPP" +# define CONFIG_NOKIA6100_BPP 8 +#endif + +#if CONFIG_NOKIA6100_BPP != 8 && CONFIG_NOKIA6100_BPP != 12 +# if CONFIG_NOKIA6100_BPP == 16 +# error "Support for 16BPP no yet implemented" +# else +# error "LCD supports only 8, 12, and 16BPP" +# endif +#endif + +#if CONFIG_NOKIA6100_BPP == 8 +# ifdef CONFIG_NX_DISABLE_8BPP +# warning "8-bit pixel support needed" +# endif +#elif CONFIG_NOKIA6100_BPP == 12 +# if defined(CONFIG_NX_DISABLE_12BPP) || !defined(CONFIG_NX_PACKEDMSFIRST) +# warning "12-bit, big-endian pixel support needed" +# endif +#elif CONFIG_NOKIA6100_BPP == 16 +# ifdef CONFIG_NX_DISABLE_16BPP +# warning "16-bit pixel support needed" +# endif +#endif + +/* Exactly one LCD controller must be selected. "The Olimex boards have both display + * controllers possible; if the LCD has a GE-12 sticker on it, it’s a Philips PCF8833. + * If it has a GE-8 sticker, it’s an Epson controller." + */ + +#if defined(CONFIG_NOKIA6100_S1D15G10) && defined(CONFIG_NOKIA6100_PCF8833) +# error "Both CONFIG_NOKIA6100_S1D15G10 and CONFIG_NOKIA6100_PCF8833 are defined" +#endif + +#if !defined(CONFIG_NOKIA6100_S1D15G10) && !defined(CONFIG_NOKIA6100_PCF8833) +# error "One of CONFIG_NOKIA6100_S1D15G10 or CONFIG_NOKIA6100_PCF8833 must be defined" +#endif + +/* Delay geometry defaults */ + +#ifndef CONFIG_NOKIA6100_INVERT +# define CONFIG_NOKIA6100_INVERT 1 +#endif + +#ifndef CONFIG_NOKIA6100_MY +# define CONFIG_NOKIA6100_MY 0 +#endif + +#ifndef CONFIG_NOKIA6100_MX +# define CONFIG_NOKIA6100_MX 1 +#endif + +#ifndef CONFIG_NOKIA6100_V +# define CONFIG_NOKIA6100_V 0 +#endif + +#ifndef CONFIG_NOKIA6100_ML +# define CONFIG_NOKIA6100_ML 0 +#endif + +#ifndef CONFIG_NOKIA6100_RGBORD +# define CONFIG_NOKIA6100_RGBORD 0 +#endif + +/* Check contrast selection */ + +#ifdef CONFIG_NOKIA6100_S1D15G10 + +# if !defined(CONFIG_LCD_MAXCONTRAST) +# define CONFIG_LCD_MAXCONTRAST 63 +# endif +# if CONFIG_LCD_MAXCONTRAST != 63 +# error "CONFIG_LCD_MAXCONTRAST must be 63 with the Epson LCD controller" +# endif +# define NOKIA_DEFAULT_CONTRAST 32 + +#else /* CONFIG_NOKIA6100_PCF8833 */ + +# if !defined(CONFIG_LCD_MAXCONTRAST) +# define CONFIG_LCD_MAXCONTRAST 127 +# endif +# if CONFIG_LCD_MAXCONTRAST != 127 +# error "CONFIG_LCD_MAXCONTRAST must be 127 with the Phillips LCD controller" +# endif +# define NOKIA_DEFAULT_CONTRAST 48 + +#endif + +/* Check initial backlight setting */ + +#ifndef CONFIG_NOKIA6100_BLINIT +# define CONFIG_NOKIA6100_BLINIT (NOKIA_DEFAULT_CONTRAST/3) +#endif + +/* Word width must be 9 bits */ + +#if defined(CONFIG_NOKIA6100_WORDWIDTH) && CONFIG_NOKIA6100_WORDWIDTH != 9 +# error "CONFIG_NOKIA6100_WORDWIDTH must be 9" +#endif +#ifndef CONFIG_NOKIA6100_WORDWIDTH +# define CONFIG_NOKIA6100_WORDWIDTH 9 +#endif + +/* If bit 9 is set, the byte is data; clear for commands */ + +#define NOKIA_LCD_DATA (1 << 8) + +/* Define CONFIG_LCD_REGDEBUG to enable register-level debug output. + * (Verbose debug must also be enabled) + */ + +#ifndef CONFIG_DEBUG +# undef CONFIG_DEBUG_VERBOSE +# undef CONFIG_DEBUG_GRAPHICS +#endif + +#ifndef CONFIG_DEBUG_VERBOSE +# undef CONFIG_LCD_REGDEBUG +#endif + +/* Controller independent definitions *************************************************/ + +#ifdef CONFIG_NOKIA6100_PCF8833 +# define LCD_NOP PCF8833_NOP +# define LCD_RAMWR PCF8833_RAMWR +# define LCD_PASET PCF8833_PASET +# define LCD_CASET PCF8833_CASET +#endif +#ifdef CONFIG_NOKIA6100_S1D15G10 +# define LCD_NOP S1D15G10_NOP +# define LCD_RAMWR S1D15G10_RAMWR +# define LCD_PASET S1D15G10_PASET +# define LCD_CASET S1D15G10_CASET +#endif + +/* Color Properties *******************************************************************/ + +/* Display Resolution */ + +#define NOKIA_XRES 132 +#define NOKIA_YRES 132 + +/* Color depth and format */ + +#if CONFIG_NOKIA6100_BPP == 8 +# define NOKIA_BPP 8 +# define NOKIA_COLORFMT FB_FMT_RGB8_332 +# define NOKIA_STRIDE NOKIA_XRES +# define NOKIA_PIX2BYTES(p) (p) +#elif CONFIG_NOKIA6100_BPP == 12 +# define NOKIA_BPP 12 +# define NOKIA_COLORFMT FB_FMT_RGB12_444 +# define NOKIA_STRIDE ((3*NOKIA_XRES+1)/2) +# define NOKIA_PIX2BYTES(p) ((3*(p)+1)/2) +#elif CONFIG_NOKIA6100_BPP == 16 +# define NOKIA_BPP 16 +# define NOKIA_COLORFMT FB_FMT_RGB16_565 +# define NOKIA_STRIDE (2*NOKIA_XRES) +# define NOKIA_PIX2BYTES(p) (2*(p)) +#endif + +/* Handle any potential strange behavior at edges */ + +#if 0 /* REVISIT */ +#define NOKIA_PGBIAS 2 /* May not be necessary */ +#define NOKIA_COLBIAS 0 +#define NOKIA_XBIAS 2 /* May not be necessary, if so need to subtract from XRES */ +#define NOKIA_YBIAS 0 +#else +#define NOKIA_PGBIAS 0 +#define NOKIA_COLBIAS 0 +#define NOKIA_XBIAS 0 +#define NOKIA_YBIAS 0 +#endif + +#define NOKIA_ENDPAGE 131 +#define NOKIA_ENDCOL 131 + +/* Debug ******************************************************************************/ + +#ifdef CONFIG_LCD_REGDEBUG +# define lcddbg(format, arg...) llvdbg(format, ##arg) +#else +# define lcddbg(x...) +#endif + +/************************************************************************************** + * Private Type Definition + **************************************************************************************/ + +/* This structure describes the state of this driver */ + +struct nokia_dev_s +{ + /* Publically visible device structure */ + + struct lcd_dev_s dev; + + /* Private LCD-specific information follows */ + + FAR struct spi_dev_s *spi; /* Contained SPI driver instance */ + uint8_t contrast; /* Current contrast setting */ + uint8_t power; /* Current power (backlight) setting */ +}; + +/************************************************************************************** + * Private Function Protototypes + **************************************************************************************/ + +/* SPI support */ + +static inline void nokia_configspi(FAR struct spi_dev_s *spi); +#ifdef CONFIG_SPI_OWNBUS +static inline void nokia_select(FAR struct spi_dev_s *spi); +static inline void nokia_deselect(FAR struct spi_dev_s *spi); +#else +static void nokia_select(FAR struct spi_dev_s *spi); +static void nokia_deselect(FAR struct spi_dev_s *spi); +#endif +static void nokia_sndcmd(FAR struct spi_dev_s *spi, const uint8_t cmd); +static void nokia_cmdarray(FAR struct spi_dev_s *spi, int len, const uint8_t *cmddata); +static void nokia_clrram(FAR struct spi_dev_s *spi); + +/* LCD Data Transfer Methods */ + +static int nokia_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels); +static int nokia_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels); + +/* LCD Configuration */ + +static int nokia_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo); +static int nokia_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo); + +/* LCD RGB Mapping */ + +#ifdef CONFIG_FB_CMAP +# error "RGB color mapping not supported by this driver" +#endif + +/* Cursor Controls */ + +#ifdef CONFIG_FB_HWCURSOR +# error "Cursor control not supported by this driver" +#endif + +/* LCD Specific Controls */ + +static int nokia_getpower(struct lcd_dev_s *dev); +static int nokia_setpower(struct lcd_dev_s *dev, int power); +static int nokia_getcontrast(struct lcd_dev_s *dev); +static int nokia_setcontrast(struct lcd_dev_s *dev, unsigned int contrast); + +/* Initialization */ + +static int nokia_initialize(struct nokia_dev_s *priv); + +/************************************************************************************** + * Private Data + **************************************************************************************/ + +/* This is working memory allocated by the LCD driver for each LCD device + * and for each color plane. This memory will hold one raster line of data. + * The size of the allocated run buffer must therefore be at least + * (bpp * xres / 8). Actual alignment of the buffer must conform to the + * bitwidth of the underlying pixel type. + * + * If there are multiple planes, they may share the same working buffer + * because different planes will not be operate on concurrently. However, + * if there are multiple LCD devices, they must each have unique run buffers. + */ + +#if CONFIG_NOKIA6100_BPP == 8 +static uint8_t g_runbuffer[NOKIA_XRES]; +#elif CONFIG_NOKIA6100_BPP == 12 +static uint8_t g_runbuffer[(3*NOKIA_XRES+1)/2]; +#else /* CONFIG_NOKIA6100_BPP == 16 */ +static uint16_t g_runbuffer[NOKIA_XRES]; +#endif + +/* g_rowbuf is another buffer, but used internally by the Nokia 6100 driver in order + * expand the pixel data into 9-bit data needed by the LCD. There are some + * customizations that would eliminate the need for this extra buffer and for the + * extra expansion/copy, but those customizations would require a special, non-standard + * SPI driver that could expand 8- to 9-bit data on the fly. + */ + +static uint16_t g_rowbuf[NOKIA_STRIDE+1]; + +/* Device Driver Data Structures ******************************************************/ + +/* This structure describes the overall LCD video controller */ + +static const struct fb_videoinfo_s g_videoinfo = +{ + .fmt = NOKIA_COLORFMT, /* Color format: RGB16-565: RRRR RGGG GGGB BBBB */ + .xres = NOKIA_XRES, /* Horizontal resolution in pixel columns */ + .yres = NOKIA_YRES, /* Vertical resolution in pixel rows */ + .nplanes = 1, /* Number of color planes supported */ +}; + +/* This is the standard, NuttX Plane information object */ + +static const struct lcd_planeinfo_s g_planeinfo = +{ + .putrun = nokia_putrun, /* Put a run into LCD memory */ + .getrun = nokia_getrun, /* Get a run from LCD memory */ + .buffer = (uint8_t*)g_runbuffer, /* Run scratch buffer */ + .bpp = NOKIA_BPP, /* Bits-per-pixel */ +}; + +/* This is the standard, NuttX LCD driver object */ + +static struct nokia_dev_s g_lcddev = +{ + .dev = + { + /* LCD Configuration */ + + .getvideoinfo = nokia_getvideoinfo, + .getplaneinfo = nokia_getplaneinfo, + + /* LCD RGB Mapping -- Not supported */ + /* Cursor Controls -- Not supported */ + + /* LCD Specific Controls */ + + .getpower = nokia_getpower, + .setpower = nokia_setpower, + .getcontrast = nokia_getcontrast, + .setcontrast = nokia_setcontrast, + }, +}; + +/* LCD Command Strings ****************************************************************/ + +#ifdef CONFIG_NOKIA6100_S1D15G10 +/* Display control: + * P1: Specifies the CL dividing ratio, F1 and F2 drive-pattern switching period. + * P2: Specifies the duty of the module on block basis + * P3: Specify number of lines to be inversely highlighted on LCD panel + * P4: 0: Dispersion P40= 1: Non-dispersion + */ + +#if 1 // CONFIG_NOKIA6100_BPP == 12 +static const uint8_t g_disctl[] = +{ + S1D15G10_DISCTL, /* Display control */ + DISCTL_CLDIV_2|DISCTL_PERIOD_8, /* P1: Divide clock by 2; switching period = 8 */ +//DISCTL_CLDIV_NONE|DISCTL_PERIOD_8, /* P1: No clock division; switching period = 8 */ + 32, /* P2: nlines/4 - 1 = 132/4 - 1 = 32 */ + 0, /* P3: No inversely highlighted lines */ + 0 /* P4: No disperion */ +}; +#else /* CONFIG_NOKIA6100_BPP == 8 */ +static const uint8_t g_disctl[] = +{ + S1D15G10_DISCTL, /* Display control */ + DISCTL_CLDIV_2|DISCTL_PERIOD_FLD, /* P1: Divide clock by 2; switching period = field */ + 32, /* P2: nlines/4 - 1 = 132/4 - 1 = 32 */ + 0, /* P3: No inversely highlighted lines */ + 0 /* P4: No disperion */ +}; +#endif + +/* Common scan direction: + * P1: Cpecify the common output scan direction. + */ + +static const uint8_t g_comscn[] = +{ + S1D15G10_COMSCN, /* Common scan direction */ + 1 /* 0x01 = Scan 1->68, 132<-69 */ +}; + +/* Power control: + * P1: Turn on or off the liquid crystal driving power circuit, booster/step-down + * circuits and voltage follower circuit. + */ + +static const uint8_t g_pwrctr[] = +{ + S1D15G10_PWRCTR, /* Power control */ + PWCTR_REFVOLTAGE|PWCTR_REGULATOR|PWCTR_BOOSTER2|PWCTR_BOOSTER1 +}; + +/* Data control: + * P1: Specify the normal or inverse display of the page address and also to specify + * the page address scanning direction + * P2: RGB sequence + * P3: Grayscale setup + */ + +static const uint8_t g_datctl[] = +{ + S1D15G10_DATCTL, /* Data control */ + 0 +#if CONFIG_NOKIA6100_MY != 0 /* Display row direction */ + |DATCTL_PGADDR_INV /* Page address inverted */ +#endif +#if CONFIG_NOKIA6100_MX != 0 /* Display column direction */ + |DATCTL_COLADDR_REV /* Column address reversed */ +#endif +#if CONFIG_NOKIA6100_V != 0 /* Display address direction */ + |DATCTL_ADDR_PGDIR /* Address scan in page direction */ +#endif + , +#if CONFIG_NOKIA6100_RGBORD != 0 + DATCTL_BGR, /* RGB->BGR */ +#else + 0, /* RGB->RGB */ +#endif +#if CONFIG_NOKIA6100_BPP == 8 + DATCTL_8GRAY /* Selects 8-bit color */ +#elif CONFIG_NOKIA6100_BPP == 12 + DATCTL_16GRAY_A /* Selects 16-bit color, Type A */ +#else +# error "16-bit mode not yet implemented" +#endif +}; + +/* Voltage control (contrast setting): + * P1: Volume value + * P2: Resistance ratio + * (May need to be tuned for individual displays) + */ + +static const uint8_t g_volctr[] = +{ + S1D15G10_VOLCTR, /* Volume control */ + NOKIA_DEFAULT_CONTRAST, /* Volume value */ + 2 /* Resistance ratio */ +}; + +/* 256-color position set (RGBSET8) */ + +#if CONFIG_NOKIA6100_BPP == 8 +static const uint8_t g_rgbset8[] = +{ + S1D15G10_RGBSET8, /* 256-color position set */ + 0, 2, 4, 6, 9, 11, 13, 15, /* Red tones */ + 0, 2, 4, 6, 9, 11, 13, 15, /* Green tones */ + 0, 5, 10, 15 /* Blue tones */ +}; +#endif + +/* Page address set (PASET) */ + +static const uint8_t g_paset[] = +{ + S1D15G10_PASET, /* Page start address set */ + NOKIA_PGBIAS, + 131 +}; + +/* Column address set (CASET) */ + +static const uint8_t g_caset[] = +{ + S1D15G10_CASET, /* Column start address set */ + NOKIA_COLBIAS, + 131 +}; +#endif /* CONFIG_NOKIA6100_S1D15G10 */ + +#ifdef CONFIG_NOKIA6100_PCF8833 + +/* Color interface pixel format (COLMOD) */ + +#if CONFIG_NOKIA6100_BPP == 12 +static const uint8_t g_colmod[] = +{ + PCF8833_COLMOD, /* Color interface pixel format */ + PCF8833_FMT_12BPS /* 12 bits-per-pixel */ +}; +#else /* CONFIG_NOKIA6100_BPP == 8 */ +static const uint8_t g_colmod[] = +{ + PCF8833_COLMOD, /* Color interface pixel format */ + PCF8833_FMT_8BPS /* 8 bits-per-pixel */ +}; +#endif + +/* Memory data access control(MADCTL) */ + +static const uint8_t g_madctl[] = +{ + PCF8833_MADCTL, /* Memory data access control*/ + 0 +#ifdef CONFIG_NOKIA6100_RGBORD != 0 + |MADCTL_RGB /* RGB->BGR */ +#endif +#ifdef CONFIG_NOKIA6100_MY != 0 /* Display row direction */ + |MADCTL_MY /* Mirror Y */ +#endif +#ifdef CONFIG_NOKIA6100_MX != 0 /* Display column direction */ + |MADCTL_MX /* Mirror X */ +#endif +#ifdef CONFIG_NOKIA6100_V != 0 /* Display address direction */ + |MADCTL_V /* ertical RAM write; in Y direction */ +#endif +#ifdef CONFIG_NOKIA6100_ML != 0 /* Display scan direction */ + |MADCTL_LAO /* Line address order bottom to top */ +#endif +}; + +/* Set contrast (SETCON) */ + +static const uint8_t g_setcon[] = +{ + PCF8833_SETCON, /* Set contrast */ + NOKIA_DEFAULT_CONTRAST +}; + +#endif /* CONFIG_NOKIA6100_PCF8833 */ + +/************************************************************************************** + * Private Functions + **************************************************************************************/ + +/************************************************************************************** + * Function: nokia_configspi + * + * Description: + * Configure the SPI for use with the Nokia 6100 + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +static inline void nokia_configspi(FAR struct spi_dev_s *spi) +{ + lcddbg("Mode: %d Bits: %d Frequency: %d\n", + CONFIG_NOKIA6100_SPIMODE, CONFIG_NOKIA6100_WORDWIDTH, CONFIG_NOKIA6100_FREQUENCY); + + /* Configure SPI for the Nokia 6100. But only if we own the SPI bus. Otherwise, don't + * bother because it might change. + */ + +#ifdef CONFIG_SPI_OWNBUS + SPI_SETMODE(spi, CONFIG_NOKIA6100_SPIMODE); + SPI_SETBITS(spi, CONFIG_NOKIA6100_WORDWIDTH); + SPI_SETFREQUENCY(spi, CONFIG_NOKIA6100_FREQUENCY) +#endif +} + +/************************************************************************************** + * Function: nokia_select + * + * Description: + * Select the SPI, locking and re-configuring if necessary + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void nokia_select(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just select the chip */ + + lcddbg("SELECTED\n"); + SPI_SELECT(spi, SPIDEV_DISPLAY, true); +} +#else +static void nokia_select(FAR struct spi_dev_s *spi) +{ + /* Select Nokia 6100 chip (locking the SPI bus in case there are multiple + * devices competing for the SPI bus + */ + + lcddbg("SELECTED\n"); + SPI_LOCK(spi, true); + SPI_SELECT(spi, SPIDEV_DISPLAY, true); + + /* Now make sure that the SPI bus is configured for the Nokia 6100 (it + * might have gotten configured for a different device while unlocked) + */ + + SPI_SETMODE(spi, CONFIG_NOKIA6100_SPIMODE); + SPI_SETBITS(spi, CONFIG_NOKIA6100_WORDWIDTH); + SPI_SETFREQUENCY(spi, CONFIG_NOKIA6100_FREQUENCY); +} +#endif + +/************************************************************************************** + * Function: nokia_deselect + * + * Description: + * De-select the SPI + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void nokia_deselect(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just de-select the chip */ + + lcddbg("DE-SELECTED\n"); + SPI_SELECT(spi, SPIDEV_DISPLAY, false); +} +#else +static void nokia_deselect(FAR struct spi_dev_s *spi) +{ + /* De-select Nokia 6100 chip and relinquish the SPI bus. */ + + lcddbg("DE-SELECTED\n"); + SPI_SELECT(spi, SPIDEV_DISPLAY, false); + SPI_LOCK(spi, false); +} +#endif + +/************************************************************************************** + * Name: nokia_sndcmd + * + * Description: + * Send a 1-byte command. + * + **************************************************************************************/ + +static void nokia_sndcmd(FAR struct spi_dev_s *spi, const uint8_t cmd) +{ + /* Select the LCD */ + + lcddbg("cmd: %02x\n", cmd); + nokia_select(spi); + + /* Send the command. Bit 8 == 0 denotes a command */ + + (void)SPI_SEND(spi, (uint16_t)cmd); + + /* De-select the LCD */ + + nokia_deselect(spi); +} + +/************************************************************************************** + * Name: nokia_cmddata + * + * Description: + * Send a 1-byte command followed by datlen data bytes. + * + **************************************************************************************/ + +static void nokia_cmddata(FAR struct spi_dev_s *spi, uint8_t cmd, int datlen, + const uint8_t *data) +{ + uint16_t *rowbuf = g_rowbuf; + int i; + + lcddbg("cmd: %02x datlen: %d\n", cmd, datlen); + DEBUGASSERT(datlen <= NOKIA_STRIDE); + + /* Copy the command into the line buffer. Bit 8 == 0 denotes a command. */ + + *rowbuf++ = cmd; + + /* Copy any data after the command into the line buffer */ + + for (i = 0; i < datlen; i++) + { + /* Bit 8 == 1 denotes data */ + + *rowbuf++ = (uint16_t)*data++ | NOKIA_LCD_DATA; + } + + /* Select the LCD */ + + nokia_select(spi); + + /* Send the line buffer. */ + + (void)SPI_SNDBLOCK(spi, g_rowbuf, datlen+1); + + /* De-select the LCD */ + + nokia_deselect(spi); +} + +/************************************************************************************** + * Name: nokia_ramwr + * + * Description: + * Send a RAMWR command followed by datlen data bytes. + * + **************************************************************************************/ + +static void nokia_ramwr(FAR struct spi_dev_s *spi, int datlen, const uint8_t *data) +{ + nokia_cmddata(spi, LCD_RAMWR, datlen, data); +} + +/************************************************************************************** + * Name: nokia_cmdarray + * + * Description: + * Send a RAMWR command followed by len-1 data bytes. + * + **************************************************************************************/ + +static void nokia_cmdarray(FAR struct spi_dev_s *spi, int len, const uint8_t *cmddata) +{ +#ifdef CONFIG_LCD_REGDEBUG + int i; + + for (i = 0; i < len; i++) + { + lcddbg("cmddata[%d]: %02x\n", i, cmddata[i]); + } +#endif + nokia_cmddata(spi, cmddata[0], len-1, &cmddata[1]); +} + +/************************************************************************************** + * Name: nokia_clrram + * + * Description: + * Send a 1-byte command followed by len-1 data bytes. + * + **************************************************************************************/ + +static void nokia_clrram(FAR struct spi_dev_s *spi) +{ + uint16_t *rowbuf = g_rowbuf; + int i; + + /* Set all zero data in the line buffer */ + + for (i = 0; i < NOKIA_STRIDE; i++) + { + /* Bit 8 == 1 denotes data */ + + *rowbuf++ = NOKIA_LCD_DATA; + } + + /* Select the LCD and send the RAMWR command */ + + nokia_select(spi); + SPI_SEND(spi, LCD_RAMWR); + + /* Send the line buffer, once for each row. */ + + for (i = 0; i < NOKIA_YRES; i++) + { + (void)SPI_SNDBLOCK(spi, g_rowbuf, NOKIA_STRIDE); + } + + /* De-select the LCD */ + + nokia_deselect(spi); +} + +/************************************************************************************** + * Name: nokia_putrun + * + * Description: + * This method can be used to write a partial raster line to the LCD: + * + * row - Starting row to write to (range: 0 <= row < yres) + * col - Starting column to write to (range: 0 <= col <= xres-npixels) + * buffer - The buffer containing the run to be written to the LCD + * npixels - The number of pixels to write to the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +static int nokia_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels) +{ + struct nokia_dev_s *priv = &g_lcddev; + FAR struct spi_dev_s *spi = priv->spi; + uint16_t cmd[3]; + + gvdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + +#if NOKIA_XBIAS > 0 + col += NOKIA_XBIAS; +#endif +#if NOKIA_YBIAS > 0 + row += NOKIA_YBIAS; +#endif + DEBUGASSERT(buffer && col >=0 && (col + npixels) <= NOKIA_XRES && row >= 0 && row < NOKIA_YRES); + + /* Set up to write the run. */ + + nokia_select(spi); + cmd[0] = LCD_PASET; + cmd[1] = col | NOKIA_LCD_DATA; + cmd[2] = NOKIA_ENDPAGE | NOKIA_LCD_DATA; + (void)SPI_SNDBLOCK(spi, cmd, 3); + nokia_deselect(spi); + + /* De-select the LCD */ + + nokia_select(spi); + cmd[0] = LCD_CASET; + cmd[1] = row | NOKIA_LCD_DATA; + cmd[2] = NOKIA_ENDCOL | NOKIA_LCD_DATA; + (void)SPI_SNDBLOCK(spi, cmd, 3); + nokia_deselect(spi); + + /* Then send the run */ + + nokia_ramwr(spi, NOKIA_PIX2BYTES(npixels), buffer); + return OK; +} + +/************************************************************************************** + * Name: nokia_getrun + * + * Description: + * This method can be used to read a partial raster line from the LCD: + * + * row - Starting row to read from (range: 0 <= row < yres) + * col - Starting column to read read (range: 0 <= col <= xres-npixels) + * buffer - The buffer in which to return the run read from the LCD + * npixels - The number of pixels to read from the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +static int nokia_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels) +{ + gvdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer && ((uintptr_t)buffer & 1) == 0); + + /* At present, this is a write-only LCD driver */ + +#warning "Not implemented" + return -ENOSYS; +} + +/************************************************************************************** + * Name: nokia_getvideoinfo + * + * Description: + * Get information about the LCD video controller configuration. + * + **************************************************************************************/ + +static int nokia_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo) +{ + DEBUGASSERT(dev && vinfo); + gvdbg("fmt: %d xres: %d yres: %d nplanes: %d\n", + g_videoinfo.fmt, g_videoinfo.xres, g_videoinfo.yres, g_videoinfo.nplanes); + memcpy(vinfo, &g_videoinfo, sizeof(struct fb_videoinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: nokia_getplaneinfo + * + * Description: + * Get information about the configuration of each LCD color plane. + * + **************************************************************************************/ + +static int nokia_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo) +{ + DEBUGASSERT(dev && pinfo && planeno == 0); + gvdbg("planeno: %d bpp: %d\n", planeno, g_planeinfo.bpp); + memcpy(pinfo, &g_planeinfo, sizeof(struct lcd_planeinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: nokia_getpower + * + * Description: + * Get the LCD panel power status (0: full off - CONFIG_LCD_MAXPOWER: full on. On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int nokia_getpower(struct lcd_dev_s *dev) +{ + struct nokia_dev_s *priv = (struct nokia_dev_s *)dev; + gvdbg("power: %d\n", priv->power); + return priv->power; +} + +/************************************************************************************** + * Name: nokia_setpower + * + * Description: + * Enable/disable LCD panel power (0: full off - CONFIG_LCD_MAXPOWER: full on). On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int nokia_setpower(struct lcd_dev_s *dev, int power) +{ + struct nokia_dev_s *priv = (struct nokia_dev_s *)dev; + int ret; + + gvdbg("power: %d\n", power); + DEBUGASSERT(power <= CONFIG_LCD_MAXPOWER); + + /* Set new power level. The backlight power is controlled outside of the LCD + * assembly and must be managmed by board-specific logic. + */ + + ret = nokia_backlight(power); + if (ret == OK) + { + priv->power = power; + } + return ret; +} + +/************************************************************************************** + * Name: nokia_getcontrast + * + * Description: + * Get the current contrast setting (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int nokia_getcontrast(struct lcd_dev_s *dev) +{ + struct nokia_dev_s *priv = (struct nokia_dev_s *)dev; + gvdbg("contrast: %d\n", priv->contrast); + return priv->contrast; +} + +/************************************************************************************** + * Name: nokia_setcontrast + * + * Description: + * Set LCD panel contrast (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int nokia_setcontrast(struct lcd_dev_s *dev, unsigned int contrast) +{ + struct nokia_dev_s *priv = (struct nokia_dev_s *)dev; + + if (contrast < CONFIG_LCD_MAXCONTRAST) + { +#ifdef CONFIG_NOKIA6100_S1D15G10 + while (priv->contrast < contrast) + { + nokia_sndcmd(priv->spi, S1D15G10_VOLUP); + priv->contrast++; + } + while (priv->contrast > contrast) + { + nokia_sndcmd(priv->spi, S1D15G10_VOLDOWN); + priv->contrast--; + } +#else /* CONFIG_NOKIA6100_PCF8833 */ + uint8_t cmd[2]; + + cmd[0] = PCF8833_SETCON; + cmd[1] = priv->contrast; + nokia_sndarry(priv->spi, 2, cmd); + priv->contrast = contrast; +#endif + } + + gvdbg("contrast: %d\n", contrast); + return -ENOSYS; +} + +/************************************************************************************** + * Name: nokia_initialize + * + * Description: + * Initialize the LCD controller. + * + **************************************************************************************/ + +#ifdef CONFIG_NOKIA6100_S1D15G10 +static int nokia_initialize(struct nokia_dev_s *priv) +{ + struct spi_dev_s *spi = priv->spi; + + /* Configure the display */ + + nokia_cmdarray(spi, sizeof(g_disctl), g_disctl); /* Display control */ + nokia_cmdarray(spi, sizeof(g_comscn), g_comscn); /* Common scan direction */ + nokia_sndcmd(spi, S1D15G10_OSCON); /* Internal oscilator ON */ + nokia_sndcmd(spi, S1D15G10_SLPOUT); /* Sleep out */ + nokia_cmdarray(spi, sizeof(g_volctr), g_volctr); /* Volume control (contrast) */ + nokia_cmdarray(spi, sizeof(g_pwrctr), g_pwrctr); /* Turn on voltage regulators */ + up_mdelay(100); +#ifdef CONFIG_NOKIA6100_INVERT + nokia_sndcmd(spi, S1D15G10_DISINV); /* Invert display */ +#else + nokia_sndcmd(spi, S1D15G10_DISNOR); /* Normal display */ +#endif + nokia_cmdarray(spi, sizeof(g_datctl), g_datctl); /* Data control */ +#if CONFIG_NOKIA6100_BPP == 8 + nokia_cmdarray(spi, sizeof(g_rgbset8), g_rgbset8); /* Set up color lookup table */ + nokia_sndcmd(spi, S1D15G10_NOP); +#endif + nokia_cmdarray(spi, sizeof(g_paset), g_paset); /* Page address set */ + nokia_cmdarray(spi, sizeof(g_paset), g_caset); /* Column address set */ + nokia_clrram(spi); + nokia_sndcmd(spi, S1D15G10_DISON); /* Display on */ + return OK; +} +#endif + +#ifdef CONFIG_NOKIA6100_PCF8833 +static int nokia_initialize(struct nokia_dev_s *priv) +{ + struct struct spi_dev_s *spi = priv->spi; + + nokia_sndcmd(spi, PCF8833_SLEEPOUT); /* Exit sleep mode */ + nokia_sndcmd(spi, PCF8833_BSTRON); /* Turn on voltage booster */ +#ifdef CONFIG_NOKIA6100_INVERT + nokia_sndcmd(spi, PCF8833_INVON); /* Invert display */ +#else + nokia_sndcmd(spi, PCF8833_INVOFF); /* Don't invert display */ +#endif + nokia_cmdarray(spi, sizeof(g_madctl), g_madctl); /* Memory data access control */ + nokia_cmdarray(spi, sizeof(g_colmod), g_colmod); /* Color interface pixel format */ + nokia_cmdarray(spi, sizeof(g_setcon), g_setcon); /* Set contrast */ + nokia_sndcmd(spi, PCF8833_NOP); /* No operation */ + nokia_clrram(spi); + nokia_sndcmd(spi, PCF8833_DISPON); /* Display on */ + return OK; +} +#endif /* CONFIG_NOKIA6100_PCF8833 */ + +/************************************************************************************** + * Public Functions + **************************************************************************************/ + +/************************************************************************************** + * Name: nokia_lcdinitialize + * + * Description: + * Initialize the NOKIA6100 video hardware. The initial state of the LCD is fully + * initialized, display memory cleared, and the LCD ready to use, but with the power + * setting at 0 (full off == sleep mode). + * + * Input Parameters: + * + * spi - A reference to the SPI driver instance. + * devno - A value in the range of 0 throuh CONFIG_NOKIA6100_NINTERFACES-1. This + * allows support for multiple LCD devices. + * + * Returned Value: + * + * On success, this function returns a reference to the LCD object for the specified + * LCD. NULL is returned on any failure. + * + **************************************************************************************/ + +FAR struct lcd_dev_s *nokia_lcdinitialize(FAR struct spi_dev_s *spi, unsigned int devno) +{ + struct nokia_dev_s *priv = &g_lcddev; + + gvdbg("Initializing\n"); + DEBUGASSERT(devno == 0); + + /* Initialize the driver data structure */ + + priv->spi = spi; /* Save the SPI instance */ + priv->contrast = NOKIA_DEFAULT_CONTRAST; /* Initial contrast setting */ + + /* Configure and enable the LCD controller */ + + nokia_configspi(spi); + if (nokia_initialize(priv) == OK) + { + /* Turn on the backlight */ + + nokia_backlight(CONFIG_NOKIA6100_BLINIT); + return &priv->dev; + } + return NULL; +} diff --git a/nuttx/drivers/lcd/p14201.c b/nuttx/drivers/lcd/p14201.c new file mode 100644 index 000000000..9dd2e6da9 --- /dev/null +++ b/nuttx/drivers/lcd/p14201.c @@ -0,0 +1,1244 @@ +/************************************************************************************** + * drivers/lcd/p14201.c + * Driver for RiT P14201 series display (wih sd1329 IC controller) + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************/ + +/************************************************************************************** + * Included Files + **************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/spi.h> +#include <nuttx/lcd/lcd.h> +#include <nuttx/lcd/p14201.h> + +#include <arch/irq.h> + +#include "sd1329.h" + +#ifdef CONFIG_LCD_P14201 + +/************************************************************************************** + * Pre-processor Definitions + **************************************************************************************/ + +/* Configuration **********************************************************************/ + +/* P14201 Configuration Settings: + * + * CONFIG_P14201_SPIMODE - Controls the SPI mode + * CONFIG_P14201_FREQUENCY - Define to use a different bus frequency + * CONFIG_P14201_NINTERFACES - Specifies the number of physical P14201 devices that + * will be supported. + * CONFIG_P14201_FRAMEBUFFER - If defined, accesses will be performed using an in-memory + * copy of the OLEDs GDDRAM. This cost of this buffer is 128 * 96 / 2 = 6Kb. If this + * is defined, then the driver will be fully functional. If not, then it will have the + * following limitations: + * + * - Reading graphics memory cannot be supported, and + * - All pixel writes must be aligned to byte boundaries. + * + * The latter limitation effectively reduces the 128x96 disply to 64x96. + * + * Required LCD driver settings: + * CONFIG_LCD_P14201 - Enable P14201 support + * CONFIG_LCD_MAXCONTRAST should be 255, but any value >0 and <=255 will be accepted. + * CONFIG_LCD_MAXPOWER must be 1 + * + * Required SPI driver settings: + * CONFIG_SPI_CMDDATA - Include support for cmd/data selection. + */ + +#ifndef CONFIG_SPI_CMDDATA +# error "CONFIG_SPI_CMDDATA must be defined in your NuttX configuration" +#endif + +/* The P14201 spec says that is supports SPI mode 0,0 only. However, + * somtimes you need to tinker with these things. + */ + +#ifndef CONFIG_P14201_SPIMODE +# define CONFIG_P14201_SPIMODE SPIDEV_MODE2 +#endif + +/* CONFIG_P14201_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_P14201_NINTERFACES +# define CONFIG_P14201_NINTERFACES 1 +#endif + +#if CONFIG_P14201_NINTERFACES != 1 +# error "This implementation supports only a single OLED device" +#endif + +/* Check contrast selection */ + +#if !defined(CONFIG_LCD_MAXCONTRAST) +# define CONFIG_LCD_MAXCONTRAST 255 +#endif + +#if CONFIG_LCD_MAXCONTRAST <= 0|| CONFIG_LCD_MAXCONTRAST > 255 +# error "CONFIG_LCD_MAXCONTRAST exceeds supported maximum" +#endif + +/* Check power setting */ + +#if !defined(CONFIG_LCD_MAXPOWER) +# define CONFIG_LCD_MAXPOWER 1 +#endif + +#if CONFIG_LCD_MAXPOWER != 1 +# warning "CONFIG_LCD_MAXPOWER exceeds supported maximum" +# undef CONFIG_LCD_MAXPOWER +# define CONFIG_LCD_MAXPOWER 1 +#endif + +/* Color is 4bpp greyscale with leftmost column contained in bits 7:4 */ + +#if defined(CONFIG_NX_DISABLE_4BPP) || !defined(CONFIG_NX_PACKEDMSFIRST) +# warning "4-bit, big-endian pixel support needed" +#endif + +/* Define the CONFIG_LCD_RITDEBUG to enable detailed debug output (stuff you would + * never want to see unless you are debugging this file). + * + * Verbose debug must also be enabled + */ + +#ifndef CONFIG_DEBUG +# undef CONFIG_DEBUG_VERBOSE +# undef CONFIG_DEBUG_GRAPHICS +#endif + +#ifndef CONFIG_DEBUG_VERBOSE +# undef CONFIG_LCD_RITDEBUG +#endif + +/* Color Properties *******************************************************************/ + +/* Display Resolution */ + +#define RIT_XRES 128 +#define RIT_YRES 96 + +/* Color depth and format */ + +#define RIT_BPP 4 +#define RIT_COLORFMT FB_FMT_Y4 + +/* Default contrast */ + +#define RIT_CONTRAST ((23 * (CONFIG_LCD_MAXCONTRAST+1) / 32) - 1) + +/* Helper Macros **********************************************************************/ + +#define rit_sndcmd(p,b,l) rit_sndbytes(p,b,l,true); +#define rit_snddata(p,b,l) rit_sndbytes(p,b,l,false); + +/* Debug ******************************************************************************/ + +#ifdef CONFIG_LCD_RITDEBUG +# define ritdbg(format, arg...) vdbg(format, ##arg) +#else +# define ritdbg(x...) +#endif + +/************************************************************************************** + * Private Type Definition + **************************************************************************************/ + +/* This structure describes the state of this driver */ + +struct rit_dev_s +{ + struct lcd_dev_s dev; /* Publically visible device structure */ + FAR struct spi_dev_s *spi; /* Cached SPI device reference */ + uint8_t contrast; /* Current contrast setting */ + bool on; /* true: display is on */ +}; + +/************************************************************************************** + * Private Function Protototypes + **************************************************************************************/ + +/* Low-level SPI helpers */ + +static inline void rit_configspi(FAR struct spi_dev_s *spi); +#ifdef CONFIG_SPI_OWNBUS +static inline void rit_select(FAR struct spi_dev_s *spi); +static inline void rit_deselect(FAR struct spi_dev_s *spi); +#else +static void rit_select(FAR struct spi_dev_s *spi); +static void rit_deselect(FAR struct spi_dev_s *spi); +#endif +static void rit_sndbytes(FAR struct rit_dev_s *priv, FAR const uint8_t *buffer, + size_t buflen, bool cmd); +static void rit_sndcmds(FAR struct rit_dev_s *priv, FAR const uint8_t *table); + +/* LCD Data Transfer Methods */ + +static int rit_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels); +static int rit_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels); + +/* LCD Configuration */ + +static int rit_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo); +static int rit_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo); + +/* LCD RGB Mapping */ + +#ifdef CONFIG_FB_CMAP +# error "RGB color mapping not supported by this driver" +#endif + +/* Cursor Controls */ + +#ifdef CONFIG_FB_HWCURSOR +# error "Cursor control not supported by this driver" +#endif + +/* LCD Specific Controls */ + +static int rit_getpower(struct lcd_dev_s *dev); +static int rit_setpower(struct lcd_dev_s *dev, int power); +static int rit_getcontrast(struct lcd_dev_s *dev); +static int rit_setcontrast(struct lcd_dev_s *dev, unsigned int contrast); + +/************************************************************************************** + * Private Data + **************************************************************************************/ + +/* This is working memory allocated by the LCD driver for each LCD device + * and for each color plane. This memory will hold one raster line of data. + * The size of the allocated run buffer must therefore be at least + * (bpp * xres / 8). Actual alignment of the buffer must conform to the + * bitwidth of the underlying pixel type. + * + * If there are multiple planes, they may share the same working buffer + * because different planes will not be operate on concurrently. However, + * if there are multiple LCD devices, they must each have unique run buffers. + */ + +static uint8_t g_runbuffer[RIT_XRES / 2]; + +/* CONFIG_P14201_FRAMEBUFFER - If defined, accesses will be performed using an in-memory + * copy of the OLEDs GDDRAM. This cost of this buffer is 128 * 64 / 2 = 4Kb. If this + * is defined, then the driver will be full functioned. If not, then: + * + * - Reading graphics memory cannot be supported, and + * - All pixel writes must be aligned to byte boundaries. + */ + +#ifdef CONFIG_P14201_FRAMEBUFFER +static uint8_t g_framebuffer[RIT_YRES * RIT_XRES / 2]; +#endif + +/* This structure describes the overall LCD video controller */ + +static const struct fb_videoinfo_s g_videoinfo = +{ + .fmt = RIT_COLORFMT, /* Color format: RGB16-565: RRRR RGGG GGGB BBBB */ + .xres = RIT_XRES, /* Horizontal resolution in pixel columns */ + .yres = RIT_YRES, /* Vertical resolution in pixel rows */ + .nplanes = 1, /* Number of color planes supported */ +}; + +/* This is the standard, NuttX Plane information object */ + +static const struct lcd_planeinfo_s g_planeinfo = +{ + .putrun = rit_putrun, /* Put a run into LCD memory */ + .getrun = rit_getrun, /* Get a run from LCD memory */ + .buffer = (uint8_t*)g_runbuffer, /* Run scratch buffer */ + .bpp = RIT_BPP, /* Bits-per-pixel */ +}; + +/* This is the OLED driver instance (only a single device is supported for now) */ + +static struct rit_dev_s g_oleddev = +{ + .dev = + { + /* LCD Configuration */ + + .getvideoinfo = rit_getvideoinfo, + .getplaneinfo = rit_getplaneinfo, + + /* LCD RGB Mapping -- Not supported */ + /* Cursor Controls -- Not supported */ + + /* LCD Specific Controls */ + + .getpower = rit_getpower, + .setpower = rit_setpower, + .getcontrast = rit_getcontrast, + .setcontrast = rit_setcontrast, + }, +}; + +/* A table of magic initialization commands. This initialization sequence is + * derived from RiT Application Note for the P14201 (with a few tweaked values + * as discovered in some Luminary code examples). + */ + +static const uint8_t g_initcmds[] = +{ + 3, SSD1329_CMD_LOCK, /* Set lock command */ + SSD1329_LOCK_OFF, /* Disable locking */ + SSD1329_NOOP, + 2, SSD1329_SLEEP_ON, /* Matrix display OFF */ + SSD1329_NOOP, + 3, SSD1329_ICON_ALL, /* Set all ICONs to OFF */ + SSD1329_ICON_OFF, /* OFF selection */ + SSD1329_NOOP, + 3, SSD1329_MUX_RATIO, /* Set MUX ratio */ + 95, /* 96 MUX */ + SSD1329_NOOP, + 3, SSD1329_SET_CONTRAST, /* Set contrast */ + RIT_CONTRAST, /* Default contrast */ + SSD1329_NOOP, + 3, SSD1329_PRECHRG2_SPEED, /* Set second pre-charge speed */ + (31 << 1) | SSD1329_PRECHRG2_DBL, /* Pre-charge speed == 32, doubled */ + SSD1329_NOOP, + 3, SSD1329_GDDRAM_REMAP, /* Set GDDRAM re-map */ + (SSD1329_COM_SPLIT| /* Enable COM slip even/odd */ + SSD1329_COM_REMAP| /* Enable COM re-map */ + SSD1329_NIBBLE_REMAP), /* Enable nibble re-map */ + SSD1329_NOOP, + 3, SSD1329_VERT_START, /* Set Display Start Line */ + 0, /* Line = 0 */ + SSD1329_NOOP, + 3, SSD1329_VERT_OFFSET, /* Set Display Offset */ + 0, /* Offset = 0 */ + SSD1329_NOOP, + 2, SSD1329_DISP_NORMAL, /* Display mode normal */ + SSD1329_NOOP, + 3, SSD1329_PHASE_LENGTH, /* Set Phase Length */ + 1 | /* Phase 1 period = 1 DCLK */ + (1 << 4), /* Phase 2 period = 1 DCLK */ + SSD1329_NOOP, + 3, SSD1329_FRAME_FREQ, + 35, /* 35 DCLK's per row */ + SSD1329_NOOP, + 3, SSD1329_DCLK_DIV, /* Set Front Clock Divider / Oscillator Frequency */ + 2 | /* Divide ration = 3 */ + (14 << 4), /* Oscillator Frequency, FOSC, setting */ + SSD1329_NOOP, + 17, SSD1329_GSCALE_LOOKUP, /* Look Up Table for Gray Scale Pulse width */ + 1, 2, 3, 4, 5, /* Value for GS1-5 level Pulse width */ + 6, 8, 10, 12, 14, /* Value for GS6-10 level Pulse width */ + 16, 19, 22, 26, 30, /* Value for GS11-15 level Pulse width */ + SSD1329_NOOP, + 3, SSD1329_PRECHRG2_PERIOD, /* Set Second Pre-charge Period */ + 1, /* 1 DCLK */ + SSD1329_NOOP, + 3, SSD1329_PRECHRG1_VOLT, /* Set First Precharge voltage, VP */ + 0x3f, /* 1.00 x Vcc */ + SSD1329_NOOP, + 0 /* Zero length command terminates table */ +}; + +/* Turn the maxtrix display on (sleep mode off) */ + +static const uint8_t g_sleepoff[] = +{ + SSD1329_SLEEP_OFF, /* Matrix display ON */ + SSD1329_NOOP, +}; + +/* Turn the maxtrix display off (sleep mode on) */ + +static const uint8_t g_sleepon[] = +{ + SSD1329_SLEEP_ON, /* Matrix display OFF */ + SSD1329_NOOP, +}; + +/* Set horizontal increment mode */ + +static const uint8_t g_horzinc[] = +{ + SSD1329_GDDRAM_REMAP, + (SSD1329_COM_SPLIT|SSD1329_COM_REMAP|SSD1329_NIBBLE_REMAP), +}; + +/* The following set a window that covers the entire display */ + +static const uint8_t g_setallcol[] = +{ + SSD1329_SET_COLADDR, + 0, + (RIT_XRES/2)-1 +}; + +static const uint8_t g_setallrow[] = +{ + SSD1329_SET_ROWADDR, + 0, + RIT_YRES-1 +}; + +/************************************************************************************** + * Private Functions + **************************************************************************************/ + +/************************************************************************************** + * Function: rit_configspi + * + * Description: + * Configure the SPI for use with the P14201 + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +static inline void rit_configspi(FAR struct spi_dev_s *spi) +{ +#ifdef CONFIG_P14201_FREQUENCY + ritdbg("Mode: %d Bits: 8 Frequency: %d\n", + CONFIG_P14201_SPIMODE, CONFIG_P14201_FREQUENCY); +#else + ritdbg("Mode: %d Bits: 8\n", CONFIG_P14201_SPIMODE); +#endif + + /* Configure SPI for the P14201. But only if we own the SPI bus. Otherwise, don't + * bother because it might change. + */ + +#ifdef CONFIG_SPI_OWNBUS + SPI_SETMODE(spi, CONFIG_P14201_SPIMODE); + SPI_SETBITS(spi, 8); +#ifdef CONFIG_P14201_FREQUENCY + SPI_SETFREQUENCY(spi, CONFIG_P14201_FREQUENCY) +#endif +#endif +} + +/************************************************************************************** + * Function: rit_select + * + * Description: + * Select the SPI, locking and re-configuring if necessary + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void rit_select(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just select the chip */ + + SPI_SELECT(spi, SPIDEV_DISPLAY, true); +} +#else +static void rit_select(FAR struct spi_dev_s *spi) +{ + /* Select P14201 chip (locking the SPI bus in case there are multiple + * devices competing for the SPI bus + */ + + SPI_LOCK(spi, true); + SPI_SELECT(spi, SPIDEV_DISPLAY, true); + + /* Now make sure that the SPI bus is configured for the P14201 (it + * might have gotten configured for a different device while unlocked) + */ + + SPI_SETMODE(spi, CONFIG_P14201_SPIMODE); + SPI_SETBITS(spi, 8); +#ifdef CONFIG_P14201_FREQUENCY + SPI_SETFREQUENCY(spi, CONFIG_P14201_FREQUENCY); +#endif +} +#endif + +/************************************************************************************** + * Function: rit_deselect + * + * Description: + * De-select the SPI + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void rit_deselect(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just de-select the chip */ + + SPI_SELECT(spi, SPIDEV_DISPLAY, false); +} +#else +static void rit_deselect(FAR struct spi_dev_s *spi) +{ + /* De-select P14201 chip and relinquish the SPI bus. */ + + SPI_SELECT(spi, SPIDEV_DISPLAY, false); + SPI_LOCK(spi, false); +} +#endif + +/************************************************************************************** + * Function: rit_sndbytes + * + * Description: + * Send a sequence of command or data bytes to the SSD1329 controller. + * + * Parameters: + * spi - Reference to the SPI driver structure + * buffer - A reference to memory containing the command bytes to be sent. + * buflen - The number of command bytes in buffer to be sent + * + * Returned Value: + * None + * + * Assumptions: + * The caller as selected the OLED device. + * + **************************************************************************************/ + +static void rit_sndbytes(FAR struct rit_dev_s *priv, FAR const uint8_t *buffer, + size_t buflen, bool cmd) +{ + FAR struct spi_dev_s *spi = priv->spi; + uint8_t tmp; + + ritdbg("buflen: %d cmd: %s [%02x %02x %02x]\n", + buflen, cmd ? "YES" : "NO", buffer[0], buffer[1], buffer[2] ); + DEBUGASSERT(spi); + + /* Clear/set the D/Cn bit to enable command or data mode */ + + (void)SPI_CMDDATA(spi, SPIDEV_DISPLAY, cmd); + + /* Loop until the entire command/data block is transferred */ + + while (buflen-- > 0) + { + /* Write the next byte to the controller */ + + tmp = *buffer++; + (void)SPI_SEND(spi, tmp); + } +} + +/************************************************************************************** + * Function: rit_sndcmd + * + * Description: + * Send multiple commands from a table of commands. + * + * Parameters: + * spi - Reference to the SPI driver structure + * table - A reference to table containing all of the commands to be sent. + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +static void rit_sndcmds(FAR struct rit_dev_s *priv, FAR const uint8_t *table) +{ + int cmdlen; + + /* Table terminates with a zero length command */ + + while ((cmdlen = *table++) != 0) + { + ritdbg("command: %02x cmdlen: %d\n", *table, cmdlen); + rit_sndcmd(priv, table, cmdlen); + table += cmdlen; + } +} + +/************************************************************************************** + * Name: rit_clear + * + * Description: + * This method can be used to write a partial raster line to the LCD: + * + * rpriv - Reference to private driver structure + * + * Assumptions: + * Caller has selected the OLED section. + * + **************************************************************************************/ + +#ifdef CONFIG_P14201_FRAMEBUFFER +static inline void rit_clear(FAR struct rit_dev_s *priv) +{ + FAR uint8_t *ptr = g_framebuffer; + unsigned int row; + + ritdbg("Clear display\n"); + + /* Initialize the framebuffer */ + + memset(g_framebuffer, (RIT_Y4_BLACK << 4) | RIT_Y4_BLACK, RIT_YRES * RIT_XRES / 2); + + /* Set a window to fill the entire display */ + + rit_sndcmd(priv, g_setallcol, sizeof(g_setallcol)); + rit_sndcmd(priv, g_setallrow, sizeof(g_setallrow)); + rit_sndcmd(priv, g_horzinc, sizeof(g_horzinc)); + + /* Display each row */ + + for(row = 0; row < RIT_YRES; row++) + { + /* Display a horizontal run */ + + rit_snddata(priv, ptr, RIT_XRES / 2); + ptr += RIT_XRES / 2; + } +} +#else +static inline void rit_clear(FAR struct rit_dev_s *priv) +{ + unsigned int row; + + ritdbg("Clear display\n"); + + /* Create a black row */ + + memset(g_runbuffer, (RIT_Y4_BLACK << 4) | RIT_Y4_BLACK, RIT_XRES / 2); + + /* Set a window to fill the entire display */ + + rit_sndcmd(priv, g_setallcol, sizeof(g_setallcol)); + rit_sndcmd(priv, g_setallrow, sizeof(g_setallrow)); + rit_sndcmd(priv, g_horzinc, sizeof(g_horzinc)); + + /* Display each row */ + + for(row = 0; row < RIT_YRES; row++) + { + /* Display a horizontal run */ + + rit_snddata(priv, g_runbuffer, RIT_XRES / 2); + } +} +#endif + +/************************************************************************************** + * Name: rit_putrun + * + * Description: + * This method can be used to write a partial raster line to the LCD: + * + * row - Starting row to write to (range: 0 <= row < yres) + * col - Starting column to write to (range: 0 <= col <= xres-npixels) + * buffer - The buffer containing the run to be written to the LCD + * npixels - The number of pixels to write to the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +#ifdef CONFIG_P14201_FRAMEBUFFER +static int rit_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels) +{ + FAR struct rit_dev_s *priv = (FAR struct rit_dev_s *)&g_oleddev; + uint8_t cmd[3]; + uint8_t *run; + int start; + int end; + int aend; + int i; + + ritdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer); + + /* Toss out the special case of the empty run now */ + + if (npixels < 1) + { + return OK; + } + + /* Get the beginning of the line containing run in the framebuffer */ + + run = g_framebuffer + row * RIT_XRES / 2; + + /* Get the starting and ending byte offsets containing the run. + * the run starts at &run[start] and continues through run[end-1]. + * However, the first and final pixels at these locations may + * not be byte aligned. + */ + + start = col >> 1; + aend = (col + npixels) >> 1; + end = (col + npixels + 1) >> 1; + ritdbg("start: %d aend: %d end: %d\n", start, aend, end); + + /* Copy the run into the framebuffer, handling nibble alignment. + * + * CASE 1: First pixel X position is byte aligned + * + * example col=6 npixels = 8 example col=6 npixels=7 + * + * Run: |AB|AB|AB|AB| |AB|AB|AB|AB| + * GDDRAM row: + * Byte | 0| 1| 2| 3| 4| 5| 6| | 0| 1| 2| 3| 4| 5| 6| + * Pixel: |--|--|--|AB|AB|AB|AB| |--|--|--|AB|AB|AB|A-| + * + * start = 3 start = 3 + * aend = 6 aend = 6 + * end = 6 end = 7 + * + */ + + if ((col & 1) == 0) + { + /* Check for the special case of only 1 pixel being blitted */ + + if (npixels > 1) + { + /* Beginning of buffer is properly aligned, from start to aend */ + + memcpy(&run[start], buffer, aend - start); + } + + /* An even number of byte-aligned pixel pairs have been written (where + * zero counts as an even number). If npixels was was odd (including + * npixels == 1), then handle the final, byte aligned pixel. + */ + + if (aend != end) + { + /* The leftmost column is contained in source bits 7:4 and in + * destination bits 7:4 + */ + + run[aend] = (run[aend] & 0x0f) | (buffer[aend - start] & 0xf0); + } + } + + /* CASE 2: First pixel X position is byte aligned + * + * example col=7 npixels = 8 example col=7 npixels=7 + * + * Run: |AB|AB|AB|AB| |AB|AB|AB|AB| + * GDDRAM row: + * Byte | 0| 1| 2| 3| 4| 5| 6| 7| | 0| 1| 2| 3| 4| 5| 6| + * Pixel: |--|--|--|-A|BA|BA|BA|B-| |--|--|--|-A|BA|BA|BA| + * + * start = 3 start = 3 + * aend = 7 aend = 7 + * end = 8 end = 7 + */ + + else + { + uint8_t curr = buffer[0]; + uint8_t last; + + /* Handle the initial unaligned pixel. Source bits 7:4 into + * destination bits 3:0. In the special case of npixel == 1, + * this finished the job. + */ + + run[start] = (run[start] & 0xf0) | (curr >> 4); + + /* Now construct the rest of the bytes in the run (possibly special + * casing the final, partial byte below). + */ + + for (i = start + 1; i < aend; i++) + { + /* bits 3:0 from previous byte to run bits 7:4; + * bits 7:4 of current byte to run bits 3:0 + */ + + last = curr; + curr = buffer[i-start]; + run[i] = (last << 4) | (curr >> 4); + } + + /* An odd number of unaligned pixel have been written (where npixels + * may have been as small as one). If npixels was was even, then handle + * the final, unaligned pixel. + */ + + if (aend != end) + { + /* The leftmost column is contained in source bits 3:0 and in + * destination bits 7:4 + */ + + run[aend] = (run[aend] & 0x0f) | (curr << 4); + } + } + + /* Select the SD1329 controller */ + + rit_select(priv->spi); + + /* Setup a window that describes a run starting at the specified column + * and row, and ending at the column + npixels on the same row. + */ + + cmd[0] = SSD1329_SET_COLADDR; + cmd[1] = start; + cmd[2] = end - 1; + rit_sndcmd(priv, cmd, 3); + + cmd[0] = SSD1329_SET_ROWADDR; + cmd[1] = row; + cmd[2] = row; + rit_sndcmd(priv, cmd, 3); + + /* Write the run to GDDRAM. */ + + rit_sndcmd(priv, g_horzinc, sizeof(g_horzinc)); + rit_snddata(priv, &run[start], end - start); + + /* De-select the SD1329 controller */ + + rit_deselect(priv->spi); + return OK; +} +#else +static int rit_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels) +{ + FAR struct rit_dev_s *priv = (FAR struct rit_dev_s *)&g_oleddev; + uint8_t cmd[3]; + + ritdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer); + + if (npixels > 0) + { + /* Check that the X and Y coordinates are within range */ + + DEBUGASSERT(col < RIT_XRES && (col + npixels) <= RIT_XRES && row < RIT_YRES); + + /* Check that the X coordinates are aligned to 8-bit boundaries + * (this needs to get fixed somehow) + */ + + DEBUGASSERT((col & 1) == 0 && (npixels & 1) == 0); + + /* Select the SD1329 controller */ + + rit_select(priv->spi); + + /* Setup a window that describes a run starting at the specified column + * and row, and ending at the column + npixels on the same row. + */ + + cmd[0] = SSD1329_SET_COLADDR; + cmd[1] = col >> 1; + cmd[2] = ((col + npixels) >> 1) - 1; + rit_sndcmd(priv, cmd, 3); + + cmd[0] = SSD1329_SET_ROWADDR; + cmd[1] = row; + cmd[2] = row; + rit_sndcmd(priv, cmd, 3); + + /* Write the run to GDDRAM. */ + + rit_sndcmd(priv, g_horzinc, sizeof(g_horzinc)); + rit_snddata(priv, buffer, npixels >> 1); + + /* De-select the SD1329 controller */ + + rit_deselect(priv->spi); + } + return OK; +} +#endif + +/************************************************************************************** + * Name: rit_getrun + * + * Description: + * This method can be used to read a partial raster line from the LCD: + * + * row - Starting row to read from (range: 0 <= row < yres) + * col - Starting column to read read (range: 0 <= col <= xres-npixels) + * buffer - The buffer in which to return the run read from the LCD + * npixels - The number of pixels to read from the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +#ifdef CONFIG_P14201_FRAMEBUFFER +static int rit_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels) +{ + uint8_t *run; + int start; + int end; + int aend; + int i; + + ritdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer); + + /* Can't read from OLED GDDRAM in SPI mode, but we can read from the framebuffer */ + /* Toss out the special case of the empty run now */ + + if (npixels < 1) + { + return OK; + } + + /* Get the beginning of the line containing run in the framebuffer */ + + run = g_framebuffer + row * RIT_XRES / 2; + + /* Get the starting and ending byte offsets containing the run. + * the run starts at &run[start] and continues through run[end-1]. + * However, the first and final pixels at these locations may + * not be byte aligned (see examples in putrun()). + */ + + start = col >> 1; + aend = (col + npixels) >> 1; + end = (col + npixels + 1) >> 1; + + /* Copy the run into the framebuffer, handling nibble alignment */ + + if ((col & 1) == 0) + { + /* Check for the special case of only 1 pixels being copied */ + + if (npixels > 1) + { + /* Beginning of buffer is properly aligned, from start to aend */ + + memcpy(buffer, &run[start], aend - start + 1); + } + + /* Handle any final pixel (including the special case where npixels == 1). */ + + if (aend != end) + { + /* The leftmost column is contained in source bits 7:4 and in + * destination bits 7:4 + */ + + buffer[aend - start] = run[aend] & 0xf0; + } + } + else + { + uint8_t curr = run[start]; + uint8_t last; + + /* Now construct the rest of the bytes in the run (possibly special + * casing the final, partial byte below). + */ + + for (i = start + 1; i < aend; i++) + { + /* bits 3:0 from previous byte to run bits 7:4; + * bits 7:4 of current byte to run bits 3:0 + */ + + last = curr; + curr = run[i]; + *buffer++ = (last << 4) | (curr >> 4); + } + + /* Handle any final pixel (including the special case where npixels == 1). */ + + if (aend != end) + { + /* The leftmost column is contained in source bits 3:0 and in + * destination bits 7:4 + */ + + *buffer = (curr << 4); + } + } + return OK; +} +#else +static int rit_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels) +{ + /* Can't read from OLED GDDRAM in SPI mode */ + + return -ENOSYS; +} +#endif + +/************************************************************************************** + * Name: rit_getvideoinfo + * + * Description: + * Get information about the LCD video controller configuration. + * + **************************************************************************************/ + +static int rit_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo) +{ + DEBUGASSERT(dev && vinfo); + gvdbg("fmt: %d xres: %d yres: %d nplanes: %d\n", + g_videoinfo.fmt, g_videoinfo.xres, g_videoinfo.yres, g_videoinfo.nplanes); + memcpy(vinfo, &g_videoinfo, sizeof(struct fb_videoinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: rit_getplaneinfo + * + * Description: + * Get information about the configuration of each LCD color plane. + * + **************************************************************************************/ + +static int rit_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo) +{ + DEBUGASSERT(pinfo && planeno == 0); + gvdbg("planeno: %d bpp: %d\n", planeno, g_planeinfo.bpp); + memcpy(pinfo, &g_planeinfo, sizeof(struct lcd_planeinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: rit_getpower + * + * Description: + * Get the LCD panel power status (0: full off - CONFIG_LCD_MAXPOWER: full on. On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int rit_getpower(FAR struct lcd_dev_s *dev) +{ + FAR struct rit_dev_s *priv = (FAR struct rit_dev_s *)dev; + DEBUGASSERT(priv); + + gvdbg("power: %s\n", priv->on ? "ON" : "OFF"); + return priv->on ? CONFIG_LCD_MAXPOWER : 0; +} + +/************************************************************************************** + * Name: rit_setpower + * + * Description: + * Enable/disable LCD panel power (0: full off - CONFIG_LCD_MAXPOWER: full on). On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int rit_setpower(struct lcd_dev_s *dev, int power) +{ + struct rit_dev_s *priv = (struct rit_dev_s *)dev; + DEBUGASSERT(priv && (unsigned)power <= CONFIG_LCD_MAXPOWER && priv->spi); + + gvdbg("power: %d\n", power); + + /* Select the SD1329 controller */ + + rit_select(priv->spi); + + /* Only two power settings -- 0: sleep on, 1: sleep off */ + + if (power > 0) + { + /* Re-initialize the SSD1329 controller */ + + rit_sndcmds(priv, g_initcmds); + + /* Take the display out of sleep mode */ + + rit_sndcmd(priv, g_sleepoff, sizeof(g_sleepoff)); + priv->on = true; + } + else + { + /* Put the display into sleep mode */ + + rit_sndcmd(priv, g_sleepon, sizeof(g_sleepon)); + priv->on = false; + } + + /* De-select the SD1329 controller */ + + rit_deselect(priv->spi); + return OK; +} + +/************************************************************************************** + * Name: rit_getcontrast + * + * Description: + * Get the current contrast setting (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int rit_getcontrast(struct lcd_dev_s *dev) +{ + struct rit_dev_s *priv = (struct rit_dev_s *)dev; + + gvdbg("contrast: %d\n", priv->contrast); + return priv->contrast; +} + +/************************************************************************************** + * Name: rit_setcontrast + * + * Description: + * Set LCD panel contrast (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int rit_setcontrast(struct lcd_dev_s *dev, unsigned int contrast) +{ + struct rit_dev_s *priv = (struct rit_dev_s *)dev; + uint8_t cmd[3]; + + gvdbg("contrast: %d\n", contrast); + DEBUGASSERT(contrast <= CONFIG_LCD_MAXCONTRAST); + + /* Select the SD1329 controller */ + + rit_select(priv->spi); + + /* Set new contrast */ + + cmd[0] = SSD1329_SET_CONTRAST; + cmd[1] = contrast; + cmd[2] = SSD1329_NOOP; + rit_sndcmd(priv, cmd, 3); + + /* De-select the SD1329 controller */ + + rit_deselect(priv->spi); + priv->contrast = contrast; + return OK; +} + +/************************************************************************************** + * Public Functions + **************************************************************************************/ + +/************************************************************************************** + * Name: rit_initialize + * + * Description: + * Initialize the P14201 video hardware. The initial state of the OLED is fully + * initialized, display memory cleared, and the OLED ready to use, but with the power + * setting at 0 (full off == sleep mode). + * + * Input Parameters: + * + * spi - A reference to the SPI driver instance. + * devno - A value in the range of 0 throuh CONFIG_P14201_NINTERFACES-1. This allows + * support for multiple OLED devices. + * + * Returned Value: + * + * On success, this function returns a reference to the LCD object for the specified + * OLED. NULL is returned on any failure. + * + **************************************************************************************/ + +FAR struct lcd_dev_s *rit_initialize(FAR struct spi_dev_s *spi, unsigned int devno) +{ + FAR struct rit_dev_s *priv = (FAR struct rit_dev_s *)&g_oleddev; + DEBUGASSERT(devno == 0 && spi); + + gvdbg("Initializing devno: %d\n", devno); + + /* Driver state data */ + + priv->spi = spi; + priv->contrast = RIT_CONTRAST; + priv->on = false; + + /* Select the SD1329 controller */ + + rit_configspi(spi); + rit_select(spi); + + /* Clear the display */ + + rit_clear(priv); + + /* Configure (but don't enable) the OLED */ + + rit_sndcmds(priv, g_initcmds); + + /* De-select the SD1329 controller */ + + rit_deselect(spi); + return &priv->dev; +} +#endif /* CONFIG_LCD_P14201 */ diff --git a/nuttx/drivers/lcd/pcf8833.h b/nuttx/drivers/lcd/pcf8833.h new file mode 100644 index 000000000..b0a7e14d4 --- /dev/null +++ b/nuttx/drivers/lcd/pcf8833.h @@ -0,0 +1,152 @@ +/**************************************************************************************
+ * drivers/lcd/pcf8833.h
+ * Definitions for the Phillips PCF8833 LCD controller
+ *
+ * Copyright (C) 2010 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * References: "Data Sheet, PCF8833 STN RGB 132x132x3 driver," Phillips, 2003 Feb 14.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ **************************************************************************************/
+
+#ifndef __DRIVERS_LCD_PCF8833_H
+#define __DRIVERS_LCD_PCF8833_H
+
+/**************************************************************************************
+ * Included Files
+ **************************************************************************************/
+
+/**************************************************************************************
+ * Pre-processor Definitions
+ **************************************************************************************/
+/* Pixel format codes */
+
+#define PCF8833_FMT_8BPS (2)
+#define PCF8833_FMT_12BPS (3)
+#define PCF8833_FMT_16BPS (5)
+
+/* LCD Commands */
+
+#define PCF8833_NOP 0x00 /* No operation; Data: none */
+#define PCF8833_SWRESET 0x01 /* Software reset ; Data: none */
+#define PCF8833_BSTROFF 0x02 /* Booster voltage off; Data: none */
+#define PCF8833_BSTRON 0x03 /* Booster voltage on; Data: none */
+#define PCF8833_RDDIDIF 0x04 /* Read display identification; Data: none */
+#define PCF8833_RDDST 0x09 /* Read display status; Data: none */
+#define PCF8833_SLEEPIN 0x10 /* Sleep_IN; Data: none */
+#define PCF8833_SLEEPOUT 0x11 /* Sleep_OUT; Data: none */
+#define PCF8833_PTLON 0x12 /* Partial mode on; Data: none */
+#define PCF8833_NORON 0x13 /* Normal Display mode on; Data: none */
+#define PCF8833_INVOFF 0x20 /* Display inversion off; Data: none */
+#define PCF8833_INVON 0x21 /* Display inversion on; Data: none */
+#define PCF8833_DALO 0x22 /* All pixel off; Data: none */
+#define PCF8833_DAL 0x23 /* All pixel on; Data: none */
+#define PCF8833_SETCON 0x25 /* Set contrast; Data: (1) contrast */
+#define PCF8833_DISPOFF 0x28 /* Display off; Data: none */
+#define PCF8833_DISPON 0x29 /* Display on; Data: none */
+#define PCF8833_CASET 0x2a /* Column address set; Data: (1) X start (2) X end */
+#define PCF8833_PASET 0x2b /* Page address set Data: (1) Y start (2) Y end */
+#define PCF8833_RAMWR 0x2c /* Memory write; Data: (1) write data */
+#define PCF8833_RGBSET 0x2d /* Colour set; Data: (1-8) red tones, (9-16) green tones, (17-20) blue tones */
+#define PCF8833_PTLAR 0x30 /* Partial area; Data: (1) start address (2) end address */
+#define PCF8833_VSCRDEF 0x33 /* Vertical scroll definition; Data: (1) top fixed, (2) scrol area, (3) bottom fixed */
+#define PCF8833_TEOFF 0x34 /* Tearing line off; Data: none */
+#define PCF8833_TEON 0x35 /* Tearing line on; Data: (1) don't care */
+#define PCF8833_MADCTL 0x36 /* Memory data access control; Data: (1) access control settings */
+#define PCF8833_SEP 0x37 /* Set Scroll Entry Point; Data: (1) scroll entry point */
+#define PCF8833_IDMOFF 0x38 /* Idle mode off; Data: none */
+#define PCF8833_IDMON 0x39 /* Idle mode on; Data: none */
+#define PCF8833_COLMOD 0x3a /* Interface pixel format; Data: (1) color interface format */
+#define PCF8833_SETVOP 0xb0 /* Set VOP; Data: (1) VOP5-8 (2) VOP0-4 */
+#define PCF8833_BRS 0xb4 /* Bottom Row Swap; Data: none */
+#define PCF8833_TRS 0xb6 /* Top Row Swap; Data: none */
+#define PCF8833_FINV 0xb9 /* Super Frame INVersion; Data: none */
+#define PCF8833_DOR 0xba /* Data ORder; Data: none */
+#define PCF8833_TCDFE 0xbd /* Enable/disable DF temp comp; Data: none */
+#define PCF8833_TCVOPE 0xbf /* Enable or disable VOP temp comp; Data: none */
+#define PCF8833_EC 0xc0 /* Internal or external oscillator; Data: none */
+#define PCF8833_SETMUL 0xc2 /* Set multiplication factor; Data: (1) Multiplication factor */
+#define PCF8833_TCVOPAB 0xc3 /* Set TCVOP slopes A and B; Data: (1) SLB and SLA */
+#define PCF8833_TCVOPCD 0xc4 /* Set TCVOP slopes C and D; Data: (1) SLD and SLC */
+#define PCF8833_TCDF 0xc5 /* Set divider frequency; Data: Divider factor in region (1) A (2) B (3) C (4) D */
+#define PCF8833_DF8COLOR 0xc6 /* Set divider frequency 8-colour mode; Data: (1) DF80-6 */
+#define PCF8833_SETBS 0xc7 /* Set bias system; Data: (1) Bias systems */
+#define PCF8833_RDTEMP 0xc8 /* Temperature read back; Data: none */
+#define PCF8833_NLI 0xc9 /* N-Line Inversion; Data: (1) NLI time slots invervsion */
+#define PCF8833_RDID1 0xda /* Read ID1; Data: none */
+#define PCF8833_RDID2 0xdb /* Read ID2; Data: none */
+#define PCF8833_RDID3 0xdc /* Read ID3; Data: none */
+#define PCF8833_SFD 0xef /* Select factory defaults; Data: none */
+#define PCF8833_ECM 0xf0 /* Enter Calibration mode; Data: (1) Calibration control settings */
+#define PCF8833_OTPSHTIN 0xf1 /* Shift data in OTP shift registers; Data: Any number of bytes */
+
+/* Memory data access control (MADCTL) bit definitions */
+
+#define MADCTL_RGB (1 << 3) /* Bit 3: BGR */
+#define MADCTL_LAO (1 << 4) /* Bit 4: Line address order bottom to top */
+#define MADCTL_V (1 << 5) /* Bit 5: Vertical RAM write; in Y direction */
+#define MADCTL_MX (1 << 6) /* Bit 6: Mirror X */
+#define MADCTL_MY (1 << 7) /* Bit 7: Mirror Y */
+
+/* PCF8833 status register bit definitions */
+/* CMD format: RDDST command followed by four status bytes: */
+/* Byte 1: D31 d30 D29 D28 D27 D26 --- --- */
+
+#define PCF8833_ST_RGB (1 << 2) /* Bit 2: D26 - RGB/BGR order */
+#define PCF8833_ST_LINEADDR (1 << 3) /* Bit 3: D27 - Line address order */
+#define PCF8833_ST_ADDRMODE (1 << 4) /* Bit 4: D28 - Vertical/horizontal addressing mode */
+#define PCF8833_ST_XADDR (1 << 5) /* Bit 5: D29 - X address order */
+#define PCF8833_ST_YADDR (1 << 6) /* Bit 6: D30 - Y address order */
+#define PCF8833_ST_BOOSTER (1 << 7) /* Bit 7: D31 - Booster voltage status */
+
+/* Byte 2: --- D22 D21 D20 D19 D18 D17 D16 */
+
+#define PCF8833_ST_NORMAL (1 << 0) /* Bit 0: D16 - Normal display mode */
+#define PCF8833_ST_SLEEPIN (1 << 1) /* Bit 1: D17 - Sleep in selected */
+#define PCF8833_ST_PARTIAL (1 << 2) /* Bit 2: D18 - Partial mode on */
+#define PCF8833_ST_IDLE (1 << 3) /* Bit 3: D19 - Idle mode selected */
+#define PCF8833_ST_PIXELFMT_SHIFT (4) /* Bits 4-6: D20-D22 - Interface pixel format */
+#define PCF8833_ST_PIXELFMT_MASK (7 << PCF8833_ST_PIXELFMT_SHIFT)
+# define PCF8833_ST_PIXELFMT_8BPS (PCF8833_FMT_8BPS << PCF8833_ST_PIXELFMT_SHIFT)
+# define PCF8833_ST_PIXELFMT_12BPS (PCF8833_FMT_12BPS << PCF8833_ST_PIXELFMT_SHIFT)
+# define PCF8833_ST_PIXELFMT_16BPS (PCF8833_FMT_16BPS << PCF8833_ST_PIXELFMT_SHIFT)
+
+/* Byte 3: D15 -- D13 D12 D11 D10 D9 --- */
+
+#define PCF8833_ST_TEARING (1 << 1) /* Bit 1: D9 - Tearing effect on */
+#define PCF8833_ST_DISPLAYON (1 << 2) /* Bit 2: D10 - Display on */
+#define PCF8833_ST_PIXELSOFF (1 << 3) /* Bit 3: D11 - All pixels off */
+#define PCF8833_ST_PIXELSON (1 << 4) /* Bit 4: D12 - All pixels on */
+#define PCF8833_ST_INV (1 << 5) /* Bit 5: D13 - Display inversion */
+#define PCF8833_ST_VSCROLL (1 << 7) /* Bit 6: D15 - Vertical scroll mode */
+
+/* Byte 4: All zero */
+
+#endif /* __DRIVERS_LCD_PCF8833_H */
\ No newline at end of file diff --git a/nuttx/drivers/lcd/s1d15g10.h b/nuttx/drivers/lcd/s1d15g10.h new file mode 100644 index 000000000..df2dd8be2 --- /dev/null +++ b/nuttx/drivers/lcd/s1d15g10.h @@ -0,0 +1,141 @@ +/**************************************************************************************
+ * drivers/lcd/s1d15g10.h
+ * Definitions for the Epson S1D15G0 LCD controller
+ *
+ * Copyright (C) 2010 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * References: S1D15G0D08B000, Seiko Epson Corportation, 2002.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ **************************************************************************************/
+
+#ifndef __DRIVERS_LCD_S1D15G10_H
+#define __DRIVERS_LCD_S1D15G10_H
+
+/**************************************************************************************
+ * Included Files
+ **************************************************************************************/
+
+/**************************************************************************************
+ * Pre-processor Definitions
+ **************************************************************************************/
+
+/* Epson S1D15G10 Command Set */
+
+#define S1D15G10_DISON 0xaf /* Display on; Data: none */
+#define S1D15G10_DISOFF 0xae /* Display off; Data: none */
+#define S1D15G10_DISNOR 0xa6 /* Normal display; Data: none */
+#define S1D15G10_DISINV 0xa7 /* Inverse display; Data: none */
+#define S1D15G10_COMSCN 0xbb /* Common scan direction; Data: (1) common scan direction */
+#define S1D15G10_DISCTL 0xca /* Display control; Data: Data: (1) CL div, F1/2 pat, (2) duty, (3) FR inverse (4) dispersion */
+#define S1D15G10_SLPIN 0x95 /* Sleep in; Data: none */
+#define S1D15G10_SLPOUT 0x94 /* Sleep out; Data: none */
+#define S1D15G10_PASET 0x75 /* Page address set; Data: (1) start page, (2) end page */
+#define S1D15G10_CASET 0x15 /* Column address set; Data: (1) start addr, (2) end addr */
+#define S1D15G10_DATCTL 0xbc /* Data scan direction, etc.; Data: (1) inverse, scan dir (2) RGB, (3) gray-scale */
+#define S1D15G10_RGBSET8 0xce /* 256-color position set; Data: (1-8) red tones, (9-16) green tones, (17-20) blue tones */
+#define S1D15G10_RAMWR 0x5c /* Writing to memory; Data: (1) write data */
+#define S1D15G10_RAMRD 0x5d /* Reading from memory; Data: (1) read data */
+#define S1D15G10_PTLIN 0xa8 /* Partial display in; Data: (1) start addr, (2) end addr */
+#define S1D15G10_PTLOUT 0xa9 /* Partial display out; Data: none */
+#define S1D15G10_RMWIN 0xe0 /* Read and modify write; Data: none */
+#define S1D15G10_RMWOUT 0xee /* End; Data: none */
+#define S1D15G10_ASCSET 0xaa /* Area scroll set; Data: (1) top addr, (2) bottom addr, (3) Num blocks, (4) scroll mode */
+#define S1D15G10_SCSTART 0xab /* Scroll start set; Data: (1) start block addr */
+#define S1D15G10_OSCON 0xd1 /* Internal oscillation on; Data: none */
+#define S1D15G10_OSCOFF 0xd2 /* Internal oscillation off; Data: none */
+#define S1D15G10_PWRCTR 0x20 /* Power control; Data: (1) LCD drive power */
+#define S1D15G10_VOLCTR 0x81 /* Electronic volume control; Data: (1) volume value, (2) resistance ratio */
+#define S1D15G10_VOLUP 0xd6 /* Increment electronic control by 1; Data: none */
+#define S1D15G10_VOLDOWN 0xd7 /* Decrement electronic control by 1; Data: none */
+#define S1D15G10_TMPGRD 0x82 /* Temperature gradient set; Data: (1-14) temperature gradient */
+#define S1D15G10_EPCTIN 0xcd /* Control EEPROM; Data: (1) read/write */
+#define S1D15G10_EPCOUT 0xcc /* Cancel EEPROM control; Data: none */
+#define S1D15G10_EPMWR 0xfc /* Write into EEPROM; Data: none */
+#define S1D15G10_EPMRD 0xfd /* Read from EEPROM; Data: none */
+#define S1D15G10_EPSRRD1 0x7c /* Read register 1; Data: none */
+#define S1D15G10_EPSRRD2 0x7d /* Read regiser 2; Data: none */
+#define S1D15G10_NOP 0x25 /* NOP intruction (0x45?); Data: none */
+#define S1D15G10_STREAD 0x20 /* Status read; Data: none */
+
+/* Display control (DISCTL) bit definitions */
+
+#define DISCTL_PERIOD_SHIFT (0) /* P1: Bits 0-1, F1 and F2 drive-pattern switching period */
+#define DISCTL_PERIOD_MASK (3 << DISCTL_PERIOD_SHIFT)
+# define DISCTL_PERIOD_8 (0 << DISCTL_PERIOD_SHIFT)
+# define DISCTL_PERIOD_4 (1 << DISCTL_PERIOD_SHIFT)
+# define DISCTL_PERIOD_16 (2 << DISCTL_PERIOD_SHIFT)
+# define DISCTL_PERIOD_FLD (3 << DISCTL_PERIOD_SHIFT)
+#define DISCTL_CLDIV_SHIFT (2) /* P1: Bits 2-4, Clock divider */
+#define DISCTL_CLDIV_MASK (7 << DISCTL_CLDIV_SHIFT)
+# define DISCTL_CLDIV_2 (0 << DISCTL_CLDIV_SHIFT)
+# define DISCTL_CLDIV_4 (1 << DISCTL_CLDIV_SHIFT)
+# define DISCTL_CLDIV_8 (2 << DISCTL_CLDIV_SHIFT)
+# define DISCTL_CLDIV_NONE (3 << DISCTL_CLDIV_SHIFT)
+
+/* Power control (PWRCTR) bit definitions */
+
+#define PWCTR_REFVOLTAGE (1 << 0) /* P1: Bit 0, Turn on reference voltage generation circuit. */
+#define PWCTR_REGULATOR (1 << 1) /* P1: Bit 1, Turn on voltage regulator and circuit voltage follower. */
+#define PWCTR_BOOSTER2 (1 << 2) /* P1: Bit 2, Turn on secondary booster/step-down circuit. */
+#define PWCTR_BOOSTER1 (1 << 3) /* P1: Bit 3, Turn on primary booster circuit. */
+#define PWCTR_EXTR (1 << 4) /* P1: Bit 4, Use external resistance to adjust voltage. */
+
+/* Data control (DATCTL) bit definitions */
+
+#define DATCTL_PGADDR_INV (1 << 0) /* P1: Bit 0, Inverse display of the page address. */
+#define DATCTL_COLADDR_REV (1 << 1) /* P1: Bit 1, Reverse turn of column address. */
+#define DATCTL_ADDR_PGDIR (1 << 2) /* P1: Bit 2, Address-scan direction in page (vs column) direction. */
+
+#define DATCTL_BGR (1 << 0) /* P2: Bit0, RGB->BGR */
+
+#define DATCTL_8GRAY (1) /* P3: Bits 0-2 = 001, 8 gray-scale */
+#define DATCTL_16GRAY_A (2) /* P3: Bits 0-2 = 010, 16 gray-scale display type A */
+#define DATCTL_16GRAY_B (4) /* P3: Bits 0-2 = 100, 16 gray-scale display type B */
+
+/* Status register bit definions (after reset or NOP) */
+
+#define S1D15G10_SR_PARTIAL (1 << 0) /* Bit 0: Partial display */
+#define S1D15G10_SR_NORMAL (1 << 1) /* Bit 1: Normal (vs. inverse) display */
+#define S1D15G10_SR_EEPROM (1 << 2) /* Bit 2: EEPROM access */
+#define S1D15G10_SR_DISPON (1 << 3) /* Bit 3: Display on */
+#define S1D15G10_SR_COLSCAN (1 << 4) /* Bit 4: Column (vs. page) scan direction */
+#define S1D15G10_SR_RMW (1 << 5) /* Bit 5: Read modify write */
+#define S1D15G10_SR_SCROLL (3 << 6) /* Bits 6-7: Area scroll mode */
+
+/* Status register bit definions (after EPSRRD1) */
+
+#define S1D15G10_SR_VOLUME 0x3f /* Bits 0-5: Electronic volume control values */
+
+/* Status register bit definions (after EPSRRD2) */
+
+#define S1D15G10_SR_RRATIO 0x07 /* Bits 0-2: Built-in resistance ratio */
+
+#endif /* __DRIVERS_LCD_S1D15G10_H */
\ No newline at end of file diff --git a/nuttx/drivers/lcd/sd1329.h b/nuttx/drivers/lcd/sd1329.h new file mode 100644 index 000000000..f578d808c --- /dev/null +++ b/nuttx/drivers/lcd/sd1329.h @@ -0,0 +1,527 @@ +/**************************************************************************** + * drivers/lcd/sd1329.h + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_LCD_SD1329_H +#define __DRIVERS_LCD_SD1329_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <stdint.h> + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* SD1329 Commands **********************************************************/ +/* Set column Address. + * + * This triple byte command specifies column start address and end address of + * the display data RAM. This command also sets the column address pointer to + * column start address. This pointer is used to define the current read/write + * column address in graphic display data RAM. If horizontal address increment + * mode is enabled by command 0xa0, after finishing read/write one column data, + * it is incremented automatically to the next column address. Whenever the + * column address pointer finishes accessing the end column address, it is + * reset back to start column address and the row address is incremented to the + * next row. + * + * Byte 1: 0x15 + * Byte 2: A[5:0]: Start Address, range: 0x00-0x3f + * Byte 3: B[5:0]: End Address, range: 0x00-0x3f + */ + +#define SSD1329_SET_COLADDR 0x15 + +/* Set Row Address. + * + * This triple byte command specifies row start address and end address of the + * display data RAM. This command also sets the row address pointer to row + * start address. This pointer is used to define the current read/write row + * address in graphic display data RAM. If vertical address increment mode is + * enabled by command 0xa0, after finishing read/write one row data, it is + * incremented automatically to the next row address. Whenever the row address + * pointer finishes accessing the end row address, it is reset back to start + * row address. + * + * Byte 1: 0x75 + * Byte 2: A[6:0]: Start Address, range: 0x00-0x7f + * Byte 3: B[6:0]: End Address, range: 0x00-0x7f + */ + +#define SSD1329_SET_ROWADDR 0x75 + +/* Set Contract Current + * + * This double byte command is to set Contrast Setting of the display. The + * chip has 256 contrast steps from 0x00 to 0xff. The segment output current + * increases linearly with the increase of contrast step. + * + * Byte 1: 0x81 + * Byte 2: A[7:0]: Contrast Value, range: 0-255 + */ + +#define SSD1329_SET_CONTRAST 0x81 + +/* Set Second Pre-Charge Speed + * + * This command is used to set the speed of second pre-charge in phase 3. + * This speed can be doubled to achieve faster pre-charging through setting + * 0x82 A[0]. + * + * Byte 1: 0x82 + * Byte 2: A[7:1]: Second Pre-charge Speed + * A[0] = 1, Enable doubling the Second Pre-charge speed + */ + +#define SSD1329_PRECHRG2_SPEED 0x82 +# define SSD1329_PRECHRG2_DBL 0x01 + +/* Set Master Icon Control + * + * This double command is used to set the ON / OFF conditions of internal + * charge pump, icon circuits and overall icon status. + * + * Byte 1: 0x90 + * Byte 2: Icon control (OR of bits 0-1,4-5) + */ + +#define SSD1329_ICON_CONTROL 0x90 +# define SSD1329_ICON_NORMAL 0x00 /* A[1:0]1=00: Icon RESET to normal display */ +# define SSD1329_ICON_ALLON 0x01 /* A[1:0]1=01: Icon All ON */ +# define SSD1329_ICON_ALLOFF 0x02 /* A[1:0]=10: Icon All OFF */ +# define SSD1329_ICON_DISABLE 0x00 /* A[4]=0: Disable Icon display */ +# define SSD1329_ICON_ENABLE 0x10 /* A[4]=1: Enable Icon display */ +# define SSD1329_VICON_DISABLE 0x00 /* A[5]=0: Disable VICON charge pump circuit */ +# define SSD1329_VICON_ENABLE 0x20 /* A[5]=1: Enable VICON charge pump circuit */ + +/* Set Icon Current Range + * + * This double byte command is used to set one fix current range for all icons + * between the range of 0uA and 127.5uA. The uniformity improves as the icon + * current range increases. + * + * Byte 1: 0x91 + * Byte 2: A[7:0]: Max icon current: + * 00 = 0.0 uA + * 01 = 0.5 uA + * ... + * ff = 127.5 uA + */ + +#define SSD1329_ICON_CURRRNG 0x91 + +/* Set Individual Icon Current + * + * This multiple byte command is used to fine tune the current for each of the + * 64 icons. Command 0x92 followed by 64 single byte data. These 64 byte data + * have to be entered in order to make this command function. Below is the + * formula for calculating the icon current. + * + * Icon Current = Single byte value / 127 x Maximum icon current set with command 0x91 + * + * Byte 1: 0x92 + * Byte 2-65: An[6:0]: icon current for ICSn, range: 0x00-0x7f + * Icon Current of ICSn = An[6:0]/127) x max icon current + */ + +#define SSD1329_ICON_CURRENT 0x92 + +/* Set Individual Icon ON / OFF Register + * + * This double byte command is used to select one of the 64 icons and choose the + * ON, OFF or blinking condition of the selected icon. + * + * Byte 1: 0x93 + * Byte 2: A[5:0]: Select one of the 64 icons from ICS0 ~ ICS63 + * A[7:6]: OFF/ON/BLINK + */ + +#define SSD1329_ICON_SELECT 0x93 +# define SSD1329_ICON_OFF 0x00 +# define SSD1329_ICON_ON 0x40 +# define SSD1329_ICON_BLINK 0xc0 + +/* Set Icon ON / OFF Registers + * + * This double byte command is used to set the ON / OFF status of all 64 icons. + * + * Byte 1: 0x94 + * Byte 2: A[7:6]: OFF/ON/BLINK (Same as 0x93) + */ + +#define SSD1329_ICON_ALL 0x94 + +/* Set Icon Blinking Cycle + * + * This double byte command is used to set icon oscillator frequency and + * blinking cycle selected with above command 0x93. + * + * Byte 1: 0x95 + * Byte 2: + * - A[2:0]:Icon Blinking cycle + * - A[5:4]:Icon oscillation frequency + */ + +#define SSD1329_ICON_BLINKING 0x95 +# define SSD1329_ICON_BLINK_0p25S 0x00 /* 0.25 sec */ +# define SSD1329_ICON_BLINK_0p50S 0x01 /* 0.50 sec */ +# define SSD1329_ICON_BLINK_0p75S 0x02 /* 0.75 sec */ +# define SSD1329_ICON_BLINK_0p100S 0x03 /* 1.00 sec */ +# define SSD1329_ICON_BLINK_0p125S 0x04 /* 1.25 sec */ +# define SSD1329_ICON_BLINK_0p150S 0x05 /* 1.50 sec */ +# define SSD1329_ICON_BLINK_0p175S 0x06 /* 1.75 sec */ +# define SSD1329_ICON_BLINK_0p200S 0x07 /* 2.00 sec */ +# define SSD1329_ICON_BLINK_61KHZ 0x00 /* 61 KHz */ +# define SSD1329_ICON_BLINK_64KHZ 0x10 /* 64 KHz */ +# define SSD1329_ICON_BLINK_68KHZ 0x20 /* 68 KHz */ +# define SSD1329_ICON_BLINK_73KHZ 0x30 /* 73 KHz */ + +/* Set Icon Duty + * + * This double byte command is used to set the icon frame frequency and icon AC + * drive duty ratio. + * + * Byte 1: 0x96 + * Byte 2: + * - A[2:0]: AC Drive + * - A[7:4]: con frame frequency + */ + +#define SSD1329_ICON_ACDRIVE 0x96 +# define SSD1329_ICON_DUTY_DC 0x00 +# define SSD1329_ICON_DUTY_63_64 0x01 +# define SSD1329_ICON_DUTY_62_64 0x02 +# define SSD1329_ICON_DUTY_61_64 0x03 +# define SSD1329_ICON_DUTY_60_64 0x04 +# define SSD1329_ICON_DUTY_59_64 0x05 +# define SSD1329_ICON_DUTY_58_64 0x06 +# define SSD1329_ICON_DUTY_57_64 0x07 + +/* Set Re-map + * + * This double command has multiple configurations and each bit setting is + * described as follows: + * + * Column Address Remapping (A[0]) + * This bit is made for increase the flexibility layout of segment signals in + * OLED module with segment arranged from left to right (when A[0] is set to 0) + * or from right to left (when A[0] is set to 1). + * + * Nibble Remapping (A[1]) + * When A[1] is set to 1, the two nibbles of the data bus for RAM access are + * re-mapped, such that (D7, D6, D5, D4, D3, D2, D1, D0) acts like (D3, D2, D1, + * D0, D7, D6, D5, D4) If this feature works together with Column Address + * Re-map, it would produce an effect of flipping the outputs from SEG0-127 to + * SEG127-SEG0. + * + * Address increment mode (A[2]) + * When A[2] is set to 0, the driver is set as horizontal address incremen + * mode. After the display RAM is read/written, the column address pointer is + * increased automatically by 1. If the column address pointer reaches column + * end address, the column address pointer is reset to column start address and + * row address pointer is increased by 1. + * + * When A[2] is set to 1, the driver is set to vertical address increment mode. + * After the display RAM is read/written, the row address pointer is increased + * automatically by 1. If the row address pointer reaches the row end address, + * the row address pointer is reset to row start address and column address + * pointer is increased by 1. + * + * COM Remapping (A[4]) + * This bit defines the scanning direction of the common for flexible layout + * of common signals in OLED module either from up to down (when A[4] is set to + * 0) or from bottom to up (when A[4] is set to 1). + * + * Splitting of Odd / Even COM Signals (A[6]) + * This bit is made to match the COM layout connection on the panel. When A[6] + * is set to 0, no splitting odd / even of the COM signal is performed. When + * A[6] is set to 1, splitting odd / even of the COM signal is performed, + * output pin assignment sequence is shown as below (for 128MUX ratio): + * + * Byte 1: 0xa0 + * Byte 2: A[7:0] + */ + +#define SSD1329_GDDRAM_REMAP 0xa0 +# define SSD1329_COLADDR_REMAP 0x01 /* A[0]: Enable column re-map */ +# define SSD1329_NIBBLE_REMAP 0x02 /* A[1]: Enable nibble re-map */ +# define SSD1329_VADDR_INCR 0x04 /* A[1]: Enable vertical address increment */ +# define SSD1329_COM_REMAP 0x10 /* A[4]: Enable COM re-map */ +# define SSD1329_COM_SPLIT 0x40 /* A[6]: Enable COM slip even/odd */ + +/* Set Display Start Line + * + * This double byte command is to set Display Start Line register for + * determining the starting address of display RAM to be displayed by selecting + * a value from 0 to 127. + * + * Byte 1: 0xa1 + * Byte 2: A[6:0]: Vertical scroll by setting the starting address of + * display RAM from 0-127 + */ + +#define SSD1329_VERT_START 0xa1 + +/* Set Display Offset + * + * This double byte command specifies the mapping of display start line (it is + * assumed that COM0 is the display start line, display start line register + * equals to 0) to one of COM0-COM127. + * + * Byte 1: 0xa2 + * Byte 2: A[6:0]: Set vertical offset by COM from 0-127 + */ + +#define SSD1329_VERT_OFFSET 0xa2 + +/* Set Display Mode - Normal, all on, all off, inverse + * + * These are single byte commands and are used to set display status to Normal + * Display, Entire Display ON, Entire Display OFF or Inverse Display. + * + * Normal Display (0xa4) + * Reset the “Entire Display ON, Entire Display OFF or Inverse Display” effects + * and turn the data to ON at the corresponding gray level. + * + * Set Entire Display ON (0xa5) + * Force the entire display to be at gray scale level GS15, regardless of the + * contents of the display data RAM. + * + * Set Entire Display OFF (0xa6) + * Force the entire display to be at gray scale level GS0, regardless of the + * contents of the display data RAM. + * + * Inverse Display (0xa7) + * The gray scale level of display data are swapped such that “GS0” <-> “GS15”, + * “GS1” <-> “GS14”, etc. + * + * Byte 1: Display mode command + */ + +#define SSD1329_DISP_NORMAL 0xa4 +#define SSD1329_DISP_OFF 0xa5 +#define SSD1329_DISP_ON 0xa6 +#define SSD1329_DISP_INVERT 0xa7 + +/* Set MUX Ratio + * + * This double byte command sets multiplex ratio (MUX ratio) from 16MUX to + * 128MUX. In POR, multiplex ratio is 128MUX. + * + * Byte 1: 0xa8 + * Byte 2: A[6:0] 15-127 representing 16-128 MUX + */ + +#define SSD1329_MUX_RATIO 0xa8 + +/* Set Sleep mode ON / OFF + * + * These single byte commands are used to turn the matrix display on the OLED + * panel display either ON or OFF. When the sleep mode is set to ON (0xae), the + * display is OFF, the segment and common output are in high impedance state + * and circuits will be turned OFF. When the sleep mode is set to OFF (0xaf), + * the display is ON. + * + * Byte 1: sleep mode command + */ + +#define SSD1329_SLEEP_ON 0xae +#define SSD1329_SLEEP_OFF 0xaf + +/* Set Phase Length + * + * In the second byte of this double command, lower nibble and higher nibble is + * defined separately. The lower nibble adjusts the phase length of Reset (phase + * 1). The higher nibble is used to select the phase length of first pre-charge + * phase (phase 2). The phase length is ranged from 1 to 16 DCLK's. RESET for + * A[3:0] is set to 3 which means 4 DCLK’s selected for Reset phase. POR for + * A[7:4] is set to 5 which means 6 DCLK’s is selected for first pre-charge + * phase. Please refer to Table 9-1 for detail breakdown levels of each step. + * + * Byte 1: 0xb1 + * Byte 2: A[3:0]: Phase 1 period of 1~16 DCLK’s + * A[7:4]: Phase 2 period of 1~16 DCLK’s + */ + +#define SSD1329_PHASE_LENGTH 0xb1 + +/* Set Frame Frequency + * + * This double byte command is used to set the number of DCLK’s per row between + * the range of 0x14 and 0x7f. Then the Frame frequency of the matrix display + * is equal to DCLK frequency / A[6:0]. + * + * Byte 1: 0xb2 + * Byte 2: A[6:0]:Total number of DCLK’s per row. Ranging from + * 0x14 to 0x4e DCLK’s. frame Frequency = DCLK freq /A[6:0]. + */ + +#define SSD1329_FRAME_FREQ 0xb2 + +/* Set Front Clock Divider / Oscillator Frequency + * + * This double command is used to set the frequency of the internal display + * clocks, DCLK's. It is defined by dividing the oscillator frequency by the + * divide ratio (Value from 1 to 16). Frame frequency is determined by divide + * ratio, number of display clocks per row, MUX ratio and oscillator frequency. + * The lower nibble of the second byte is used to select the oscillator + * frequency. Please refer to Table 9-1 for detail breakdown levels of each + * step. + * + * Byte 1: 0xb3 + * Byte 2: A[3:0]: Define divide ratio (D) of display clock (DCLK) + * Divide ratio=A[3:0]+1 + * A[7:4] : Set the Oscillator Frequency, FOSC. Range:0-15 + */ + +#define SSD1329_DCLK_DIV 0xb3 + +/* Set Default Gray Scale Table + * + * This single byte command is used to set the gray scale table to initial + * default setting. + * + * Byte 1: 0xb7 + */ + +#define SSD1329_GSCALE_TABLE 0xb7 + +/* Look Up Table for Gray Scale Pulse width + * + * This command is used to set each individual gray scale level for the display. + * Except gray scale level GS0 that has no pre-charge and current drive, each + * gray scale level is programmed in the length of current drive stage pulse + * width with unit of DCLK. The longer the length of the pulse width, the + * brighter the OLED pixel when it’s turned ON. + * + * The setting of gray scale table entry can perform gamma correction on OLED + * panel display. Normally, it is desired that the brightness response of the + * panel is linearly proportional to the image data value in display data RAM. + * However, the OLED panel is somehow responded in non-linear way. Appropriate + * gray scale table setting like example below can compensate this effect. + * + * Byte 1: 0xb8 + * Bytes 2-16: An[5:0], value for GSn level Pulse width + */ + +#define SSD1329_GSCALE_LOOKUP 0xb8 + +/* Set Second Pre-charge Period + * + * This double byte command is used to set the phase 3 second pre-charge period. + * The period of phase 3 can be programmed by command 0xbb and it is ranged from + * 0 to 15 DCLK's. + * + * Byte 1: 0xbb + * Byte 2: 0-15 DCLKs + */ + +#define SSD1329_PRECHRG2_PERIOD 0xbb + +/* Set First Precharge voltage, VP + * + * This double byte command is used to set phase 2 first pre-charge voltage + * level. It can be programmed to set the first pre-charge voltage reference to + * VCC or VCOMH. + * + * Byte 1: 0xbc + * Byte 2: A[5] == 0, Pre-charge voltage is (0.30 + A[4:0]) * Vcc + * A{5] == 1, 1.00 x VCC or connect to VCOMH if VCC > VCOMH + */ + +#define SSD1329_PRECHRG1_VOLT 0xbc + +/* Set VCOMH + * + * This double byte command sets the high voltage level of common pins, VCOMH. + * The level of VCOMH is programmed with reference to VCC. + * + * Byte 1: 0xbe + * Byte 2: (0.51 + A[5:0]) * Vcc + */ + +#define SSD1329_COM_HIGH 0xbe + +/* NOOP + * + * This is a no operation command. + * + * Byte 1: 0xe3 + */ + +#define SSD1329_NOOP 0xe3 + +/* Set Command Lock + * + * This command is used to lock the MCU from accepting any command. + * + * Byte 1: 0xfd + * Byte 2: 0x12 | A[2] + * A[2] == 1, Enable locking the MCU from entering command + */ + +#define SSD1329_CMD_LOCK 0xfd +# define SSD1329_LOCK_ON 0x13 +# define SSD1329_LOCK_OFF 0x12 + +/* SD1329 Status ************************************************************/ + +#define SDD1329_STATUS_ON 0x00 /* D[6]=0: indicates the display is ON */ +#define SDD1329_STATUS_OFF 0x40 /* D[6]=1: indicates the display is OFF */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif +#endif /* __DRIVERS_LCD_SD1329_H */ diff --git a/nuttx/drivers/lcd/skeleton.c b/nuttx/drivers/lcd/skeleton.c new file mode 100644 index 000000000..1cb8b5955 --- /dev/null +++ b/nuttx/drivers/lcd/skeleton.c @@ -0,0 +1,401 @@ +/************************************************************************************** + * drivers/lcd/skeleton.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************/ + +/************************************************************************************** + * Included Files + **************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/spi.h> +#include <nuttx/lcd/lcd.h> + +#include "up_arch.h" + +/************************************************************************************** + * Pre-processor Definitions + **************************************************************************************/ + +/* Configuration **********************************************************************/ +/* Verify that all configuration requirements have been met */ + +/* Debug ******************************************************************************/ +/* Define the following to enable register-level debug output */ + +#undef CONFIG_LCD_SKELDEBUG + +/* Verbose debug must also be enabled */ + +#ifndef CONFIG_DEBUG +# undef CONFIG_DEBUG_VERBOSE +# undef CONFIG_DEBUG_GRAPHICS +#endif + +#ifndef CONFIG_DEBUG_VERBOSE +# undef CONFIG_LCD_SKELDEBUG +#endif + +/* Color Properties *******************************************************************/ + +/* Display Resolution */ + +#define SKEL_XRES 320 +#define SKEL_YRES 240 + +/* Color depth and format */ + +#define SKEL_BPP 16 +#define SKEL_COLORFMT FB_FMT_RGB16_565 + +/* Debug ******************************************************************************/ + +#ifdef CONFIG_LCD_SKELDEBUG +# define skeldbg(format, arg...) vdbg(format, ##arg) +#else +# define skeldbg(x...) +#endif + +/************************************************************************************** + * Private Type Definition + **************************************************************************************/ + +/* This structure describes the state of this driver */ + +struct skel_dev_s +{ + /* Publically visible device structure */ + + struct lcd_dev_s dev; + + /* Private LCD-specific information follows */ +}; + +/************************************************************************************** + * Private Function Protototypes + **************************************************************************************/ + +/* LCD Data Transfer Methods */ + +static int skel_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels); +static int skel_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels); + +/* LCD Configuration */ + +static int skel_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo); +static int skel_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo); + +/* LCD RGB Mapping */ + +#ifdef CONFIG_FB_CMAP +# error "RGB color mapping not supported by this driver" +#endif + +/* Cursor Controls */ + +#ifdef CONFIG_FB_HWCURSOR +# error "Cursor control not supported by this driver" +#endif + +/* LCD Specific Controls */ + +static int skel_getpower(struct lcd_dev_s *dev); +static int skel_setpower(struct lcd_dev_s *dev, int power); +static int skel_getcontrast(struct lcd_dev_s *dev); +static int skel_setcontrast(struct lcd_dev_s *dev, unsigned int contrast); + +/************************************************************************************** + * Private Data + **************************************************************************************/ + +/* This is working memory allocated by the LCD driver for each LCD device + * and for each color plane. This memory will hold one raster line of data. + * The size of the allocated run buffer must therefore be at least + * (bpp * xres / 8). Actual alignment of the buffer must conform to the + * bitwidth of the underlying pixel type. + * + * If there are multiple planes, they may share the same working buffer + * because different planes will not be operate on concurrently. However, + * if there are multiple LCD devices, they must each have unique run buffers. + */ + +static uint16_t g_runbuffer[SKEL_XRES]; + +/* This structure describes the overall LCD video controller */ + +static const struct fb_videoinfo_s g_videoinfo = +{ + .fmt = SKEL_COLORFMT, /* Color format: RGB16-565: RRRR RGGG GGGB BBBB */ + .xres = SKEL_XRES, /* Horizontal resolution in pixel columns */ + .yres = SKEL_YRES, /* Vertical resolution in pixel rows */ + .nplanes = 1, /* Number of color planes supported */ +}; + +/* This is the standard, NuttX Plane information object */ + +static const struct lcd_planeinfo_s g_planeinfo = +{ + .putrun = skel_putrun, /* Put a run into LCD memory */ + .getrun = skel_getrun, /* Get a run from LCD memory */ + .buffer = (uint8_t*)g_runbuffer, /* Run scratch buffer */ + .bpp = SKEL_BPP, /* Bits-per-pixel */ +}; + +/* This is the standard, NuttX LCD driver object */ + +static struct skel_dev_s g_lcddev = +{ + .dev = + { + /* LCD Configuration */ + + .getvideoinfo = skel_getvideoinfo, + .getplaneinfo = skel_getplaneinfo, + + /* LCD RGB Mapping -- Not supported */ + /* Cursor Controls -- Not supported */ + + /* LCD Specific Controls */ + + .getpower = skel_getpower, + .setpower = skel_setpower, + .getcontrast = skel_getcontrast, + .setcontrast = skel_setcontrast, + }, +}; + +/************************************************************************************** + * Private Functions + **************************************************************************************/ + +/************************************************************************************** + * Name: skel_putrun + * + * Description: + * This method can be used to write a partial raster line to the LCD: + * + * row - Starting row to write to (range: 0 <= row < yres) + * col - Starting column to write to (range: 0 <= col <= xres-npixels) + * buffer - The buffer containing the run to be written to the LCD + * npixels - The number of pixels to write to the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +static int skel_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels) +{ + /* Buffer must be provided and aligned to a 16-bit address boundary */ + + gvdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer && ((uintptr_t)buffer & 1) == 0); + + /* Set up to write the run. */ + + /* Write the run to GRAM. */ +#warning "Missing logic" + return OK; +} + +/************************************************************************************** + * Name: skel_getrun + * + * Description: + * This method can be used to read a partial raster line from the LCD: + * + * row - Starting row to read from (range: 0 <= row < yres) + * col - Starting column to read read (range: 0 <= col <= xres-npixels) + * buffer - The buffer in which to return the run read from the LCD + * npixels - The number of pixels to read from the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +static int skel_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels) +{ + /* Buffer must be provided and aligned to a 16-bit address boundary */ + + gvdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer && ((uintptr_t)buffer & 1) == 0); + +#warning "Missing logic" + return -ENOSYS; +} + +/************************************************************************************** + * Name: skel_getvideoinfo + * + * Description: + * Get information about the LCD video controller configuration. + * + **************************************************************************************/ + +static int skel_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo) +{ + DEBUGASSERT(dev && vinfo); + gvdbg("fmt: %d xres: %d yres: %d nplanes: %d\n", + g_videoinfo.fmt, g_videoinfo.xres, g_videoinfo.yres, g_videoinfo.nplanes); + memcpy(vinfo, &g_videoinfo, sizeof(struct fb_videoinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: skel_getplaneinfo + * + * Description: + * Get information about the configuration of each LCD color plane. + * + **************************************************************************************/ + +static int skel_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo) +{ + DEBUGASSERT(dev && pinfo && planeno == 0); + gvdbg("planeno: %d bpp: %d\n", planeno, g_planeinfo.bpp); + memcpy(pinfo, &g_planeinfo, sizeof(struct lcd_planeinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: skel_getpower + * + * Description: + * Get the LCD panel power status (0: full off - CONFIG_LCD_MAXPOWER: full on). On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int skel_getpower(struct lcd_dev_s *dev) +{ + struct skel_dev_s *priv = (struct skel_dev_s *)dev; + gvdbg("power: %d\n", 0); +#warning "Missing logic" + return 0; +} + +/************************************************************************************** + * Name: skel_setpower + * + * Description: + * Enable/disable LCD panel power (0: full off - CONFIG_LCD_MAXPOWER: full on). On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int skel_setpower(struct lcd_dev_s *dev, int power) +{ + struct skel_dev_s *priv = (struct skel_dev_s *)dev; + + gvdbg("power: %d\n", power); + DEBUGASSERT(power <= CONFIG_LCD_MAXPOWER); + + /* Set new power level */ +#warning "Missing logic" + + return OK; +} + +/************************************************************************************** + * Name: skel_getcontrast + * + * Description: + * Get the current contrast setting (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int skel_getcontrast(struct lcd_dev_s *dev) +{ + gvdbg("Not implemented\n"); +#warning "Missing logic" + return -ENOSYS; +} + +/************************************************************************************** + * Name: skel_setcontrast + * + * Description: + * Set LCD panel contrast (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int skel_setcontrast(struct lcd_dev_s *dev, unsigned int contrast) +{ + gvdbg("contrast: %d\n", contrast); +#warning "Missing logic" + return -ENOSYS; +} + +/************************************************************************************** + * Public Functions + **************************************************************************************/ + +/************************************************************************************** + * Name: up_oledinitialize + * + * Description: + * Initialize the LCD video hardware. The initial state of the LCD is fully + * initialized, display memory cleared, and the LCD ready to use, but with the power + * setting at 0 (full off). + * + **************************************************************************************/ + +FAR struct lcd_dev_s *up_oledinitialize(FAR struct spi_dev_s *spi) +{ + gvdbg("Initializing\n"); + + /* Configure GPIO pins */ +#warning "Missing logic" + + /* Enable clocking */ +#warning "Missing logic" + + /* Configure and enable LCD */ + #warning "Missing logic" + + return &g_lcddev.dev; +} diff --git a/nuttx/drivers/lcd/ssd1305.h b/nuttx/drivers/lcd/ssd1305.h new file mode 100644 index 000000000..87c955de4 --- /dev/null +++ b/nuttx/drivers/lcd/ssd1305.h @@ -0,0 +1,211 @@ +/**************************************************************************************
+ * drivers/lcd/ssd1305.h
+ * Definitions for the Solomon Systech SSD1305 132x64 Dot Matrix OLED/PLED
+ * Segment/Common Driver with C
+ *
+ * Copyright (C) 2010 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * References: SSD1305.pdf, "Solomon Systech SSD1305 132x64 Dot Matrix OLED/PLED
+ * Segment/Common Driver with Controller," Solomon Systech Limited,
+ * http://www.solomon-systech.com, May, 2008.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ **************************************************************************************/
+
+#ifndef __DRIVERS_LCD_SSD1305_H
+#define __DRIVERS_LCD_SSD1305_H
+
+/**************************************************************************************
+ * Included Files
+ **************************************************************************************/
+
+/**************************************************************************************
+ * Pre-processor Definitions
+ **************************************************************************************/
+/* General Definitions ******************************************************/
+
+#define SSD1305_COLORA 0
+#define SSD1305_COLORB 1
+#define SSD1305_COLORC 2
+#define SSD1305_COLORD 3
+
+/* Fundamental Commands *****************************************************/
+#define SSD1305_SETCOLL 0x00 /* 0x00-0x0f: Set lower column address */
+# define SSD1305_COLL_MASK 0x0f
+#define SSD1305_SETCOLH 0x10 /* 0x10-0x1f: Set higher column address */
+# define SSD1305_COLH_MASK 0x0f
+#define SSD1305_ADDRMODE 0x20 /* 0x20: Set memory address mode */
+# define SSD1305_ADDRMODE_HOR 0x00 /* Data 1: Set horizontal address mode */
+# define SSD1305_ADDRMODE_VIRT 0x01 /* Data 1: Set virtal address mode */
+# define SSD1305_ADDRMODE_PAGE 0x02 /* Data 1: Set page address mode */
+#define SSD1305_SETCOLADDR 0x21 /* 0x21: Set column address */
+ /* Data 1: Column start address: 0-131 */
+ /* Data 2: Column end address: 0-131 */
+#define SSD1305_SETPAGEADDR 0x22 /* 0x22: Set page address */
+ /* Data 1: Page start address: 0x00-0x7d */
+ /* Data 2: Page end address: 0x00-0x7d */
+#define SSD1305_SETSTARTLINE 0x40 /* 0x40-7f: Set display start line */
+# define SSD1305_STARTLINE_MASK 0x3f
+
+#define SSD1305_SETCONTRAST 0x81 /* 0x81: Set contrast control */
+ /* Data 1: Set 1 of 256 contrast steps */
+#define SSD1305_SETBRIGHTNESS 0x82 /* 0x82: Set brightness */
+ /* Data 1: Set 1 of 256 contrast steps */
+#define SSD1305_SETLUT 0x91 /* 0x01: Set lookup table */
+ /* Data 1: Pulse width: 31-63 */
+ /* Data 2: Color A: 31-63 */
+ /* Data 3: Color B: 31-63 */
+ /* Data 4: Color C: 31-63 */
+#define SSD1305_SETBANKCOLOR1 0x92 /* 0x92: Set bank 1-16 color */
+# define SSD1305_SETBANK1(c) (c) /* Data 1, Bits 0-1: Bank 1 color */
+# define SSD1305_SETBANK2(c) (c << 2) /* Data 1, Bits 2-3: Bank 2 color */
+# define SSD1305_SETBANK3(c) (c << 4) /* Data 1, Bits 4-5: Bank 3 color */
+# define SSD1305_SETBANK4(c) (c << 6) /* Data 1, Bits 6-7: Bank 4 color */
+# define SSD1305_SETBANK5(c) (c) /* Data 2, Bits 0-1: Bank 5 color */
+# define SSD1305_SETBANK6(c) (c << 2) /* Data 2, Bits 2-3: Bank 6 color */
+# define SSD1305_SETBANK7(c) (c << 4) /* Data 2, Bits 4-5: Bank 7 color */
+# define SSD1305_SETBANK8(c) (c << 6) /* Data 2, Bits 6-7: Bank 8 color */
+# define SSD1305_SETBANK9(c) (c) /* Data 3, Bits 0-1: Bank 9 color */
+# define SSD1305_SETBANK10(c) (c << 2) /* Data 3, Bits 2-3: Bank 10 color */
+# define SSD1305_SETBANK11(c) (c << 4) /* Data 3, Bits 4-5: Bank 11 color */
+# define SSD1305_SETBANK12(c) (c << 6) /* Data 3, Bits 6-7: Bank 12 color */
+# define SSD1305_SETBANK13(c) (c) /* Data 4, Bits 0-1: Bank 13 color */
+# define SSD1305_SETBANK14(c) (c << 2) /* Data 4, Bits 2-3: Bank 14 color */
+# define SSD1305_SETBANK15(c) (c << 4) /* Data 4, Bits 4-5: Bank 15 color */
+# define SSD1305_SETBANK16(c) (c << 6) /* Data 4, Bits 6-7: Bank 16 color */
+#define SSD1305_SETBANKCOLOR2 0x93 /* 0x93: Set bank 17-32 color */
+# define SSD1305_SETBANK17(c) (c) /* Data 1, Bits 0-1: Bank 17 color */
+# define SSD1305_SETBANK18(c) (c << 2) /* Data 1, Bits 2-3: Bank 18 color */
+# define SSD1305_SETBANK19(c) (c << 4) /* Data 1, Bits 4-5: Bank 19 color */
+# define SSD1305_SETBANK20(c) (c << 6) /* Data 1, Bits 6-7: Bank 20 color */
+# define SSD1305_SETBANK21(c) (c) /* Data 2, Bits 0-1: Bank 21 color */
+# define SSD1305_SETBANK22(c) (c << 2) /* Data 2, Bits 2-3: Bank 22 color */
+# define SSD1305_SETBANK23(c) (c << 4) /* Data 2, Bits 4-5: Bank 23 color */
+# define SSD1305_SETBANK24(c) (c << 6) /* Data 2, Bits 6-7: Bank 24 color */
+# define SSD1305_SETBANK25(c) (c) /* Data 3, Bits 0-1: Bank 25 color */
+# define SSD1305_SETBANK26(c) (c << 2) /* Data 3, Bits 2-3: Bank 26 color */
+# define SSD1305_SETBANK27(c) (c << 4) /* Data 3, Bits 4-5: Bank 27 color */
+# define SSD1305_SETBANK28(c) (c << 6) /* Data 3, Bits 6-7: Bank 28 color */
+# define SSD1305_SETBANK29(c) (c) /* Data 4, Bits 0-1: Bank 29 color */
+# define SSD1305_SETBANK30(c) (c << 2) /* Data 4, Bits 2-3: Bank 30 color */
+# define SSD1305_SETBANK31(c) (c << 4) /* Data 4, Bits 4-5: Bank 31 color */
+# define SSD1305_SETBANK32(c) (c << 6) /* Data 4, Bits 6-7: Bank 32 color */
+#define SSD1305_MAPCOL0 0xa0 /* 0xa0: Column address 0 is mapped to SEG0 */
+#define SSD1305_MAPCOL131 0xa1 /* 0xa1: Column address 131 is mapped to SEG0 */
+#define SSD1305_DISPRAM 0xa4 /* 0xa4: Resume to RAM content display */
+#define SSD1305_DISPENTIRE 0xa5 /* 0xa5: Entire display ON */
+#define SSD1305_DISPNORMAL 0xa6 /* 0xa6: Normal display */
+#define SSD1305_DISPINVERTED 0xa7 /* 0xa7: Inverse display */
+
+#define SSD1305_SETMUX 0xa8 /* 0xa8: Set Multiplex Ratio*/
+ /* Data 1: MUX ratio -1: 15-63 */
+#define SSD1305_DIMMODE 0xab /* 0xab: Dim mode setting */
+ /* Data 1: Reserverd, must be zero */
+ /* Data 2: Contrast for bank1: 0-255 */
+ /* Data 3: Brightness for color bank: 0-255 */
+#define SSD1305_MSTRCONFIG 0xad /* 0xad: Master configuration */
+# define SSD1305_MSTRCONFIG_EXTVCC 0x8e /* Data 1: Select external Vcc */
+#define SSD1305_DISPONDIM 0xac /* 0xac: Display ON in dim mode */
+#define SSD1305_DISPOFF 0xae /* 0xae: Display OFF (sleep mode) */
+#define SSD1305_DISPON 0xaf /* 0xaf: Display ON in normal mode */
+#define SSD1305_SETPAGESTART 0xb0 /* 0xb0-b7: Set page start address */
+# define SSD1305_PAGESTART_MASK 0x07
+#define SSD1305_SETCOMNORMAL 0xc0 /* 0xc0: Set COM output, normal mode */
+#define SSD1305_SETCOMREMAPPED 0xc8 /* 0xc8: Set COM output, remapped mode */
+
+#define SSD1305_SETOFFSET 0xd3 /* 0xd3: Set display offset */
+ /* Data 1: Vertical shift by COM: 0-63 */
+#define SSD1305_SETDCLK 0xd5 /* 0xd5: Set display clock divide ratio/oscillator */
+# define SSD1305_DCLKDIV_SHIFT (0) /* Data 1, Bits 0-3: DCLK divide ratio/frequency*/
+# define SSD1305_DCLKDIV_MASK 0x0f
+# define SSD1305_DCLKFREQ_SHIFT (4) /* Data 1, Bits 4-7: DCLK divide oscillator frequency */
+# define SSD1305_DCLKFREQ_MASK 0xf0
+#define SSD1305_SETCOLORMODE 0xd8 /* 0xd: Set area color and low power display modes */
+# define SSD1305_COLORMODE_MONO 0x00 /* Data 1, Bits 4-5: 00=monochrome */
+# define SSD1305_COLORMODE_COLOR 0x30 /* Data 1, Bits 4-5: 11=area color enable */
+# define SSD1305_POWERMODE_NORMAL 0x00 /* Data 1, Bits 0,2: 00=normal power mode */
+# define SSD1305_POWERMODE_LOW 0x05 /* Data 1, Bits 0,2: 11=low power display mode */
+#define SSD1305_SETPRECHARGE 0xd9 /* 0xd9: Set pre-charge period */
+# define SSD1305_PHASE1_SHIFT (0) /* Data 1, Bits 0-3: Phase 1 period of up to 15 DCLK clocks */
+# define SSD1305_PHASE1_MASK 0x0f
+# define SSD1305_PHASE2_SHIFT (4) /* Data 1, Bits 4-7: Phase 2 period of up to 15 DCLK clocks */
+# define SSD1305_PHASE2_MASK 0xf0
+#define SSD1305_SETCOMCONFIG 0xda /* 0xda: Set COM configuration */
+# define SSD1305_COMCONFIG_SEQ 0x02 /* Data 1, Bit 4: 0=Sequential COM pin configuration */
+# define SSD1305_COMCONFIG_ALT 0x12 /* Data 1, Bit 4: 1=Alternative COM pin configuration */
+# define SSD1305_COMCONFIG_NOREMAP 0x02 /* Data 1, Bit 5: 0=Disable COM Left/Right remap */
+# define SSD1305_COMCONFIG_REMAP 0x22 /* Data 1, Bit 5: 1=Enable COM Left/Right remap */
+#define SSD1305_SETVCOMHDESEL 0xdb /* 0xdb: Set VCOMH delselect level */
+# define SSD1305_VCOMH_x4p3 0x00 /* Data 1: ~0.43 x Vcc */
+# define SSD1305_VCOMH_x7p7 0x34 /* Data 1: ~0.77 x Vcc */
+# define SSD1305_VCOMH_x8p3 0x3c /* Data 1: ~0.83 x Vcc */
+#define SSD1305_ENTER_RMWMODE 0xe0 /* 0xe0: Enter the Read Modify Write mode */
+#define SSD1305_NOP 0xe3 /* 0xe3: NOP Command for no operation */
+#define SSD1305_EXIT_RMWMODE 0xee /* 0xee: Leave the Read Modify Write mode */
+
+/* Graphic Acceleration Commands ********************************************/
+
+#define SSD1305_HSCROLL_RIGHT 0x26 /* 0x26: Right horizontal scroll */
+#define SSD1305_HSCROLL_LEFT 0x27 /* 0x27: Left horizontal scroll */
+ /* Data 1, Bits 0-2: Column scroll offset: 0-4 */
+ /* Data 2, Bits 0-2: Start page address: 0-7 */
+#define SSD1305_HSCROLL_FRAMES6 0x00 /* Data 3, Bits 0-2: Timer interval, 000=6 frames */
+#define SSD1305_HSCROLL_FRAMES32 0x01 /* Data 3, Bits 0-2: Timer interval, 001=32 frames */
+#define SSD1305_HSCROLL_FRAMES64 0x02 /* Data 3, Bits 0-2: Timer interval, 010=64 frames */
+#define SSD1305_HSCROLL_FRAMES128 0x03 /* Data 3, Bits 0-2: Timer interval, 011=128 frames */
+#define SSD1305_HSCROLL_FRAMES3 0x04 /* Data 3, Bits 0-2: Timer interval, 100=3 frames */
+#define SSD1305_HSCROLL_FRAMES4 0x05 /* Data 3, Bits 0-2: Timer interval, 101=4 frames */
+#define SSD1305_HSCROLL_FRAMES2 0x06 /* Data 3, Bits 0-2: Timer interval, 110=2 frames */
+ /* Data 4, Bits 0-2: End page address: 0-7 */
+
+#define SSD1305_VSCROLL_RIGHT 0x29 /* 0x26: Vertical and right horizontal scroll */
+#define SSD1305_VSCROLL_LEFT 0x2a /* 0x27: Vertical and left horizontal scroll */
+ /* Data 1, Bits 0-2: Column scroll offset: 0-4 */
+ /* Data 2, Bits 0-2: Start page address: 0-7 */
+#define SSD1305_VSCROLL_FRAMES6 0x00 /* Data 3, Bits 0-2: Timer interval, 000=6 frames */
+#define SSD1305_VSCROLL_FRAMES32 0x01 /* Data 3, Bits 0-2: Timer interval, 001=32 frames */
+#define SSD1305_VSCROLL_FRAMES64 0x02 /* Data 3, Bits 0-2: Timer interval, 010=64 frames */
+#define SSD1305_VSCROLL_FRAMES128 0x03 /* Data 3, Bits 0-2: Timer interval, 011=128 frames */
+#define SSD1305_VSCROLL_FRAMES3 0x04 /* Data 3, Bits 0-2: Timer interval, 100=3 frames */
+#define SSD1305_VSCROLL_FRAMES4 0x05 /* Data 3, Bits 0-2: Timer interval, 101=4 frames */
+#define SSD1305_VSCROLL_FRAMES2 0x06 /* Data 3, Bits 0-2: Timer interval, 110=2 frames */
+ /* Data 4, Bits 0-2: End page address: 0-7 */
+ /* Data 5, Bits 0-5: Vertical scrolling offset: 0-63 */
+#define SSD1305_SCROLL_STOP 0x2e /* 0x2e: Deactivate scroll */
+#define SSD1305_SCROLL_START 0x2f /* 0x2f: Activate scroll */
+#define SSD1305_VSCROLL_AREA 0xa3 /* 0xa3: Set vertical scroll area */
+ /* Data 1: Number of rows in the top fixed area */
+ /* Data 1: Number of rows in the scroll area */
+
+/* Status register bit definitions ******************************************/
+
+#define SSD1305_STATUS_DISPOFF (1 << 6) /* Bit 6: 1=Display off */
+
+#endif /* __DRIVERS_LCD_SSD1305_H */
diff --git a/nuttx/drivers/lcd/ug-9664hswag01.c b/nuttx/drivers/lcd/ug-9664hswag01.c new file mode 100644 index 000000000..bb49f20e6 --- /dev/null +++ b/nuttx/drivers/lcd/ug-9664hswag01.c @@ -0,0 +1,1049 @@ +/************************************************************************************** + * drivers/lcd/ug-9664hswag01.c + * Driver for the Univision UG-9664HSWAG01 Display with the Solomon Systech SSD1305 LCD + * controller. + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Reference: "Product Specification, OEL Display Module, UG-9664HSWAG01", Univision + * Technology Inc., SAS1-6020-B, January 3, 2008. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************************/ + +/************************************************************************************** + * Included Files + **************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/spi.h> +#include <nuttx/lcd/lcd.h> +#include <nuttx/lcd/ug-9664hswag01.h> + +#include "ssd1305.h" + +/************************************************************************************** + * Pre-processor Definitions + **************************************************************************************/ + +/* Configuration **********************************************************************/ +/* UG-9664HSWAG01 Configuration Settings: + * + * CONFIG_UG9664HSWAG01_SPIMODE - Controls the SPI mode + * CONFIG_UG9664HSWAG01_FREQUENCY - Define to use a different bus frequency + * CONFIG_UG9664HSWAG01_NINTERFACES - Specifies the number of physical + * UG-9664HSWAG01 devices that will be supported. NOTE: At present, this + * must be undefined or defined to be 1. + * CONFIG_UG9664HSWAG01_POWER + * If the hardware supports a controllable OLED a power supply, this + * configuration shold be defined. (See ug_power() below). + * CONFIG_LCD_UGDEBUG - Enable detailed UG-9664HSWAG01 debug output + * (CONFIG_DEBUG and CONFIG_VERBOSE must also be enabled). + * + * Required LCD driver settings: + * CONFIG_LCD_UG9664HSWAG01 - Enable UG-9664HSWAG01 support + * CONFIG_LCD_MAXCONTRAST should be 255, but any value >0 and <=255 will be accepted. + * CONFIG_LCD_MAXPOWER should be 2: 0=off, 1=dim, 2=normal + * + * Required SPI driver settings: + * CONFIG_SPI_CMDDATA - Include support for cmd/data selection. + */ + +/* Verify that all configuration requirements have been met */ + +/* The UG-9664HSWAG01 spec says that is supports SPI mode 0,0 only. However, somtimes + * you need to tinker with these things. + */ + +#ifndef CONFIG_UG9664HSWAG01_SPIMODE +# define CONFIG_UG9664HSWAG01_SPIMODE SPIDEV_MODE0 +#endif + +/* SPI frequency */ + +#ifndef CONFIG_UG9664HSWAG01_FREQUENCY +# define CONFIG_UG9664HSWAG01_FREQUENCY 3500000 +#endif + +/* CONFIG_UG9664HSWAG01_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_UG9664HSWAG01_NINTERFACES +# define CONFIG_UG9664HSWAG01_NINTERFACES 1 +#endif + +#if CONFIG_UG9664HSWAG01_NINTERFACES != 1 +# warning "Only a single UG-9664HSWAG01 interface is supported" +# undef CONFIG_UG9664HSWAG01_NINTERFACES +# define CONFIG_UG9664HSWAG01_NINTERFACES 1 +#endif + +/* Verbose debug must also be enabled to use the extra OLED debug */ + +#ifndef CONFIG_DEBUG +# undef CONFIG_DEBUG_VERBOSE +# undef CONFIG_DEBUG_GRAPHICS +#endif + +#ifndef CONFIG_DEBUG_VERBOSE +# undef CONFIG_LCD_UGDEBUG +#endif + +/* Check contrast selection */ + +#ifndef CONFIG_LCD_MAXCONTRAST +# define CONFIG_LCD_MAXCONTRAST 255 +#endif + +#if CONFIG_LCD_MAXCONTRAST <= 0 || CONFIG_LCD_MAXCONTRAST > 255 +# error "CONFIG_LCD_MAXCONTRAST exceeds supported maximum" +#endif + +#if CONFIG_LCD_MAXCONTRAST < 255 +# warning "Optimal setting of CONFIG_LCD_MAXCONTRAST is 255" +#endif + +/* Check power setting */ + +#if !defined(CONFIG_LCD_MAXPOWER) +# define CONFIG_LCD_MAXPOWER 2 +#endif + +#if CONFIG_LCD_MAXPOWER != 2 +# warning "CONFIG_LCD_MAXPOWER should be 2" +# undef CONFIG_LCD_MAXPOWER +# define CONFIG_LCD_MAXPOWER 2 +#endif + +/* The OLED requires CMD/DATA SPI support */ + +#ifndef CONFIG_SPI_CMDDATA +# error "CONFIG_SPI_CMDDATA must be defined in your NuttX configuration" +#endif + +/* Color is 1bpp monochrome with leftmost column contained in bits 0 */ + +#ifdef CONFIG_NX_DISABLE_1BPP +# warning "1 bit-per-pixel support needed" +#endif + +/* Color Properties *******************************************************************/ +/* The SSD1305 display controller can handle a resolution of 132x64. The OLED + * on the base board is 96x64. + */ + +#define UG_DEV_XRES 132 +#define UG_XOFFSET 18 + +/* Display Resolution */ + +#define UG_XRES 96 +#define UG_YRES 64 + +/* Color depth and format */ + +#define UG_BPP 1 +#define UG_COLORFMT FB_FMT_Y1 + +/* Bytes per logical row andactual device row */ + +#define UG_XSTRIDE (UG_XRES >> 3) /* Pixels arrange "horizontally for user" */ +#define UG_YSTRIDE (UG_YRES >> 3) /* But actual device arrangement is "vertical" */ + +/* The size of the shadow frame buffer */ + +#define UG_FBSIZE (UG_XRES * UG_YSTRIDE) + +/* Bit helpers */ + +#define LS_BIT (1 << 0) +#define MS_BIT (1 << 7) + +/* Debug ******************************************************************************/ + +#ifdef CONFIG_LCD_UGDEBUG +# define ugdbg(format, arg...) vdbg(format, ##arg) +#else +# define ugdbg(x...) +#endif + +/************************************************************************************** + * Private Type Definition + **************************************************************************************/ + +/* This structure describes the state of this driver */ + +struct ug_dev_s +{ + /* Publically visible device structure */ + + struct lcd_dev_s dev; + + /* Private LCD-specific information follows */ + + FAR struct spi_dev_s *spi; + uint8_t contrast; + uint8_t powered; + + /* The SSD1305 does not support reading from the display memory in SPI mode. + * Since there is 1 BPP and access is byte-by-byte, it is necessary to keep + * a shadow copy of the framebuffer memory. + */ + + uint8_t fb[UG_FBSIZE]; +}; + +/************************************************************************************** + * Private Function Protototypes + **************************************************************************************/ + +/* SPI helpers */ + +#ifdef CONFIG_SPI_OWNBUS +static inline void ug_select(FAR struct spi_dev_s *spi); +static inline void ug_deselect(FAR struct spi_dev_s *spi); +#else +static void ug_select(FAR struct spi_dev_s *spi); +static void ug_deselect(FAR struct spi_dev_s *spi); +#endif + +/* LCD Data Transfer Methods */ + +static int ug_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels); +static int ug_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels); + +/* LCD Configuration */ + +static int ug_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo); +static int ug_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo); + +/* LCD RGB Mapping */ + +#ifdef CONFIG_FB_CMAP +# error "RGB color mapping not supported by this driver" +#endif + +/* Cursor Controls */ + +#ifdef CONFIG_FB_HWCURSOR +# error "Cursor control not supported by this driver" +#endif + +/* LCD Specific Controls */ + +static int ug_getpower(struct lcd_dev_s *dev); +static int ug_setpower(struct lcd_dev_s *dev, int power); +static int ug_getcontrast(struct lcd_dev_s *dev); +static int ug_setcontrast(struct lcd_dev_s *dev, unsigned int contrast); + +/* Initialization */ + +static inline void up_clear(FAR struct ug_dev_s *priv); + +/************************************************************************************** + * Private Data + **************************************************************************************/ + +/* This is working memory allocated by the LCD driver for each LCD device + * and for each color plane. This memory will hold one raster line of data. + * The size of the allocated run buffer must therefore be at least + * (bpp * xres / 8). Actual alignment of the buffer must conform to the + * bitwidth of the underlying pixel type. + * + * If there are multiple planes, they may share the same working buffer + * because different planes will not be operate on concurrently. However, + * if there are multiple LCD devices, they must each have unique run buffers. + */ + +static uint8_t g_runbuffer[UG_XSTRIDE+1]; + +/* This structure describes the overall LCD video controller */ + +static const struct fb_videoinfo_s g_videoinfo = +{ + .fmt = UG_COLORFMT, /* Color format: RGB16-565: RRRR RGGG GGGB BBBB */ + .xres = UG_XRES, /* Horizontal resolution in pixel columns */ + .yres = UG_YRES, /* Vertical resolution in pixel rows */ + .nplanes = 1, /* Number of color planes supported */ +}; + +/* This is the standard, NuttX Plane information object */ + +static const struct lcd_planeinfo_s g_planeinfo = +{ + .putrun = ug_putrun, /* Put a run into LCD memory */ + .getrun = ug_getrun, /* Get a run from LCD memory */ + .buffer = (uint8_t*)g_runbuffer, /* Run scratch buffer */ + .bpp = UG_BPP, /* Bits-per-pixel */ +}; + +/* This is the standard, NuttX LCD driver object */ + +static struct ug_dev_s g_ugdev = +{ + .dev = + { + /* LCD Configuration */ + + .getvideoinfo = ug_getvideoinfo, + .getplaneinfo = ug_getplaneinfo, + + /* LCD RGB Mapping -- Not supported */ + /* Cursor Controls -- Not supported */ + + /* LCD Specific Controls */ + + .getpower = ug_getpower, + .setpower = ug_setpower, + .getcontrast = ug_getcontrast, + .setcontrast = ug_setcontrast, + }, +}; + +/************************************************************************************** + * Private Functions + **************************************************************************************/ + +/************************************************************************************** + * Name: ug_powerstring + * + * Description: + * Convert the power setting to a string. + * + **************************************************************************************/ + + +static inline FAR const char *ug_powerstring(uint8_t power) +{ + if (power == UG_POWER_OFF) + { + return "OFF"; + } + else if (power == UG_POWER_DIM) + { + return "DIM"; + } + else if (power == UG_POWER_ON) + { + return "ON"; + } + else + { + return "ERROR"; + } +} + +/************************************************************************************** + * Function: ug_select + * + * Description: + * Select the SPI, locking and re-configuring if necessary + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void ug_select(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just select the chip */ + + SPI_SELECT(spi, SPIDEV_DISPLAY, true); +} +#else +static void ug_select(FAR struct spi_dev_s *spi) +{ + /* Select UG-9664HSWAG01 chip (locking the SPI bus in case there are multiple + * devices competing for the SPI bus + */ + + SPI_LOCK(spi, true); + SPI_SELECT(spi, SPIDEV_DISPLAY, true); + + /* Now make sure that the SPI bus is configured for the UG-9664HSWAG01 (it + * might have gotten configured for a different device while unlocked) + */ + + SPI_SETMODE(spi, CONFIG_UG9664HSWAG01_SPIMODE); + SPI_SETBITS(spi, 8); +#ifdef CONFIG_UG9664HSWAG01_FREQUENCY + SPI_SETFREQUENCY(spi, CONFIG_UG9664HSWAG01_FREQUENCY); +#endif +} +#endif + +/************************************************************************************** + * Function: ug_deselect + * + * Description: + * De-select the SPI + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + **************************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void ug_deselect(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just de-select the chip */ + + SPI_SELECT(spi, SPIDEV_DISPLAY, false); +} +#else +static void ug_deselect(FAR struct spi_dev_s *spi) +{ + /* De-select UG-9664HSWAG01 chip and relinquish the SPI bus. */ + + SPI_SELECT(spi, SPIDEV_DISPLAY, false); + SPI_LOCK(spi, false); +} +#endif + +/************************************************************************************** + * Name: ug_putrun + * + * Description: + * This method can be used to write a partial raster line to the LCD: + * + * row - Starting row to write to (range: 0 <= row < yres) + * col - Starting column to write to (range: 0 <= col <= xres-npixels) + * buffer - The buffer containing the run to be written to the LCD + * npixels - The number of pixels to write to the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +static int ug_putrun(fb_coord_t row, fb_coord_t col, FAR const uint8_t *buffer, + size_t npixels) +{ + /* Because of this line of code, we will only be able to support a single UG device */ + + FAR struct ug_dev_s *priv = &g_ugdev; + FAR uint8_t *fbptr; + FAR uint8_t *ptr; + uint8_t devcol; + uint8_t fbmask; + uint8_t page; + uint8_t usrmask; + uint8_t i; + int pixlen; + + gvdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer); + + /* Clip the run to the display */ + + pixlen = npixels; + if ((unsigned int)col + (unsigned int)pixlen > (unsigned int)UG_XRES) + { + pixlen = (int)UG_XRES - (int)col; + } + + /* Verify that some portion of the run remains on the display */ + + if (pixlen <= 0 || row > UG_YRES) + { + return OK; + } + + /* Get the page number. The range of 64 lines is divided up into eight + * pages of 8 lines each. + */ + + page = row >> 3; + + /* Update the shadow frame buffer memory. First determine the pixel + * position in the frame buffer memory. Pixels are organized like + * this: + * + * --------+---+---+---+---+-...-+-----+ + * Segment | 0 | 1 | 2 | 3 | ... | 131 | + * --------+---+---+---+---+-...-+-----+ + * Bit 0 | | X | | | | | + * Bit 1 | | X | | | | | + * Bit 2 | | X | | | | | + * Bit 3 | | X | | | | | + * Bit 4 | | X | | | | | + * Bit 5 | | X | | | | | + * Bit 6 | | X | | | | | + * Bit 7 | | X | | | | | + * --------+---+---+---+---+-...-+-----+ + * + * So, in order to draw a white, horizontal line, at row 45. we + * would have to modify all of the bytes in page 45/8 = 5. We + * would have to set bit 45%8 = 5 in every byte in the page. + */ + + fbmask = 1 << (row & 7); + fbptr = &priv->fb[page * UG_XRES + col]; + ptr = fbptr; +#ifdef CONFIG_NX_PACKEDMSFIRST + usrmask = MS_BIT; +#else + usrmask = LS_BIT; +#endif + + for (i = 0; i < pixlen; i++) + { + /* Set or clear the corresponding bit */ + + if ((*buffer & usrmask) != 0) + { + *ptr++ |= fbmask; + } + else + { + *ptr++ &= ~fbmask; + } + + /* Inc/Decrement to the next source pixel */ + +#ifdef CONFIG_NX_PACKEDMSFIRST + if (usrmask == LS_BIT) + { + buffer++; + usrmask = MS_BIT; + } + else + { + usrmask >>= 1; + } +#else + if (usrmask == MS_BIT) + { + buffer++; + usrmask = LS_BIT; + } + else + { + usrmask <<= 1; + } +#endif + } + + /* Offset the column position to account for smaller horizontal + * display range. + */ + + devcol = col + UG_XOFFSET; + + /* Select and lock the device */ + + ug_select(priv->spi); + + /* Select command transfer */ + + SPI_CMDDATA(priv->spi, SPIDEV_DISPLAY, true); + + /* Set the starting position for the run */ + + (void)SPI_SEND(priv->spi, SSD1305_SETPAGESTART+page); /* Set the page start */ + (void)SPI_SEND(priv->spi, SSD1305_SETCOLL + (devcol & 0x0f)); /* Set the low column */ + (void)SPI_SEND(priv->spi, SSD1305_SETCOLH + (devcol >> 4)); /* Set the high column */ + + /* Select data transfer */ + + SPI_CMDDATA(priv->spi, SPIDEV_DISPLAY, false); + + /* Then transfer all of the data */ + + (void)SPI_SNDBLOCK(priv->spi, fbptr, pixlen); + + /* Unlock and de-select the device */ + + ug_deselect(priv->spi); + return OK; +} + +/************************************************************************************** + * Name: ug_getrun + * + * Description: + * This method can be used to read a partial raster line from the LCD: + * + * row - Starting row to read from (range: 0 <= row < yres) + * col - Starting column to read read (range: 0 <= col <= xres-npixels) + * buffer - The buffer in which to return the run read from the LCD + * npixels - The number of pixels to read from the LCD + * (range: 0 < npixels <= xres-col) + * + **************************************************************************************/ + +static int ug_getrun(fb_coord_t row, fb_coord_t col, FAR uint8_t *buffer, + size_t npixels) +{ + /* Because of this line of code, we will only be able to support a single UG device */ + + FAR struct ug_dev_s *priv = &g_ugdev; + FAR uint8_t *fbptr; + uint8_t page; + uint8_t fbmask; + uint8_t usrmask; + uint8_t i; + int pixlen; + + gvdbg("row: %d col: %d npixels: %d\n", row, col, npixels); + DEBUGASSERT(buffer); + + /* Clip the run to the display */ + + pixlen = npixels; + if ((unsigned int)col + (unsigned int)pixlen > (unsigned int)UG_XRES) + { + pixlen = (int)UG_XRES - (int)col; + } + + /* Verify that some portion of the run is actually the display */ + + if (pixlen <= 0 || row > UG_YRES) + { + return -EINVAL; + } + + /* Then transfer the display data from the shadow frame buffer memory */ + /* Get the page number. The range of 64 lines is divided up into eight + * pages of 8 lines each. + */ + + page = row >> 3; + + /* Update the shadow frame buffer memory. First determine the pixel + * position in the frame buffer memory. Pixels are organized like + * this: + * + * --------+---+---+---+---+-...-+-----+ + * Segment | 0 | 1 | 2 | 3 | ... | 131 | + * --------+---+---+---+---+-...-+-----+ + * Bit 0 | | X | | | | | + * Bit 1 | | X | | | | | + * Bit 2 | | X | | | | | + * Bit 3 | | X | | | | | + * Bit 4 | | X | | | | | + * Bit 5 | | X | | | | | + * Bit 6 | | X | | | | | + * Bit 7 | | X | | | | | + * --------+---+---+---+---+-...-+-----+ + * + * So, in order to draw a white, horizontal line, at row 45. we + * would have to modify all of the bytes in page 45/8 = 5. We + * would have to set bit 45%8 = 5 in every byte in the page. + */ + + fbmask = 1 << (row & 7); + fbptr = &priv->fb[page * UG_XRES + col]; +#ifdef CONFIG_NX_PACKEDMSFIRST + usrmask = MS_BIT; +#else + usrmask = LS_BIT; +#endif + + *buffer = 0; + for (i = 0; i < pixlen; i++) + { + /* Set or clear the corresponding bit */ + + uint8_t byte = *fbptr++; + if ((byte & fbmask) != 0) + { + *buffer |= usrmask; + } + + /* Inc/Decrement to the next destination pixel. Hmmmm. It looks like + * this logic could write past the end of the user buffer. Revisit + * this! + */ + +#ifdef CONFIG_NX_PACKEDMSFIRST + if (usrmask == LS_BIT) + { + buffer++; + *buffer = 0; + usrmask = MS_BIT; + } + else + { + usrmask >>= 1; + } +#else + if (usrmask == MS_BIT) + { + buffer++; + *buffer = 0; + usrmask = LS_BIT; + } + else + { + usrmask <<= 1; + } +#endif + } + + return OK; +} + +/************************************************************************************** + * Name: ug_getvideoinfo + * + * Description: + * Get information about the LCD video controller configuration. + * + **************************************************************************************/ + +static int ug_getvideoinfo(FAR struct lcd_dev_s *dev, + FAR struct fb_videoinfo_s *vinfo) +{ + DEBUGASSERT(dev && vinfo); + gvdbg("fmt: %d xres: %d yres: %d nplanes: %d\n", + g_videoinfo.fmt, g_videoinfo.xres, g_videoinfo.yres, g_videoinfo.nplanes); + memcpy(vinfo, &g_videoinfo, sizeof(struct fb_videoinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: ug_getplaneinfo + * + * Description: + * Get information about the configuration of each LCD color plane. + * + **************************************************************************************/ + +static int ug_getplaneinfo(FAR struct lcd_dev_s *dev, unsigned int planeno, + FAR struct lcd_planeinfo_s *pinfo) +{ + DEBUGASSERT(dev && pinfo && planeno == 0); + gvdbg("planeno: %d bpp: %d\n", planeno, g_planeinfo.bpp); + memcpy(pinfo, &g_planeinfo, sizeof(struct lcd_planeinfo_s)); + return OK; +} + +/************************************************************************************** + * Name: ug_getpower + * + * Description: + * Get the LCD panel power status (0: full off - CONFIG_LCD_MAXPOWER: full on). On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int ug_getpower(struct lcd_dev_s *dev) +{ + struct ug_dev_s *priv = (struct ug_dev_s *)dev; + DEBUGASSERT(priv); + gvdbg("powered: %s\n", ug_powerstring(priv->powered)); + return priv->powered; +} + +/************************************************************************************** + * Name: ug_setpower + * + * Description: + * Enable/disable LCD panel power (0: full off - CONFIG_LCD_MAXPOWER: full on). On + * backlit LCDs, this setting may correspond to the backlight setting. + * + **************************************************************************************/ + +static int ug_setpower(struct lcd_dev_s *dev, int power) +{ + struct ug_dev_s *priv = (struct ug_dev_s *)dev; + + DEBUGASSERT(priv && (unsigned)power <= CONFIG_LCD_MAXPOWER); + gvdbg("power: %s powered: %s\n", + ug_powerstring(power), ug_powerstring(priv->powered)); + + /* Select and lock the device */ + + ug_select(priv->spi); + if (power <= UG_POWER_OFF) + { + /* Turn the display off */ + + (void)SPI_SEND(priv->spi, SSD1305_DISPOFF); /* Display off */ + + /* Remove power to the device */ + + ug_power(0, false); + priv->powered = UG_POWER_OFF; + } + else + { + /* Turn the display on, dim or normal */ + + if (power == UG_POWER_DIM) + { + (void)SPI_SEND(priv->spi, SSD1305_DISPONDIM); /* Display on, dim mode */ + } + else /* if (power > UG_POWER_DIM) */ + { + (void)SPI_SEND(priv->spi, SSD1305_DISPON); /* Display on, normal mode */ + power = UG_POWER_ON; + } + (void)SPI_SEND(priv->spi, SSD1305_DISPRAM); /* Resume to RAM content display */ + + /* Restore power to the device */ + + ug_power(0, true); + priv->powered = power; + } + ug_deselect(priv->spi); + + return OK; +} + +/************************************************************************************** + * Name: ug_getcontrast + * + * Description: + * Get the current contrast setting (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int ug_getcontrast(struct lcd_dev_s *dev) +{ + struct ug_dev_s *priv = (struct ug_dev_s *)dev; + DEBUGASSERT(priv); + return (int)priv->contrast; +} + +/************************************************************************************** + * Name: ug_setcontrast + * + * Description: + * Set LCD panel contrast (0-CONFIG_LCD_MAXCONTRAST). + * + **************************************************************************************/ + +static int ug_setcontrast(struct lcd_dev_s *dev, unsigned int contrast) +{ + struct ug_dev_s *priv = (struct ug_dev_s *)dev; + + gvdbg("contrast: %d\n", contrast); + DEBUGASSERT(priv); + + if (contrast > 255) + { + return -EINVAL; + } + + /* Select and lock the device */ + + ug_select(priv->spi); + + /* Select command transfer */ + + SPI_CMDDATA(priv->spi, SPIDEV_DISPLAY, true); + + /* Set the contrast */ + + (void)SPI_SEND(priv->spi, SSD1305_SETCONTRAST); /* Set contrast control register */ + (void)SPI_SEND(priv->spi, contrast); /* Data 1: Set 1 of 256 contrast steps */ + priv->contrast = contrast; + + /* Unlock and de-select the device */ + + ug_deselect(priv->spi); + return OK; +} + +/************************************************************************************** + * Name: up_clear + * + * Description: + * Clear the display. + * + **************************************************************************************/ + +static inline void up_clear(FAR struct ug_dev_s *priv) +{ + FAR struct spi_dev_s *spi = priv->spi; + int page; + int i; + + /* Clear the framebuffer */ + + memset(priv->fb, UG_Y1_BLACK, UG_FBSIZE); + + /* Select and lock the device */ + + ug_select(priv->spi); + + /* Go through all 8 pages */ + + for (page = 0, i = 0; i < 8; i++) + { + /* Select command transfer */ + + SPI_CMDDATA(spi, SPIDEV_DISPLAY, true); + + /* Set the starting position for the run */ + + (void)SPI_SEND(priv->spi, SSD1305_SETPAGESTART+i); + (void)SPI_SEND(priv->spi, SSD1305_SETCOLL + (UG_XOFFSET & 0x0f)); + (void)SPI_SEND(priv->spi, SSD1305_SETCOLH + (UG_XOFFSET >> 4)); + + /* Select data transfer */ + + SPI_CMDDATA(spi, SPIDEV_DISPLAY, false); + + /* Then transfer all 96 columns of data */ + + (void)SPI_SNDBLOCK(priv->spi, &priv->fb[page * UG_XRES], UG_XRES); + } + + /* Unlock and de-select the device */ + + ug_deselect(spi); +} + +/************************************************************************************** + * Public Functions + **************************************************************************************/ + +/************************************************************************************** + * Name: ug_initialize + * + * Description: + * Initialize the UG-9664HSWAG01 video hardware. The initial state of the + * OLED is fully initialized, display memory cleared, and the OLED ready to + * use, but with the power setting at 0 (full off == sleep mode). + * + * Input Parameters: + * + * spi - A reference to the SPI driver instance. + * devno - A value in the range of 0 through CONFIG_UG9664HSWAG01_NINTERFACES-1. + * This allows support for multiple OLED devices. + * + * Returned Value: + * + * On success, this function returns a reference to the LCD object for the specified + * OLED. NULL is returned on any failure. + * + **************************************************************************************/ + +FAR struct lcd_dev_s *ug_initialize(FAR struct spi_dev_s *spi, unsigned int devno) +{ + /* Configure and enable LCD */ + + FAR struct ug_dev_s *priv = &g_ugdev; + + gvdbg("Initializing\n"); + DEBUGASSERT(spi && devno == 0); + + /* Save the reference to the SPI device */ + + priv->spi = spi; + + /* Select and lock the device */ + + ug_select(spi); + + /* Make sure that the OLED off */ + + ug_power(0, false); + + /* Select command transfer */ + + SPI_CMDDATA(spi, SPIDEV_DISPLAY, true); + + /* Set the starting position for the run */ + + (void)SPI_SEND(spi, SSD1305_SETCOLL + 2); /* Set low column address */ + (void)SPI_SEND(spi, SSD1305_SETCOLH + 2); /* Set high column address */ + (void)SPI_SEND(spi, SSD1305_SETSTARTLINE+0); /* Display start set */ + (void)SPI_SEND(spi, SSD1305_SCROLL_STOP); /* Stop horizontal scroll */ + (void)SPI_SEND(spi, SSD1305_SETCONTRAST); /* Set contrast control register */ + (void)SPI_SEND(spi, 0x32); /* Data 1: Set 1 of 256 contrast steps */ + (void)SPI_SEND(spi, SSD1305_SETBRIGHTNESS); /* Brightness for color bank */ + (void)SPI_SEND(spi, 0x80); /* Data 1: Set 1 of 256 contrast steps */ + (void)SPI_SEND(spi, SSD1305_MAPCOL131); /* Set segment re-map */ + (void)SPI_SEND(spi, SSD1305_DISPNORMAL); /* Set normal display */ +/*(void)SPI_SEND(spi, SSD1305_DISPINVERTED); Set inverse display */ + (void)SPI_SEND(spi, SSD1305_SETMUX); /* Set multiplex ratio */ + (void)SPI_SEND(spi, 0x3f); /* Data 1: MUX ratio -1: 15-63 */ + (void)SPI_SEND(spi, SSD1305_SETOFFSET); /* Set display offset */ + (void)SPI_SEND(spi, 0x40); /* Data 1: Vertical shift by COM: 0-63 */ + (void)SPI_SEND(spi, SSD1305_MSTRCONFIG); /* Set dc-dc on/off */ + (void)SPI_SEND(spi, SSD1305_MSTRCONFIG_EXTVCC); /* Data 1: Select external Vcc */ + (void)SPI_SEND(spi, SSD1305_SETCOMREMAPPED); /* Set com output scan direction */ + (void)SPI_SEND(spi, SSD1305_SETDCLK); /* Set display clock divide + * ratio/oscillator/frequency */ + (void)SPI_SEND(spi, 15 << SSD1305_DCLKFREQ_SHIFT | 0 << SSD1305_DCLKDIV_SHIFT); + (void)SPI_SEND(spi, SSD1305_SETCOLORMODE); /* Set area color mode on/off & low power + * display mode */ + (void)SPI_SEND(spi, SSD1305_COLORMODE_MONO | SSD1305_POWERMODE_LOW); + (void)SPI_SEND(spi, SSD1305_SETPRECHARGE); /* Set pre-charge period */ + (void)SPI_SEND(spi, 15 << SSD1305_PHASE2_SHIFT | 1 << SSD1305_PHASE1_SHIFT); + (void)SPI_SEND(spi, SSD1305_SETCOMCONFIG); /* Set COM configuration */ + (void)SPI_SEND(spi, SSD1305_COMCONFIG_ALT); /* Data 1, Bit 4: 1=Alternative COM pin configuration */ + (void)SPI_SEND(spi, SSD1305_SETVCOMHDESEL); /* Set VCOMH deselect level */ + (void)SPI_SEND(spi, SSD1305_VCOMH_x7p7); /* Data 1: ~0.77 x Vcc */ + (void)SPI_SEND(spi, SSD1305_SETLUT); /* Set look up table for area color */ + (void)SPI_SEND(spi, 0x3f); /* Data 1: Pulse width: 31-63 */ + (void)SPI_SEND(spi, 0x3f); /* Data 2: Color A: 31-63 */ + (void)SPI_SEND(spi, 0x3f); /* Data 3: Color B: 31-63 */ + (void)SPI_SEND(spi, 0x3f); /* Data 4: Color C: 31-63 */ + (void)SPI_SEND(spi, SSD1305_DISPON); /* Display on, normal mode */ + (void)SPI_SEND(spi, SSD1305_DISPRAM); /* Resume to RAM content display */ + + /* Let go of the SPI lock and de-select the device */ + + ug_deselect(spi); + + /* Clear the framebuffer */ + + up_mdelay(100); + up_clear(priv); + return &priv->dev; +} diff --git a/nuttx/drivers/loop.c b/nuttx/drivers/loop.c new file mode 100644 index 000000000..77cb9daf6 --- /dev/null +++ b/nuttx/drivers/loop.c @@ -0,0 +1,506 @@ +/**************************************************************************** + * drivers/loop.c + * + * Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/mount.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <semaphore.h> +#include <debug.h> +#include <errno.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define loop_semgive(d) sem_post(&(d)->sem) /* To match loop_semtake */ +#define MAX_OPENCNT (255) /* Limit of uint8_t */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct loop_struct_s +{ + sem_t sem; /* For safe read-modify-write operations */ + uint32_t nsectors; /* Number of sectors on device */ + off_t offset; /* Offset (in bytes) to the first sector */ + uint16_t sectsize; /* The size of one sector */ + uint8_t opencnt; /* Count of open references to the loop device */ +#ifdef CONFIG_FS_WRITABLE + bool writeenabled; /* true: can write to device */ +#endif + int fd; /* Descriptor of char device/file */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void loop_semtake(FAR struct loop_struct_s *dev); +static int loop_open(FAR struct inode *inode); +static int loop_close(FAR struct inode *inode); +static ssize_t loop_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#ifdef CONFIG_FS_WRITABLE +static ssize_t loop_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#endif +static int loop_geometry(FAR struct inode *inode, struct geometry *geometry); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_bops = +{ + loop_open, /* open */ + loop_close, /* close */ + loop_read, /* read */ +#ifdef CONFIG_FS_WRITABLE + loop_write, /* write */ +#else + NULL, /* write */ +#endif + loop_geometry, /* geometry */ + NULL /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: loop_semtake + ****************************************************************************/ + +static void loop_semtake(FAR struct loop_struct_s *dev) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(&dev->sem) != 0) + { + /* The only case that an error should occur here is if + * the wait was awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: loop_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int loop_open(FAR struct inode *inode) +{ + FAR struct loop_struct_s *dev; + int ret = OK; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR struct loop_struct_s *)inode->i_private; + + /* Make sure we have exclusive access to the state structure */ + + loop_semtake(dev); + if (dev->opencnt == MAX_OPENCNT) + { + return -EMFILE; + } + else + { + /* Increment the open count */ + + dev->opencnt++; + } + loop_semgive(dev); + return ret; +} + +/**************************************************************************** + * Name: loop_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int loop_close(FAR struct inode *inode) +{ + FAR struct loop_struct_s *dev; + int ret = OK; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR struct loop_struct_s *)inode->i_private; + + /* Make sure we have exclusive access to the state structure */ + + loop_semtake(dev); + if (dev->opencnt == 0) + { + return -EIO; + } + else + { + /* Decrement the open count */ + + dev->opencnt--; + } + loop_semgive(dev); + return ret; +} + +/**************************************************************************** + * Name: loop_read + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t loop_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + FAR struct loop_struct_s *dev; + size_t nbytesread; + off_t offset; + int ret; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR struct loop_struct_s *)inode->i_private; + + if (start_sector + nsectors > dev->nsectors) + { + dbg("Read past end of file\n"); + return -EIO; + } + + /* Calculate the offset to read the sectors and seek to the position */ + + offset = start_sector * dev->sectsize + dev->offset; + ret = lseek(dev->fd, offset, SEEK_SET); + if (ret == (off_t)-1) + { + dbg("Seek failed for offset=%d: %d\n", (int)offset, errno); + return -EIO; + } + + /* Then read the requested number of sectors from that position */ + + do + { + nbytesread = read(dev->fd, buffer, nsectors * dev->sectsize); + if (nbytesread < 0 && errno != EINTR) + { + dbg("Read failed: %d\n", errno); + return -errno; + } + } + while (nbytesread < 0); + + /* Return the number of sectors read */ + + return nbytesread / dev->sectsize; +} + +/**************************************************************************** + * Name: loop_write + * + * Description: Write the specified number of sectors + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t loop_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + FAR struct loop_struct_s *dev; + size_t nbyteswritten; + off_t offset; + int ret; + + DEBUGASSERT(inode && inode->i_private); + dev = (FAR struct loop_struct_s *)inode->i_private; + + /* Calculate the offset to write the sectors and seek to the position */ + + offset = start_sector * dev->sectsize + dev->offset; + ret = lseek(dev->fd, offset, SEEK_SET); + if (ret == (off_t)-1) + { + dbg("Seek failed for offset=%d: %d\n", (int)offset, errno); + } + + /* Then write the requested number of sectors to that position */ + + do + { + nbyteswritten = write(dev->fd, buffer, nsectors * dev->sectsize); + if (nbyteswritten < 0 && errno != EINTR) + { + dbg("Write failed: %d\n", errno); + return -errno; + } + } + while (nbyteswritten < 0); + + /* Return the number of sectors written */ + + return nbyteswritten / dev->sectsize; +} +#endif + +/**************************************************************************** + * Name: loop_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int loop_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + FAR struct loop_struct_s *dev; + + DEBUGASSERT(inode); + if (geometry) + { + dev = (FAR struct loop_struct_s *)inode->i_private; + geometry->geo_available = true; + geometry->geo_mediachanged = false; +#ifdef CONFIG_FS_WRITABLE + geometry->geo_writeenabled = dev->writeenabled; +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = dev->nsectors; + geometry->geo_sectorsize = dev->sectsize; + return OK; + } + return -EINVAL; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: losetup + * + * Description: + * Setup the loop device so that it exports the file referenced by 'filename' + * as a block device. + * + ****************************************************************************/ + +int losetup(const char *devname, const char *filename, uint16_t sectsize, + off_t offset, bool readonly) +{ + FAR struct loop_struct_s *dev; + struct stat sb; + int ret; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!devname || !filename || !sectsize) + { + return -EINVAL; + } +#endif + + /* Get the size of the file */ + + ret = stat(filename, &sb); + if (ret < 0) + { + dbg("Failed to stat %s: %d\n", filename, errno); + return -errno; + } + + /* Check if the file system is big enough for one block */ + + if (sb.st_size - offset < sectsize) + { + dbg("File is too small for blocksize\n"); + return -ERANGE; + } + + /* Allocate a loop device structure */ + + dev = (FAR struct loop_struct_s *)kzalloc(sizeof(struct loop_struct_s)); + if (!dev) + { + return -ENOMEM; + } + + /* Initialize the loop device structure. */ + + sem_init(&dev->sem, 0, 1); + dev->nsectors = (sb.st_size - offset) / sectsize; + dev->sectsize = sectsize; + dev->offset = offset; + + /* Open the file. */ + +#ifdef CONFIG_FS_WRITABLE + dev->writeenabled = false; /* Assume failure */ + dev->fd = -1; + + /* First try to open the device R/W access (unless we are asked + * to open it readonly). + */ + + if (!readonly) + { + dev->fd = open(filename, O_RDWR); + } + + if (dev->fd >= 0) + { + dev->writeenabled = true; /* Success */ + } + else +#endif + { + /* If that fails, then try to open the device read-only */ + + dev->fd = open(filename, O_RDWR); + if (dev->fd < 0) + { + dbg("Failed to open %s: %d\n", filename, errno); + ret = -errno; + goto errout_with_dev; + } + } + + /* Inode private data will be reference to the loop device structure */ + + ret = register_blockdriver(devname, &g_bops, 0, dev); + if (ret < 0) + { + fdbg("register_blockdriver failed: %d\n", -ret); + goto errout_with_fd; + } + + return OK; + +errout_with_fd: + close(dev->fd); +errout_with_dev: + kfree(dev); + return ret; +} + +/**************************************************************************** + * Name: loteardown + * + * Description: + * Undo the setup performed by losetup + * + ****************************************************************************/ + +int loteardown(const char *devname) +{ + FAR struct loop_struct_s *dev; + FAR struct inode *inode; + int ret; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!devname) + { + return -EINVAL; + } +#endif + + /* Open the block driver associated with devname so that we can get the inode + * reference. + */ + + ret = open_blockdriver(devname, MS_RDONLY, &inode); + if (ret < 0) + { + dbg("Failed to open %s: %d\n", devname, -ret); + return ret; + } + + /* Inode private data is a reference to the loop device stgructure */ + + dev = (FAR struct loop_struct_s *)inode->i_private; + close_blockdriver(inode); + + DEBUGASSERT(dev); + + /* Are there still open references to the device */ + + if (dev->opencnt > 0) + { + return -EBUSY; + } + + /* Otherwise, unregister the block device */ + + ret = unregister_blockdriver(devname); + + /* Release the device structure */ + + if (dev->fd >= 0) + { + (void)close(dev->fd); + } + + kfree(dev); + return ret; +} diff --git a/nuttx/drivers/mmcsd/Make.defs b/nuttx/drivers/mmcsd/Make.defs new file mode 100644 index 000000000..48e5d4fb6 --- /dev/null +++ b/nuttx/drivers/mmcsd/Make.defs @@ -0,0 +1,46 @@ +############################################################################ +# drivers/mmcsd/Make.defs +# +# Copyright (C) 2008, 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Include MMC/SD drivers + +CSRCS += mmcsd_sdio.c mmcsd_spi.c mmcsd_debug.c + +# Include MMC/SD driver build support + +DEPPATH += --dep-path mmcsd +VPATH += :mmcsd +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/mmcsd} + + diff --git a/nuttx/drivers/mmcsd/mmcsd_csd.h b/nuttx/drivers/mmcsd/mmcsd_csd.h new file mode 100644 index 000000000..e35eacad5 --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_csd.h @@ -0,0 +1,424 @@ +/**************************************************************************** + * drivers/mmcsd/mmcsd_csd.h + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_MMCSD_MMCSD_CSD_H +#define __DRIVERS_MMCSD_MMCSD_CSD_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <stdint.h> + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* CSD **********************************************************************/ + +/* The CSD is a 16 byte, / 128-bit packed structure. The following macros + * can be used to extract each packed field from the CSD. Two types of + * macros are supported, selected by CONFIG_MMCSD_BE16: (1) byte data (with + * MS byte first), or (2) 16-bit big-endian data (with MS hword first). + */ + +#ifdef CONFIG_MMCSD_BE16 + +/* CSD_STRUCTURE 126-127 */ + +#define MMCSD_CSD_CSDSTRUCT(csd) (csd[0] >> 14) + +/* SPEC_VERS 122-125 = (word 0, bits 13:10) (MMC) Spec version */ + +#define MMC_CSD_SPECVERS(csd) ((csd[0] >> 10) & 0x0f) + +/* Reserved 120-125 */ + +/* TAAC 112-119 = Data read access-time-1 + * TIME_VALUE 3-6 = Time mantissa + * TIME_UNIT 0-2 = Time exponent + */ + +#define MMCSD_CSD_TAAC_TIMEVALUE(csd) ((csd[0] >> 3) & 0x0f) +#define MMCSD_CSD_TAAC_TIMEUNIT(csd) (csd[0] & 7) + +/* NSAC 111:104 = Data read access-time-2 in CLK cycle(NSAC*100) */ + +#define MMCSD_CSD_NSAC(csd) (csd[1] >> 8) + +/* TRAN_SPEED 96-103 = Max. data transfer rate + * TIME_VALUE 3-6 = Rate exponent + * TRANSFER_RATE_UNIT 0-2 = Rate mantissa + */ + +#define MMCSD_CSD_TRANSPEED_TIMEVALUE(csd) ((csd[1] >> 3) & 0x0f) +#define MMCSD_CSD_TRANSPEED_TRANSFERRATEUNIT(csd) (csd[1] & 7) + +/* CCC 84-95 = Card command classes */ + +#define MMCSD_CSD_CCC(csd) ((csd[2] >> 4) & 0x0fff) + + /* READ_BL_LEN 80-83 = Max. read data block length */ + +#define MMCSD_CSD_READBLLEN(csd) (csd[2] & 0x0f) + +/* READ_BL_PARTIAL 79-79 = Partial blocks for read allowed */ + +#define MMCSD_CSD_READBLPARTIAL(csd) (csd[3] >> 15) + +/* WRITE_BLK_MISALIGN 78-78 = Write block misalignment */ + +#define MMCSD_CSD_WRITEBLKMISALIN(csd) ((csd[3] >> 14) & 1) + +/* READ_BLK_MISALIGN 77:77 = Read block misalignment */ + +#define MMCSD_CSD_READBLKMISALIN(csd) ((csd[3] >> 13) & 1) + +/* DSR_IMP 76-76 = DSR implemented */ + +#define MMCSD_CSD_DSRIMP(csd) ((csd[3] >> 12) & 1) + +/* C_SIZE 62-73 Device size */ + +#define MMCSD_CSD_CSIZE(csd) (((csd[3] & 0x03ff) << 2) | ((csd[4] >> 14) & 3)) + +/* VDD_R_CURR_MIN 59-61 = Max. read current at Vcc min */ + +#define MMCSD_CSD_VDDRCURRMIN(csd) ((csd[4] >> 11) & 7) + +/* VDD_R_CURR_MAX 56-58 = Max. read current at Vcc max */ + +#define MMCSD_CSD_VDDRCURRMAX(csd) ((csd[4] >> 8) & 7) + +/* VDD_W_CURR_MIN 53-55 = Max. write current at Vcc min */ + +#define MMCSD_CSD_VDDWCURRMIN(csd) ((csd[4] >> 5) & 7) + +/* VDD_W_CURR_MAX 50-52 = Max. write current at Vcc max */ + +#define MMCSD_CSD_VDDWCURRMAX(csd) ((csd[4] >> 2) & 7) + +/* C_SIZE_MULT 47-49 Device size multiplier */ + +#define MMCSD_CSD_CSIZEMULT(csd) (((csd[4] & 3) << 1) | (csd[5] >> 15)) + +/* ER_BLK_EN 46-46 =Erase single block enable (SD) */ + +#define SD_CSD_SDERBLKEN(csd) ((csd[5] >> 14) & 1) + +/* SECTOR_SIZE 39-45 = Erase sector size (SD) */ + +#define SD_CSD_SECTORSIZE(csd) ((csd[5] >> 7) & 0x7f) + +/* SECTOR_SIZE 42-46 = Erase sector size (MMC) */ + +#define MMC_CSD_SECTORSIZE(csd) ((csd[5] >> 10) & 0x1f) + +/* ER_GRP_SIZE 37-41 = Erase group size (MMC)*/ + +#define MMC_CSD_ERGRPSIZE(csd) ((csd[5] >> 5) & 0x1f) + +/* WP_GRP_SIZE 32-38 = Write protect group size (SD) */ + +#define SD_CSD_WPGRPSIZE(csd) (csd[5] & 0x7f) + +/* WP_GRP_SIZE 32-36 = Write protect group size (MMC) */ + +#define MMC_CSD_WPGRPSIZE(csd) (csd[5] & 0x1f) + +/* WP_GRP_EN 31-31 = Write protect group enable */ + +#define MMCSD_WPGRPEN(csd) (csd[6] >> 15) + +/* DFLT_ECC 29-30 = Manufacturer default ECC (MMC) */ + +#define MMC_CSD_DFLTECC(csd) ((csd[6] >> 13) & 3) + +/* R2W_FACTOR 26-28 = Write speed factor */ + +#define MMCSD_CSD_R2WFACTOR(csd) ((csd[6] >> 10) & 7) + +/* WRITE_BL_LEN 22-25 = Max. write data block length */ + +#define MMCSD_CSD_WRITEBLLEN(csd) ((csd[6] >> 6) & 0x0f) + +/* WRITE_BL_PARTIAL 21-21 = Partial blocks for write allowed */ + +#define MMCSD_CSD_WRITEBLPARTIAL(csd) ((csd[6] >> 5) & 1) + +/* Reserved 16-20 */ + +/* FILE_FORMAT_GROUP 15-15 = File format group */ + +#define MMCSD_CSD_FILEFORMATGRP(csd) (csd[7] >> 15) + +/* COPY 14-14 = Copy flag (OTP) */ + +#define MMCSD_CSD_COPY(csd) ((csd[7] >> 14) & 1) + +/* PERM_WRITE_PROTECT 13-13 = Permanent write protection */ + +#define MMCSD_CSD_PERMWRITEPROTECT(csd) ((csd[7] >> 13) & 1) + +/* TMP_WRITE_PROTECT 12-12 = Temporary write protection */ + +#define MMCSD_CSD_TMPWRITEPROTECT(csd) ((csd[7] >> 12) & 1) + +/* FILE_FORMAT 10-11 = File format */ + +#define MMCSD_CSD_FILEFORMAT(csd) ((csd[7] >> 10) & 3) + +/* ECC 8-9 = ECC (MMC) */ + +#define MMC_CSD_ECC(csd) ((csd[7] >> 8) & 3) + +/* CRC 1-7 = CRC */ + +#define MMCSD_CSD_CRC(csd) ((csd[7] >> 1) & 0x7f) + +/* Reserved 0-0 */ + +#else /* CONFIG_MMCSD_BE16 */ + +/* CSD_STRUCTURE 126-127 */ + +#define MMCSD_CSD_CSDSTRUCT(csd) (csd[0] >> 6) + +/* SPEC_VERS 122-125 = (word 0, bits 13:10) (MMC) Spec version */ + +#define MMC_CSD_SPECVERS(csd) ((csd[0] >> 2) & 0x0f) + +/* Reserved 120-155 */ + +/* TAAC 112-119 = Data read access-time-1 + * TIME_VALUE 3-6 = Time mantissa + * TIME_UNIT 0-2 = Time exponent + */ + +#define MMCSD_CSD_TAAC_TIMEVALUE(csd) ((csd[1] >> 3) & 0x0f) +#define MMCSD_CSD_TAAC_TIMEUNIT(csd) (csd[1] & 7) + +#define SD20_CSD_TAC_TIMEVALUE(csd) (1) +#define SD20_CSD_TAC_TIMEUNIT(csd) (6) + +/* NSAC 111:104 = Data read access-time-2 in CLK cycle(NSAC*100) */ + +#define MMCSD_CSD_NSAC(csd) (csd[2]) +#define SD20_CSD_NSAC(csd) (0) + +/* TRAN_SPEED 96-103 = Max. data transfer rate + * TIME_VALUE 3-6 = Rate exponent + * TRANSFER_RATE_UNIT 0-2 = Rate mantissa + */ + +#define MMCSD_CSD_TRANSPEED_TIMEVALUE(csd) ((csd[3] >> 3) & 0x0f) +#define MMCSD_CSD_TRANSPEED_TRANSFERRATEUNIT(csd) (csd[3] & 7) + +#define SD20_CSD_TRANSPEED_TIMEVALUE(csd) MMCSD_CSD_TRANSPEED_TIMEVALUE(csd) +#define SD20_CSD_TRANSPEED_TRANSFERRATEUNIT(csd) MMCSD_CSD_TRANSPEED_TRANSFERRATEUNIT(csd) + +/* CCC 84-95 = Card command classes */ + +#define MMCSD_CSD_CCC(csd) (((uint16_t)csd[4] << 4) | ((uint16_t)csd[5] >> 4)) +#define SD20_CSD_CCC(csd) MMCSD_CSD_CCC(csd) + + /* READ_BL_LEN 80-83 = Max. read data block length */ + +#define MMCSD_CSD_READBLLEN(csd) (csd[5] & 0x0f) +#define SD20_CSD_READBLLEN(csd) (9) + +/* READ_BL_PARTIAL 79-79 = Partial blocks for read allowed */ + +#define MMCSD_CSD_READBLPARTIAL(csd) (csd[6] >> 7) +#define SD20_CSD_READBLPARTIAL(csd) (0) + +/* WRITE_BLK_MISALIGN 78-78 = Write block misalignment */ + +#define MMCSD_CSD_WRITEBLKMISALIGN(csd) ((csd[6] >> 6) & 1) +#define SD20_CSD_WRITEBLKMISALIGN(csd) (0) + +/* READ_BLK_MISALIGN 77:77 = Read block misalignment */ + +#define MMCSD_CSD_READBLKMISALIGN(csd) ((csd[6] >> 5) & 1) +#define SD20_CSD_READBLKMISALIGN(csd) (0) + +/* DSR_IMP 76-76 = DSR implemented */ + +#define MMCSD_CSD_DSRIMP(csd) ((csd[6] >> 4) & 1) +#define SD20_CSD_DSRIMP(csd) MMCSD_CSD_DSRIMP(csd) + +/* C_SIZE 62-73 Device size */ + +#define MMCSD_CSD_CSIZE(csd) (((csd[6] & 3) << 10) | (csd[7] << 2) | (csd[8] >> 6)) +#define SD20_CSD_CSIZE(csd) ((((uint32_t)csd[7] & 0x3f) << 16) | (csd[8] << 8) | csd[9]) + +/* VDD_R_CURR_MIN 59-61 = Max. read current at Vcc min */ + +#define MMCSD_CSD_VDDRCURRMIN(csd) ((csd[8] >> 3) & 7) +#define SD20_CSD_VDDRCURRMIN(csd) (7) + +/* VDD_R_CURR_MAX 56-58 = Max. read current at Vcc max */ + +#define MMCSD_CSD_VDDRCURRMAX(csd) (csd[8] & 7) +#define SD20_CSD_VDDRCURRMAX(csd) (6) + +/* VDD_W_CURR_MIN 53-55 = Max. write current at Vcc min */ + +#define MMCSD_CSD_VDDWCURRMIN(csd) ((csd[9] >> 5) & 7) +#define SD20_CSD_VDDWCURRMIN(csd) (7) + +/* VDD_W_CURR_MAX 50-52 = Max. write current at Vcc max */ + +#define MMCSD_CSD_VDDWCURRMAX(csd) ((csd[9] >> 2) & 7) +#define SD20_CSD_VDDWCURRMAX(csd) (6) + +/* C_SIZE_MULT 47-49 Device size multiplier */ + +#define MMCSD_CSD_CSIZEMULT(csd) (((csd[9] & 3) << 1) | (csd[10] >> 7)) +#define SD20_CSD_CSIZEMULT(csd) (10-2) + +/* ER_BLK_EN 46-46 = Erase single block enable (SD) */ + +#define SD_CSD_SDERBLKEN(csd) ((csd[10] >> 6) & 1) +#define SD20_CSD_SDERBLKEN(csd) (1) + +/* SECTOR_SIZE 39-45 = Erase sector size (SD) */ + +#define SD_CSD_SECTORSIZE(csd) (((csd[10] & 0x3f) << 1) | (csd[11] >> 7)) +#define SD20_CSD_SECTORSIZE(csd) (0x7f) + +/* SECTOR_SIZE 42-46 = Erase sector size (MMC) */ + +#define MMC_CSD_SECTORSIZE(csd) ((csd[10] >> 2) & 0x1f) + +/* ER_GRP_SIZE 37-41 = Erase group size (MMC)*/ + +#define MMC_CSD_ERGRPSIZE(csd) (((csd[10] & 3) << 3) | (csd[11] > 5)) + +/* WP_GRP_SIZE 32-38 = Write protect group size (SD) */ + +#define SD_CSD_WPGRPSIZE(csd) (csd[11] & 0x7f) +#define SD20_CSD_WPGRPSIZE(csd) (0) + +/* WP_GRP_SIZE 32-36 = Write protect group size (MMC) */ + +#define MMC_CSD_WPGRPSIZE(csd) (csd[11] & 0x1f) + +/* WP_GRP_EN 31-31 = Write protect group enable */ + +#define MMCSD_WPGRPEN(csd) (csd[12] >> 7) +#define SD20_WPGRPEN(csd) (0) + +/* DFLT_ECC 29-30 = Manufacturer default ECC (MMC) */ + +#define MMC_CSD_DFLTECC(csd) ((csd[12] >> 5) & 3) + +/* R2W_FACTOR 26-28 = Write speed factor */ + +#define MMCSD_CSD_R2WFACTOR(csd) ((csd[12] >> 2) & 7) +#define SD20_CSD_R2WFACTOR(csd) (2) + +/* WRITE_BL_LEN 22-25 = Max. write data block length */ + +#define MMCSD_CSD_WRITEBLLEN(csd) (((csd[12] & 3) << 2) | (csd[13] >> 6)) +#define SD20_CSD_WRITEBLLEN(csd) (9) + +/* WRITE_BL_PARTIAL 21-21 = Partial blocks for write allowed */ + +#define MMCSD_CSD_WRITEBLPARTIAL(csd) ((csd[13] >> 5) & 1) +#define SD20_CSD_WRITEBLPARTIAL(csd) (0) + +/* Reserved 16-20 */ + +/* FILE_FORMAT_GROUP 15-15 = File format group */ + +#define MMCSD_CSD_FILEFORMATGRP(csd) (csd[14] >> 7) +#define SD20_CSD_FILEFORMATGRP(csd) (0) + +/* COPY 14-14 = Copy flag (OTP) */ + +#define MMCSD_CSD_COPY(csd) ((csd[14] >> 6) & 1) +#define SD20_CSD_COPY(csd) MMCSD_CSD_COPY(csd) + +/* PERM_WRITE_PROTECT 13-13 = Permanent write protection */ + +#define MMCSD_CSD_PERMWRITEPROTECT(csd) ((csd[14] >> 5) & 1) +#define SD20_CSD_PERMWRITEPROTECT(csd) MMCSD_CSD_PERMWRITEPROTECT(csd) + +/* TMP_WRITE_PROTECT 12-12 = Temporary write protection */ + +#define MMCSD_CSD_TMPWRITEPROTECT(csd) ((csd[14] >> 4) & 1) +#define SD20_CSD_TMPWRITEPROTECT(csd) MMCSD_CSD_TMPWRITEPROTECT(csd) + +/* FILE_FORMAT 10-11 = File format */ + +#define MMCSD_CSD_FILEFORMAT(csd) ((csd[14] >> 2) & 3) +#define SD20_CSD_FILEFORMAT(csd) (0) + +/* ECC 8-9 = ECC (MMC) */ + +#define MMC_CSD_ECC(csd) (csd[14] & 3) + +/* CRC 1-7 = CRC */ + +#define MMCSD_CSD_CRC(csd) (csd[15] >> 1) +#define SD20_CSD_CRC(csd) MMCSD_CSD_CRC(csd) + +/* Reserved 0-0 */ + +#endif /* CONFIG_MMCSD_BE16 */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif +#endif /* __DRIVERS_MMCSD_MMCSD_CSD_H */ diff --git a/nuttx/drivers/mmcsd/mmcsd_debug.c b/nuttx/drivers/mmcsd/mmcsd_debug.c new file mode 100644 index 000000000..03872f5cf --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_debug.c @@ -0,0 +1,183 @@ +/**************************************************************************** + * drivers/mmcsd/mmcsd_debug.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include "mmcsd_csd.h" +#include "mmcsd_internal.h" + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* This needs to match the logic in include/debug.h */ + +#ifdef CONFIG_CPP_HAVE_VARARGS +# define message(format, arg...) lib_rawprintf(format, ##arg) +#else +# define message lib_rawprintf +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_dmpcsd + * + * Description: + * Dump the contents of the CSD + * + ****************************************************************************/ + +#if defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) +void mmcsd_dmpcsd(FAR const uint8_t *csd, uint8_t cardtype) +{ + bool mmc = (cardtype == MMCSD_CARDTYPE_MMC); + bool sd2 = (MMCSD_CSD_CSDSTRUCT(csd) == 1); + + fvdbg("CSD\n"); + fvdbg(" CSD_STRUCTURE: 1.%d\n", MMCSD_CSD_CSDSTRUCT(csd)); + if (mmc) + { + fvdbg(" MMC SPEC_VERS: %d\n", MMC_CSD_SPECVERS(csd)); + } + fvdbg(" TAAC:\n", + sd2 ? SD20_CSD_TAC_TIMEVALUE(csd) : MMCSD_CSD_TAAC_TIMEVALUE(csd)); + fvdbg(" TIME_VALUE: 0x%02x\n", + sd2 ? SD20_CSD_TAC_TIMEVALUE(csd) : MMCSD_CSD_TAAC_TIMEVALUE(csd)); + fvdbg(" TIME_UNIT: 0x%02x\n", + sd2 ? SD20_CSD_TAC_TIMEUNIT(csd) : MMCSD_CSD_TAAC_TIMEUNIT(csd)); + fvdbg(" NSAC: 0x%02x\n", + sd2 ? SD20_CSD_NSAC(csd) : MMCSD_CSD_NSAC(csd)); + fvdbg(" TRAN_SPEED:\n"); + fvdbg(" TIME_VALUE: 0x%02x\n", + sd2 ? SD20_CSD_TRANSPEED_TIMEVALUE(csd) : MMCSD_CSD_TRANSPEED_TIMEVALUE(csd)); + fvdbg(" RATE_UNIT: 0x%02x\n", + sd2 ? SD20_CSD_TRANSPEED_TRANSFERRATEUNIT(csd) : MMCSD_CSD_TRANSPEED_TRANSFERRATEUNIT(csd)); + fvdbg(" CCC: 0x%03x\n", + sd2 ? SD20_CSD_CCC(csd) : MMCSD_CSD_CCC(csd)); + fvdbg(" READ_BL_LEN: %d\n", + sd2 ? SD20_CSD_READBLLEN(csd) : MMCSD_CSD_READBLLEN(csd)); + fvdbg(" READ_BL_PARTIAL: %d\n", + sd2 ? SD20_CSD_READBLPARTIAL(csd) : MMCSD_CSD_READBLPARTIAL(csd)); + fvdbg(" WRITE_BLK_MISALIGN: %d\n", + sd2 ? SD20_CSD_WRITEBLKMISALIGN(csd) : MMCSD_CSD_WRITEBLKMISALIGN(csd)); + fvdbg(" READ_BLK_MISALIGN: %d\n", + sd2 ? SD20_CSD_READBLKMISALIGN(csd) : MMCSD_CSD_READBLKMISALIGN(csd)); + fvdbg(" DSR_IMP: %d\n", + sd2 ? SD20_CSD_DSRIMP(csd) : MMCSD_CSD_DSRIMP(csd)); + fvdbg(" C_SIZE: %d\n", + sd2 ? SD20_CSD_CSIZE(csd) : MMCSD_CSD_CSIZE(csd)); + fvdbg(" VDD_R_CURR_MIN: %d\n", + sd2 ? SD20_CSD_VDDRCURRMIN(csd) : MMCSD_CSD_VDDRCURRMIN(csd)); + fvdbg(" VDD_R_CURR_MAX: %d\n", + sd2 ? SD20_CSD_VDDRCURRMAX(csd) : MMCSD_CSD_VDDRCURRMAX(csd)); + fvdbg(" VDD_W_CURR_MIN: %d\n", + sd2 ? SD20_CSD_VDDWCURRMIN(csd) : MMCSD_CSD_VDDWCURRMIN(csd)); + fvdbg(" VDD_W_CURR_MAX: %d\n", + sd2 ? SD20_CSD_VDDWCURRMAX(csd) : MMCSD_CSD_VDDWCURRMAX(csd)); + fvdbg(" C_SIZE_MULT: %d\n", + sd2 ? SD20_CSD_CSIZEMULT(csd) : MMCSD_CSD_CSIZEMULT(csd)); + if (mmc) + { + fvdbg(" MMC SECTOR_SIZE: %d\n", MMC_CSD_SECTORSIZE(csd)); + fvdbg(" MMC ER_GRP_SIZE: %d\n", MMC_CSD_ERGRPSIZE(csd)); + fvdbg(" MMC WP_GRP_SIZE: %d\n", MMC_CSD_WPGRPSIZE(csd)); + fvdbg(" MMC DFLT_ECC: %d\n", MMC_CSD_DFLTECC(csd)); + } + else + { + fvdbg(" SD ER_BLK_EN: %d\n", + sd2 ? SD20_CSD_SDERBLKEN(csd) : SD_CSD_SDERBLKEN(csd)); + fvdbg(" SD SECTOR_SIZE: %d\n", + sd2 ? SD20_CSD_SECTORSIZE(csd) : SD_CSD_SECTORSIZE(csd)); + fvdbg(" SD WP_GRP_SIZE: %d\n", + sd2 ? SD_CSD_WPGRPSIZE(csd) : SD_CSD_WPGRPSIZE(csd)); + } + fvdbg(" WP_GRP_EN: %d\n", + sd2 ? SD20_WPGRPEN(csd) : MMCSD_WPGRPEN(csd)); + fvdbg(" R2W_FACTOR: %d\n", + sd2 ? SD20_CSD_R2WFACTOR(csd) : MMCSD_CSD_R2WFACTOR(csd)); + fvdbg(" WRITE_BL_LEN: %d\n", + sd2 ? SD20_CSD_WRITEBLLEN(csd) : MMCSD_CSD_WRITEBLLEN(csd)); + fvdbg(" WRITE_BL_PARTIAL: %d\n", + sd2 ? SD20_CSD_WRITEBLPARTIAL(csd) : MMCSD_CSD_WRITEBLPARTIAL(csd)); + fvdbg(" FILE_FORMAT_GROUP: %d\n", + sd2 ? SD20_CSD_FILEFORMATGRP(csd) : MMCSD_CSD_FILEFORMATGRP(csd)); + fvdbg(" COPY: %d\n", + sd2 ? SD20_CSD_COPY(csd) : MMCSD_CSD_COPY(csd)); + fvdbg(" PERM_WRITE_PROTECT: %d\n", + sd2 ? SD20_CSD_PERMWRITEPROTECT(csd) : MMCSD_CSD_PERMWRITEPROTECT(csd)); + fvdbg(" TMP_WRITE_PROTECT: %d\n", + sd2 ?SD20_CSD_TMPWRITEPROTECT(csd) : MMCSD_CSD_TMPWRITEPROTECT(csd)); + fvdbg(" FILE_FORMAT: %d\n", + sd2 ? SD20_CSD_FILEFORMAT(csd) : MMCSD_CSD_FILEFORMAT(csd)); + if (mmc) + { + fvdbg(" MMC ECC: %d\n", + sd2 ? MMC_CSD_ECC(csd) : MMC_CSD_ECC(csd)); + } + fvdbg(" CRC: %02x\n", + sd2 ? SD20_CSD_CRC(csd) : MMCSD_CSD_CRC(csd)); +} +#endif diff --git a/nuttx/drivers/mmcsd/mmcsd_internal.h b/nuttx/drivers/mmcsd/mmcsd_internal.h new file mode 100644 index 000000000..577ebbba9 --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_internal.h @@ -0,0 +1,105 @@ +/**************************************************************************** + * drivers/mmcsd/mmcsd_internal.h + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_MMCSD_MMCSD_INTERNAL_H +#define __DRIVERS_MMCSD_MMCSD_INTERNAL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <stdint.h> +#include <debug.h> + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* Enable excessive debug options */ + +#undef CONFIG_MMCSD_DUMPALL /* MUST BE DEFINED MANUALLY */ + +#if !defined(CONFIG_DEBUG_VERBOSE) || !defined(CONFIG_DEBUG_FS) +# undef CONFIG_MMCSD_DUMPALL +#endif + +/* Card type */ + +#define MMCSD_CARDTYPE_UNKNOWN 0 /* Unknown card type */ +#define MMCSD_CARDTYPE_MMC 1 /* Bit 0: MMC card */ +#define MMCSD_CARDTYPE_SDV1 2 /* Bit 1: SD version 1.x */ +#define MMCSD_CARDTYPE_SDV2 4 /* Bit 2: SD version 2.x with byte addressing */ +#define MMCSD_CARDTYPE_BLOCK 8 /* Bit 3: SD version 2.x with block addressing */ + +#define IS_MMC(t) (((t) & MMCSD_CARDTYPE_MMC) != 0) +#define IS_SD(t) (((t) & (MMCSD_CARDTYPE_SDV1|MMCSD_CARDTYPE_SDV2)) != 0) +#define IS_SDV1(t) (((t) & MMCSD_CARDTYPE_SDV1) != 0) +#define IS_SDV2(t) (((t) & MMCSD_CARDTYPE_SDV2) != 0) +#define IS_BLOCK(t) (((t) & MMCSD_CARDTYPE_BLOCK) != 0) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +#ifdef CONFIG_MMCSD_DUMPALL +# define mmcsd_dumpbuffer(m,b,l) fvdbgdumpbuffer(m,b,l) +#else +# define mmcsd_dumpbuffer(m,b,l) +#endif + +#if defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) +EXTERN void mmcsd_dmpcsd(FAR const uint8_t *csd, uint8_t cardtype); +#else +# define mmcsd_dmpcsd(csd,cadtype) +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif +#endif /* __DRIVERS_MMCSD_MMCSD_INTERNAL_H */ diff --git a/nuttx/drivers/mmcsd/mmcsd_sdio.c b/nuttx/drivers/mmcsd/mmcsd_sdio.c new file mode 100644 index 000000000..b30610ca5 --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_sdio.c @@ -0,0 +1,3062 @@ +/**************************************************************************** + * drivers/mmcsd/mmcsd_sdio.c + * + * Copyright (C) 2009-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <nuttx/compiler.h> + +#include <sys/types.h> +#include <sys/ioctl.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <semaphore.h> +#include <debug.h> +#include <errno.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> +#include <nuttx/ioctl.h> +#include <nuttx/clock.h> +#include <nuttx/arch.h> +#include <nuttx/rwbuffer.h> +#include <nuttx/sdio.h> +#include <nuttx/mmcsd.h> + +#include "mmcsd_internal.h" +#include "mmcsd_sdio.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The maximum number of references on the driver (because a uint8_t is used. + * Use a larger type if more references are needed. + */ + +#define MAX_CREFS 0xff + +/* Timing (all in units of microseconds) */ + +#define MMCSD_POWERUP_DELAY ((useconds_t)250) /* 74 clock cycles @ 400KHz = 185uS */ +#define MMCSD_IDLE_DELAY ((useconds_t)50000) /* Short delay to allow change to IDLE state */ +#define MMCSD_DSR_DELAY ((useconds_t)100000) /* Time to wait after setting DSR */ +#define MMCSD_CLK_DELAY ((useconds_t)500000) /* Delay after changing clock speeds */ + +/* Data delays (all in units of milliseconds). + * + * For MMC & SD V1.x, these should be based on Nac = TAAC + NSAC; The maximum + * value of TAAC is 80MS and the maximum value of NSAC is 25.5K clock cycle. + * For SD V2.x, a fixed delay of 100MS is recommend which is pretty close to + * the worst case SD V1.x Nac. Here we just use 100MS delay for all data + * transfers. + */ + +#define MMCSD_SCR_DATADELAY (100) /* Wait up to 100MS to get SCR */ +#define MMCSD_BLOCK_DATADELAY (100) /* Wait up to 100MS to get one data block */ + +#define IS_EMPTY(priv) (priv->type == MMCSD_CARDTYPE_UNKNOWN) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure is contains the unique state of the MMC/SD block driver */ + +struct mmcsd_state_s +{ + FAR struct sdio_dev_s *dev; /* The SDIO device bound to this instance */ + uint8_t crefs; /* Open references on the driver */ + sem_t sem; /* Assures mutually exclusive access to the slot */ + + /* Status flags */ + + uint8_t probed:1; /* true: mmcsd_probe() discovered a card */ + uint8_t widebus:1; /* true: Wide 4-bit bus selected */ + uint8_t mediachanged:1; /* true: Media changed since last check */ + uint8_t wrbusy:1; /* true: Last transfer was a write, card may be busy */ + uint8_t wrprotect:1; /* true: Card is write protected (from CSD) */ + uint8_t locked:1; /* true: Media is locked (from R1) */ + uint8_t dsrimp:1; /* true: card supports CMD4/DSR setting (from CSD) */ +#ifdef CONFIG_SDIO_DMA + uint8_t dma:1; /* true: hardware supports DMA */ +#endif + + uint8_t mode:2; /* (See MMCSDMODE_* definitions) */ + uint8_t type:4; /* Card type (See MMCSD_CARDTYPE_* definitions) */ + uint8_t buswidth:4; /* Bus widthes supported (SD only) */ + uint16_t selblocklen; /* The currently selected block length */ + uint16_t rca; /* Relative Card Address (RCS) register */ + + /* Memory card geometry (extracted from the CSD) */ + + uint8_t blockshift; /* Log2 of blocksize */ + uint16_t blocksize; /* Read block length (== block size) */ + uint32_t nblocks; /* Number of blocks */ + +#ifdef CONFIG_HAVE_LONG_LONG + uint64_t capacity; /* Total capacity of volume */ +#else + uint32_t capacity; /* Total capacity of volume (Limited to 4Gb) */ +#endif + /* Read-ahead and write buffering support */ + +#if defined(CONFIG_FS_WRITEBUFFER) || defined(CONFIG_FS_READAHEAD) + struct rwbuffer_s rwbuffer; +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Misc Helpers *************************************************************/ + +static void mmcsd_takesem(FAR struct mmcsd_state_s *priv); + +#ifndef CONFIG_SDIO_MUXBUS +# define mmcsd_givesem(p) sem_post(&priv->sem); +#endif + +/* Command/response helpers *************************************************/ + +static int mmcsd_sendcmdpoll(FAR struct mmcsd_state_s *priv, + uint32_t cmd, uint32_t arg); +static int mmcsd_recvR1(FAR struct mmcsd_state_s *priv, uint32_t cmd); +static int mmcsd_recvR6(FAR struct mmcsd_state_s *priv, uint32_t cmd); +static int mmcsd_getSCR(FAR struct mmcsd_state_s *priv, uint32_t scr[2]); + +static void mmcsd_decodeCSD(FAR struct mmcsd_state_s *priv, + uint32_t csd[4]); +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) +static void mmcsd_decodeCID(FAR struct mmcsd_state_s *priv, + uint32_t cid[4]); +#else +# define mmcsd_decodeCID(priv,cid) +#endif +static void mmcsd_decodeSCR(FAR struct mmcsd_state_s *priv, + uint32_t scr[2]); + +static int mmcsd_getR1(FAR struct mmcsd_state_s *priv, FAR uint32_t *r1); +static int mmcsd_verifystate(FAR struct mmcsd_state_s *priv, + uint32_t status); + +/* Transfer helpers *********************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static bool mmcsd_wrprotected(FAR struct mmcsd_state_s *priv); +#endif +static int mmcsd_eventwait(FAR struct mmcsd_state_s *priv, + sdio_eventset_t failevents, uint32_t timeout); +static int mmcsd_transferready(FAR struct mmcsd_state_s *priv); +static int mmcsd_stoptransmission(FAR struct mmcsd_state_s *priv); +static int mmcsd_setblocklen(FAR struct mmcsd_state_s *priv, + uint32_t blocklen); +static ssize_t mmcsd_readsingle(FAR struct mmcsd_state_s *priv, + FAR uint8_t *buffer, off_t startblock); +static ssize_t mmcsd_readmultiple(FAR struct mmcsd_state_s *priv, + FAR uint8_t *buffer, off_t startblock, size_t nblocks); +#ifdef CONFIG_FS_READAHEAD +static ssize_t mmcsd_reload(FAR void *dev, FAR uint8_t *buffer, + off_t startblock, size_t nblocks); +#endif +#ifdef CONFIG_FS_WRITABLE +static ssize_t mmcsd_writesingle(FAR struct mmcsd_state_s *priv, + FAR const uint8_t *buffer, off_t startblock); +static ssize_t mmcsd_writemultiple(FAR struct mmcsd_state_s *priv, + FAR const uint8_t *buffer, off_t startblock, size_t nblocks); +#ifdef CONFIG_FS_WRITEBUFFER +static ssize_t mmcsd_flush(FAR void *dev, FAR const uint8_t *buffer, + off_t startblock, size_t nblocks); +#endif +#endif + +/* Block driver methods *****************************************************/ + +static int mmcsd_open(FAR struct inode *inode); +static int mmcsd_close(FAR struct inode *inode); +static ssize_t mmcsd_read(FAR struct inode *inode, FAR unsigned char *buffer, + size_t startsector, unsigned int nsectors); +#ifdef CONFIG_FS_WRITABLE +static ssize_t mmcsd_write(FAR struct inode *inode, + FAR const unsigned char *buffer, size_t startsector, + unsigned int nsectors); +#endif +static int mmcsd_geometry(FAR struct inode *inode, + FAR struct geometry *geometry); +static int mmcsd_ioctl(FAR struct inode *inode, int cmd, + unsigned long arg); + +/* Initialization/uninitialization/reset ************************************/ + +static void mmcsd_mediachange(FAR void *arg); +static int mmcsd_widebus(FAR struct mmcsd_state_s *priv); +#ifdef CONFIG_MMCSD_MMCSUPPORT +static int mmcsd_mmcinitialize(FAR struct mmcsd_state_s *priv); +#endif +static int mmcsd_sdinitialize(FAR struct mmcsd_state_s *priv); +static int mmcsd_cardidentify(FAR struct mmcsd_state_s *priv); +static int mmcsd_probe(FAR struct mmcsd_state_s *priv); +static int mmcsd_removed(FAR struct mmcsd_state_s *priv); +static int mmcsd_hwinitialize(FAR struct mmcsd_state_s *priv); +static void mmcsd_hwuninitialize(FAR struct mmcsd_state_s *priv); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_bops = +{ + mmcsd_open, /* open */ + mmcsd_close, /* close */ + mmcsd_read, /* read */ +#ifdef CONFIG_FS_WRITABLE + mmcsd_write, /* write */ +#else + NULL, /* write */ +#endif + mmcsd_geometry, /* geometry */ + mmcsd_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Misc Helpers + ****************************************************************************/ + +static void mmcsd_takesem(FAR struct mmcsd_state_s *priv) +{ + /* Take the semaphore, giving exclusive access to the driver (perhaps + * waiting) + */ + + while (sem_wait(&priv->sem) != 0) + { + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } + + /* Lock the bus if mutually exclusive access to the SDIO bus is required + * on this platform. + */ + +#ifdef CONFIG_SDIO_MUXBUS + SDIO_LOCK(priv->dev, TRUE); +#endif +} + +#ifdef CONFIG_SDIO_MUXBUS +static void mmcsd_givesem(FAR struct mmcsd_state_s *priv) +{ + /* Release the SDIO bus lock, then the MMC/SD driver semaphore in the + * opposite order that they were taken to assure that no deadlock + * conditions will arise. + */ + + SDIO_LOCK(priv->dev, FALSE); + sem_post(&priv->sem); +} +#endif + +/**************************************************************************** + * Command/Response Helpers + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_sendcmdpoll + * + * Description: + * Send a command and poll-wait for the response. + * + ****************************************************************************/ + +static int mmcsd_sendcmdpoll(FAR struct mmcsd_state_s *priv, uint32_t cmd, + uint32_t arg) +{ + int ret; + + /* Send the command */ + + ret = SDIO_SENDCMD(priv->dev, cmd, arg); + if (ret == OK) + { + /* Then poll-wait until the response is available */ + + ret = SDIO_WAITRESPONSE(priv->dev, cmd); + if (ret != OK) + { + fdbg("ERROR: Wait for response to cmd: %08x failed: %d\n", cmd, ret); + } + } + return ret; +} + +/**************************************************************************** + * Name: mmcsd_sendcmd4 + * + * Description: + * Set the Driver Stage Register (DSR) if (1) a CONFIG_MMCSD_DSR has been + * provided and (2) the card supports a DSR register. If no DSR value + * the card default value (0x0404) will be used. + * + ****************************************************************************/ + +static inline int mmcsd_sendcmd4(FAR struct mmcsd_state_s *priv) +{ + int ret = OK; + +#ifdef CONFIG_MMCSD_DSR + /* The dsr_imp bit from the CSD will tell us if the card supports setting + * the DSR via CMD4 or not. + */ + + if (priv->dsrimp != false) + { + /* CMD4 = SET_DSR will set the cards DSR register. The DSR and CMD4 + * support are optional. However, since this is a broadcast command + * with no response (like CMD0), we will never know if the DSR was + * set correctly or not + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD4, CONFIG_MMCSD_DSR << 16); + up_udelay(MMCSD_DSR_DELAY); + + /* Send it again to have more confidence */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD4, CONFIG_MMCSD_DSR << 16); + up_udelay(MMCSD_DSR_DELAY); + } +#endif + return ret; +} + +/**************************************************************************** + * Name: mmcsd_recvR1 + * + * Description: + * Receive R1 response and check for errors. + * + ****************************************************************************/ + +static int mmcsd_recvR1(FAR struct mmcsd_state_s *priv, uint32_t cmd) +{ + uint32_t r1; + int ret; + + /* Get the R1 response from the hardware */ + + ret = SDIO_RECVR1(priv->dev, cmd, &r1); + if (ret == OK) + { + /* Check if R1 reports an error */ + + if ((r1 & MMCSD_R1_ERRORMASK) != 0) + { + /* Card locked is considered an error. Save the card locked + * indication for later use. + */ + + fvdbg("ERROR: R1=%08x\n", r1); + priv->locked = ((r1 & MMCSD_R1_CARDISLOCKED) != 0); + ret = -EIO; + } + } + return ret; +} + +/**************************************************************************** + * Name: mmcsd_recvR6 + * + * Description: + * Receive R6 response and check for errors. On success, priv->rca is set + * to the received RCA + * + ****************************************************************************/ + +static int mmcsd_recvR6(FAR struct mmcsd_state_s *priv, uint32_t cmd) +{ + uint32_t r6 = 0; + int ret; + + /* R6 Published RCA Response (48-bit, SD card only) + * 47 0 Start bit + * 46 0 Transmission bit (0=from card) + * 45:40 bit5 - bit0 Command index (0-63) + * 39:8 bit31 - bit0 32-bit Argument Field, consisting of: + * [31:16] New published RCA of card + * [15:0] Card status bits {23,22,19,12:0} + * 7:1 bit6 - bit0 CRC7 + * 0 1 End bit + * + * Get the R1 response from the hardware + */ + + ret = SDIO_RECVR6(priv->dev, cmd, &r6); + if (ret == OK) + { + /* Check if R6 reports an error */ + + if ((r6 & MMCSD_R6_ERRORMASK) == 0) + { + /* No, save the RCA and return success */ + + priv->rca = (uint16_t)(r6 >> 16); + return OK; + } + + /* Otherwise, return an I/O failure */ + + ret = -EIO; + } + + fdbg("ERROR: Failed to get RCA. R6=%08x: %d\n", r6, ret); + return ret; +} + +/**************************************************************************** + * Name: mmcsd_getSCR + * + * Description: + * Obtain the SD card's Configuration Register (SCR) + * + * Returned Value: + * OK on success; a negated ernno on failure. + * + ****************************************************************************/ + +static int mmcsd_getSCR(FAR struct mmcsd_state_s *priv, uint32_t scr[2]) +{ + int ret; + + /* Set Block Size To 8 Bytes */ + + ret = mmcsd_setblocklen(priv, 8); + if (ret != OK) + { + fdbg("ERROR: mmcsd_setblocklen failed: %d\n", ret); + return ret; + } + + /* Send CMD55 APP_CMD with argument as card's RCA */ + + mmcsd_sendcmdpoll(priv, SD_CMD55, (uint32_t)priv->rca << 16); + ret = mmcsd_recvR1(priv, SD_CMD55); + if (ret != OK) + { + fdbg("ERROR: RECVR1 for CMD55 failed: %d\n", ret); + return ret; + } + + /* Setup up to receive data with interrupt mode */ + + SDIO_BLOCKSETUP(priv->dev, 8, 1); + SDIO_RECVSETUP(priv->dev, (FAR uint8_t*)scr, 8); + + /* Send ACMD51 SD_APP_SEND_SCR with argument as 0 to start data receipt */ + + (void)SDIO_WAITENABLE(priv->dev, SDIOWAIT_TRANSFERDONE|SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR); + mmcsd_sendcmdpoll(priv, SD_ACMD51, 0); + ret = mmcsd_recvR1(priv, SD_ACMD51); + if (ret != OK) + { + fdbg("ERROR: RECVR1 for ACMD51 failed: %d\n", ret); + SDIO_CANCEL(priv->dev); + return ret; + } + + /* Wait for data to be transferred */ + + ret = mmcsd_eventwait(priv, SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR, MMCSD_SCR_DATADELAY); + if (ret != OK) + { + fdbg("ERROR: mmcsd_eventwait for READ DATA failed: %d\n", ret); + } + return ret; +} + +/**************************************************************************** + * Name: mmcsd_decodeCSD + * + * Description: + * Decode and extract necessary information from the CSD. If debug is + * enabled, then decode and show the full contents of the CSD. + * + * Returned Value: + * OK on success; a negated ernno on failure. On success, the following + * values will be set in the driver state structure: + * + * priv->dsrimp true: card supports CMD4/DSR setting (from CSD) + * priv->wrprotect true: card is write protected (from CSD) + * priv->blocksize Read block length (== block size) + * priv->nblocks Number of blocks + * priv->capacity Total capacity of volume + * + ****************************************************************************/ + +static void mmcsd_decodeCSD(FAR struct mmcsd_state_s *priv, uint32_t csd[4]) +{ +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) + struct mmcsd_csd_s decoded; +#endif + unsigned int readbllen; + bool permwriteprotect; + bool tmpwriteprotect; + + /* Word 1: Bits 127-96: + * + * CSD_STRUCTURE 127:126 CSD structure + * SPEC_VERS 125:122 (MMC) Spec version + * TAAC 119:112 Data read access-time-1 + * TIME_VALUE 6:3 Time mantissa + * TIME_UNIT 2:0 Time exponent + * NSAC 111:104 Data read access-time-2 in CLK cycle(NSAC*100) + * TRAN_SPEED 103:96 Max. data transfer rate + * TIME_VALUE 6:3 Rate exponent + * TRANSFER_RATE_UNIT 2:0 Rate mantissa + */ + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) + memset(&decoded, 0, sizeof(struct mmcsd_csd_s)); + decoded.csdstructure = csd[0] >> 30; + decoded.mmcspecvers = (csd[0] >> 26) & 0x0f; + decoded.taac.timevalue = (csd[0] >> 19) & 0x0f; + decoded.taac.timeunit = (csd[0] >> 16) & 7; + decoded.nsac = (csd[0] >> 8) & 0xff; + decoded.transpeed.timevalue = (csd[0] >> 3) & 0x0f; + decoded.transpeed.transferrateunit = csd[0] & 7; +#endif + + /* Word 2: Bits 64:95 + * CCC 95:84 Card command classes + * READ_BL_LEN 83:80 Max. read data block length + * READ_BL_PARTIAL 79:79 Partial blocks for read allowed + * WRITE_BLK_MISALIGN 78:78 Write block misalignment + * READ_BLK_MISALIGN 77:77 Read block misalignment + * DSR_IMP 76:76 DSR implemented + * Byte addressed SD and MMC: + * C_SIZE 73:62 Device size + * Block addressed SD: + * 75:70 (reserved) + * C_SIZE 48:69 Device size + */ + + priv->dsrimp = (csd[1] >> 12) & 1; + readbllen = (csd[1] >> 16) & 0x0f; + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) + decoded.ccc = (csd[1] >> 20) & 0x0fff; + decoded.readbllen = (csd[1] >> 16) & 0x0f; + decoded.readblpartial = (csd[1] >> 15) & 1; + decoded.writeblkmisalign = (csd[1] >> 14) & 1; + decoded.readblkmisalign = (csd[1] >> 13) & 1; + decoded.dsrimp = priv->dsrimp; +#endif + + /* Word 3: Bits 32-63 + * + * Byte addressed SD: + * C_SIZE 73:62 Device size + * VDD_R_CURR_MIN 61:59 Max. read current at Vcc min + * VDD_R_CURR_MAX 58:56 Max. read current at Vcc max + * VDD_W_CURR_MIN 55:53 Max. write current at Vcc min + * VDD_W_CURR_MAX 52:50 Max. write current at Vcc max + * C_SIZE_MULT 49:47 Device size multiplier + * SD_ER_BLK_EN 46:46 Erase single block enable (SD only) + * SD_SECTOR_SIZE 45:39 Erase sector size + * SD_WP_GRP_SIZE 38:32 Write protect group size + * Block addressed SD: + * 75:70 (reserved) + * C_SIZE 48:69 Device size + * 47:47 (reserved) + * SD_ER_BLK_EN 46:46 Erase single block enable (SD only) + * SD_SECTOR_SIZE 45:39 Erase sector size + * SD_WP_GRP_SIZE 38:32 Write protect group size + * MMC: + * C_SIZE 73:62 Device size + * VDD_R_CURR_MIN 61:59 Max. read current at Vcc min + * VDD_R_CURR_MAX 58:56 Max. read current at Vcc max + * VDD_W_CURR_MIN 55:53 Max. write current at Vcc min + * VDD_W_CURR_MAX 52:50 Max. write current at Vcc max + * C_SIZE_MULT 49:47 Device size multiplier + * MMC_SECTOR_SIZE 46:42 Erase sector size + * MMC_ER_GRP_SIZE 41:37 Erase group size (MMC) + * MMC_WP_GRP_SIZE 36:32 Write protect group size + */ + + if (IS_BLOCK(priv->type)) + { + /* Block addressed SD: + * + * C_SIZE: 69:64 from Word 2 and 63:48 from Word 3 + * + * 512 = (1 << 9) + * 1024 = (1 << 10) + * 512*1024 = (1 << 19) + */ + + uint32_t csize = ((csd[1] & 0x3f) << 16) | (csd[2] >> 16); +#ifdef CONFIG_HAVE_LONG_LONG + priv->capacity = ((uint64_t)(csize + 1)) << 19; +#else + priv->capacity = (csize + 1) << 19; +#endif + priv->blockshift = 9; + priv->blocksize = 1 << 9; + priv->nblocks = priv->capacity >> 9; + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) + decoded.u.sdblock.csize = csize; + decoded.u.sdblock.sderblen = (csd[2] >> 14) & 1; + decoded.u.sdblock.sdsectorsize = (csd[2] >> 7) & 0x7f; + decoded.u.sdblock.sdwpgrpsize = csd[2] & 0x7f; +#endif + } + else + { + /* Byte addressed SD: + * + * C_SIZE: 73:64 from Word 2 and 63:62 from Word 3 + */ + + uint16_t csize = ((csd[1] & 0x03ff) << 2) | ((csd[2] >> 30) & 3); + uint8_t csizemult = (csd[2] >> 15) & 7; + + priv->nblocks = ((uint32_t)csize + 1) * (1 << (csizemult + 2)); + priv->blockshift = readbllen; + priv->blocksize = (1 << readbllen); + priv->capacity = (priv->nblocks << readbllen); + + /* Some devices, such as 2Gb devices, report blocksizes larger than 512 bytes + * but still expect to be accessed with a 512 byte blocksize. + * + * NOTE: A minor optimization would be to eliminated priv->blocksize and + * priv->blockshift: Those values will be 512 and 9 in all cases anyway. + */ + + if (priv->blocksize > 512) + { + priv->nblocks <<= (priv->blockshift - 9); + priv->blocksize = 512; + priv->blockshift = 9; + } + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) + if (IS_SD(priv->type)) + { + decoded.u.sdbyte.csize = csize; + decoded.u.sdbyte.vddrcurrmin = (csd[2] >> 27) & 7; + decoded.u.sdbyte.vddrcurrmax = (csd[2] >> 24) & 7; + decoded.u.sdbyte.vddwcurrmin = (csd[2] >> 21) & 7; + decoded.u.sdbyte.vddwcurrmax = (csd[2] >> 18) & 7; + decoded.u.sdbyte.csizemult = csizemult; + decoded.u.sdbyte.sderblen = (csd[2] >> 14) & 1; + decoded.u.sdbyte.sdsectorsize = (csd[2] >> 7) & 0x7f; + decoded.u.sdbyte.sdwpgrpsize = csd[2] & 0x7f; + } +#ifdef CONFIG_MMCSD_MMCSUPPORT + else if (IS_MMC(priv->type)) + { + decoded.u.mmc.csize = csize; + decoded.u.mmc.vddrcurrmin = (csd[2] >> 27) & 7; + decoded.u.mmc.vddrcurrmax = (csd[2] >> 24) & 7; + decoded.u.mmc.vddwcurrmin = (csd[2] >> 21) & 7; + decoded.u.mmc.vddwcurrmax = (csd[2] >> 18) & 7; + decoded.u.mmc.csizemult = csizemult; + decoded.u.mmc.er.mmc22.sectorsize = (csd[2] >> 10) & 0x1f; + decoded.u.mmc.er.mmc22.ergrpsize = (csd[2] >> 5) & 0x1f; + decoded.u.mmc.mmcwpgrpsize = csd[2] & 0x1f; + } +#endif +#endif + } + + /* Word 4: Bits 0-31 + * WP_GRP_EN 31:31 Write protect group enable + * MMC DFLT_ECC 30:29 Manufacturer default ECC (MMC only) + * R2W_FACTOR 28:26 Write speed factor + * WRITE_BL_LEN 25:22 Max. write data block length + * WRITE_BL_PARTIAL 21:21 Partial blocks for write allowed + * FILE_FORMAT_GROUP 15:15 File format group + * COPY 14:14 Copy flag (OTP) + * PERM_WRITE_PROTECT 13:13 Permanent write protection + * TMP_WRITE_PROTECT 12:12 Temporary write protection + * FILE_FORMAT 10:11 File format + * ECC 9:8 ECC (MMC only) + * CRC 7:1 CRC + * Not used 0:0 + */ + + permwriteprotect = (csd[3] >> 13) & 1; + tmpwriteprotect = (csd[3] >> 12) & 1; + priv->wrprotect = (permwriteprotect || tmpwriteprotect); + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) + decoded.wpgrpen = csd[3] >> 31; + decoded.mmcdfltecc = (csd[3] >> 29) & 3; + decoded.r2wfactor = (csd[3] >> 26) & 7; + decoded.writebllen = (csd[3] >> 22) & 0x0f; + decoded.writeblpartial = (csd[3] >> 21) & 1; + decoded.fileformatgrp = (csd[3] >> 15) & 1; + decoded.copy = (csd[3] >> 14) & 1; + decoded.permwriteprotect = permwriteprotect; + decoded.tmpwriteprotect = tmpwriteprotect; + decoded.fileformat = (csd[3] >> 10) & 3; + decoded.mmcecc = (csd[3] >> 8) & 3; + decoded.crc = (csd[3] >> 1) & 0x7f; + + fvdbg("CSD:\n"); + fvdbg(" CSD_STRUCTURE: %d SPEC_VERS: %d (MMC)\n", + decoded.csdstructure, decoded.mmcspecvers); + fvdbg(" TAAC {TIME_UNIT: %d TIME_VALUE: %d} NSAC: %d\n", + decoded.taac.timeunit, decoded.taac.timevalue, decoded.nsac); + fvdbg(" TRAN_SPEED {TRANSFER_RATE_UNIT: %d TIME_VALUE: %d}\n", + decoded.transpeed.transferrateunit, decoded.transpeed.timevalue); + fvdbg(" CCC: %d\n", decoded.ccc); + fvdbg(" READ_BL_LEN: %d READ_BL_PARTIAL: %d\n", + decoded.readbllen, decoded.readblpartial); + fvdbg(" WRITE_BLK_MISALIGN: %d READ_BLK_MISALIGN: %d\n", + decoded.writeblkmisalign, decoded.readblkmisalign); + fvdbg(" DSR_IMP: %d\n", + decoded.dsrimp); + + if (IS_BLOCK(priv->type)) + { + fvdbg(" SD Block Addressing:\n"); + fvdbg(" C_SIZE: %d SD_ER_BLK_EN: %d\n", + decoded.u.sdblock.csize, decoded.u.sdblock.sderblen); + fvdbg(" SD_SECTOR_SIZE: %d SD_WP_GRP_SIZE: %d\n", + decoded.u.sdblock.sdsectorsize, decoded.u.sdblock.sdwpgrpsize); + } + else if (IS_SD(priv->type)) + { + fvdbg(" SD Byte Addressing:\n"); + fvdbg(" C_SIZE: %d C_SIZE_MULT: %d\n", + decoded.u.sdbyte.csize, decoded.u.sdbyte.csizemult); + fvdbg(" VDD_R_CURR_MIN: %d VDD_R_CURR_MAX: %d\n", + decoded.u.sdbyte.vddrcurrmin, decoded.u.sdbyte.vddrcurrmax); + fvdbg(" VDD_W_CURR_MIN: %d VDD_W_CURR_MAX: %d\n", + decoded.u.sdbyte.vddwcurrmin, decoded.u.sdbyte.vddwcurrmax); + fvdbg(" SD_ER_BLK_EN: %d SD_SECTOR_SIZE: %d (SD) SD_WP_GRP_SIZE: %d\n", + decoded.u.sdbyte.sderblen, decoded.u.sdbyte.sdsectorsize, decoded.u.sdbyte.sdwpgrpsize); + } +#ifdef CONFIG_MMCSD_MMCSUPPORT + else if (IS_MMC(priv->type)) + { + fvdbg(" MMC:\n"); + fvdbg(" C_SIZE: %d C_SIZE_MULT: %d\n", + decoded.u.mmc.csize, decoded.u.mmc.csizemult); + fvdbg(" VDD_R_CURR_MIN: %d VDD_R_CURR_MAX: %d\n", + decoded.u.mmc.vddrcurrmin, decoded.u.mmc.vddrcurrmax); + fvdbg(" VDD_W_CURR_MIN: %d VDD_W_CURR_MAX: %d\n", + decoded.u.mmc.vddwcurrmin, decoded.u.mmc.vddwcurrmax); + fvdbg(" MMC_SECTOR_SIZE: %d MMC_ER_GRP_SIZE: %d MMC_WP_GRP_SIZE: %d\n", + decoded.u.mmc.er.mmc22.sectorsize, decoded.u.mmc.er.mmc22.ergrpsize, + decoded.u.mmc.mmcwpgrpsize); + } +#endif + + fvdbg(" WP_GRP_EN: %d MMC DFLT_ECC: %d (MMC) R2W_FACTOR: %d\n", + decoded.wpgrpen, decoded.mmcdfltecc, decoded.r2wfactor); + fvdbg(" WRITE_BL_LEN: %d WRITE_BL_PARTIAL: %d\n", + decoded.writebllen, decoded.writeblpartial); + fvdbg(" FILE_FORMAT_GROUP: %d COPY: %d\n", + decoded.fileformatgrp, decoded.copy); + fvdbg(" PERM_WRITE_PROTECT: %d TMP_WRITE_PROTECT: %d\n", + decoded.permwriteprotect, decoded.tmpwriteprotect); + fvdbg(" FILE_FORMAT: %d ECC: %d (MMC) CRC: %d\n", + decoded.fileformat, decoded.mmcecc, decoded.crc); + + fvdbg("Capacity: %luKb, Block size: %db, nblocks: %d wrprotect: %d\n", + (unsigned long)(priv->capacity / 1024), priv->blocksize, + priv->nblocks, priv->wrprotect); +#endif +} + +/**************************************************************************** + * Name: mmcsd_decodeCID + * + * Description: + * Show the contents of the Card Indentification Data (CID) (for debug + * purposes only) + * + ****************************************************************************/ + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) +static void mmcsd_decodeCID(FAR struct mmcsd_state_s *priv, uint32_t cid[4]) +{ + struct mmcsd_cid_s decoded; + + /* Word 1: Bits 127-96: + * mid - 127-120 8-bit Manufacturer ID + * oid - 119-104 16-bit OEM/Application ID (ascii) + * pnm - 103-64 40-bit Product Name (ascii) + null terminator + * pnm[0] 103:96 + */ + + decoded.mid = cid[0] >> 24; + decoded.oid = (cid[0] >> 16) & 0xffff; + decoded.pnm[0] = cid[0] & 0xff; + + /* Word 2: Bits 64:95 + * pnm - 103-64 40-bit Product Name (ascii) + null terminator + * pnm[1] 95:88 + * pnm[2] 87:80 + * pnm[3] 79:72 + * pnm[4] 71:64 + */ + + decoded.pnm[1] = cid[1] >> 24; + decoded.pnm[2] = (cid[1] >> 16) & 0xff; + decoded.pnm[3] = (cid[1] >> 8) & 0xff; + decoded.pnm[4] = cid[1] & 0xff; + decoded.pnm[5] = '\0'; + + /* Word 3: Bits 32-63 + * prv - 63-56 8-bit Product revision + * psn - 55-24 32-bit Product serial number + */ + + decoded.prv = cid[2] >> 24; + decoded.psn = cid[2] << 8; + + /* Word 4: Bits 0-31 + * psn - 55-24 32-bit Product serial number + * 23-20 4-bit (reserved) + * mdt - 19:8 12-bit Manufacturing date + * crc - 7:1 7-bit CRC7 + */ + + decoded.psn |= cid[3] >> 24; + decoded.mdt = (cid[3] >> 8) & 0x0fff; + decoded.crc = (cid[3] >> 1) & 0x7f; + + fvdbg("mid: %02x oid: %04x pnm: %s prv: %d psn: %d mdt: %02x crc: %02x\n", + decoded.mid, decoded.oid, decoded.pnm, decoded.prv, + decoded.psn, decoded.mdt, decoded.crc); +} +#endif + +/**************************************************************************** + * Name: mmcsd_decodeSCR + * + * Description: + * Show the contents of the SD Configuration Register (SCR). The only + * value retained is: priv->buswidth; + * + ****************************************************************************/ + +static void mmcsd_decodeSCR(FAR struct mmcsd_state_s *priv, uint32_t scr[2]) +{ +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) +struct mmcsd_scr_s decoded; +#endif + + /* Word 1, bits 63:32 + * SCR_STRUCTURE 63:60 4-bit SCR structure version + * SD_VERSION 59:56 4-bit SD memory spec. version + * DATA_STATE_AFTER_ERASE 55:55 1-bit erase status + * SD_SECURITY 54:52 3-bit SD security support level + * SD_BUS_WIDTHS 51:48 4-bit bus width indicator + * Reserved 47:32 16-bit SD reserved space + */ + +#ifdef CONFIG_ENDIAN_BIG /* Card transfers SCR in big-endian order */ + priv->buswidth = (scr[0] >> 16) & 15; +#else + priv->buswidth = (scr[0] >> 8) & 15; +#endif + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) +#ifdef CONFIG_ENDIAN_BIG /* Card SCR is big-endian order / CPU also big-endian + * 60 56 52 48 44 40 36 32 + * VVVV SSSS ESSS BBBB RRRR RRRR RRRR RRRR */ + decoded.scrversion = scr[0] >> 28; + decoded.sdversion = (scr[0] >> 24) & 15; + decoded.erasestate = (scr[0] >> 23) & 1; + decoded.security = (scr[0] >> 20) & 7; +#else /* Card SCR is big-endian order / CPU is little-endian + * 36 32 44 40 52 48 60 56 + * RRRR RRRR RRRR RRRR ESSS BBBB VVVV SSSS */ + decoded.scrversion = (scr[0] >> 4) & 15; + decoded.sdversion = scr[0] & 15; + decoded.erasestate = (scr[0] >> 15) & 1; + decoded.security = (scr[0] >> 12) & 7; +#endif + decoded.buswidth = priv->buswidth; +#endif + + /* Word 1, bits 63:32 + * Reserved 31:0 32-bits reserved for manufacturing usage. + */ + +#if defined(CONFIG_DEBUG) && defined (CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS) + decoded.mfgdata = scr[1]; /* Might be byte reversed! */ + + fvdbg("SCR:\n"); + fvdbg(" SCR_STRUCTURE: %d SD_VERSION: %d\n", + decoded.scrversion,decoded.sdversion); + fvdbg(" DATA_STATE_AFTER_ERASE: %d SD_SECURITY: %d SD_BUS_WIDTHS: %x\n", + decoded.erasestate, decoded.security, decoded.buswidth); + fvdbg(" Manufacturing data: %08x\n", + decoded.mfgdata); +#endif +} + +/**************************************************************************** + * Name: mmcsd_getR1 + * + * Description: + * Get the R1 status of the card using CMD13 + * + ****************************************************************************/ + +static int mmcsd_getR1(FAR struct mmcsd_state_s *priv, FAR uint32_t *r1) +{ + uint32_t localR1; + int ret; + + DEBUGASSERT(priv != NULL && r1 != NULL); + + /* Send CMD13, SEND_STATUS. The addressed card responds by sending its + * R1 card status register. + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD13, (uint32_t)priv->rca << 16); + ret = SDIO_RECVR1(priv->dev, MMCSD_CMD13, &localR1); + if (ret == OK) + { + /* Check if R1 reports an error */ + + if ((localR1 & MMCSD_R1_ERRORMASK) != 0) + { + /* Card locked is considered an error. Save the card locked + * indication for later use. + */ + + priv->locked = ((localR1 & MMCSD_R1_CARDISLOCKED) != 0); + ret = -EIO; + } + else + { + /* No errors, return R1 */ + + *r1 = localR1; + } + } + return ret; +} + +/**************************************************************************** + * Name: mmcsd_verifystate + * + * Description: + * Verify that the card is in STANDBY state + * + ****************************************************************************/ + +static int mmcsd_verifystate(FAR struct mmcsd_state_s *priv, uint32_t state) +{ + uint32_t r1; + int ret; + + /* Get the current R1 status from the card */ + + ret = mmcsd_getR1(priv, &r1); + if (ret != OK) + { + fdbg("ERROR: mmcsd_getR1 failed: %d\n", ret); + return ret; + } + + /* Now check if the card is in the expected state. */ + + if (IS_STATE(r1, state)) + { + /* Yes.. return Success */ + + priv->wrbusy = false; + return OK; + } + return -EINVAL; +} + +/**************************************************************************** + * Transfer Helpers + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_wrprotected + * + * Description: + * Return true if the the card is unlocked an not write protected. The + * + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static bool mmcsd_wrprotected(FAR struct mmcsd_state_s *priv) +{ + /* Check if the card is locked (priv->locked) or write protected either (1) + * via software as reported via the CSD and retained in priv->wrprotect or + * (2) via the mechanical write protect on the card (which we get from the + * SDIO driver via SDIO_WRPROTECTED) + */ + + return (priv->wrprotect || priv->locked || SDIO_WRPROTECTED(priv->dev)); +} +#endif + +/**************************************************************************** + * Name: mmcsd_eventwait + * + * Description: + * Wait for the specified events to occur. Check for wakeup on error events. + * + ****************************************************************************/ + +static int mmcsd_eventwait(FAR struct mmcsd_state_s *priv, + sdio_eventset_t failevents, uint32_t timeout) +{ + sdio_eventset_t wkupevent; + + /* Wait for the set of events enabled by SDIO_EVENTENABLE. */ + + wkupevent = SDIO_EVENTWAIT(priv->dev, timeout); + + /* SDIO_EVENTWAIT returns the event set containing the event(s) that ended + * the wait. It should always be non-zero, but may contain failure as + * well as success events. Check if it contains any failure events. + */ + + if ((wkupevent & failevents) != 0) + { + /* Yes.. the failure event is probably SDIOWAIT_TIMEOUT */ + + fdbg("ERROR: Awakened with %02x\n", wkupevent); + return wkupevent & SDIOWAIT_TIMEOUT ? -ETIMEDOUT : -EIO; + } + + /* Since there are no failure events, we must have been awakened by one + * (or more) success events. + */ + + return OK; +} + +/**************************************************************************** + * Name: mmcsd_transferready + * + * Description: + * Check if the MMC/SD card is ready for the next read or write transfer. + * Ready means: (1) card still in the slot, and (2) if the last transfer + * was a write transfer, the card is no longer busy from that transfer. + * + ****************************************************************************/ + +static int mmcsd_transferready(FAR struct mmcsd_state_s *priv) +{ + uint32_t starttime; + uint32_t elapsed; + uint32_t r1; + int ret; + + /* First, check if the card has been removed. */ + + if (!SDIO_PRESENT(priv->dev)) + { + fdbg("ERROR: Card has been removed\n"); + return -ENODEV; + } + + /* If the last data transfer was not a write, then we do not have to check + * the card status. + */ + + else if (!priv->wrbusy) + { + return OK; + } + + /* The card is still present and the last transfer was a write transfer. + * Loop, querying the card state. Return when (1) the card is in the TRANSFER + * state, (2) the card stays in the PROGRAMMING state too long, or (3) the + * card is in any other state. + * + * The PROGRAMMING state occurs normally after a WRITE operation. During this + * time, the card may be busy completing the WRITE and is not available for + * other operations. The card will transition from the PROGRAMMING state to + * the TRANSFER state when the card completes the WRITE operation. + */ + + starttime = clock_systimer(); + do + { + /* Get the current R1 status from the card */ + + ret = mmcsd_getR1(priv, &r1); + if (ret != OK) + { + fdbg("ERROR: mmcsd_getR1 failed: %d\n", ret); + return ret; + } + + /* Now check if the card is in the expected transfer state. */ + + if (IS_STATE(r1, MMCSD_R1_STATE_TRAN)) + { + /* Yes.. return Success */ + + priv->wrbusy = false; + return OK; + } + + /* Check for the programming state. This is not an error. It means + * that the card is still busy from the last (write) transfer. + */ + + else if (!IS_STATE(r1, MMCSD_R1_STATE_PRG)) + { + /* Any other state would be an error in this context. There is + * a possibility that the card is not selected. In this case, + * it could be in STANDBY or DISCONNECTED state and the fix + * might b to send CMD7 to re-select the card. Consider this + * if this error occurs. + */ + + fdbg("ERROR: Unexpected R1 state: %08x\n", r1); + return -EINVAL; + } + + /* We are still in the programming state. Calculate the elapsed + * time... we can't stay in this loop forever! + */ + + elapsed = clock_systimer() - starttime; + } + while (elapsed < TICK_PER_SEC); + return -ETIMEDOUT; +} + +/**************************************************************************** + * Name: mmcsd_stoptransmission + * + * Description: + * Send STOP_TRANSMISSION + * + ****************************************************************************/ + +static int mmcsd_stoptransmission(FAR struct mmcsd_state_s *priv) +{ + int ret; + + /* Send CMD12, STOP_TRANSMISSION, and verify good R1 return status */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD12, 0); + ret = mmcsd_recvR1(priv, MMCSD_CMD12); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for CMD12 failed: %d\n", ret); + } + return ret; +} + +/**************************************************************************** + * Name: mmcsd_setblocklen + * + * Description: + * Read a single block of data. + * + ****************************************************************************/ + +static int mmcsd_setblocklen(FAR struct mmcsd_state_s *priv, uint32_t blocklen) +{ + int ret = OK; + + /* Is the block length already selected in the card? */ + + if (priv->selblocklen != blocklen) + { + /* Send CMD16 = SET_BLOCKLEN. This command sets the block length (in + * bytes) for all following block commands (read and write). Default + * block length is specified in the CSD. + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD16, blocklen); + ret = mmcsd_recvR1(priv, MMCSD_CMD16); + if (ret == OK) + { + priv->selblocklen = blocklen; + } + else + { + fdbg("ERROR: mmcsd_recvR1 for CMD16 failed: %d\n", ret); + } + } + + return ret; +} + +/**************************************************************************** + * Name: mmcsd_readsingle + * + * Description: + * Read a single block of data. + * + ****************************************************************************/ + +static ssize_t mmcsd_readsingle(FAR struct mmcsd_state_s *priv, + FAR uint8_t *buffer, off_t startblock) +{ + off_t offset; + int ret; + + fvdbg("startblock=%d\n", startblock); + DEBUGASSERT(priv != NULL && buffer != NULL); + + /* Check if the card is locked */ + + if (priv->locked) + { + fdbg("ERROR: Card is locked\n"); + return -EPERM; + } + + /* Verify that the card is ready for the transfer. The card may still be + * busy from the preceding write transfer. It would be simpler to check + * for write busy at the end of each write, rather than at the beginning of + * each read AND write, but putting the busy-wait at the beginning of the + * transfer allows for more overlap and, hopefully, better performance + */ + + ret = mmcsd_transferready(priv); + if (ret != OK) + { + fdbg("ERROR: Card not ready: %d\n", ret); + return ret; + } + + /* If this is a byte addressed SD card, then convert sector start sector + * number to a byte offset + */ + + if (IS_BLOCK(priv->type)) + { + offset = startblock; + } + else + { + offset = startblock << priv->blockshift; + } + fvdbg("offset=%d\n", offset); + + /* Select the block size for the card */ + + ret = mmcsd_setblocklen(priv, priv->blocksize); + if (ret != OK) + { + fdbg("ERROR: mmcsd_setblocklen failed: %d\n", ret); + return ret; + } + + /* Configure SDIO controller hardware for the read transfer */ + + SDIO_BLOCKSETUP(priv->dev, priv->blocksize, 1); + SDIO_WAITENABLE(priv->dev, SDIOWAIT_TRANSFERDONE|SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR); +#ifdef CONFIG_SDIO_DMA + if (priv->dma) + { + SDIO_DMARECVSETUP(priv->dev, buffer, priv->blocksize); + } + else +#endif + { + SDIO_RECVSETUP(priv->dev, buffer, priv->blocksize); + } + + /* Send CMD17, READ_SINGLE_BLOCK: Read a block of the size selected + * by the mmcsd_setblocklen() and verify that good R1 status is + * returned. The card state should change from Transfer to Sending-Data + * state. + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD17, offset); + ret = mmcsd_recvR1(priv, MMCSD_CMD17); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for CMD17 failed: %d\n", ret); + SDIO_CANCEL(priv->dev); + return ret; + } + + /* Then wait for the data transfer to complete */ + + ret = mmcsd_eventwait(priv, SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR, MMCSD_BLOCK_DATADELAY); + if (ret != OK) + { + fdbg("ERROR: CMD17 transfer failed: %d\n", ret); + return ret; + } + + /* Return value: One sector read */ + + return 1; +} + +/**************************************************************************** + * Name: mmcsd_readmultiple + * + * Description: + * Read multiple, contiguous blocks of data from the physical device. + * + ****************************************************************************/ + +static ssize_t mmcsd_readmultiple(FAR struct mmcsd_state_s *priv, + FAR uint8_t *buffer, off_t startblock, + size_t nblocks) +{ + size_t nbytes; + off_t offset; + int ret; + + fvdbg("startblock=%d nblocks=%d\n", startblock, nblocks); + DEBUGASSERT(priv != NULL && buffer != NULL && nblocks > 1); + + /* Check if the card is locked */ + + if (priv->locked) + { + fdbg("ERROR: Card is locked\n"); + return -EPERM; + } + + /* Verify that the card is ready for the transfer. The card may still be + * busy from the preceding write transfer. It would be simpler to check + * for write busy at the end of each write, rather than at the beginning of + * each read AND write, but putting the busy-wait at the beginning of the + * transfer allows for more overlap and, hopefully, better performance + */ + + ret = mmcsd_transferready(priv); + if (ret != OK) + { + fdbg("ERROR: Card not ready: %d\n", ret); + return ret; + } + + /* If this is a byte addressed SD card, then convert both the total transfer + * size to bytes and the sector start sector number to a byte offset + */ + + nbytes = nblocks << priv->blockshift; + if (IS_BLOCK(priv->type)) + { + offset = startblock; + } + else + { + offset = startblock << priv->blockshift; + } + fvdbg("nbytes=%d byte offset=%d\n", nbytes, offset); + + /* Select the block size for the card */ + + ret = mmcsd_setblocklen(priv, priv->blocksize); + if (ret != OK) + { + fdbg("ERROR: mmcsd_setblocklen failed: %d\n", ret); + return ret; + } + + /* Configure SDIO controller hardware for the read transfer */ + + SDIO_BLOCKSETUP(priv->dev, priv->blocksize, nblocks); + SDIO_WAITENABLE(priv->dev, SDIOWAIT_TRANSFERDONE|SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR); +#ifdef CONFIG_SDIO_DMA + if (priv->dma) + { + SDIO_DMARECVSETUP(priv->dev, buffer, nbytes); + } + else +#endif + { + SDIO_RECVSETUP(priv->dev, buffer, nbytes); + } + + /* Send CMD18, READ_MULT_BLOCK: Read a block of the size selected by + * the mmcsd_setblocklen() and verify that good R1 status is returned + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD18, offset); + ret = mmcsd_recvR1(priv, MMCSD_CMD18); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for CMD18 failed: %d\n", ret); + SDIO_CANCEL(priv->dev); + return ret; + } + + /* Wait for the transfer to complete */ + + ret = mmcsd_eventwait(priv, SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR, nblocks * MMCSD_BLOCK_DATADELAY); + if (ret != OK) + { + fdbg("ERROR: CMD18 transfer failed: %d\n", ret); + return ret; + } + + /* Send STOP_TRANSMISSION */ + + ret = mmcsd_stoptransmission(priv); + if (ret != OK) + { + fdbg("ERROR: mmcsd_stoptransmission failed: %d\n", ret); + } + + /* On success, return the number of blocks read */ + + return nblocks; +} + +/**************************************************************************** + * Name: mmcsd_reload + * + * Description: + * Reload the specified number of sectors from the physical device into the + * read-ahead buffer. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_READAHEAD +static ssize_t mmcsd_reload(FAR void *dev, FAR uint8_t *buffer, + off_t startblock, size_t nblocks) +{ + FAR struct mmcsd_state_s *priv = (FAR struct mmcsd_state_s *)dev; + ssize_t ret; + + DEBUGASSERT(priv != NULL && buffer != NULL && nblocks > 0) + + if (nblocks == 1) + { + ret = mmcsd_readsingle(priv, buffer, startblock); + } + else + { + ret = mmcsd_readmultiple(priv, buffer, startblock, nblocks); + } + + /* On success, return the number of blocks read */ + + return ret; +} +#endif + +/**************************************************************************** + * Name: mmcsd_writesingle + * + * Description: + * Write a single block of data to the physical device. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t mmcsd_writesingle(FAR struct mmcsd_state_s *priv, + FAR const uint8_t *buffer, off_t startblock) +{ + off_t offset; + int ret; + + fvdbg("startblock=%d\n", startblock); + DEBUGASSERT(priv != NULL && buffer != NULL); + + /* Check if the card is locked or write protected (either via software or + * via the mechanical write protect on the card) + */ + + if (mmcsd_wrprotected(priv)) + { + fdbg("ERROR: Card is locked or write protected\n"); + return -EPERM; + } + + /* Verify that the card is ready for the transfer. The card may still be + * busy from the preceding write transfer. It would be simpler to check + * for write busy at the end of each write, rather than at the beginning of + * each read AND write, but putting the busy-wait at the beginning of the + * transfer allows for more overlap and, hopefully, better performance + */ + + ret = mmcsd_transferready(priv); + if (ret != OK) + { + fdbg("ERROR: Card not ready: %d\n", ret); + return ret; + } + + /* If this is a byte addressed SD card, then convert sector start sector + * number to a byte offset + */ + + if (IS_BLOCK(priv->type)) + { + offset = startblock; + } + else + { + offset = startblock << priv->blockshift; + } + fvdbg("offset=%d\n", offset); + + /* Select the block size for the card */ + + ret = mmcsd_setblocklen(priv, priv->blocksize); + if (ret != OK) + { + fdbg("ERROR: mmcsd_setblocklen failed: %d\n", ret); + return ret; + } + + /* Send CMD24, WRITE_BLOCK, and verify that good R1 status is returned */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD24, offset); + ret = mmcsd_recvR1(priv, MMCSD_CMD24); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for CMD24 failed: %d\n", ret); + return ret; + } + + /* Configure SDIO controller hardware for the write transfer */ + + SDIO_BLOCKSETUP(priv->dev, priv->blocksize, 1); + SDIO_WAITENABLE(priv->dev, SDIOWAIT_TRANSFERDONE|SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR); +#ifdef CONFIG_SDIO_DMA + if (priv->dma) + { + SDIO_DMASENDSETUP(priv->dev, buffer, priv->blocksize); + } + else +#endif + { + SDIO_SENDSETUP(priv->dev, buffer, priv->blocksize); + } + + /* Flag that a write transfer is pending that we will have to check for + * write complete at the beginning of the next transfer. + */ + + priv->wrbusy = true; + + /* Wait for the transfer to complete */ + + ret = mmcsd_eventwait(priv, SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR, MMCSD_BLOCK_DATADELAY); + if (ret != OK) + { + fdbg("ERROR: CMD24 transfer failed: %d\n", ret); + return ret; + } + + /* On success, return the number of blocks written */ + + return 1; +} +#endif + +/**************************************************************************** + * Name: mmcsd_writemultiple + * + * Description: + * Write multiple, contiguous blocks of data to the physical device. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t mmcsd_writemultiple(FAR struct mmcsd_state_s *priv, + FAR const uint8_t *buffer, off_t startblock, + size_t nblocks) +{ + off_t offset; + size_t nbytes; + int ret; + + fvdbg("startblockr=%d nblocks=%d\n", startblock, nblocks); + DEBUGASSERT(priv != NULL && buffer != NULL && nblocks > 1); + + /* Check if the card is locked or write protected (either via software or + * via the mechanical write protect on the card) + */ + + if (mmcsd_wrprotected(priv)) + { + fdbg("ERROR: Card is locked or write protected\n"); + return -EPERM; + } + + /* Verify that the card is ready for the transfer. The card may still be + * busy from the preceding write transfer. It would be simpler to check + * for write busy at the end of each write, rather than at the beginning of + * each read AND write, but putting the busy-wait at the beginning of the + * transfer allows for more overlap and, hopefully, better performance + */ + + ret = mmcsd_transferready(priv); + if (ret != OK) + { + fdbg("ERROR: Card not ready: %d\n", ret); + return ret; + } + + /* If this is a byte addressed SD card, then convert both the total transfer + * size to bytes and the sector start sector number to a byte offset + */ + + nbytes = nblocks << priv->blockshift; + if (IS_BLOCK(priv->type)) + { + offset = startblock; + } + else + { + offset = startblock << priv->blockshift; + } + fvdbg("nbytes=%d byte offset=%d\n", nbytes, offset); + + /* Select the block size for the card */ + + ret = mmcsd_setblocklen(priv, priv->blocksize); + if (ret != OK) + { + fdbg("ERROR: mmcsd_setblocklen failed: %d\n", ret); + return ret; + } + + /* If this is an SD card, then send ACMD23 (SET_WR_BLK_COUNT) just before + * sending CMD25 (WRITE_MULTIPLE_BLOCK). This sets the number of write + * blocks to be pre-erased and might make the following multiple block write + * command faster. + */ + + if (IS_SD(priv->type)) + { + /* Send CMD55, APP_CMD, a verify that good R1 status is retured */ + + mmcsd_sendcmdpoll(priv, SD_CMD55, 0); + ret = mmcsd_recvR1(priv, SD_CMD55); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for CMD55 (ACMD23) failed: %d\n", ret); + return ret; + } + + /* Send CMD23, SET_WR_BLK_COUNT, and verify that good R1 status is returned */ + + mmcsd_sendcmdpoll(priv, SD_ACMD23, 0); + ret = mmcsd_recvR1(priv, SD_ACMD23); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for ACMD23 failed: %d\n", ret); + return ret; + } + } + + /* Send CMD25, WRITE_MULTIPLE_BLOCK, and verify that good R1 status + * is returned + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD25, offset); + ret = mmcsd_recvR1(priv, MMCSD_CMD25); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for CMD24 failed: %d\n", ret); + return ret; + } + + /* Configure SDIO controller hardware for the write transfer */ + + SDIO_BLOCKSETUP(priv->dev, priv->blocksize, nblocks); + SDIO_WAITENABLE(priv->dev, SDIOWAIT_TRANSFERDONE|SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR); +#ifdef CONFIG_SDIO_DMA + if (priv->dma) + { + SDIO_DMASENDSETUP(priv->dev, buffer, nbytes); + } + else +#endif + { + SDIO_SENDSETUP(priv->dev, buffer, nbytes); + } + + /* Flag that a write transfer is pending that we will have to check for + * write complete at the beginning of the next transfer. + */ + + priv->wrbusy = true; + + /* Wait for the transfer to complete */ + + ret = mmcsd_eventwait(priv, SDIOWAIT_TIMEOUT|SDIOWAIT_ERROR, nblocks * MMCSD_BLOCK_DATADELAY); + if (ret != OK) + { + fdbg("ERROR: CMD18 transfer failed: %d\n", ret); + return ret; + } + + /* Send STOP_TRANSMISSION */ + + ret = mmcsd_stoptransmission(priv); + if (ret != OK) + { + fdbg("ERROR: mmcsd_stoptransmission failed: %d\n", ret); + return ret; + } + + /* On success, return the number of blocks read */ + + return nblocks; +} +#endif + +/**************************************************************************** + * Name: mmcsd_flush + * + * Description: + * Flush the specified number of sectors from the write buffer to the card. + * + ****************************************************************************/ + +#if defined(CONFIG_FS_WRITABLE) && defined(CONFIG_FS_WRITEBUFFER) +static ssize_t mmcsd_flush(FAR void *dev, FAR const uint8_t *buffer, + off_t startblock, size_t nblocks) +{ + FAR struct mmcsd_state_s *priv = (FAR struct mmcsd_state_s *)dev; + ssize_t ret; + + DEBUGASSERT(priv != NULL && buffer != NULL && nblocks > 0) + + if (nblocks == 1) + { + ret = mmcsd_writesingle(priv, buffer, startblock); + } + else + { + ret = mmcsd_writemultiple(priv, buffer, startblock, nblocks); + } + + /* On success, return the number of blocks written */ + + return ret; +} +#endif + +/**************************************************************************** + * Block Driver Methods + ****************************************************************************/ +/**************************************************************************** + * Name: mmcsd_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int mmcsd_open(FAR struct inode *inode) +{ + FAR struct mmcsd_state_s *priv; + + fvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct mmcsd_state_s *)inode->i_private; + + /* Just increment the reference count on the driver */ + + DEBUGASSERT(priv->crefs < MAX_CREFS); + mmcsd_takesem(priv); + priv->crefs++; + mmcsd_givesem(priv); + return OK; +} + +/**************************************************************************** + * Name: mmcsd_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int mmcsd_close(FAR struct inode *inode) +{ + FAR struct mmcsd_state_s *priv; + + fvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct mmcsd_state_s *)inode->i_private; + + /* Decrement the reference count on the block driver */ + + DEBUGASSERT(priv->crefs > 0); + mmcsd_takesem(priv); + priv->crefs--; + mmcsd_givesem(priv); + return OK; +} + +/**************************************************************************** + * Name: mmcsd_read + * + * Description: + * Read the specified numer of sectors from the read-ahead buffer or from + * the physical device. + * + ****************************************************************************/ + +static ssize_t mmcsd_read(FAR struct inode *inode, unsigned char *buffer, + size_t startsector, unsigned int nsectors) +{ + FAR struct mmcsd_state_s *priv; + ssize_t ret = 0; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct mmcsd_state_s *)inode->i_private; + fvdbg("startsector: %d nsectors: %d sectorsize: %d\n", + startsector, nsectors, priv->blocksize); + + if (nsectors > 0) + { + mmcsd_takesem(priv); +#ifdef CONFIG_FS_READAHEAD + ret = rwb_read(&priv->rwbuffer, startsector, nsectors, buffer); +#else + if (nsectors == 1) + { + ret = mmcsd_readsingle(priv, buffer, startsector); + } + else + { + ret = mmcsd_readmultiple(priv, buffer, startsector, nsectors); + } +#endif + mmcsd_givesem(priv); + } + + /* On success, return the number of blocks read */ + + return ret; +} + +/**************************************************************************** + * Name: mmcsd_write + * + * Description: + * Write the specified number of sectors to the write buffer or to the + * physical device. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t mmcsd_write(FAR struct inode *inode, const unsigned char *buffer, + size_t startsector, unsigned int nsectors) +{ + FAR struct mmcsd_state_s *priv; + int ret; + + fvdbg("sector: %d nsectors: %d sectorsize: %d\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct mmcsd_state_s *)inode->i_private; + + mmcsd_takesem(priv); +#ifdef CONFIG_FS_WRITEBUFFER + ret = rwb_write(&priv->rwbuffer, startsector, nsectors, buffer); +#else + if (nsectors == 1) + { + ret = mmcsd_writesingle(priv, buffer, startsector); + } + else + { + ret = mmcsd_writemultiple(priv, buffer, startsector, nsectors); + } +#endif + mmcsd_givesem(priv); + + /* On success, return the number of blocks written */ + + return ret; +} +#endif + +/**************************************************************************** + * Name: mmcsd_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int mmcsd_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + FAR struct mmcsd_state_s *priv; + int ret = -EINVAL; + + fvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + if (geometry) + { + /* Is there a (supported) card inserted in the slot? */ + + priv = (FAR struct mmcsd_state_s *)inode->i_private; + mmcsd_takesem(priv); + if (IS_EMPTY(priv)) + { + /* No.. return ENODEV */ + + fvdbg("IS_EMPTY\n"); + ret = -ENODEV; + } + else + { + /* Yes.. return the geometry of the card */ + + geometry->geo_available = true; + geometry->geo_mediachanged = priv->mediachanged; +#ifdef CONFIG_FS_WRITABLE + geometry->geo_writeenabled = !mmcsd_wrprotected(priv); +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = priv->nblocks; + geometry->geo_sectorsize = priv->blocksize; + + fvdbg("available: true mediachanged: %s writeenabled: %s\n", + geometry->geo_mediachanged ? "true" : "false", + geometry->geo_writeenabled ? "true" : "false"); + fvdbg("nsectors: %ld sectorsize: %d\n", + (long)geometry->geo_nsectors, geometry->geo_sectorsize); + + priv->mediachanged = false; + ret = OK; + } + mmcsd_givesem(priv); + } + + return ret; +} + +/**************************************************************************** + * Name: mmcsd_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int mmcsd_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) +{ + FAR struct mmcsd_state_s *priv; + int ret; + + fvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct mmcsd_state_s *)inode->i_private; + + /* Process the IOCTL by command */ + + mmcsd_takesem(priv); + switch (cmd) + { + case BIOC_PROBE: /* Check for media in the slot */ + { + fvdbg("BIOC_PROBE\n"); + + /* Probe the MMC/SD slot for media */ + + ret = mmcsd_probe(priv); + if (ret != OK) + { + fdbg("ERROR: mmcsd_probe failed: %d\n", ret); + } + } + break; + + case BIOC_EJECT: /* Media has been removed from the slot */ + { + fvdbg("BIOC_EJECT\n"); + + /* Process the removal of the card */ + + ret = mmcsd_removed(priv); + if (ret != OK) + { + fdbg("ERROR: mmcsd_removed failed: %d\n", ret); + } + + /* Enable logic to detect if a card is re-inserted */ + + SDIO_CALLBACKENABLE(priv->dev, SDIOMEDIA_INSERTED); + } + break; + + default: + ret = -ENOTTY; + break; + } + + mmcsd_givesem(priv); + return ret; +} + +/**************************************************************************** + * Initialization/uninitialization/reset + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_mediachange + * + * Description: + * This is a callback function from the SDIO driver that indicates that + * there has been a change in the slot... either a card has been inserted + * or a card has been removed. + * + * Assumptions: + * This callback is NOT supposd to run in the context of an interrupt + * handler; it is probably running in the context of work thread. + * + ****************************************************************************/ + +static void mmcsd_mediachange(FAR void *arg) +{ + FAR struct mmcsd_state_s *priv = (FAR struct mmcsd_state_s *)arg; + + fvdbg("arg: %p\n", arg); + DEBUGASSERT(priv); + + /* Is there a card present in the slot? */ + + mmcsd_takesem(priv); + if (SDIO_PRESENT(priv->dev)) + { + /* No... process the card insertion. This could cause chaos if we think + * that a card is already present and there are mounted filesystems! + * NOTE that mmcsd_probe() will always re-enable callbacks appropriately. + */ + + (void)mmcsd_probe(priv); + } + else + { + /* No... process the card removal. This could have very bad implications + * for any mounted file systems! NOTE that mmcsd_removed() does NOT + * re-enable callbacks so we will need to do that here. + */ + + (void)mmcsd_removed(priv); + + /* Enable logic to detect if a card is re-inserted */ + + SDIO_CALLBACKENABLE(priv->dev, SDIOMEDIA_INSERTED); + } + mmcsd_givesem(priv); +} + +/**************************************************************************** + * Name: mmcsd_widebus + * + * Description: + * An SD card has been inserted and its SCR has been obtained. Select wide + * (4-bit) bus operation if the card supports it. + * + * Assumptions: + * This function is called only once per card insertion as part of the SD + * card initialization sequence. It is not necessary to reselect the card + * there is not need to check if wide bus operation has already been + * selected. + * + ****************************************************************************/ + +static int mmcsd_widebus(FAR struct mmcsd_state_s *priv) +{ +#ifndef CONFIG_SDIO_WIDTH_D1_ONLY + int ret; + + /* Check if the SD card supports this feature (as reported in the SCR) */ + + if ((priv->buswidth & MMCSD_SCR_BUSWIDTH_4BIT) != 0) + { + /* Disconnect any CD/DAT3 pull up using ACMD42. ACMD42 is optional and + * need not be supported by all SD calls. + * + * First end CMD55 APP_CMD with argument as card's RCA. + */ + + mmcsd_sendcmdpoll(priv, SD_CMD55, (uint32_t)priv->rca << 16); + ret = mmcsd_recvR1(priv, SD_CMD55); + if (ret != OK) + { + fdbg("ERROR: RECVR1 for CMD55 of ACMD42: %d\n", ret); + return ret; + } + + /* Then send ACMD42 with the argument to disconnect the CD/DAT3 + * pullup + */ + + mmcsd_sendcmdpoll(priv, SD_ACMD42, MMCSD_ACMD42_CD_DISCONNECT); + ret = mmcsd_recvR1(priv, SD_ACMD42); + if (ret != OK) + { + fvdbg("WARNING: SD card does not support ACMD42: %d\n", ret); + return ret; + } + + /* Now send ACMD6 to select wide, 4-bit bus operation, beginning + * with CMD55, APP_CMD: + */ + + mmcsd_sendcmdpoll(priv, SD_CMD55, (uint32_t)priv->rca << 16); + ret = mmcsd_recvR1(priv, SD_CMD55); + if (ret != OK) + { + fdbg("ERROR: RECVR1 for CMD55 of ACMD6: %d\n", ret); + return ret; + } + + /* Then send ACMD6 */ + + mmcsd_sendcmdpoll(priv, SD_ACMD6, MMCSD_ACMD6_BUSWIDTH_4); + ret = mmcsd_recvR1(priv, SD_ACMD6); + if (ret != OK) + { + return ret; + } + + /* Configure the SDIO peripheral */ + + fvdbg("Wide bus operation selected\n"); + SDIO_WIDEBUS(priv->dev, true); + priv->widebus = true; + + SDIO_CLOCK(priv->dev, CLOCK_SD_TRANSFER_4BIT); + up_udelay(MMCSD_CLK_DELAY); + return OK; + } + + /* Wide bus operation not supported */ + + fdbg("WARNING: Card does not support wide-bus operation\n"); + return -ENOSYS; + +#else /* CONFIG_SDIO_WIDTH_D1_ONLY */ + + fvdbg("Wide-bus operation is disabled\n"); + return -ENOSYS; + +#endif /* CONFIG_SDIO_WIDTH_D1_ONLY */ +} + +/**************************************************************************** + * Name: mmcsd_mmcinitialize + * + * Description: + * We believe that there is an MMC card in the slot. Attempt to initialize + * and configure the MMC card. This is called only from mmcsd_probe(). + * + ****************************************************************************/ + +#ifdef CONFIG_MMCSD_MMCSUPPORT +static int mmcsd_mmcinitialize(FAR struct mmcsd_state_s *priv) +{ + uint32_t cid[4]; + uint32_t csd[4]; + int ret; + + /* At this point, slow, ID mode clocking has been supplied to the card + * and CMD0 has been sent successfully. CMD1 succeeded and ACMD41 failed + * so there is good evidence that we have an MMC card inserted into the + * slot. + * + * Send CMD2, ALL_SEND_CID. This implementation supports only one MMC slot. + * If mulitple cards were installed, each card would respond to CMD2 by + * sending its CID (only one card completes the response at a time). The + * driver should send CMD2 and assign an RCAs until no response to + * ALL_SEND_CID is received. CMD2 causes transition to identification state/ + * card-identification mode */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD2, 0); + ret = SDIO_RECVR2(priv->dev, MMCSD_CMD2, cid); + if (ret != OK) + { + fdbg("ERROR: SDIO_RECVR2 for MMC CID failed: %d\n", ret); + return ret; + } + mmcsd_decodeCID(priv, cid); + + /* Send CMD3, SET_RELATIVE_ADDR. This command is used to assign a logical + * address to the card. For MMC, the host assigns the address. CMD3 causes + * transition to standby state/data-transfer mode + */ + + priv->rca = 1; /* There is only one card */ + mmcsd_sendcmdpoll(priv, MMC_CMD3, priv->rca << 16); + ret = mmcsd_recvR1(priv, MMC_CMD3); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1(CMD3) failed: %d\n", ret); + return ret; + } + + /* This should have caused a transition to standby state. However, this will + * not be reflected in the present R1 status. R1/6 contains the state of the + * card when the command was received, not when it completed execution. + * + * Verify that we are in standby state/data-transfer mode + */ + + ret = mmcsd_verifystate(priv, MMCSD_R1_STATE_STBY); + if (ret != OK) + { + fdbg("ERROR: Failed to enter standby state\n"); + return ret; + } + + /* Send CMD9, SEND_CSD in standby state/data-transfer mode to obtain the + * Card Specific Data (CSD) register, e.g., block length, card storage + * capacity, etc. (Stays in standy state/data-transfer mode) + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD9, priv->rca << 16); + ret = SDIO_RECVR2(priv->dev, MMCSD_CMD9, csd); + if (ret != OK) + { + fdbg("ERROR: Could not get SD CSD register: %d\n", ret); + return ret; + } + mmcsd_decodeCSD(priv, csd); + + /* Set the Driver Stage Register (DSR) if (1) a CONFIG_MMCSD_DSR has been + * provided and (2) the card supports a DSR register. If no DSR value + * the card default value (0x0404) will be used. + */ + + (void)mmcsd_sendcmd4(priv); + + /* Select high speed MMC clocking (which may depend on the DSR setting) */ + + SDIO_CLOCK(priv->dev, CLOCK_MMC_TRANSFER); + up_udelay(MMCSD_CLK_DELAY); + return OK; +} +#endif + +/**************************************************************************** + * Name: mmcsd_sdinitialize + * + * Description: + * We believe that there is an SD card in the slot. Attempt to initialize + * and configure the SD card. This is called only from mmcsd_probe(). + * + ****************************************************************************/ + +static int mmcsd_sdinitialize(FAR struct mmcsd_state_s *priv) +{ + uint32_t cid[4]; + uint32_t csd[4]; + uint32_t scr[2]; + int ret; + + /* At this point, clocking has been supplied to the card, both CMD0 and + * ACMD41 (with OCR=0) have been sent successfully, the card is no longer + * busy and (presumably) in the IDLE state so there is good evidence + * that we have an SD card inserted into the slot. + * + * Send CMD2, ALL_SEND_CID. The SD CMD2 is similar to the MMC CMD2 except + * that the buffer type used to transmit to response of the card (SD Memory + * Card: Push-Pull, MMC: Open-Drain). This implementation supports only a + * single SD card. If multiple cards were installed in the slot, each card + * would respond to CMD2 by sending its CID (only one card completes the + * response at a time). The driver should send CMD2 and obtain RCAs until + * no response to ALL_SEND_CID is received. + * + * When an SD card receives the CMD2 command it should transition to the + * identification state/card-identification mode + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD2, 0); + ret = SDIO_RECVR2(priv->dev, MMCSD_CMD2, cid); + if (ret != OK) + { + fdbg("ERROR: SDIO_RECVR2 for SD CID failed: %d\n", ret); + return ret; + } + mmcsd_decodeCID(priv, cid); + + /* Send CMD3, SET_RELATIVE_ADDR. In both protocols, this command is used + * to assign a logical address to the card. For MMC, the host assigns the + * address; for SD, the memory card has this responsibility. CMD3 causes + * transition to standby state/data-transfer mode + * + * Send CMD3 with argument 0, SD card publishes its RCA in the response. + */ + + mmcsd_sendcmdpoll(priv, SD_CMD3, 0); + ret = mmcsd_recvR6(priv, SD_CMD3); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR2 for SD RCA failed: %d\n", ret); + return ret; + } + fvdbg("RCA: %04x\n", priv->rca); + + /* This should have caused a transition to standby state. However, this will + * not be reflected in the present R1 status. R1/6 contains the state of + * the card when the command was received, not when it completed execution. + * + * Verify that we are in standby state/data-transfer mode + */ + + ret = mmcsd_verifystate(priv, MMCSD_R1_STATE_STBY); + if (ret != OK) + { + fdbg("ERROR: Failed to enter standby state\n"); + return ret; + } + + /* Send CMD9, SEND_CSD, in standby state/data-transfer mode to obtain the + * Card Specific Data (CSD) register. The argument is the RCA that we + * just obtained from CMD3. The card stays in standy state/data-transfer + * mode. + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD9, (uint32_t)priv->rca << 16); + ret = SDIO_RECVR2(priv->dev, MMCSD_CMD9, csd); + if (ret != OK) + { + fdbg("ERROR: Could not get SD CSD register(%d)\n", ret); + return ret; + } + mmcsd_decodeCSD(priv, csd); + + /* Send CMD7 with the argument == RCA in order to select the card. + * Since we are supporting only a single card, we just leave the + * card selected all of the time. + */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD7S, (uint32_t)priv->rca << 16); + ret = mmcsd_recvR1(priv, MMCSD_CMD7S); + if (ret != OK) + { + fdbg("ERROR: mmcsd_recvR1 for CMD7 failed: %d\n", ret); + return ret; + } + + /* Set the Driver Stage Register (DSR) if (1) a CONFIG_MMCSD_DSR has been + * provided and (2) the card supports a DSR register. If no DSR value + * the card default value (0x0404) will be used. + */ + + (void)mmcsd_sendcmd4(priv); + + /* Select high speed SD clocking (which may depend on the DSR setting) */ + + SDIO_CLOCK(priv->dev, CLOCK_SD_TRANSFER_1BIT); + up_udelay(MMCSD_CLK_DELAY); + + /* Get the SD card Configuration Register (SCR). We need this now because + * that configuration register contains the indication whether or not + * this card supports wide bus operation. + */ + + ret = mmcsd_getSCR(priv, scr); + if (ret != OK) + { + fdbg("ERROR: Could not get SD SCR register(%d)\n", ret); + return ret; + } + mmcsd_decodeSCR(priv, scr); + + /* Select width (4-bit) bus operation (if the card supports it) */ + + ret = mmcsd_widebus(priv); + if (ret != OK) + { + fdbg("WARN: Failed to set wide bus operation: %d\n", ret); + } + + /* TODO: If widebus selected, then send CMD6 to see if the card supports + * high speed mode. A new SDIO method will be needed to set high speed + * mode. + */ + + return OK; +} + +/**************************************************************************** + * Name: mmcsd_cardidentify + * + * Description: + * We believe that there is media in the slot. Attempt to initialize and + * configure the card. This is called only from mmcsd_probe(). + * + ****************************************************************************/ + +static int mmcsd_cardidentify(FAR struct mmcsd_state_s *priv) +{ + uint32_t response; + uint32_t start; + uint32_t elapsed; + uint32_t sdcapacity = MMCSD_ACMD41_STDCAPACITY; + int ret; + + /* Assume failure to identify the card */ + + priv->type = MMCSD_CARDTYPE_UNKNOWN; + + /* Check if there is a card present in the slot. This is normally a matter is + * of GPIO sensing. + */ + + if (!SDIO_PRESENT(priv->dev)) + { + fvdbg("No card present\n"); + return -ENODEV; + } + + /* Set ID mode clocking (<400KHz) */ + + SDIO_CLOCK(priv->dev, CLOCK_IDMODE); + + /* After power up at least 74 clock cycles are required prior to starting bus + * communication + */ + + up_udelay(MMCSD_POWERUP_DELAY); + + /* Then send CMD0 (twice just to be sure) */ + + mmcsd_sendcmdpoll(priv, MMCSD_CMD0, 0); + mmcsd_sendcmdpoll(priv, MMCSD_CMD0, 0); + up_udelay(MMCSD_IDLE_DELAY); + + /* Check for SDHC Version 2.x. Send CMD8 to verify SD card interface + * operating condition. CMD 8 is reserved on SD version 1.0 and MMC. + * + * CMD8 Argument: + * [31:12]: Reserved (shall be set to '0')
* [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V) + * [7:0]: Check Pattern (recommended 0xaa) + * CMD8 Response: R7 + */ + + ret = mmcsd_sendcmdpoll(priv, SD_CMD8, MMCSD_CMD8CHECKPATTERN|MMCSD_CMD8VOLTAGE_27); + if (ret == OK) + { + /* CMD8 was sent successfully... Get the R7 response */ + + ret = SDIO_RECVR7(priv->dev, SD_CMD8, &response); + } + + /* Were both the command sent and response received correctly? */ + + if (ret == OK) + { + /* CMD8 succeeded this is probably a SDHC card. Verify the operating + * voltage and that the check pattern was correctly echoed + */ + + if (((response & MMCSD_R7VOLTAGE_MASK) == MMCSD_R7VOLTAGE_27) && + ((response & MMCSD_R7ECHO_MASK) == MMCSD_R7CHECKPATTERN)) + { + fvdbg("SD V2.x card\n"); + priv->type = MMCSD_CARDTYPE_SDV2; + sdcapacity = MMCSD_ACMD41_HIGHCAPACITY; + } + else + { + fdbg("ERROR: R7: %08x\n", response); + return -EIO; + } + } + + /* At this point, type is either UNKNOWN or SDV2. Try sending + * CMD55 and (maybe) ACMD41 for up to 1 second or until the card + * exits the IDLE state. CMD55 is supported by SD V1.x and SD V2.x, + * but not MMC + */ + + start = clock_systimer(); + elapsed = 0; + do + { + /* We may have already determined that his card is an MMC card from + * an earlier pass through through this loop. In that case, we should + * skip the SD-specific commands. + */ + +#ifdef CONFIG_MMCSD_MMCSUPPORT + if (priv->type != MMCSD_CARDTYPE_MMC) +#endif + { + /* Send CMD55 with argument = 0 */ + + mmcsd_sendcmdpoll(priv, SD_CMD55, 0); + ret = mmcsd_recvR1(priv, SD_CMD55); + if (ret != OK) + { + /* I am a little confused.. I think both SD and MMC cards support + * CMD55 (but maybe only SD cards support CMD55). We'll make the + * the MMC vs. SD decision based on CMD1 and ACMD41. + */ + + fdbg("ERROR: mmcsd_recvR1(CMD55) failed: %d\n", ret); + } + else + { + /* Send ACMD41 */ + + mmcsd_sendcmdpoll(priv, SD_ACMD41, MMCSD_ACMD41_VOLTAGEWINDOW|sdcapacity); + ret = SDIO_RECVR3(priv->dev, SD_ACMD41, &response); + if (ret != OK) + { + /* If the error is a timeout, then it is probably an MMC card, + * but we will make the decision based on CMD1 below + */ + + fdbg("ERROR: ACMD41 RECVR3: %d\n", ret); + } + else + { + /* ACMD41 succeeded. ACMD41 is supported by SD V1.x and SD V2.x, + * but not MMC. If we did not previously determine that this is + * an SD V2.x (via CMD8), then this must be SD V1.x + */ + + fvdbg("R3: %08x\n", response); + if (priv->type == MMCSD_CARDTYPE_UNKNOWN) + { + fvdbg("SD V1.x card\n"); + priv->type = MMCSD_CARDTYPE_SDV1; + } + + /* Check if the card is busy. Very confusing, BUSY is set LOW + * if the card has not finished its initialization, so it really + * means NOT busy. + */ + + if ((response & MMCSD_CARD_BUSY) != 0) + { + /* No.. We really should check the current state to see if + * the SD card successfully made it to the IDLE state, but + * at least for now, we will simply assume that that is the + * case. + * + * Now, check if this is a SD V2.x card that supports block + * addressing + */ + + if ((response & MMCSD_R3_HIGHCAPACITY) != 0) + { + fvdbg("SD V2.x card with block addressing\n"); + DEBUGASSERT(priv->type == MMCSD_CARDTYPE_SDV2); + priv->type |= MMCSD_CARDTYPE_BLOCK; + } + + /* And break out of the loop with an SD card identified */ + + break; + } + } + } + } + + /* If we get here then either (1) CMD55 failed, (2) CMD41 failed, or (3) + * and SD or MMC card has been identified, but it is not yet in the IDLE state. + * If SD card has not been identified, then we might be looking at an + * MMC card. We can send the CMD1 to find out for sure. CMD1 is supported + * by MMC cards, but not by SD cards. + */ +#ifdef CONFIG_MMCSD_MMCSUPPORT + if (priv->type == MMCSD_CARDTYPE_UNKNOWN || priv->type == MMCSD_CARDTYPE_MMC) + { + /* Send the MMC CMD1 to specify the operating voltage. CMD1 causes + * transition to ready state/ card-identification mode. NOTE: If the + * card does not support this voltage range, it will go the inactive + * state. + * + * NOTE: An MMC card will only respond once to CMD1 (unless it is busy). + * This is part of the logic used to determine how many MMC cards are + * connected (This implementation supports only a single MMC card). So + * we cannot re-send CMD1 without first placing the card back into + * stand-by state (if the card is busy, it will automatically + * go back to the the standby state). + */ + + mmcsd_sendcmdpoll(priv, MMC_CMD1, MMCSD_VDD_33_34); + ret = SDIO_RECVR3(priv->dev, MMC_CMD1, &response); + + /* Was the operating range set successfully */ + + if (ret != OK) + { + fdbg("ERROR: CMD1 RECVR3: %d\n", ret); + } + else + { + /* CMD1 succeeded... this must be an MMC card */ + + fdbg("CMD1 succeeded, assuming MMC card\n"); + priv->type = MMCSD_CARDTYPE_MMC; + + /* Check if the card is busy. Very confusing, BUSY is set LOW + * if the card has not finished its initialization, so it really + * means NOT busy. + */ + + if ((response & MMCSD_CARD_BUSY) != 0) + { + /* NO.. We really should check the current state to see if the + * MMC successfully made it to the IDLE state, but at least for now, + * we will simply assume that that is the case. + * + * Then break out of the look with an MMC card identified + */ + + break; + } + } + } +#endif + /* Check the elapsed time. We won't keep trying this forever! */ + + elapsed = clock_systimer() - start; + } + while( elapsed < TICK_PER_SEC ); /* On successful reception while 'breaks', see above. */ + + /* We get here when the above loop completes, either (1) we could not + * communicate properly with the card due to errors (and the loop times + * out), or (3) it is an MMC or SD card that has successfully transitioned + * to the IDLE state (well, at least, it provided its OCR saying that it + * it is no longer busy). + */ + + if (elapsed >= TICK_PER_SEC || priv->type == MMCSD_CARDTYPE_UNKNOWN) + { + fdbg("ERROR: Failed to identify card\n"); + return -EIO; + } + + return OK; +} + +/**************************************************************************** + * Name: mmcsd_probe + * + * Description: + * Check for media inserted in a slot. Called (1) during initialization to + * see if there was a card in the slot at power up, (2) when/if a media + * insertion event occurs, or (3) if the BIOC_PROBE ioctl command is + * received. + * + ****************************************************************************/ + +static int mmcsd_probe(FAR struct mmcsd_state_s *priv) +{ + int ret; + + fvdbg("type: %d probed: %d\n", priv->type, priv->probed); + + /* If we have reliable card detection events and if we have + * already probed the card, then we don't need to do anything + * else + */ + +#ifdef CONFIG_MMCSD_HAVECARDDETECT + if (priv->probed && SDIO_PRESENT(priv->dev)) + { + return OK; + } +#endif + + /* Otherwise, we are going to probe the card. There are lots of + * possibilities here: We may think that there is a card in the slot, + * or not. There may be a card in the slot, or not. If there is + * card in the slot, perhaps it is a different card than we one we + * think is there? The safest thing to do is to process the card + * removal first and start from known place. + */ + + mmcsd_removed(priv); + + /* Now.. is there a card in the slot? */ + + if (SDIO_PRESENT(priv->dev)) + { + /* Yes.. probe it. First, what kind of card was inserted? */ + + ret = mmcsd_cardidentify(priv); + if (ret != OK) + { + fdbg("ERROR: Failed to initialize card: %d\n", ret); +#ifdef CONFIG_MMCSD_HAVECARDDETECT + SDIO_CALLBACKENABLE(priv->dev, SDIOMEDIA_INSERTED); +#endif + } + else + { + /* Then initialize the driver according to the identified card type */ + + switch (priv->type) + { + case MMCSD_CARDTYPE_SDV1: /* Bit 1: SD version 1.x */ + case MMCSD_CARDTYPE_SDV2: /* SD version 2.x with byte addressing */ + case MMCSD_CARDTYPE_SDV2|MMCSD_CARDTYPE_BLOCK: /* SD version 2.x with block addressing */ + ret = mmcsd_sdinitialize(priv); + break; + + case MMCSD_CARDTYPE_MMC: /* MMC card */ +#ifdef CONFIG_MMCSD_MMCSUPPORT + ret = mmcsd_mmcinitialize(priv); + break; +#endif + case MMCSD_CARDTYPE_UNKNOWN: /* Unknown card type */ + default: + fdbg("ERROR: Internal confusion: %d\n", priv->type); + ret = -EPERM; + break; + }; + + /* Was the card configured successfully? */ + + if (ret == OK) + { + /* Yes... */ + + fvdbg("Capacity: %lu Kbytes\n", (unsigned long)(priv->capacity / 1024)); + priv->mediachanged = true; + + /* Set up to receive asynchronous, media removal events */ + + SDIO_CALLBACKENABLE(priv->dev, SDIOMEDIA_EJECTED); + } + } + + /* In any event, we have probed this card */ + + priv->probed = true; + } + else + { + /* There is no card in the slot */ + + fvdbg("No card\n"); +#ifdef CONFIG_MMCSD_HAVECARDDETECT + SDIO_CALLBACKENABLE(priv->dev, SDIOMEDIA_INSERTED); +#endif + ret = -ENODEV; + } + + return ret; +} + +/**************************************************************************** + * Name: mmcsd_removed + * + * Description: + * Disable support for media in the slot. Called (1) when/if a media + * removal event occurs, or (2) if the BIOC_EJECT ioctl command is + * received. + * + ****************************************************************************/ + +static int mmcsd_removed(FAR struct mmcsd_state_s *priv) +{ + fvdbg("type: %d present: %d\n", priv->type, SDIO_PRESENT(priv->dev)); + + /* Forget the card geometry, pretend the slot is empty (it might not + * be), and that the card has never been initialized. + */ + + priv->capacity = 0; /* Capacity=0 sometimes means no media */ + priv->blocksize = 0; + priv->mediachanged = false; + priv->type = MMCSD_CARDTYPE_UNKNOWN; + priv->probed = false; + priv->rca = 0; + priv->selblocklen = 0; + + /* Go back to the default 1-bit data bus. */ + + SDIO_WIDEBUS(priv->dev, false); + priv->widebus = false; + + /* Disable clocking to the card */ + + (void)SDIO_CLOCK(priv->dev, CLOCK_SDIO_DISABLED); + return OK; +} + +/**************************************************************************** + * Name: mmcsd_hwinitialize + * + * Description: + * One-time hardware initialization. Called only from sdio_slotinitialize. + * + ****************************************************************************/ + +static int mmcsd_hwinitialize(FAR struct mmcsd_state_s *priv) +{ + int ret; + + mmcsd_takesem(priv); + +#ifdef CONFIG_SDIO_DMA + /* Does this architecture support DMA with the MMC/SD device? */ + + priv->dma = SDIO_DMASUPPORTED(priv->dev); + fvdbg("DMA supported: %d\n", priv->dma); +#endif + + /* Attach and prepare MMC/SD interrupts */ + + if (SDIO_ATTACH(priv->dev)) + { + fdbg("ERROR: Unable to attach MMC/SD interrupts\n"); + mmcsd_givesem(priv); + return -EBUSY; + } + fvdbg("Attached MMC/SD interrupts\n"); + + /* Register a callback so that we get informed if media is inserted or + * removed from the slot (Initially all callbacks are disabled). + */ + + SDIO_REGISTERCALLBACK(priv->dev, mmcsd_mediachange, (FAR void *)priv); + + /* Is there a card in the slot now? For an MMC/SD card, there are three + * possible card detect mechanisms: + * + * 1. Mechanical insertion that can be detected using the WP switch + * that is closed when a card is inserted into then SD slot (SD + * "hot insertion capable" card conector only) + * 2. Electrical insertion that can be sensed using the pull-up resistor + * on CD/DAT3 (both SD/MMC), + * 3. Or by periodic attempts to initialize the card from software. + * + * The behavior of SDIO_PRESENT() is to use whatever information is available + * on the particular platform. If no card insertion information is available + * (polling only), then SDIO_PRESENT() will always return true and we will + * try to initialize the card. + */ + + if (SDIO_PRESENT(priv->dev)) + { + /* Yes... probe for a card in the slot */ + + ret = mmcsd_probe(priv); + if (ret != OK) + { + fvdbg("Slot not empty, but initialization failed: %d\n", ret); + + /* NOTE: The failure to initialize a card does not mean that + * initialization has failed! A card could be installed in the slot + * at a later time. ENODEV is return in this case, + * sdio_slotinitialize will use this return value to set up the + * card inserted callback event. + */ + + ret = -ENODEV; + } + } + else + { + /* ENODEV is returned to indicate that no card is inserted in the slot. + * sdio_slotinitialize will use this return value to set up the card + * inserted callback event. + */ + + ret = -ENODEV; + } + + /* OK is returned only if the slot initialized correctly AND the card in + * the slot was successfully configured. + */ + + mmcsd_givesem(priv); + return ret; +} + +/**************************************************************************** + * Name: mmcsd_hwuninitialize + * + * Description: + * Restore the MMC/SD slot to the uninitialized state. Called only from + * sdio_slotinitialize on a failure to initialize. + * + ****************************************************************************/ + +static void mmcsd_hwuninitialize(FAR struct mmcsd_state_s *priv) +{ + if (priv) + { + mmcsd_removed(priv); + SDIO_RESET(priv->dev); + kfree(priv); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_slotinitialize + * + * Description: + * Initialize one slot for operation using the MMC/SD interface + * + * Input Parameters: + * minor - The MMC/SD minor device number. The MMC/SD device will be + * registered as /dev/mmcsdN where N is the minor number + * dev - And instance of an MMC/SD interface. The MMC/SD hardware should + * be initialized and ready to use. + * + ****************************************************************************/ + +int mmcsd_slotinitialize(int minor, FAR struct sdio_dev_s *dev) +{ + FAR struct mmcsd_state_s *priv; + char devname[16]; + int ret = -ENOMEM; + + fvdbg("minor: %d\n", minor); + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (minor < 0 || minor > 255 || !dev) + { + return -EINVAL; + } +#endif + + /* Allocate a MMC/SD state structure */ + + priv = (FAR struct mmcsd_state_s *)kmalloc(sizeof(struct mmcsd_state_s)); + if (priv) + { + /* Initialize the MMC/SD state structure */ + + memset(priv, 0, sizeof(struct mmcsd_state_s)); + sem_init(&priv->sem, 0, 1); + + /* Bind the MMCSD driver to the MMCSD state structure */ + + priv->dev = dev; + + /* Initialize the hardware associated with the slot */ + + ret = mmcsd_hwinitialize(priv); + + /* Was the slot initialized successfully? */ + + if (ret != OK) + { + /* No... But the error ENODEV is returned if hardware initialization + * succeeded but no card is inserted in the slot. In this case, the + * no error occurred, but the driver is still not ready. + */ + + if (ret == -ENODEV) + { + /* No card in the slot (or if there is, we could not recognize + * it).. Setup to receive the media inserted event + */ + + SDIO_CALLBACKENABLE(priv->dev, SDIOMEDIA_INSERTED); + + fdbg("MMC/SD slot is empty\n"); + } + else + { + /* Some other non-recoverable bad thing happened */ + + fdbg("ERROR: Failed to initialize MMC/SD slot: %d\n", ret); + goto errout_with_alloc; + } + } + + /* Initialize buffering */ + +#if defined(CONFIG_FS_WRITEBUFFER) || defined(CONFIG_FS_READAHEAD) + + ret = rwb_initialize(&priv->rwbuffer); + if (ret < 0) + { + fdbg("ERROR: Buffer setup failed: %d\n", ret); + goto errout_with_hwinit; + } +#endif + + /* Create a MMCSD device name */ + + snprintf(devname, 16, "/dev/mmcsd%d", minor); + + /* Inode private data is a reference to the MMCSD state structure */ + + ret = register_blockdriver(devname, &g_bops, 0, priv); + if (ret < 0) + { + fdbg("ERROR: register_blockdriver failed: %d\n", ret); + goto errout_with_buffers; + } + } + return OK; + +errout_with_buffers: +#if defined(CONFIG_FS_WRITEBUFFER) || defined(CONFIG_FS_READAHEAD) + rwb_uninitialize(&priv->rwbuffer); +errout_with_hwinit: +#endif + mmcsd_hwuninitialize(priv); +errout_with_alloc: + kfree(priv); + return ret; +} diff --git a/nuttx/drivers/mmcsd/mmcsd_sdio.h b/nuttx/drivers/mmcsd/mmcsd_sdio.h new file mode 100644 index 000000000..75c97bd65 --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_sdio.h @@ -0,0 +1,339 @@ +/********************************************************************************************
+ * drivers/mmcsd/mmcsd_sdio.h
+ *
+ * Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ********************************************************************************************/
+
+#ifndef __DRIVERS_MMCSD_MMCSD_SDIO_H
+#define __DRIVERS_MMCSD_MMCSD_SDIO_H
+
+/********************************************************************************************
+ * Included Files
+ ********************************************************************************************/
+
+#include <nuttx/config.h>
+#include <stdint.h>
+
+/********************************************************************************************
+ * Pre-Processor Definitions
+ ********************************************************************************************/
+
+/* CMD8 Argument:
+ * [31:12]: Reserved (shall be set to '0')
+ * [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V)
+ * [7:0]: Check Pattern (recommended 0xaa)
+ * CMD8 Response: R7
+ */
+
+#define MMCSD_CMD8VOLTAGE_SHIFT (8) /* Bits 8-11: Supply voltage */
+#define MMCSD_CMD8VOLTAGE_MASK ((uint32_t)0x0f << MMCSD_CMD8VOLTAGE_SHIFT)
+# define MMCSD_CMD8VOLTAGE_27 ((uint32_t)0x01 << MMCSD_CMD8VOLTAGE_SHIFT) /* 2.7-3.6V */
+#define MMCSD_CMD8ECHO_SHIFT (0) /* Bits 0-7: Check pattern */
+#define MMCSD_CMD8ECHO_MASK ((uint32_t)0xff << MMCSD_CMD8ECHO_SHIFT)
+# define MMCSD_CMD8CHECKPATTERN ((uint32_t)0xaa << MMCSD_CMD8ECHO_SHIFT)
+
+/* ACMD6 argument */
+
+#define MMCSD_ACMD6_BUSWIDTH_1 ((uint32_t)0) /* Bus width = 1-bit */
+#define MMCSD_ACMD6_BUSWIDTH_4 ((uint32_t)2) /* Bus width = 4-bit */
+
+/* ACMD41 argument */
+
+#define MMCSD_ACMD41_VOLTAGEWINDOW ((uint32_t)0x80100000)
+#define MMCSD_ACMD41_HIGHCAPACITY ((uint32_t)1 << 30)
+#define MMCSD_ACMD41_STDCAPACITY ((uint32_t)0)
+
+/* ACMD42 argument */
+
+#define MMCSD_ACMD42_CD_DISCONNECT ((uint32_t)0) /* Disconnect card detection logic */
+#define MMCSD_ACMD42_CD_CONNECT ((uint32_t)1) /* Connect card detection logic */
+
+/* R1 Card Status bit definitions */
+
+#define MMCSD_R1_OUTOFRANGE ((uint32_t)1 << 31) /* Bad argument */
+#define MMCSD_R1_ADDRESSERROR ((uint32_t)1 << 30) /* Bad address */
+#define MMCSD_R1_BLOCKLENERROR ((uint32_t)1 << 29) /* Bad block length */
+#define MMCSD_R1_ERASESEQERROR ((uint32_t)1 << 28) /* Erase cmd error */
+#define MMCSD_R1_ERASEPARAM ((uint32_t)1 << 27) /* Bad write blocks */
+#define MMCSD_R1_WPVIOLATION ((uint32_t)1 << 26) /* Erase access failure */
+#define MMCSD_R1_CARDISLOCKED ((uint32_t)1 << 25) /* Card is locked */
+#define MMCSD_R1_LOCKUNLOCKFAILED ((uint32_t)1 << 24) /* Password error */
+#define MMCSD_R1_COMCRCERROR ((uint32_t)1 << 23) /* CRC error */
+#define MMCSD_R1_ILLEGALCOMMAND ((uint32_t)1 << 22) /* Bad command */
+#define MMCSD_R1_CARDECCFAILED ((uint32_t)1 << 21) /* Failed to correct data */
+#define MMCSD_R1_CCERROR ((uint32_t)1 << 20) /* Card controller error */
+#define MMCSD_R1_ERROR ((uint32_t)1 << 19) /* General error */
+#define MMCSD_R1_UNDERRUN ((uint32_t)1 << 18) /* Underrun (MMC only) */
+#define MMCSD_R1_OVERRRUN ((uint32_t)1 << 17) /* Overrun (MMC only) */
+#define MMCSD_R1_CIDCSDOVERWRITE ((uint32_t)1 << 16) /* CID/CSD error */
+#define MMCSD_R1_WPERASESKIP ((uint32_t)1 << 15) /* Not all erased */
+#define MMCSD_R1_CARDECCDISABLED ((uint32_t)1 << 14) /* Internal ECC not used */
+#define MMCSD_R1_ERASERESET ((uint32_t)1 << 13) /* Reset sequence cleared */
+#define MMCSD_R1_STATE_SHIFT (9) /* Current card state */
+#define MMCSD_R1_STATE_MASK ((uint32_t)15 << MMCSD_R1_STATE_SHIFT)
+ /* Card identification mode states */
+# define MMCSD_R1_STATE_IDLE ((uint32_t)0 << MMCSD_R1_STATE_SHIFT) /* 0=Idle state */
+# define MMCSD_R1_STATE_READY ((uint32_t)1 << MMCSD_R1_STATE_SHIFT) /* 1=Ready state */
+# define MMCSD_R1_STATE_IDENT ((uint32_t)2 << MMCSD_R1_STATE_SHIFT) /* 2=Identification state */
+ /* Data transfer states */
+# define MMCSD_R1_STATE_STBY ((uint32_t)3 << MMCSD_R1_STATE_SHIFT) /* 3=Standby state */
+# define MMCSD_R1_STATE_TRAN ((uint32_t)4 << MMCSD_R1_STATE_SHIFT) /* 4=Transfer state */
+# define MMCSD_R1_STATE_DATA ((uint32_t)5 << MMCSD_R1_STATE_SHIFT) /* 5=Sending data state */
+# define MMCSD_R1_STATE_RCV ((uint32_t)6 << MMCSD_R1_STATE_SHIFT) /* 6=Receiving data state */
+# define MMCSD_R1_STATE_PRG ((uint32_t)7 << MMCSD_R1_STATE_SHIFT) /* 7=Programming state */
+# define MMCSD_R1_STATE_DIS ((uint32_t)8 << MMCSD_R1_STATE_SHIFT) /* 8=Disconnect state */
+#define MMCSD_R1_READYFORDATA ((uint32_t)1 << 8) /* Buffer empty */
+#define MMCSD_R1_APPCMD ((uint32_t)1 << 5) /* Next CMD is ACMD */
+#define MMCSD_R1_AKESEQERROR ((uint32_t)1 << 3) /* Authentication error */
+#define MMCSD_R1_ERRORMASK ((uint32_t)0xfdffe008) /* Error mask */
+
+#define IS_STATE(v,s) ((((uint32_t)v)&MMCSD_R1_STATE_MASK)==(s))
+
+/* R3 (OCR) */
+
+#define MMC_VDD_20_36 ((uint32_t)0x00ffff00) /* VDD voltage 2.0-3.6 */
+
+#define MMCSD_VDD_145_150 ((uint32_t)1 << 0) /* VDD voltage 1.45 - 1.50 */
+#define MMCSD_VDD_150_155 ((uint32_t)1 << 1) /* VDD voltage 1.50 - 1.55 */
+#define MMCSD_VDD_155_160 ((uint32_t)1 << 2) /* VDD voltage 1.55 - 1.60 */
+#define MMCSD_VDD_160_165 ((uint32_t)1 << 3) /* VDD voltage 1.60 - 1.65 */
+#define MMCSD_VDD_165_170 ((uint32_t)1 << 4) /* VDD voltage 1.65 - 1.70 */
+#define MMCSD_VDD_17_18 ((uint32_t)1 << 5) /* VDD voltage 1.7 - 1.8 */
+#define MMCSD_VDD_18_19 ((uint32_t)1 << 6) /* VDD voltage 1.8 - 1.9 */
+#define MMCSD_VDD_19_20 ((uint32_t)1 << 7) /* VDD voltage 1.9 - 2.0 */
+#define MMCSD_VDD_20_21 ((uint32_t)1 << 8) /* VDD voltage 2.0-2.1 */
+#define MMCSD_VDD_21_22 ((uint32_t)1 << 9) /* VDD voltage 2.1-2.2 */
+#define MMCSD_VDD_22_23 ((uint32_t)1 << 10) /* VDD voltage 2.2-2.3 */
+#define MMCSD_VDD_23_24 ((uint32_t)1 << 11) /* VDD voltage 2.3-2.4 */
+#define MMCSD_VDD_24_25 ((uint32_t)1 << 12) /* VDD voltage 2.4-2.5 */
+#define MMCSD_VDD_25_26 ((uint32_t)1 << 13) /* VDD voltage 2.5-2.6 */
+#define MMCSD_VDD_26_27 ((uint32_t)1 << 14) /* VDD voltage 2.6-2.7 */
+#define MMCSD_VDD_27_28 ((uint32_t)1 << 15) /* VDD voltage 2.7-2.8 */
+#define MMCSD_VDD_28_29 ((uint32_t)1 << 16) /* VDD voltage 2.8-2.9 */
+#define MMCSD_VDD_29_30 ((uint32_t)1 << 17) /* VDD voltage 2.9-3.0 */
+#define MMCSD_VDD_30_31 ((uint32_t)1 << 18) /* VDD voltage 3.0-3.1 */
+#define MMCSD_VDD_31_32 ((uint32_t)1 << 19) /* VDD voltage 3.1-3.2 */
+#define MMCSD_VDD_32_33 ((uint32_t)1 << 20) /* VDD voltage 3.2-3.3 */
+#define MMCSD_VDD_33_34 ((uint32_t)1 << 21) /* VDD voltage 3.3-3.4 */
+#define MMCSD_VDD_34_35 ((uint32_t)1 << 22) /* VDD voltage 3.4-3.5 */
+#define MMCSD_VDD_35_36 ((uint32_t)1 << 23) /* VDD voltage 3.5-3.6 */
+#define MMCSD_R3_HIGHCAPACITY ((uint32_t)1 << 30) /* true: Card supports block addressing */
+#define MMCSD_CARD_BUSY ((uint32_t)1 << 31) /* Card power-up busy bit */
+
+/* R6 Card Status bit definitions */
+
+#define MMCSD_R6_RCA_SHIFT (16) /* New published RCA */
+#define MMCSD_R6_RCA_MASK ((uint32_t)0xffff << MMCSD_R6_RCA_SHIFT)
+#define MMCSD_R6_COMCRCERROR ((uint32_t)1 << 15) /* CRC error */
+#define MMCSD_R6_ILLEGALCOMMAND ((uint32_t)1 << 14) /* Bad command */
+#define MMCSD_R6_ERROR ((uint32_t)1 << 13) /* General error */
+#define MMCSD_R6_STATE_SHIFT (9) /* Current card state */
+#define MMCSD_R6_STATE_MASK ((uint32_t)15 << MMCSD_R6_STATE_SHIFT)
+ /* Card identification mode states */
+# define MMCSD_R6_STATE_IDLE ((uint32_t)0 << MMCSD_R6_STATE_SHIFT) /* 0=Idle state */
+# define MMCSD_R6_STATE_READY ((uint32_t)1 << MMCSD_R6_STATE_SHIFT) /* 1=Ready state */
+# define MMCSD_R6_STATE_IDENT ((uint32_t)2 << MMCSD_R6_STATE_SHIFT) /* 2=Identification state */
+ /* Data transfer states */
+# define MMCSD_R6_STATE_STBY ((uint32_t)3 << MMCSD_R6_STATE_SHIFT) /* 3=Standby state */
+# define MMCSD_R6_STATE_TRAN ((uint32_t)4 << MMCSD_R6_STATE_SHIFT) /* 4=Transfer state */
+# define MMCSD_R6_STATE_DATA (5(uint32_t) << MMCSD_R6_STATE_SHIFT) /* 5=Sending data state */
+# define MMCSD_R6_STATE_RCV ((uint32_t)6 << MMCSD_R6_STATE_SHIFT) /* 6=Receiving data state */
+# define MMCSD_R6_STATE_PRG ((uint32_t)7 << MMCSD_R6_STATE_SHIFT) /* 7=Programming state */
+# define MMCSD_R6_STATE_DIS ((uint32_t) << MMCSD_R6_STATE_SHIFT) /* 8=Disconnect state */
+#define MMCSD_R6_ERRORMASK ((uint32_t)0x0000e000) /* Error mask */
+
+/* SD Configuration Register (SCR) encoding */
+
+#define MMCSD_SCR_BUSWIDTH_1BIT (1)
+#define MMCSD_SCR_BUSWIDTH_2BIT (2)
+#define MMCSD_SCR_BUSWIDTH_4BIT (4)
+#define MMCSD_SCR_BUSWIDTH_8BIT (8)
+
+/* Last 4 bytes of the 48-bit R7 response */
+
+#define MMCSD_R7VERSION_SHIFT (28) /* Bits 28-31: Command version number */
+#define MMCSD_R7VERSION_MASK ((uint32_t)0x0f << MMCSD_R7VERSION_SHIFT)
+#define MMCSD_R7VOLTAGE_SHIFT (8) /* Bits 8-11: Voltage accepted */
+#define MMCSD_R7VOLTAGE_MASK ((uint32_t)0x0f << MMCSD_R7VOLTAGE_SHIFT)
+# define MMCSD_R7VOLTAGE_27 ((uint32_t)0x01 << MMCSD_R7VOLTAGE_SHIFT) /* 2.7-3.6V */
+#define MMCSD_R7ECHO_SHIFT (0) /* Bits 0-7: Echoed check pattern */
+#define MMCSD_R7ECHO_MASK ((uint32_t)0xff << MMCSD_R7ECHO_SHIFT)
+# define MMCSD_R7CHECKPATTERN ((uint32_t)0xaa << MMCSD_R7ECHO_SHIFT)
+
+/********************************************************************************************
+ * Public Types
+ ********************************************************************************************/
+
+/* Decoded Card Identification (CID) register */
+
+struct mmcsd_cid_s
+{
+ uint8_t mid; /* 127:120 8-bit Manufacturer ID */
+ uint16_t oid; /* 119:104 16-bit OEM/Application ID (ascii) */
+ uint8_t pnm[6]; /* 103:64 40-bit Product Name (ascii) + null terminator */
+ uint8_t prv; /* 63:56 8-bit Product revision */
+ uint32_t psn; /* 55:24 32-bit Product serial number */
+ /* 23:20 4-bit (reserved) */
+ uint16_t mdt; /* 19:8 12-bit Manufacturing date */
+ uint8_t crc; /* 7:1 7-bit CRC7 */
+ /* 0:0 1-bit (not used) */
+};
+
+/* Decoded Card Specific Data (CSD) register */
+
+struct mmcsd_csd_s
+{
+ uint8_t csdstructure; /* 127:126 CSD structure */
+ uint8_t mmcspecvers; /* 125:122 MMC Spec version (MMC only) */
+
+ struct
+ {
+ uint8_t timeunit; /* 2:0 Time exponent */
+ uint8_t timevalue; /* 6:3 Time mantissa */
+ } taac; /* 119:112 Data read access-time-1 */
+
+ uint8_t nsac; /* 111:104 Data read access-time-2 in CLK cycle(NSAC*100) */
+
+ struct
+ {
+ uint8_t transferrateunit; /* 2:0 Rate exponent */
+ uint8_t timevalue; /* 6:3 Rate mantissa */
+ } transpeed; /* 103:96 Max. data transfer rate */
+
+ uint16_t ccc; /* 95:84 Card command classes */
+ uint8_t readbllen; /* 83:80 Max. read data block length */
+ uint8_t readblpartial; /* 79:79 Partial blocks for read allowed */
+ uint8_t writeblkmisalign; /* 78:78 Write block misalignment */
+ uint8_t readblkmisalign; /* 77:77 Read block misalignment */
+ uint8_t dsrimp; /* 76:76 DSR implemented */
+
+ union
+ {
+#ifdef CONFIG_MMCSD_MMCSUPPORT
+ struct
+ {
+ uint16_t csize; /* 73:62 Device size */
+ uint8_t vddrcurrmin; /* 61:59 Max. read current at Vdd min */
+ uint8_t vddrcurrmax; /* 58:56 Max. read current at Vdd max */
+ uint8_t vddwcurrmin; /* 55:53 Max. write current at Vdd min */
+ uint8_t vddwcurrmax; /* 52:50 Max. write current at Vdd max */
+ uint8_t csizemult; /* 49:47 Device size multiplier */
+
+ union
+ {
+ struct /* MMC system specification version 3.1 */
+ {
+ uint8_t ergrpsize; /* 46:42 Erase group size (MMC 3.1) */
+ uint8_t ergrpmult; /* 41:37 Erase group multiplier (MMC 3.1) */
+ } mmc31;
+ struct /* MMC system specification version 2.2 */
+ {
+ uint8_t sectorsize; /* 46:42 Erase sector size (MMC 2.2) */
+ uint8_t ergrpsize; /* 41:37 Erase group size (MMC 2.2) */
+ } mmc22;
+ } er;
+
+ uint8_t mmcwpgrpsize; /* 36:32 Write protect group size (MMC) */
+ } mmc;
+#endif
+ struct
+ {
+ uint16_t csize; /* 73:62 Device size */
+ uint8_t vddrcurrmin; /* 61:59 Max. read current at Vdd min */
+ uint8_t vddrcurrmax; /* 58:56 Max. read current at Vdd max */
+ uint8_t vddwcurrmin; /* 55:53 Max. write current at Vdd min */
+ uint8_t vddwcurrmax; /* 52:50 Max. write current at Vdd max */
+ uint8_t csizemult; /* 49:47 Device size multiplier */
+ uint8_t sderblen; /* 46:46 Erase single block enable (SD) */
+ uint8_t sdsectorsize; /* 45:39 Erase sector size (SD) */
+ uint8_t sdwpgrpsize; /* 38:32 Write protect group size (SD) */
+ } sdbyte;
+
+ struct
+ {
+ /* 73:70 (reserved) */
+ uint32_t csize; /* 69:48 Device size */
+ /* 47:47 (reserved) */
+ uint8_t sderblen; /* 46:46 Erase single block enable (SD) */
+ uint8_t sdsectorsize; /* 45:39 Erase sector size (SD) */
+ uint8_t sdwpgrpsize; /* 38:32 Write protect group size (SD) */
+ } sdblock;
+ } u;
+
+ uint8_t wpgrpen; /* 31:31 Write protect group enable */
+ uint8_t mmcdfltecc; /* 30:29 Manufacturer default ECC (MMC) */
+ uint8_t r2wfactor; /* 28:26 Write speed factor */
+ uint8_t writebllen; /* 25:22 Max. write data block length */
+ uint8_t writeblpartial; /* 21:21 Partial blocks for write allowed */
+ uint8_t fileformatgrp; /* 15:15 File format group */
+ uint8_t copy; /* 14:14 Copy flag (OTP) */
+ uint8_t permwriteprotect; /* 13:13 Permanent write protection */
+ uint8_t tmpwriteprotect; /* 12:12 Temporary write protection */
+ uint8_t fileformat; /* 10:11 File format */
+ uint8_t mmcecc; /* 9:8 ECC (MMC) */
+ uint8_t crc; /* 7:1 CRC */
+ /* 0:0 Not used */
+};
+
+struct mmcsd_scr_s
+{
+ uint8_t scrversion; /* 63:60 Version of SCR structure */
+ uint8_t sdversion; /* 59:56 SD memory card physical layer version */
+ uint8_t erasestate; /* 55:55 Data state after erase (1 or 0) */
+ uint8_t security; /* 54:52 SD security support */
+ uint8_t buswidth; /* 51:48 DAT bus widthes supported */
+ /* 47:32 SD reserved space */
+ uint32_t mfgdata; /* 31:0 Reserved for manufacturing data */
+};
+
+/********************************************************************************************
+ * Public Data
+ ********************************************************************************************/
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C" {
+#else
+#define EXTERN extern
+#endif
+
+/********************************************************************************************
+ * Public Functions
+ ********************************************************************************************/
+
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+#endif /* __DRIVERS_MMCSD_MMCSD_SDIO_H */
diff --git a/nuttx/drivers/mmcsd/mmcsd_spi.c b/nuttx/drivers/mmcsd/mmcsd_spi.c new file mode 100644 index 000000000..b5f5829a2 --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_spi.c @@ -0,0 +1,1878 @@ +/**************************************************************************** + * drivers/mmcsd/mmcsd_spi.c + * + * Copyright (C) 2008-2010, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <sys/types.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/clock.h> +#include <nuttx/spi.h> +#include <nuttx/fs.h> +#include <nuttx/mmcsd.h> + +#include "mmcsd_spi.h" +#include "mmcsd_csd.h" +#include "mmcsd_internal.h" + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_MMCSD_NSLOTS +# ifdef CONFIG_CPP_HAVE_WARNING +# warning "CONFIG_MMCSD_NSLOTS not defined" +# endif +# define CONFIG_MMCSD_NSLOTS 1 +#endif + +#define MMCSD_IDMODE_CLOCK (400000) + +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +# define MMCSD_MODE 0666 +#else +# define MMCSD_MODE 0444 +#endif + +#ifndef CONFIG_MMCSD_SPICLOCK +# define CONFIG_MMCSD_SPICLOCK 20000000 +#endif + +#ifndef CONFIG_MMCSD_SECTOR512 +# define CONFIG_MMCSD_SECTOR512 /* Force 512 byte sectors on all cards */ +#endif + +/* Slot struct info *********************************************************/ +/* Slot status definitions */ + +#define MMCSD_SLOTSTATUS_NOTREADY 0x01 /* Card not initialized */ +#define MMCSD_SLOTSTATUS_NODISK 0x02 /* No card in the slot */ +#define MMCSD_SLOTSTATUS_WRPROTECT 0x04 /* Card is write protected */ +#define MMCSD_SLOTSTATUS_MEDIACHGD 0x08 /* Media changed in slot */ + +/* Values in the MMC/SD command table ***************************************/ +/* These define the value returned by the MMC/SD command */ + +#define MMCSD_CMDRESP_R1 0 +#define MMCSD_CMDRESP_R1B 1 +#define MMCSD_CMDRESP_R2 2 +#define MMCSD_CMDRESP_R3 3 +#define MMCSD_CMDRESP_R7 4 + +#ifdef CONFIG_MMCSD_SECTOR512 +# define SECTORSIZE(s) (512) +#else +# define SECTORSIZE(s) ((s)->sectorsize) +#endif + +/* Time delays in units of the system clock. CLK_TCK is the number of clock + * ticks per second. + */ + +#define MMCSD_DELAY_10MS (CLK_TCK/100 + 1) +#define MMCSD_DELAY_50MS (CLK_TCK/20 + 1) +#define MMCSD_DELAY_100MS (CLK_TCK/10 + 1) +#define MMCSD_DELAY_250MS (CLK_TCK/4 + 1) +#define MMCSD_DELAY_500MS (CLK_TCK/2 + 1) +#define MMCSD_DELAY_1SEC (CLK_TCK + 1) +#define MMCSD_DELAY_10SEC (10 * CLK_TCK + 1) + +#define ELAPSED_TIME(t) (clock_systimer()-(t)) +#define START_TIME (clock_systimer()) + +/* SD read timeout: ~100msec, Write Time out ~250ms. Units of clock ticks */ + +#define SD_READACCESS MMCSD_DELAY_100MS +#define SD_WRITEACCESS MMCSD_DELAY_250MS + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure represents the state of one card slot */ + +struct mmcsd_slot_s +{ + FAR struct spi_dev_s *spi; /* SPI port bound to this slot */ + sem_t sem; /* Assures mutually exclusive accesss to card and SPI */ + uint8_t state; /* State of the slot (see MMCSD_SLOTSTATUS_* definitions) */ + uint8_t type; /* Disk type */ + uint8_t csd[16]; /* Copy of card CSD */ +#ifndef CONFIG_MMCSD_SECTOR512 + uint16_t sectorsize; /* Media block size (in bytes) */ +#endif + uint32_t nsectors; /* Number of blocks on the media */ + uint32_t taccess; /* Card access time */ + uint32_t twrite; /* Card write time */ + uint32_t ocr; /* Last 4 bytes of OCR (R3) */ + uint32_t r7; /* Last 4 bytes of R7 */ +}; + +struct mmcsd_cmdinfo_s +{ + uint8_t cmd; + uint8_t resp; + uint8_t chksum; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Misc *********************************************************************/ + +static void mmcsd_semtake(sem_t *sem); + +/* Card SPI interface *******************************************************/ + +static int mmcsd_waitready(FAR struct mmcsd_slot_s *slot); +static uint32_t mmcsd_sendcmd(FAR struct mmcsd_slot_s *slot, + const struct mmcsd_cmdinfo_s *cmd, uint32_t arg); +static void mmcsd_setblklen(FAR struct mmcsd_slot_s *slot, + uint32_t length); +static uint32_t mmcsd_nsac(FAR struct mmcsd_slot_s *slot, uint8_t *csd, + uint32_t frequency); +static uint32_t mmcsd_taac(FAR struct mmcsd_slot_s *slot, uint8_t *csd); +static void mmcsd_decodecsd(FAR struct mmcsd_slot_s *slot, uint8_t *csd); +static void mmcsd_checkwrprotect(FAR struct mmcsd_slot_s *slot, + uint8_t *csd); +static int mmcsd_getcardinfo(FAR struct mmcsd_slot_s *slot, + uint8_t *buffer, const struct mmcsd_cmdinfo_s *cmd); + +#define mmcsd_getcsd(slot, csd) mmcsd_getcardinfo(slot, csd, &g_cmd9); +#define mmcsd_getcid(slot, cid) mmcsd_getcardinfo(slot, cid, &g_cmd10); + +static int mmcsd_recvblock(FAR struct mmcsd_slot_s *slot, + uint8_t *buffer, int nbytes); +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static int mmcsd_xmitblock(FAR struct mmcsd_slot_s *slot, + const uint8_t *buffer, int nbytes, uint8_t token); +#endif + +/* Block driver interfaces **************************************************/ + +static int mmcsd_open(FAR struct inode *inode); +static int mmcsd_close(FAR struct inode *inode); +static ssize_t mmcsd_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static ssize_t mmcsd_write(FAR struct inode *inode, + const unsigned char *buffer, size_t start_sector, + unsigned int nsectors); +#endif +static int mmcsd_geometry(FAR struct inode *inode, + struct geometry *geometry); + +/* Initialization ***********************************************************/ + +static int mmcsd_mediainitialize(FAR struct mmcsd_slot_s *slot); +static void mmcsd_mediachanged(void *arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Driver state *************************************************************/ + +/* These are the lock driver methods supported by this file */ + +static const struct block_operations g_bops = +{ + mmcsd_open, /* open */ + mmcsd_close, /* close */ + mmcsd_read, /* read */ +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) + mmcsd_write, /* write */ +#else + NULL, /* write */ +#endif + mmcsd_geometry, /* geometry */ + NULL /* ioctl */ +}; + +/* A slot structure allocated for each configured slot */ + +static struct mmcsd_slot_s g_mmcsdslot[CONFIG_MMCSD_NSLOTS]; + +/* Timing *******************************************************************/ + +/* We will use the TRAN_SPEED from the CSD to determine the maximum SPI + * clocking (TRAN_SPEED defines the maximum transfer rate per bit per data + * line). + * + * The CSD TRAN_SPEED is provided as a 3 bit rate unit (RU) and a 4 bit time + * value (TU). We need the transfer frequency which is: RU*TU bits/sec + * + * g_transpeedru holds RU/10 and g_transpeedtu holds TU*10 so that the + * correct value is returned in the product + */ + +static const uint32_t g_transpeedru[8] = +{ + 10000, /* 0: 10 Kbit/sec / 10 */ + 100000, /* 1: 1 Mbit/sec / 10 */ + 1000000, /* 2: 10 Mbit/sec / 10 */ + 10000000, /* 3: 100 Mbit/sec / 10*/ + + 0, 0, 0, 0 /* 4-7: Reserved values */ +}; + +static const uint32_t g_transpeedtu[16] = +{ + 0, 10, 12, 13, /* 0-3: Reserved, 1.0, 1.1, 1.2, 1.3 */ + 15, 20, 25, 30, /* 4-7: 1.5, 2.0, 2.5, 3.0 */ + 35, 40, 45, 50, /* 8-11: 3.5, 4.0, 4.5, 5.0 */ + 55, 60, 70, 80, /* 12-15: 5.5, 6.0, 7.0, 8.0 */ +}; + +/* The TAAC defines the asynchronous part of the data access time. The + * read access time the sum of the TAAC and the NSAC. These define the + * time from the end bit of the read command to start bit of the data block. + * + * The TAAC consists of a 3-bit time unit (TU) and a 4-bit time value (TV). + * TAAC is in units of time; NSAC is in units of SPI clocks. + * The access time we need is then given by: + * + * taccess = TU*TV + NSAC/spifrequency + * + * g_taactu holds TU in units of nanoseconds and microseconds (you have to use + * the index to distiguish). g_taactv holds TV with 8-bits of fraction. + */ + +#define MAX_USTUNDX 2 +static const uint16_t g_taactu[8] = +{ + /* Units of nanoseconds */ + + 1, /* 0: 1 ns */ + 10, /* 1: 10 ns */ + 100, /* 2: 100 ns */ + + /* Units of microseconds */ + + 1, /* 3: 1 us 1,000 ns */ + 10, /* 4: 10 us 10,000 ns */ + 100, /* 5: 100 us 100,000 ns */ + 1000, /* 6: 1 ms 1,000,000 ns*/ + 10000, /* 7: 10 ms 10,000,000 ns */ +}; + +static const uint16_t g_taactv[] = +{ + 0x000, 0x100, 0x133, 0x14d, /* 0-3: Reserved, 1.0, 1.2, 1.3 */ + 0x180, 0x200, 0x280, 0x300, /* 4-7: 1.5, 2.0, 2.5, 3.0 */ + 0x380, 0x400, 0x480, 0x500, /* 8-11: 3.5, 4.0, 4.5, 5.0 */ + 0x580, 0x600, 0x700, 0x800 /* 12-15: 5.5, 6.0, 7.0, 8.0 */ +}; + +/* Commands *****************************************************************/ + +static const struct mmcsd_cmdinfo_s g_cmd0 = {CMD0, MMCSD_CMDRESP_R1, 0x95}; +static const struct mmcsd_cmdinfo_s g_cmd1 = {CMD1, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd8 = {CMD8, MMCSD_CMDRESP_R7, 0x87}; +static const struct mmcsd_cmdinfo_s g_cmd9 = {CMD9, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd10 = {CMD10, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd12 = {CMD12, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd16 = {CMD16, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd17 = {CMD17, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd18 = {CMD18, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd24 = {CMD24, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd25 = {CMD25, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd55 = {CMD55, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_cmd58 = {CMD58, MMCSD_CMDRESP_R3, 0xff}; +static const struct mmcsd_cmdinfo_s g_acmd23 = {ACMD23, MMCSD_CMDRESP_R1, 0xff}; +static const struct mmcsd_cmdinfo_s g_acmd41 = {ACMD41, MMCSD_CMDRESP_R1, 0xff}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_semtake + ****************************************************************************/ + +static void mmcsd_semtake(sem_t *sem) +{ + while (sem_wait(sem) != 0) + { + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +#define mmcsd_semgive(sem) sem_post(sem) + +/**************************************************************************** + * Name: mmcsd_waitready + * + * Description: + * Wait until the card is no longer busy + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static int mmcsd_waitready(FAR struct mmcsd_slot_s *slot) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint8_t response; + uint32_t start; + uint32_t elapsed; + + /* Wait until the card is no longer busy (up to 500MS) */ + + start = START_TIME; + do + { + response = SPI_SEND(spi, 0xff); + if (response == 0xff) + { + return OK; + } + elapsed = ELAPSED_TIME(start); + } + while (elapsed < MMCSD_DELAY_500MS); + + fdbg("Card still busy, last response: %02x\n", response); + return -EBUSY; +} + +/**************************************************************************** + * Name: mmcsd_sendcmd + * + * Description: + * Send a command to MMC + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static uint32_t mmcsd_sendcmd(FAR struct mmcsd_slot_s *slot, + const struct mmcsd_cmdinfo_s *cmd, uint32_t arg) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t result; + uint8_t response = 0xff; + int ret; + int i; + + /* Wait until the card is not busy */ + + ret = mmcsd_waitready(slot); + if (ret != OK) + { + return ret; + } + + /* Send command code */ + + SPI_SEND(spi, cmd->cmd); + + /* Send command's arguments (should be zero if there are no arguements) */ + + SPI_SEND(spi, (arg >> 24) & 0xff); + SPI_SEND(spi, (arg >> 16) & 0xff); + SPI_SEND(spi, (arg >> 8) & 0xff); + SPI_SEND(spi, arg & 0xff); + + /* Send CRC if needed. The SPI interface is initialized in non-protected + * mode. However, the reset command (CMD0) and CMD8 are received by the + * card while it is still in SD mode and, therefore, must have a valid + * CRC field. + */ + + SPI_SEND(spi, cmd->chksum); + + /* Skip stuff byte on CMD12 */ + + if (cmd->cmd == CMD12) + { + SPI_SEND(spi, 0xff); + } + + /* Get the response to the command. A valid response will have bit7=0. + * Usually, the non-response is 0xff, but I have seen 0xc0 too. + */ + + for (i = 0; i < 9 && (response & 0x80) != 0; i++) + { + response = SPI_SEND(spi, 0xff); + } + + if ((response & 0x80) != 0) + { + fdbg("Failed: i=%d response=%02x\n", i, response); + return (uint32_t)-1; + } + + /* Interpret the response according to the command */ + + result = response; + switch (cmd->resp) + { + /* The R1B response is two bytes long */ + + case MMCSD_CMDRESP_R1B: + { + uint32_t busy = 0; + uint32_t start; + uint32_t elapsed; + + start = START_TIME; + do + { + busy = SPI_SEND(spi, 0xff); + elapsed = ELAPSED_TIME(start); + } + while (elapsed < slot->twrite && busy != 0xff); + + if (busy != 0xff) + { + fdbg("Failed: card still busy (%02x)\n", busy); + return (uint32_t)-1; + } + + fvdbg("CMD%d[%08x] R1B=%02x\n", + cmd->cmd & 0x3f, arg, response); + } + break; + + /* The R1 response is a single byte */ + + case MMCSD_CMDRESP_R1: + { + fvdbg("CMD%d[%08x] R1=%02x\n", + cmd->cmd & 0x3f, arg, response); + } + break; + + /* The R2 response is two bytes long */ + + case MMCSD_CMDRESP_R2: + { + result = ((uint32_t)(response & 0xff) << 8); + result |= SPI_SEND(spi, 0xff) & 0xff; + + fvdbg("CMD%d[%08x] R2=%04x\n", + cmd->cmd & 0x3f, arg, result); + } + break; + + /* The R3 response is 5 bytes long */ + + case MMCSD_CMDRESP_R3: + { + slot->ocr = ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 24); + slot->ocr |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 16); + slot->ocr |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 8); + slot->ocr |= SPI_SEND(spi, 0xff) & 0xff; + + fvdbg("CMD%d[%08x] R1=%02x OCR=%08x\n", + cmd->cmd & 0x3f, arg, response, slot->ocr); + } + + /* The R7 response is 5 bytes long */ + case MMCSD_CMDRESP_R7: + default: + { + slot->r7 = ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 24); + slot->r7 |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 16); + slot->r7 |= ((uint32_t)(SPI_SEND(spi, 0xff) & 0xff) << 8); + slot->r7 |= SPI_SEND(spi, 0xff) & 0xff; + + fvdbg("CMD%d[%08x] R1=%02x R7=%08x\n", + cmd->cmd & 0x3f, arg, response, slot->r7); + } + break; + } + + return result; +} + +/**************************************************************************** + * Name: mmcsd_setblklen + * + * Description: + * Set block length + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static void mmcsd_setblklen(FAR struct mmcsd_slot_s *slot, uint32_t length) +{ + uint32_t response; + + fvdbg("Set block length to %d\n", length); + response = mmcsd_sendcmd(slot, &g_cmd16, length); + if (response != MMCSD_SPIR1_OK) + { + fdbg("Failed to set block length: %02x\n", response); + } +} + +/**************************************************************************** + * Name: mmcsd_nsac + * + * Description: Convert the value of the NSAC to microseconds + * + ****************************************************************************/ + +static uint32_t mmcsd_nsac(FAR struct mmcsd_slot_s *slot, uint8_t *csd, + uint32_t frequency) +{ + /* NSAC is 8-bits wide and is in units of 100 clock cycles. Therefore, the + * maximum value is 25.5K clock cycles. + */ + + uint32_t nsac = MMCSD_CSD_NSAC(csd) * ((uint32_t)100*1000); + uint32_t fhkz = (frequency + 500) / 1000; + return (nsac + (fhkz >> 1)) / fhkz; +} + +/**************************************************************************** + * Name: mmcsd_taac + * + * Description: Convert the value of the TAAC to microseconds + * + ****************************************************************************/ + +static uint32_t mmcsd_taac(FAR struct mmcsd_slot_s *slot, uint8_t *csd) +{ + int tundx; + + /*The TAAC consists of a 3-bit time unit (TU) and a 4-bit time value (TV). + * TAAC is in units of time; NSAC is in units of SPI clocks. + * The access time we need is then given by: + * + * taccess = TU*TV + NSAC/spifrequency + * + * g_taactu holds TU in units of nanoseconds and microseconds (you have to use + * the index to distiguish. g_taactv holds TV with 8-bits of fraction. + */ + + tundx = MMCSD_CSD_TAAC_TIMEUNIT(csd); + if (tundx <= MAX_USTUNDX) + { + /* The maximum value of the nanosecond TAAC is 800 ns. The rounded + * answer in microseconds will be at most 1. + */ + + return 1; + } + else + { + /* Return the answer in microseconds */ + + return (g_taactu[tundx]*g_taactv[MMCSD_CSD_TAAC_TIMEVALUE(csd)] + 0x80) >> 8; + } +} + +/**************************************************************************** + * Name: mmcsd_decodecsd + * + * Description: + * + ****************************************************************************/ + +static void mmcsd_decodecsd(FAR struct mmcsd_slot_s *slot, uint8_t *csd) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t maxfrequency; + uint32_t frequency; + uint32_t readbllen; + uint32_t csizemult; + uint32_t csize; + + /* Calculate SPI max clock */ + + maxfrequency = + g_transpeedtu[MMCSD_CSD_TRANSPEED_TIMEVALUE(csd)] * + g_transpeedru[MMCSD_CSD_TRANSPEED_TRANSFERRATEUNIT(csd)]; + + /* Clip the max frequency to account for board limitations */ + + frequency = maxfrequency; + if (frequency > CONFIG_MMCSD_SPICLOCK) + { + frequency = CONFIG_MMCSD_SPICLOCK; + } + + /* Set the actual SPI frequency as close as possible to that value */ + + frequency = SPI_SETFREQUENCY(spi, frequency); + + /* Now determine the delay to access data */ + + if (slot->type == MMCSD_CARDTYPE_MMC) + { + /* The TAAC consists of a 3-bit time unit (TU) and a 4-bit time value (TV). + * TAAC is in units of time; NSAC is in units of SPI clocks. + * The access time we need is then given by: + * + * taccess = TU*TV + NSAC/spifrequency + * + * Example: TAAC = 1.5 ms, NSAC = 0, r2wfactor = 4, CLK_TCK=100 + * taccessus = 1,500uS + * taccess = (1,500 * 100) / 100,000) + 1 = 2 (ideal, 1.5) + * twrite = (1,500 * 4 * 100) / 100,000) + 1 = 7 (ideal 6.0) + * + * First get the access time in microseconds + */ + + uint32_t taccessus = mmcsd_taac(slot, csd) + mmcsd_nsac(slot, csd, frequency); + + /* Then convert to system clock ticks. The maximum read access is 10 times + * the tacc value: taccess = 10 * (taccessus / 1,000,000) * CLK_TCK, or + */ + + slot->taccess = (taccessus * CLK_TCK) / 100000 + 1; + + /* NOTE that we add one to taccess to assure that we wait at least this + * time. The write access time is larger by the R2WFACTOR: */ + + slot->taccess = (taccessus * MMCSD_CSD_R2WFACTOR(csd) * CLK_TCK) / 100000 + 1; + } + else + { + /* For SD, the average is still given by the TAAC+NSAC, but the + * maximum are the constants 100 and 250MS + */ + + slot->taccess = SD_READACCESS; + slot->twrite = SD_WRITEACCESS; + } + + fvdbg("SPI Frequency\n"); + fvdbg(" Maximum: %d Hz\n", maxfrequency); + fvdbg(" Actual: %d Hz\n", frequency); + fvdbg("Read access time: %d ticks\n", slot->taccess); + fvdbg("Write access time: %d ticks\n", slot->twrite); + + /* Get the physical geometry of the card: sector size and number of + * sectors. The card's total capacity is computed from + * + * capacity = BLOCKNR * BLOCK_LEN + * BLOCKNR = (C_SIZE+1)*MULT + * MULT = 2**(C_SIZE_MULT+2) (C_SIZE_MULT < 8) + * BLOCK_LEN = 2**READ_BL_LEN (READ_BL_LEN < 12) + * + * Or + * + * capacity = ((C_SIZE+1) << (READD_BL_LEN + C_SIZE_MULT + 2)) + * + * In units of the sector size (1 << READ_BL_LEN), then simplifies to + * + * nsectors = ((C_SIZE+1) << (C_SIZE_MULT + 2)) + */ + + if (MMCSD_CSD_CSDSTRUCT(csd) != 0) + { + /* SDC structure ver 2.xx */ + /* Note: On SD card WRITE_BL_LEN is always the same as READ_BL_LEN */ + + readbllen = SD20_CSD_READBLLEN(csd); + csizemult = SD20_CSD_CSIZEMULT(csd) + 2; + csize = SD20_CSD_CSIZE(csd) + 1; + } + else + { + /* MMC or SD structure ver 1.xx */ + /* Note: On SD card WRITE_BL_LEN is always the same as READ_BL_LEN */ + + readbllen = MMCSD_CSD_READBLLEN(csd); + csizemult = MMCSD_CSD_CSIZEMULT(csd) + 2; + csize = MMCSD_CSD_CSIZE(csd) + 1; + } + + /* SDHC ver2.x cards have fixed block transfer size of 512 bytes. SDC + * ver1.x cards with capacity less than 1Gb, will have sector size + * 512 byes. SDC ver1.x cards with capacity of 2Gb will report readbllen + * of 1024 but should use 512 bytes for block transfers. SDC ver1.x 4Gb + * cards will report readbllen of 2048 bytes -- are they also 512 bytes? + */ + +#ifdef CONFIG_MMCSD_SECTOR512 + if (readbllen > 9) + { + csizemult += (readbllen - 9); + } + else + { + DEBUGASSERT(readbllen == 9); + } +#else + if (IS_SDV2(slot->type)) + { + if (readbllen > 9) + { + fdbg("Forcing 512 byte sector size\n"); + csizemult += (readbllen - 9); + readbllen = 9; + } + } + + slot->sectorsize = 1 << readbllen; +#endif + slot->nsectors = csize << csizemult; + fvdbg("Sector size: %d\n", SECTORSIZE(slot)); + fvdbg("Number of sectors: %d\n", slot->nsectors); +} + +/**************************************************************************** + * Name: mmcsd_checkwrprotect + * + * Description: + * + ****************************************************************************/ + +static void mmcsd_checkwrprotect(FAR struct mmcsd_slot_s *slot, uint8_t *csd) +{ + FAR struct spi_dev_s *spi = slot->spi; + + /* Check if (1) the slot is reporting that reporting that write protection + * is set, (2) the card reports permanent write protect, or (2) the card + * reports temporary write protect. + */ + + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_WRPROTECTED) != 0 || + MMCSD_CSD_PERMWRITEPROTECT(csd) || + MMCSD_CSD_TMPWRITEPROTECT(csd)) + { + slot->state |= MMCSD_SLOTSTATUS_WRPROTECT; + } + else + { + slot->state &= ~MMCSD_SLOTSTATUS_WRPROTECT; + } +} + +/**************************************************************************** + * Name: mmcsd_getcardinfo + * + * Description: + * Read CSD or CID registers + * + * Assumptions: + * MMC/SD card already selected + * + ****************************************************************************/ + +static int mmcsd_getcardinfo(FAR struct mmcsd_slot_s *slot, uint8_t *buffer, + const struct mmcsd_cmdinfo_s *cmd) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t result; + uint8_t response; + int i; + + SPI_SEND(spi, 0xff); + + /* Send the CMD9 or CMD10 */ + + result = mmcsd_sendcmd(slot, cmd, 0); + if (result != MMCSD_SPIR1_OK) + { + fdbg("CMD9/10 failed: R1=%02x\n", result); + return -EIO; + } + + /* Try up to 8 times to find the start of block (or until an error occurs) */ + + for (i = 0; i < 8; i++) + { + response = SPI_SEND(spi, 0xff); + fvdbg("%d. SPI send returned %02x\n", i, response); + + /* If a read operation fails and the card cannot provide the requested + * data, it will send a data error token instead. The 4 least + * significant bits are the same as those in the R2 response. + */ + + if (response != 0 && (response & MMCSD_SPIDET_UPPER) == 0) + { + fdbg("%d. Data transfer error: %02x\n", i, response); + return -EIO; + } + else if (response == MMCSD_SPIDT_STARTBLKSNGL) + { + for (i = 0; i < 16; ++i) + { + *buffer++ = SPI_SEND(spi, 0xff); + } + + /* CRC receive */ + + SPI_SEND(spi, 0xff); + SPI_SEND(spi, 0xff); + return OK; + } + } + + fdbg("%d. Did not find start of block\n"); + return -EIO; +} + +/**************************************************************************** + * Name: mmcsd_recvblock + * + * Description: Receive a data block from the card + * + ****************************************************************************/ + +static int mmcsd_recvblock(FAR struct mmcsd_slot_s *slot, uint8_t *buffer, int nbytes) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint32_t start; + uint32_t elapsed; + uint8_t token; + + /* Wait up to the maximum to receive a valid data token. taccess is the + * time from when the command is sent until the first byte of data is + * received */ + + start = START_TIME; + do + { + token = SPI_SEND(spi, 0xff); + elapsed = ELAPSED_TIME(start); + } + while (token == 0xff && elapsed < slot->taccess); + + if (token == MMCSD_SPIDT_STARTBLKSNGL) + { + /* Receive the block */ + + SPI_RECVBLOCK(spi, buffer, nbytes); + + /* Discard the CRC */ + + SPI_SEND(spi, 0xff); + SPI_SEND(spi, 0xff); + return OK; + } + + fdbg("Did not receive data token (%02x)\n", token); + return ERROR; +} + +/**************************************************************************** + * Name: mmcsd_xmitblock + * + * Description: Transmit a data block to the card + * + ****************************************************************************/ + +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static int mmcsd_xmitblock(FAR struct mmcsd_slot_s *slot, const uint8_t *buffer, + int nbytes, uint8_t token) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint8_t response; + + /* Start the block transfer: + * 1. 0xff (sync) + * 2. 0xfe or 0xfc (start of block token) + * 3. Followed by the block of data and 2 byte CRC + */ + + SPI_SEND(spi, 0xff); /* sync */ + SPI_SEND(spi, token); /* data token */ + + /* Transmit the block to the MMC/SD card */ + + (void)SPI_SNDBLOCK(spi, buffer, nbytes); + + /* Add the bogus CRC. By default, the SPI interface is initialized in + * non-protected mode. However, we still have to send bogus CRC values + */ + + SPI_SEND(spi, 0xff); + SPI_SEND(spi, 0xff); + + /* Now get the data response */ + + response = SPI_SEND(spi, 0xff); + if ((response & MMCSD_SPIDR_MASK) != MMCSD_SPIDR_ACCEPTED) + { + fdbg("Bad data response: %02x\n", response); + return -EIO; + } + return OK; +} +#endif /* CONFIG_FS_WRITABLE && !CONFIG_MMCSD_READONLY */ + +/**************************************************************************** + * Block Driver Operations + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int mmcsd_open(FAR struct inode *inode) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + int ret; + + fvdbg("Entry\n"); + +#ifdef CONFIG_DEBUG + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Verify that an MMC/SD card has been inserted */ + + ret = -ENODEV; + mmcsd_semtake(&slot->sem); + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_PRESENT) != 0) + { + /* Yes.. a card is present. Has it been initialized? */ + + if (slot->type == MMCSD_CARDTYPE_UNKNOWN) + { + /* Ininitialize for the media in the slot */ + + ret = mmcsd_mediainitialize(slot); + if (ret < 0) + { + fvdbg("Failed to initialize card\n"); + goto errout_with_sem; + } + } + + /* Make sure that the card is ready */ + + SPI_SELECT(spi, SPIDEV_MMCSD, true); + ret = mmcsd_waitready(slot); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + } + +errout_with_sem: + mmcsd_semgive(&slot->sem); + return ret; +} + +/**************************************************************************** + * Name: mmcsd_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int mmcsd_close(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: mmcsd_read + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t mmcsd_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + size_t nbytes; + off_t offset; + uint8_t response; + int i; + + fvdbg("start_sector=%d nsectors=%d\n", start_sector, nsectors); + +#ifdef CONFIG_DEBUG + if (!buffer) + { + fdbg("Invalid parameters\n"); + return -EINVAL; + } + + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Verify that card is available */ + + if (slot->state & MMCSD_SLOTSTATUS_NOTREADY) + { + fdbg("Slot not ready\n"); + return -ENODEV; + } + + /* Do nothing on zero-length transfer */ + + if (nsectors < 1) + { + return 0; + } + + /* Convert sector and nsectors to nbytes and byte offset */ + + nbytes = nsectors * SECTORSIZE(slot); + if (IS_BLOCK(slot->type)) + { + offset = start_sector; + fvdbg("nbytes=%d sector offset=%d\n", nbytes, offset); + } + else + { + offset = start_sector * SECTORSIZE(slot); + fvdbg("nbytes=%d byte offset=%d\n", nbytes, offset); + } + + /* Select the slave */ + + mmcsd_semtake(&slot->sem); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + + /* Single or multiple block read? */ + + if (nsectors == 1) + { + /* Send CMD17: Reads a block of the size selected by the SET_BLOCKLEN + * command and verify that good R1 status is returned + */ + + response = mmcsd_sendcmd(slot, &g_cmd17, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD17 failed: R1=%02x\n", response); + goto errout_with_eio; + } + + /* Receive the block */ + + if (mmcsd_recvblock(slot, buffer, SECTORSIZE(slot)) != 0) + { + fdbg("Failed: to receive the block\n"); + goto errout_with_eio; + } + } + else + { + /* Send CMD18: Reads a block of the size selected by the SET_BLOCKLEN + * command and verify that good R1 status is returned + */ + + response = mmcsd_sendcmd(slot, &g_cmd18, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD118 failed: R1=%02x\n", response); + goto errout_with_eio; + } + + /* Receive each block */ + + for (i = 0; i < nsectors; i++) + { + if (mmcsd_recvblock(slot, buffer, SECTORSIZE(slot)) != 0) + { + fdbg("Failed: to receive the block\n"); + goto errout_with_eio; + } + buffer += SECTORSIZE(slot); + } + + /* Send CMD12: Stops transmission */ + + response = mmcsd_sendcmd(slot, &g_cmd12, 0); + } + + /* On success, return the number of sectors transfer */ + + SPI_SELECT(spi, SPIDEV_MMCSD, false); + SPI_SEND(spi, 0xff); + mmcsd_semgive(&slot->sem); + + fvdbg("Read %d bytes:\n", nbytes); + mmcsd_dumpbuffer("Read buffer", buffer, nbytes); + return nsectors; + +errout_with_eio: + SPI_SELECT(spi, SPIDEV_MMCSD, false); + mmcsd_semgive(&slot->sem); + return -EIO; +} + +/**************************************************************************** + * Name: mmcsd_write + * + * Description: + * Write the specified number of sectors + * + ****************************************************************************/ + +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) +static ssize_t mmcsd_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + size_t nbytes; + off_t offset; + uint8_t response; + int ret; + int i; + + fvdbg("start_sector=%d nsectors=%d\n", start_sector, nsectors); + +#ifdef CONFIG_DEBUG + if (!buffer) + { + fdbg("Invalid parameters\n"); + return -EINVAL; + } + + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Verify that card is available */ + + if (slot->state & MMCSD_SLOTSTATUS_NOTREADY) + { + fdbg("Slot not ready\n"); + return -ENODEV; + } + + /* Verify that the card is write enabled */ + + if (slot->state & MMCSD_SLOTSTATUS_WRPROTECT) + { + fdbg("Not write enabled\n"); + return -EACCES; + } + + /* Do nothing on zero-length transfer */ + + if (nsectors < 1) + { + return 0; + } + + /* Convert sector and nsectors to nbytes and byte offset */ + + nbytes = nsectors * SECTORSIZE(slot); + if (IS_BLOCK(slot->type)) + { + offset = start_sector; + fvdbg("nbytes=%d sector offset=%d\n", nbytes, offset); + } + else + { + offset = start_sector * SECTORSIZE(slot); + fvdbg("nbytes=%d byte offset=%d\n", nbytes, offset); + } + mmcsd_dumpbuffer("Write buffer", buffer, nbytes); + + /* Select the slave */ + + mmcsd_semtake(&slot->sem); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + + /* Single or multiple block transfer? */ + + if (nsectors == 1) + { + /* Send CMD24 (WRITE_BLOCK) and verify that good R1 status is returned */ + + response = mmcsd_sendcmd(slot, &g_cmd24, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD24 failed: R1=%02x\n", response); + goto errout_with_sem; + } + + /* Then transfer the sector */ + + if (mmcsd_xmitblock(slot, buffer, SECTORSIZE(slot), 0xfe) != 0) + { + fdbg("Block transfer failed\n"); + goto errout_with_sem; + } + } + else + { + /* Set the number of blocks to be pre-erased (SD only) */ + + if (IS_SD(slot->type)) + { + response = mmcsd_sendcmd(slot, &g_acmd23, nsectors); + if (response != MMCSD_SPIR1_OK) + { + fdbg("ACMD23 failed: R1=%02x\n", response); + goto errout_with_sem; + } + } + + /* Send CMD25: Continuously write blocks of data until the + * tranmission is stopped. + */ + + response = mmcsd_sendcmd(slot, &g_cmd25, offset); + if (response != MMCSD_SPIR1_OK) + { + fdbg("CMD25 failed: R1=%02x\n", response); + goto errout_with_sem; + } + + /* Transmit each block */ + + for (i = 0; i < nsectors; i++) + { + if (mmcsd_xmitblock(slot, buffer, SECTORSIZE(slot), 0xfc) != 0) + { + fdbg("Failed: to receive the block\n"); + goto errout_with_sem; + } + buffer += SECTORSIZE(slot); + } + + /* Send the stop transmission token */ + + SPI_SEND(spi, MMCSD_SPIDT_STOPTRANS); + } + + /* Wait until the card is no longer busy */ + + ret = mmcsd_waitready(slot); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + SPI_SEND(spi, 0xff); + mmcsd_semgive(&slot->sem); + + /* The success return value is the number of sectors written */ + + return nsectors; + +errout_with_sem: + SPI_SELECT(spi, SPIDEV_MMCSD, false); + mmcsd_semgive(&slot->sem); + return -EIO; +} +#endif + +/**************************************************************************** + * Name: mmcsd_geometry + * + * Description: + * Return device geometry + * + ****************************************************************************/ + +static int mmcsd_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + FAR struct mmcsd_slot_s *slot; + FAR struct spi_dev_s *spi; + uint8_t csd[16]; + int ret; + +#ifdef CONFIG_DEBUG + if (!geometry) + { + fdbg("Invalid parameters\n"); + return -EINVAL; + } + + if (!inode || !inode->i_private) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Extract our private data from the inode structure */ + + slot = (FAR struct mmcsd_slot_s *)inode->i_private; + spi = slot->spi; + +#ifdef CONFIG_DEBUG + if (!spi) + { + fdbg("Internal confusion\n"); + return -EIO; + } +#endif + + /* Re-sample the CSD */ + + mmcsd_semtake(&slot->sem); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + ret = mmcsd_getcsd(slot, csd); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + + if (ret < 0) + { + mmcsd_semgive(&slot->sem); + fdbg("mmcsd_getcsd returned %d\n", ret); + return ret; + } + + /* Check for changes related to write protection */ + + mmcsd_checkwrprotect(slot, csd); + + /* Then return the card geometry */ + + geometry->geo_available = + ((slot->state & (MMCSD_SLOTSTATUS_NOTREADY|MMCSD_SLOTSTATUS_NODISK)) == 0); + geometry->geo_mediachanged = + ((slot->state & MMCSD_SLOTSTATUS_MEDIACHGD) != 0); +#if defined(CONFIG_FS_WRITABLE) && !defined(CONFIG_MMCSD_READONLY) + geometry->geo_writeenabled = + ((slot->state & MMCSD_SLOTSTATUS_WRPROTECT) == 0); +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = slot->nsectors; + geometry->geo_sectorsize = SECTORSIZE(slot); + + /* After reporting mediachanged, clear the indication so that it is not + * reported again. + */ + + slot->state &= ~MMCSD_SLOTSTATUS_MEDIACHGD; + mmcsd_semgive(&slot->sem); + + fvdbg("geo_available: %d\n", geometry->geo_available); + fvdbg("geo_mediachanged: %d\n", geometry->geo_mediachanged); + fvdbg("geo_writeenabled: %d\n", geometry->geo_writeenabled); + fvdbg("geo_nsectors: %d\n", geometry->geo_nsectors); + fvdbg("geo_sectorsize: %d\n", geometry->geo_sectorsize); + + return OK; +} + +/**************************************************************************** + * Initialization + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_mediainitialize + * + * Description: + * Detect media and initialize + * + ****************************************************************************/ + +static int mmcsd_mediainitialize(FAR struct mmcsd_slot_s *slot) +{ + FAR struct spi_dev_s *spi = slot->spi; + uint8_t csd[16]; + uint32_t result = MMCSD_SPIR1_IDLESTATE; + uint32_t start; + uint32_t elapsed; + int i, j; + + /* Assume that the card is not ready (we'll clear this on successful card + * initialization. + */ + + slot->state |= MMCSD_SLOTSTATUS_NOTREADY; + + /* Check if there is a card present in the slot. This is normally a matter is + * of GPIO sensing and does not really involve SPI, but by putting this + * functionality in the SPI interface, we encapuslate the SPI MMC/SD + * interface + */ + + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_PRESENT) == 0) + { + fdbg("No card present\n"); + slot->state |= MMCSD_SLOTSTATUS_NODISK; + return -ENODEV; + } + + /* Clock Freq. Identification Mode < 400kHz */ + + SPI_SETFREQUENCY(spi, MMCSD_IDMODE_CLOCK); + + /* Set the maximum access time out */ + + slot->taccess = SD_READACCESS; + + /* The SD card wakes up in SD mode. It will enter SPI mode if the chip select signal is + * asserted (negative) during the reception of the reset command (CMD0) and the card is in + * IDLE state. + */ + + for (i = 0; i < 2; i++) + { + /* After power up at least 74 clock cycles are required prior to + * starting bus communication + */ + + for (j = 10; j; j--) + { + SPI_SEND(spi, 0xff); + } + + /* Send CMD0 (GO_TO_IDLE) with CS asserted to put MMC/SD in + * IDLE/SPI mode. Return from CMD0 is R1 which should now + * show IDLE STATE + */ + + fvdbg("Send CMD0\n"); + SPI_SELECT(spi, SPIDEV_MMCSD, true); + result = mmcsd_sendcmd(slot, &g_cmd0, 0); + if (result == MMCSD_SPIR1_IDLESTATE) + { + /* Break out of the loop with card selected */ + + fvdbg("Card is in IDLE state\n"); + break; + } + + /* De-select card and try again */ + + SPI_SELECT(spi, SPIDEV_MMCSD, false); + } + + /* Verify that we exit the above loop with the card reporting IDLE state */ + + if (result != MMCSD_SPIR1_IDLESTATE) + { + fdbg("Send CMD0 failed: R1=%02x\n", result); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + + slot->type = MMCSD_CARDTYPE_UNKNOWN; + + /* Check for SDHC Version 2.x. CMD 8 is reserved on SD version 1.0 and MMC. */ + + fvdbg("Send CMD8\n"); + result = mmcsd_sendcmd(slot, &g_cmd8, 0x1aa); + if (result == MMCSD_SPIR1_IDLESTATE) + { + /* Verify the operating voltage and that the 0xaa was correctly echoed */ + + if (((slot->r7 & MMCSD_SPIR7_VOLTAGE_MASK) == MMCSD_SPIR7_VOLTAGE_27) && + ((slot->r7 & MMCSD_SPIR7_ECHO_MASK) == 0xaa)) + { + /* Try CMD55/ACMD41 for up to 1 second or until the card exits + * the IDLE state + */ + + start = START_TIME; + elapsed = 0; + do + { + fvdbg("%d. Send CMD55/ACMD41\n", elapsed); + result = mmcsd_sendcmd(slot, &g_cmd55, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + result = mmcsd_sendcmd(slot, &g_acmd41, (uint32_t)1 << 30); + if (result == MMCSD_SPIR1_OK) + { + break; + } + } + elapsed = ELAPSED_TIME(start); + } + while (elapsed < MMCSD_DELAY_1SEC); + + /* Check if ACMD41 was sent successfully */ + + if (elapsed < MMCSD_DELAY_1SEC) + { + fvdbg("Send CMD58\n"); + SPI_SEND(spi, 0xff); + result = mmcsd_sendcmd(slot, &g_cmd58, 0); + if (result == MMCSD_SPIR1_OK) + { + fvdbg("OCR: %08x\n", slot->ocr); + if ((slot->ocr & MMCSD_OCR_CCS) != 0) + { + fdbg("Identified SD ver2 card/with block access\n"); + slot->type = MMCSD_CARDTYPE_SDV2|MMCSD_CARDTYPE_BLOCK; + } + else + { + fdbg("Identified SD ver2 card\n"); + slot->type = MMCSD_CARDTYPE_SDV2; + } + } + } + } + } + + /* Check for SDC version 1.x or MMC */ + + else + { + /* Both the MMC card and the SD card support CMD55 */ + + fvdbg("Send CMD55/ACMD41\n"); + result = mmcsd_sendcmd(slot, &g_cmd55, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + /* But ACMD41 is supported only on SD */ + + result = mmcsd_sendcmd(slot, &g_acmd41, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + fdbg("Identified SD ver1 card\n"); + slot->type = MMCSD_CARDTYPE_SDV1; + } + } + + /* Make sure that we are out of the Idle state */ + + start = START_TIME; + elapsed = 0; + do + { + if (IS_SD(slot->type)) + { + fvdbg("%d. Send CMD55/ACMD41\n", elapsed); + result = mmcsd_sendcmd(slot, &g_cmd55, 0); + if (result == MMCSD_SPIR1_IDLESTATE || result == MMCSD_SPIR1_OK) + { + result = mmcsd_sendcmd(slot, &g_acmd41, 0); + if (result == MMCSD_SPIR1_OK) + { + break; + } + } + } + else + { + fvdbg("%d. Send CMD1\n", i); + result = mmcsd_sendcmd(slot, &g_cmd1, 0); + if (result == MMCSD_SPIR1_OK) + { + fdbg("%d. Identified MMC card\n", i); + slot->type = MMCSD_CARDTYPE_MMC; + break; + } + } + elapsed = ELAPSED_TIME(start); + } + while (elapsed < MMCSD_DELAY_1SEC); + + if (elapsed >= MMCSD_DELAY_1SEC) + { + fdbg("Failed to exit IDLE state\n"); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + } + + if (slot->type == MMCSD_CARDTYPE_UNKNOWN) + { + fdbg("Failed to identify card\n"); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + + /* Read CSD. CSD must always be valid */ + + fvdbg("Get CSD\n"); + result = mmcsd_getcsd(slot, csd); + if (result != OK) + { + fdbg("mmcsd_getcsd(CMD9) failed: %d\n", result); + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return -EIO; + } + mmcsd_dmpcsd(csd, slot->type); + + /* CSD data and set block size */ + + mmcsd_decodecsd(slot, csd); + mmcsd_checkwrprotect(slot, csd); + + /* SDHC ver2.x cards have fixed block transfer size of 512 bytes. SDC + * ver1.x cards with capacity less than 1Gb, will have sector size + * 512 byes. SDC ver1.x cards with capacity of 2Gb will report readbllen + * of 1024 but should use 512 bytes for block transfers. SDC ver1.x 4Gb + * cards will report readbllen of 2048 bytes -- are they also 512 bytes? + * I think that none of these high capacity cards support setting the + * block length?? + */ + +#ifdef CONFIG_MMCSD_SECTOR512 + /* Using 512 byte sectors, the maximum ver1.x capacity is 4096 x 512 blocks. + * The saved slot->nsectors is converted to 512 byte blocks, so if slot->nsectors + * exceeds 4096 x 512, then we must be dealing with a card with read_bl_len + * of 1024 or 2048. + */ + + if (!IS_SDV2(slot->type) && slot->nsectors <= ((uint32_t)4096*12)) + { + /* Don't set the block len on high capacity cards (ver1.x or ver2.x) */ + + mmcsd_setblklen(slot, SECTORSIZE(slot)); + } +#else + if (!IS_SDV2(slot->type)) + { + /* Don't set the block len on ver2.x cards */ + + mmcsd_setblklen(slot, SECTORSIZE(slot)); + } +#endif + + slot->state &= ~MMCSD_SLOTSTATUS_NOTREADY; + SPI_SELECT(spi, SPIDEV_MMCSD, false); + return OK; +} + +/**************************************************************************** + * Name: mmcsd_mediachanged + * + * Description: + * Handle initialization/media change events + * + ****************************************************************************/ + +static void mmcsd_mediachanged(void *arg) +{ + struct mmcsd_slot_s *slot = (struct mmcsd_slot_s*)arg; + FAR struct spi_dev_s *spi; + uint8_t oldstate; + int ret; + +#ifdef CONFIG_DEBUG + if (!slot || !slot->spi) + { + fdbg("Internal confusion\n"); + return; + } +#endif + spi = slot->spi; + + /* Save the current slot state and reassess the new state */ + + mmcsd_semtake(&slot->sem); + oldstate = slot->state; + + /* Check if media was removed or inserted */ + + slot->state &= ~(MMCSD_SLOTSTATUS_NODISK|MMCSD_SLOTSTATUS_NOTREADY|MMCSD_SLOTSTATUS_MEDIACHGD); + if ((SPI_STATUS(spi, SPIDEV_MMCSD) & SPI_STATUS_PRESENT) == 0) + { + /* Media is not present */ + + fdbg("No card present\n"); + slot->state |= (MMCSD_SLOTSTATUS_NODISK|MMCSD_SLOTSTATUS_NOTREADY); + + /* Was media removed? */ + + if ((oldstate & MMCSD_SLOTSTATUS_NODISK) == 0) + { + slot->state |= MMCSD_SLOTSTATUS_MEDIACHGD; + } + } + + /* Media is present, was it just inserted? Or, if it was previously not ready, + * then try re-initializing it + */ + + else if ((oldstate & (MMCSD_SLOTSTATUS_NODISK|MMCSD_SLOTSTATUS_NOTREADY)) != 0) + { + /* (Re-)ininitialize for the media in the slot */ + + ret = mmcsd_mediainitialize(slot); + if (ret == 0) + { + fvdbg("mmcsd_mediainitialize returned OK\n"); + slot->state |= MMCSD_SLOTSTATUS_MEDIACHGD; + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mmcsd_spislotinitialize + * + * Description: + * Initialize one slot for operation using the SPI MMC/SD interface + * + * Input Parameters: + * minor - The MMC/SD minor device number. The MMC/SD device will be + * registered as /dev/mmcsdN where N is the minor number + * slotno - The slot number to use. This is only meaningful for architectures + * that support multiple MMC/SD slots. This value must be in the range + * {0, ..., CONFIG_MMCSD_NSLOTS}. + * spi - And instance of an SPI interface obtained by called + * up_spiinitialize() with the appropriate port number (see spi.h) + * + ****************************************************************************/ + +int mmcsd_spislotinitialize(int minor, int slotno, FAR struct spi_dev_s *spi) +{ + struct mmcsd_slot_s *slot; + char devname[16]; + int ret; + +#ifdef CONFIG_DEBUG + if ((unsigned)slotno >= CONFIG_MMCSD_NSLOTS || (unsigned)minor > 255 || !spi) + { + fdbg("Invalid arguments\n"); + return -EINVAL; + } +#endif + + /* Select the slot structure */ + + slot = &g_mmcsdslot[slotno]; + memset(slot, 0, sizeof(struct mmcsd_slot_s)); + sem_init(&slot->sem, 0, 1); + +#ifdef CONFIG_DEBUG + if (slot->spi) + { + fdbg("Already registered\n"); + return -EBUSY; + } +#endif + + /* Bind the SPI port to the slot */ + + slot->spi = spi; + + /* Ininitialize for the media in the slot (if any) */ + + ret = mmcsd_mediainitialize(slot); + if (ret == 0) + { + fvdbg("mmcsd_mediainitialize returned OK\n"); + slot->state |= MMCSD_SLOTSTATUS_MEDIACHGD; + } + + /* Create a MMC/SD device name */ + + snprintf(devname, 16, "/dev/mmcsd%d", minor); + + /* Register the driver, even on a failure condition. A + * card may be inserted later, for example. + */ + + ret = register_blockdriver(devname, &g_bops, MMCSD_MODE, slot); + if (ret < 0) + { + fdbg("register_blockdriver failed: %d\n", -ret); + slot->spi = NULL; + return ret; + } + + /* Register a media change callback to handler insertion and + * removal of cards. + */ + + (void)SPI_REGISTERCALLBACK(spi, mmcsd_mediachanged, (void*)slot); + return OK; +} diff --git a/nuttx/drivers/mmcsd/mmcsd_spi.h b/nuttx/drivers/mmcsd/mmcsd_spi.h new file mode 100644 index 000000000..055862beb --- /dev/null +++ b/nuttx/drivers/mmcsd/mmcsd_spi.h @@ -0,0 +1,187 @@ +/**************************************************************************** + * drivers/mmcsd/mmcsd_spi.h + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_MMCSD_MMCSD_SPI_H +#define __DRIVERS_MMCSD_MMCSD_SPI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/* SPI *******************************************************************/ + +/* SPI Command Set */ + +#define CMD0 0x40 /* GO_IDLE_STATE: Resets all cards to idle state */ +#define CMD1 0x41 /* SEND_OP_COND: Sends capacity support information */ +#define CMD6 0x46 /* SWITCH_FUNC: Checks switchable function */ +#define CMD8 0x48 /* SEND_IF_COND: Sends SD Memory Card interface condition */ +#define CMD9 0x49 /* SEND_CSD: Asks card to send its card specific data (CSD) */ +#define CMD10 0x4a /* SEND_CID: Asks card to send its card identification (CID) */ +#define CMD12 0x4c /* STOP_TRANSMISSION: Forces the card to stop transmission */ +#define CMD13 0x4d /* SEND_STATUS: Asks card to send its status register */ +#define CMD16 0x50 /* SET_BLOCKLEN: Sets a block length (in bytes) */ +#define CMD17 0x51 /* READ_SINGLE_BLOCK: Reads a block of the selected size */ +#define CMD18 0x52 /* READ_MULTIPLE_BLOCK: Continuously transfers blocks from card to host */ +#define CMD20 0x54 /* CMD_WRITEBLOCK: Write block to memory (MMC) */ +#define CMD24 0x58 /* WRITE_BLOCK: Writes a block of the selected size */ +#define CMD25 0x59 /* WRITE_MULTIPLE_BLOCK: Continuously writes blocks of data */ +#define CMD27 0x5b /* PROGRAM_CSD: Set programmable bits of the CSD */ +#define CMD28 0x5c /* SET_WRITE_PROT: Sets the write protection bit of group */ +#define CMD29 0x5d /* CLR_WRITE_PROT: Clears the write protection bit of group */ +#define CMD30 0x5e /* SEND_WRITE_PROT: Asks card to send state of write protection bits */ +#define CMD32 0x60 /* ERASE_WR_BLK_START_ADDR: Sets address of first block to erase */ +#define CMD33 0x61 /* ERASE_WR_BLK_END_ADDR: Sets address of last block to erase */ +#define CMD34 0x62 /* UNTAG_SECTOR: (MMC) */ +#define CMD35 0x63 /* TAG_ERASE_GROUP_START: (MMC) */ +#define CMD36 0x64 /* TAG_ERASE_GOUPR_END: (MMC) */ +#define CMD37 0x65 /* UNTAG_ERASE_GROUP: (MMC) */ +#define CMD38 0x66 /* ERASE: Erases all previously selected write blocks */ +#define CMD40 0x68 /* CRC_ON_OFF: (MMC) */ +#define CMD42 0x6a /* LOCK_UNLOCK: Used to Set/Reset the Password or lock/unlock card */ +#define CMD55 0x77 /* APP_CMD: Tells card that the next command is an application specific command */ +#define CMD56 0x78 /* GEN_CMD: Used transfer a block to or get block from card */ +#define CMD58 0x7a /* READ_OCR :Reads the OCR register of a card */ +#define CMD59 0x7b /* CRC_ON_OFF: Turns the CRC option on or off */ +#define ACMD13 0x4d /* SD_STATUS: Send the SD Status */ +#define ACMD22 0x56 /* SEND_NUM_WR_BLOCKS: Send number of the errorfree blocks */ +#define ACMD23 0x57 /* SET_WR_BLK_ERASE_COUNT: Set number blocks to erase before writing */ +#define ACMD41 0x69 /* SD_SEND_OP_COND: Sends host capacity support information */ +#define ACMD42 0x6a /* SET_CLR_CARD_DETECT: Connect/disconnect pull-up resistor on CS */ +#define ACMD51 0x73 /* SEND_SCR: Reads the SD Configuration Register (SCR) */ + +/* SPI 8-bit R1 response */ + +#define MMCSD_SPIR1_OK 0x00 /* No error bits set */ +#define MMCSD_SPIR1_IDLESTATE 0x01 /* Idle state */ +#define MMCSD_SPIR1_ERASERESET 0x02 /* Erase reset */ +#define MMCSD_SPIR1_ILLEGALCMD 0x04 /* Illegal command */ +#define MMCSD_SPIR1_CRCERROR 0x08 /* Com CRC error */ +#define MMCSD_SPIR1_ERASEERROR 0x10 /* Erase sequence error */ +#define MMCSD_SPIR1_ADDRERROR 0x20 /* Address error */ +#define MMCSD_SPIR1_PARAMERROR 0x40 /* Parameter error */ + +/* SPI 8-bit R2 response */ + +#define MMCSD_SPIR2_CARDLOCKED 0x0001 /* Card is locked */ +#define MMCSD_SPIR2_WPERASESKIP 0x0002 /* WP erase skip */ +#define MMCSD_SPIR2_LOCKFAIL 0x0002 /* Lock/unlock cmd failed */ +#define MMCSD_SPIR2_ERROR 0x0004 /* Error */ +#define MMCSD_SPIR2_CCERROR 0x0008 /* CC error */ +#define MMCSD_SPIR2_CARDECCFAIL 0x0010 /* Card ECC failed */ +#define MMCSD_SPIR2_WPVIOLATION 0x0020 /* WP violoation */ +#define MMCSD_SPIR2_ERASEPARAM 0x0040 /* Erase parameter */ +#define MMCSD_SPIR2_OUTOFRANGE 0x0080 /* Out of range */ +#define MMCSD_SPIR2_CSDOVERWRITE 0x0080 /* CSD overwrite */ +#define MMCSD_SPIR2_IDLESTATE 0x0100 /* In idle state */ +#define MMCSD_SPIR2_ERASERESET 0x0200 /* Erase reset */ +#define MMCSD_SPIR2_ILLEGALCMD 0x0400 /* Illegal command */ +#define MMCSD_SPIR2_CRCERROR 0x0800 /* Com CRC error */ +#define MMCSD_SPIR2_ERASEERROR 0x1000 /* Erase sequence error */ +#define MMCSD_SPIR2_ADDRERROR 0x2000 /* Address error */ +#define MMCSD_SPIR2_PARAMERROR 0x4000 /* Parameter error */ + +/* Last 4 bytes of the 5 byte R7 response */ + +#define MMCSD_SPIR7_VERSION_SHIFT (28) /* Bits 28-31: Command version number */ +#define MMCSD_SPIR7_VERSION_MASK ((uint32_t)0x0f << MMCSD_SPIR7_VERSION_SHIFT) +#define MMCSD_SPIR7_VOLTAGE_SHIFT (8) /* Bits 8-11: Voltage accepted */ +#define MMCSD_SPIR7_VOLTAGE_MASK ((uint32_t)0x0f << MMCSD_SPIR7_VOLTAGE_SHIFT) +#define MMCSD_SPIR7_VOLTAGE_27 ((uint32_t)0x01 << MMCSD_SPIR7_VOLTAGE_SHIFT) /* 2.7-3.6V */ +#define MMCSD_SPIR7_ECHO_SHIFT (0) /* Bits 0-7: Echoed check pattern */ +#define MMCSD_SPIR7_ECHO_MASK ((uint32_t)0xff << MMCSD_SPIR7_ECHO_SHIFT) + +/* Data Response */ + +#define MMCSD_SPIDR_MASK 0x1f /* Mask for valid data response bits */ +#define MMCSD_SPIDR_ACCEPTED 0x05 /* Data accepted */ +#define MMCSD_SPIDR_CRCERROR 0x0b /* Data rejected due to CRC error */ +#define MMCSD_SPIDR_WRERROR 0x0d /* Data rejected due to write error */ + +/* Data Tokens */ + +#define MMCSD_SPIDT_STARTBLKSNGL 0xfe /* First byte of block, single block */ +#define MMCSD_SPIDT_STARTBLKMULTI 0xfc /* First byte of block, multi-block */ +#define MMCSD_SPIDT_STOPTRANS 0xfd /* Stop transmission */ + +/* Data error token */ + +#define MMCSD_SPIDET_UPPER 0xf0 /* The upper four bits are zero */ +#define MMCSD_SPIDET_ERROR 0x01 /* Error */ +#define MMCSD_SPIDET_CCERROR 0x02 /* CC error */ +#define MMCSD_SPIDET_CARDECCFAIL 0x04 /* Card ECC failed */ +#define MMCSD_SPIDET_OUTOFRANGE 0x08 /* Out of range */ + +/* Operating Conditions register */ + +#define MMCSD_OCR_V27 ((uint32_t)1 << 15) /* Bit 15: 2.7-2.8V */ +#define MMCSD_OCR_V28 ((uint32_t)1 << 16) /* Bit 16: 2.8-2.9V */ +#define MMCSD_OCR_V29 ((uint32_t)1 << 17) /* Bit 17: 2.9-3.0V */ +#define MMCSD_OCR_V30 ((uint32_t)1 << 18) /* Bit 18: 3.0-3.1V */ +#define MMCSD_OCR_V31 ((uint32_t)1 << 19) /* Bit 19: 3.1-3.2V */ +#define MMCSD_OCR_V32 ((uint32_t)1 << 20) /* Bit 20: 3.2-3.3V */ +#define MMCSD_OCR_V33 ((uint32_t)1 << 21) /* Bit 21: 3.3-3.4V */ +#define MMCSD_OCR_V34 ((uint32_t)1 << 22) /* Bit 22: 3.4-3.5V */ +#define MMCSD_OCR_V35 ((uint32_t)1 << 23) /* Bit 23: 3.5-3.6V */ +#define MMCSD_OCR_CCS ((uint32_t)1 << 30) /* Bit 30: Card capacity status */ +#define MMCSD_OCR_BUSY ((uint32_t)1 << 31) /* Bit 31: Card powered up status bit */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif +#endif /* __DRIVERS_MMCSD_MMCSD_SPI_H */ diff --git a/nuttx/drivers/mtd/Make.defs b/nuttx/drivers/mtd/Make.defs new file mode 100644 index 000000000..2f4b9e86d --- /dev/null +++ b/nuttx/drivers/mtd/Make.defs @@ -0,0 +1,49 @@ +############################################################################ +# drivers/mtd/Make.defs +# This driver supports a block of RAM as a NuttX MTD device +# +# Copyright (C) 2009-2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Include MTD drivers + +CSRCS += at45db.c flash_eraseall.c ftl.c m25px.c rammtd.c ramtron.c + +ifeq ($(CONFIG_MTD_AT24XX),y) +CSRCS += at24xx.c +endif + +# Include MTD driver support + +DEPPATH += --dep-path mtd +VPATH += :mtd + diff --git a/nuttx/drivers/mtd/at24xx.c b/nuttx/drivers/mtd/at24xx.c new file mode 100644 index 000000000..67d7f1f52 --- /dev/null +++ b/nuttx/drivers/mtd/at24xx.c @@ -0,0 +1,429 @@ +/************************************************************************************ + * drivers/mtd/at24xx.c + * Driver for I2C-based at24cxx EEPROM(at24c32,at24c64,at24c128,at24c256) + * + * Copyright (C) 2011 Li Zhuoyi. All rights reserved. + * Author: Li Zhuoyi <lzyy.cn@gmail.com> + * History: 0.1 2011-08-20 initial version + * + * 2011-11-1 Added support for larger MTD block sizes: Hal Glenn <hglenn@2g-eng.com> + * + * Derived from drivers/mtd/m25px.c + * + * Copyright (C) 2009-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/ioctl.h> +#include <nuttx/i2c.h> +#include <nuttx/mtd.h> + +#ifdef CONFIG_MTD_AT24XX + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* As a minimum, the size of the AT24 part and its 7-bit I2C address are required. */ + +#ifndef CONFIG_AT24XX_SIZE +# warning "Assuming AT24 size 64" +# define CONFIG_AT24XX_SIZE 64 +#endif +#ifndef CONFIG_AT24XX_ADDR +# warning "Assuming AT24 address of 0x50" +# define CONFIG_AT24XX_ADDR 0x50 +#endif + +/* Get the part configuration based on the size configuration */ + +#if CONFIG_AT24XX_SIZE == 32 +# define AT24XX_NPAGES 128 +# define AT24XX_PAGESIZE 32 +#elif CONFIG_AT24XX_SIZE == 48 +# define AT24XX_NPAGES 192 +# define AT24XX_PAGESIZE 32 +#elif CONFIG_AT24XX_SIZE == 64 +# define AT24XX_NPAGES 256 +# define AT24XX_PAGESIZE 32 +#elif CONFIG_AT24XX_SIZE == 128 +# define AT24XX_NPAGES 256 +# define AT24XX_PAGESIZE 64 +#elif CONFIG_AT24XX_SIZE == 256 +# define AT24XX_NPAGES 512 +# define AT24XX_PAGESIZE 64 +#endif + +/* For applications where a file system is used on the AT24, the tiny page sizes + * will result in very inefficient FLASH usage. In such cases, it is better if + * blocks are comprised of "clusters" of pages so that the file system block + * size is, say, 256 or 512 bytes. In any event, the block size *must* be an + * even multiple of the pages. + */ + +#ifndef CONFIG_AT24XX_MTD_BLOCKSIZE +# warning "Assuming driver block size is the same as the FLASH page size" +# define CONFIG_AT24XX_MTD_BLOCKSIZE AT24XX_PAGESIZE +#endif + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +/* This type represents the state of the MTD device. The struct mtd_dev_s + * must appear at the beginning of the definition so that you can freely + * cast between pointers to struct mtd_dev_s and struct at24c_dev_s. + */ + +struct at24c_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct i2c_dev_s *dev; /* Saved I2C interface instance */ + uint8_t addr; /* I2C address */ + uint16_t pagesize; /* 32, 63 */ + uint16_t npages; /* 128, 256, 512, 1024 */ +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* MTD driver methods */ + +static int at24c_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t at24c_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t at24c_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t at24c_read(FAR struct mtd_dev_s *dev, off_t offset, + size_t nbytes,FAR uint8_t *buffer); +static int at24c_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/* At present, only a signal AT24 part is supported. In this case, a statically + * allocated state structure may be used. + */ + +static struct at24c_dev_s g_at24c; + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +static int at24c_eraseall(FAR struct at24c_dev_s *priv) +{ + int startblock = 0; + uint8_t buf[AT24XX_PAGESIZE + 2]; + + memset(&buf[2],0xff,priv->pagesize); + I2C_SETADDRESS(priv->dev,priv->addr,7); + I2C_SETFREQUENCY(priv->dev,100000); + + for (startblock = 0; startblock < priv->npages; startblock++) + { + uint16_t offset = startblock * priv->pagesize; + buf[1] = offset & 0xff; + buf[0] = (offset >> 8) & 0xff; + + while (I2C_WRITE(priv->dev, buf, 2) < 0) + { + usleep(1000); + } + I2C_WRITE(priv->dev, buf, priv->pagesize + 2); + } + + return OK; +} + +/************************************************************************************ + * Name: at24c_erase + ************************************************************************************/ + +static int at24c_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + /* EEprom need not erase */ + + return (int)nblocks; +} + +/************************************************************************************ + * Name: at24c_bread + ************************************************************************************/ + +static ssize_t at24c_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buffer) +{ + FAR struct at24c_dev_s *priv = (FAR struct at24c_dev_s *)dev; + size_t blocksleft; + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + startblock *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); + nblocks *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#endif + blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + if (startblock >= priv->npages) + { + return 0; + } + + if (startblock + nblocks > priv->npages) + { + nblocks = priv->npages - startblock; + } + + I2C_SETADDRESS(priv->dev,priv->addr,7); + I2C_SETFREQUENCY(priv->dev,100000); + + while (blocksleft-- > 0) + { + uint16_t offset = startblock * priv->pagesize; + uint8_t buf[2]; + buf[1] = offset & 0xff; + buf[0] = (offset >> 8) & 0xff; + + while (I2C_WRITE(priv->dev, buf, 2) < 0) + { + fvdbg("wait\n"); + usleep(1000); + } + + I2C_READ(priv->dev, buffer, priv->pagesize); + startblock++; + buffer += priv->pagesize; + } + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + return nblocks / (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#else + return nblocks; +#endif +} + +/************************************************************************************ + * Name: at24c_bwrite + * + * Operates on MTD block's and translates to FLASH pages + * + ************************************************************************************/ + +static ssize_t at24c_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct at24c_dev_s *priv = (FAR struct at24c_dev_s *)dev; + size_t blocksleft; + uint8_t buf[AT24XX_PAGESIZE+2]; + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + startblock *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); + nblocks *= (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#endif + blocksleft = nblocks; + + if (startblock >= priv->npages) + { + return 0; + } + + if (startblock + nblocks > priv->npages) + { + nblocks = priv->npages - startblock; + } + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + I2C_SETADDRESS(priv->dev, priv->addr, 7); + I2C_SETFREQUENCY(priv->dev, 100000); + + while (blocksleft-- > 0) + { + uint16_t offset = startblock * priv->pagesize; + while (I2C_WRITE(priv->dev, (uint8_t *)&offset, 2) < 0) + { + fvdbg("wait\n"); + usleep(1000); + } + + buf[1] = offset & 0xff; + buf[0] = (offset >> 8) & 0xff; + memcpy(&buf[2], buffer, priv->pagesize); + + I2C_WRITE(priv->dev, buf, priv->pagesize + 2); + startblock++; + buffer += priv->pagesize; + } + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + return nblocks / (CONFIG_AT24XX_MTD_BLOCKSIZE / AT24XX_PAGESIZE); +#else + return nblocks; +#endif +} + +/************************************************************************************ + * Name: at24c_ioctl + ************************************************************************************/ + +static int at24c_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct at24c_dev_s *priv = (FAR struct at24c_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("cmd: %d \n", cmd); + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + * + * blocksize: + * May be user defined. The block size for the at24XX devices may be + * larger than the page size in order to better support file systems. + * The read and write functions translate BLOCKS to pages for the + * small flash devices + * erasesize: + * It has to be at least as big as the blocksize, bigger serves no + * purpose. + * neraseblocks + * Note that the device size is in kilobits and must be scaled by + * 1024 / 8 + */ + +#if CONFIG_AT24XX_MTD_BLOCKSIZE > AT24XX_PAGESIZE + geo->blocksize = CONFIG_AT24XX_MTD_BLOCKSIZE; + geo->erasesize = CONFIG_AT24XX_MTD_BLOCKSIZE; + geo->neraseblocks = (CONFIG_AT24XX_SIZE * 1024 / 8) / CONFIG_AT24XX_MTD_BLOCKSIZE; +#else + geo->blocksize = priv->pagesize; + geo->erasesize = priv->pagesize; + geo->neraseblocks = priv->npages; +#endif + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + ret=at24c_eraseall(priv); + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: at24c_initialize + * + * Description: + * Create an initialize MTD device instance. MTD devices are not registered + * in the file system, but are created as instances that can be bound to + * other functions (such as a block or character driver front end). + * + ************************************************************************************/ + +FAR struct mtd_dev_s *at24c_initialize(FAR struct i2c_dev_s *dev) +{ + FAR struct at24c_dev_s *priv; + + fvdbg("dev: %p\n", dev); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per I2C + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same I2C bus. + */ + + priv = &g_at24c; + if (priv) + { + /* Initialize the allocated structure */ + + priv->addr = CONFIG_AT24XX_ADDR; + priv->pagesize = AT24XX_PAGESIZE; + priv->npages = AT24XX_NPAGES; + + priv->mtd.erase = at24c_erase; + priv->mtd.bread = at24c_bread; + priv->mtd.bwrite = at24c_bwrite; + priv->mtd.ioctl = at24c_ioctl; + priv->dev = dev; + } + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; +} + +#endif diff --git a/nuttx/drivers/mtd/at45db.c b/nuttx/drivers/mtd/at45db.c new file mode 100644 index 000000000..fab9d0d9b --- /dev/null +++ b/nuttx/drivers/mtd/at45db.c @@ -0,0 +1,899 @@ +/************************************************************************************ + * drivers/mtd/at45db.c + * Driver for SPI-based AT45DB161D (16Mbit) + * + * Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +/* Ordering Code Detail: + * + * AT 45DB 16 1 D – SS U + * | | | | | | `- Device grade + * | | | | | `- Package Option + * | | | | `- Device revision + * | | | `- Interface: 1=serial + * | | `- Capacity: 16=16Mbit + * | `- Product family + * `- Atmel designator + */ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/ioctl.h> +#include <nuttx/spi.h> +#include <nuttx/mtd.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* Configuration ********************************************************************/ +/* CONFIG_AT45DB_PREWAIT enables higher performance write logic: We leave the chip + * busy after write and erase operations. This improves write and erase performance + * because we do not have to wait as long between transactions (other processing can + * occur while the chip is busy) but means that the chip must stay powered: + */ + +#if defined(CONFIG_AT45DB_PWRSAVE) && defined(CONFIG_AT45DB_PREWAIT) +# error "Both CONFIG_AT45DB_PWRSAVE and CONFIG_AT45DB_PREWAIT are defined" +#endif + +/* If the user has provided no frequency, use 1MHz */ + +#ifndef CONFIG_AT45DB_FREQUENCY +# define CONFIG_AT45DB_FREQUENCY 1000000 +#endif + +/* SPI Commands *********************************************************************/ + +/* Read commands */ + +#define AT45DB_RDMN 0xd2 /* Main Memory Page Read */ +#define AT45DB_RDARRY 0xe8 /* Continuous Array Read (Legacy Command) */ +#define AT45DB_RDARRAYLF 0x03 /* Continuous Array Read (Low Frequency) */ +#define AT45DB_RDARRAYHF 0x0b /* Continuous Array Read (High Frequency) */ +#define AT45DB_RDBF1LF 0xd1 /* Buffer 1 Read (Low Frequency) */ +#define AT45DB_RDBF2LF 0xd3 /* Buffer 2 Read (Low Frequency) */ +#define AT45DB_RDBF1 0xd4 /* Buffer 1 Read */ +#define AT45DB_RDBF2 0xd6 /* Buffer 2 Read */ + +/* Program and Erase Commands */ + +#define AT45DB_WRBF1 0x84 /* Buffer 1 Write */ +#define AT45DB_WRBF2 0x87 /* Buffer 2 Write */ +#define AT45DB_BF1TOMNE 0x83 /* Buffer 1 to Main Memory Page Program with Built-in Erase */ +#define AT45DB_BF2TOMNE 0x86 /* Buffer 2 to Main Memory Page Program with Built-in Erase */ +#define AT45DB_BF1TOMN 0x88 /* Buffer 1 to Main Memory Page Program without Built-in Erase */ +#define AT45DB_BF2TOMN 0x89 /* Buffer 2 to Main Memory Page Program without Built-in Erase */ +#define AT45DB_PGERASE 0x81 /* Page Erase */ +#define AT45DB_BLKERASE 0x50 /* Block Erase */ +#define AT45DB_SECTERASE 0x7c /* Sector Erase */ +#define AT45DB_CHIPERASE1 0xc7 /* Chip Erase - byte 1 */ +# define AT45DB_CHIPERASE2 0x94 /* Chip Erase - byte 2 */ +# define AT45DB_CHIPERASE3 0x80 /* Chip Erase - byte 3 */ +# define AT45DB_CHIPERASE4 0x9a /* Chip Erase - byte 4 */ +#define AT45DB_MNTHRUBF1 0x82 /* Main Memory Page Program Through Buffer 1 */ +#define AT45DB_MNTHRUBF2 0x85 /* Main Memory Page Program Through Buffer 2 */ + +/* Protection and Security Commands */ + +#define AT45DB_ENABPROT1 0x3d /* Enable Sector Protection - byte 1 */ +# define AT45DB_ENABPROT2 0x2a /* Enable Sector Protection - byte 2 */ +# define AT45DB_ENABPROT3 0x7f /* Enable Sector Protection - byte 3 */ +# define AT45DB_ENABPROT4 0xa9 /* Enable Sector Protection - byte 4 */ +#define AT45DB_DISABPROT1 0x3d /* Disable Sector Protection - byte 1 */ +# define AT45DB_DISABPROT2 0x2a /* Disable Sector Protection - byte 2 */ +# define AT45DB_DISABPROT3 0x7f /* Disable Sector Protection - byte 3 */ +# define AT45DB_DISABPROT4 0x9a /* Disable Sector Protection - byte 4 */ +#define AT45DB_ERASEPROT1 0x3d /* Erase Sector Protection Register - byte 1 */ +# define AT45DB_ERASEPROT2 0x2a /* Erase Sector Protection Register - byte 2 */ +# define AT45DB_ERASEPROT3 0x7f /* Erase Sector Protection Register - byte 3 */ +# define AT45DB_ERASEPROT4 0xcf /* Erase Sector Protection Register - byte 4 */ +#define AT45DB_PROGPROT1 0x3d /* Program Sector Protection Register - byte 1 */ +# define AT45DB_PROGPROT2 0x2a /* Program Sector Protection Register - byte 2 */ +# define AT45DB_PROGPROT3 0x7f /* Program Sector Protection Register - byte 3 */ +# define AT45DB_PROGPROT4 0xfc /* Program Sector Protection Register - byte 4 */ +#define AT45DB_RDPROT 0x32 /* Read Sector Protection Register */ +#define AT45DB_LOCKDOWN1 0x3d /* Sector Lockdown - byte 1 */ +# define AT45DB_LOCKDOWN2 0x2a /* Sector Lockdown - byte 2 */ +# define AT45DB_LOCKDOWN3 0x7f /* Sector Lockdown - byte 3 */ +# define AT45DB_LOCKDOWN4 0x30 /* Sector Lockdown - byte 4 */ +#define AT45DB_RDLOCKDOWN 0x35 /* Read Sector Lockdown Register */ +#define AT45DB_PROGSEC1 0x9b /* Program Security Register - byte 1 */ +# define AT45DB_PROGSEC2 0x00 /* Program Security Register - byte 2 */ +# define AT45DB_PROGSEC3 0x00 /* Program Security Register - byte 3 */ +# define AT45DB_PROGSEC4 0x00 /* Program Security Register - byte 4 */ +#define AT45DB_RDSEC 0x77 /* Read Security Register */ + +/* Additional commands */ + +#define AT45DB_MNTOBF1XFR 0x53 /* Main Memory Page to Buffer 1 Transfer */ +#define AT45DB_MNTOBF2XFR 0x55 /* Main Memory Page to Buffer 2 Transfer */ +#define AT45DB_MNBF1CMP 0x60 /* Main Memory Page to Buffer 1 Compare */ +#define AT45DB_MNBF2CMP 0x61 /* Main Memory Page to Buffer 2 Compare */ +#define AT45DB_AUTOWRBF1 0x58 /* Auto Page Rewrite through Buffer 1 */ +#define AT45DB_AUTOWRBF2 0x59 /* Auto Page Rewrite through Buffer 2 */ +#define AT45DB_PWRDOWN 0xb9 /* Deep Power-down */ +#define AT45DB_RESUME 0xab /* Resume from Deep Power-down */ +#define AT45DB_RDSR 0xd7 /* Status Register Read */ +#define AT45DB_RDDEVID 0x9f /* Manufacturer and Device ID Read */ + +#define AT45DB_MANUFACTURER 0x1f /* Manufacturer ID: Atmel */ +#define AT45DB_DEVID1_CAPMSK 0x1f /* Bits 0-4: Capacity */ +#define AT45DB_DEVID1_1MBIT 0x02 /* xxx0 0010 = 1Mbit AT45DB011 */ +#define AT45DB_DEVID1_2MBIT 0x03 /* xxx0 0012 = 2Mbit AT45DB021 */ +#define AT45DB_DEVID1_4MBIT 0x04 /* xxx0 0100 = 4Mbit AT45DB041 */ +#define AT45DB_DEVID1_8MBIT 0x05 /* xxx0 0101 = 8Mbit AT45DB081 */ +#define AT45DB_DEVID1_16MBIT 0x06 /* xxx0 0110 = 16Mbit AT45DB161 */ +#define AT45DB_DEVID1_32MBIT 0x07 /* xxx0 0111 = 32Mbit AT45DB321 */ +#define AT45DB_DEVID1_64MBIT 0x08 /* xxx0 1000 = 32Mbit AT45DB641 */ +#define AT45DB_DEVID1_FAMMSK 0xe0 /* Bits 5-7: Family */ +#define AT45DB_DEVID1_DFLASH 0x20 /* 001x xxxx = Dataflash */ +#define AT45DB_DEVID1_AT26DF 0x40 /* 010x xxxx = AT26DFxxx series (Not supported) */ +#define AT45DB_DEVID2_VERMSK 0x1f /* Bits 0-4: MLC mask */ +#define AT45DB_DEVID2_MLCMSK 0xe0 /* Bits 5-7: MLC mask */ + +/* Status register bit definitions */ + +#define AT45DB_SR_RDY (1 << 7) /* Bit 7: RDY/ Not BUSY */ +#define AT45DB_SR_COMP (1 << 6) /* Bit 6: COMP */ +#define AT45DB_SR_PROTECT (1 << 1) /* Bit 1: PROTECT */ +#define AT45DB_SR_PGSIZE (1 << 0) /* Bit 0: PAGE_SIZE */ + +/* 1 Block = 16 pages; 1 sector = 256 pages */ + +#define PG_PER_BLOCK (16) +#define PG_PER_SECTOR (256) + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +/* This type represents the state of the MTD device. The struct mtd_dev_s + * must appear at the beginning of the definition so that you can freely + * cast between pointers to struct mtd_dev_s and struct at45db_dev_s. + */ + +struct at45db_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *spi; /* Saved SPI interface instance */ + uint8_t pageshift; /* log2 of the page size (eg. 1 << 9 = 512) */ + uint32_t npages; /* Number of pages in the device */ +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Lock and per-transaction configuration */ + +static void at45db_lock(struct at45db_dev_s *priv); +static inline void at45db_unlock(struct at45db_dev_s *priv); + +/* Power management */ + +#ifdef CONFIG_AT45DB_PWRSAVE +static void at45db_pwrdown(struct at45db_dev_s *priv); +static void at45db_resume(struct at45db_dev_s *priv); +#else +# define at45db_pwrdown(priv) +# define at45db_resume(priv) +#endif + +/* Low-level AT45DB Helpers */ + +static inline int at45db_rdid(struct at45db_dev_s *priv); +static inline uint8_t at45db_rdsr(struct at45db_dev_s *priv); +static uint8_t at45db_waitbusy(struct at45db_dev_s *priv); +static inline void at45db_pgerase(struct at45db_dev_s *priv, off_t offset); +static inline int at32db_chiperase(struct at45db_dev_s *priv); +static inline void at45db_pgwrite(struct at45db_dev_s *priv, FAR const uint8_t *buffer, + off_t offset); + +/* MTD driver methods */ + +static int at45db_erase(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks); +static ssize_t at45db_bread(FAR struct mtd_dev_s *mtd, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t at45db_bwrite(FAR struct mtd_dev_s *mtd, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t at45db_read(FAR struct mtd_dev_s *mtd, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int at45db_ioctl(FAR struct mtd_dev_s *mtd, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/* Chip erase sequence */ + +#define CHIP_ERASE_SIZE 4 +static const uint8_t g_chiperase[CHIP_ERASE_SIZE] = {0xc7, 0x94, 0x80, 0x9a}; + +/* Sequence to program the device to binary page sizes{256, 512, 1024} */ + +#define BINPGSIZE_SIZE 4 +static const uint8_t g_binpgsize[BINPGSIZE_SIZE] = {0x3d, 0x2a, 0x80, 0xa6}; + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: at45db_lock + ************************************************************************************/ + +static void at45db_lock(struct at45db_dev_s *priv) +{ + /* On SPI busses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the busses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusiv access to + * the SPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + SPI_LOCK(priv->spi, true); + + /* After locking the SPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the SPI is properly configured for the device. + * If the SPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + SPI_SETMODE(priv->spi, SPIDEV_MODE0); + SPI_SETBITS(priv->spi, 8); + (void)SPI_SETFREQUENCY(priv->spi, CONFIG_AT45DB_FREQUENCY); +} + +/************************************************************************************ + * Name: at45db_unlock + ************************************************************************************/ + +static inline void at45db_unlock(struct at45db_dev_s *priv) +{ + SPI_LOCK(priv->spi, false); +} + +/************************************************************************************ + * Name: at45db_pwrdown + ************************************************************************************/ + +#ifdef CONFIG_AT45DB_PWRSAVE +static void at45db_pwrdown(struct at45db_dev_s *priv) +{ + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SEND(priv->spi, AT45DB_PWRDOWN); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); +} +#endif + +/************************************************************************************ + * Name: at45db_resume + ************************************************************************************/ + +#ifdef CONFIG_AT45DB_PWRSAVE +static void at45db_resume(struct at45db_dev_s *priv) +{ + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SEND(priv->spi, AT45DB_RESUME); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + up_udelay(50); +} +#endif + +/************************************************************************************ + * Name: at45db_rdid + ************************************************************************************/ + +static inline int at45db_rdid(struct at45db_dev_s *priv) +{ + uint8_t capacity; + uint8_t devid[3]; + + fvdbg("priv: %p\n", priv); + + /* Configure the bus, and select this FLASH part. (The caller should alread have + * loced the bus for exclusive access) + */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + + /* Send the " Manufacturer and Device ID Read" command and read the next three + * ID bytes from the FLASH. + */ + + (void)SPI_SEND(priv->spi, AT45DB_RDDEVID); + SPI_RECVBLOCK(priv->spi, devid, 3); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + fvdbg("manufacturer: %02x devid1: %02x devid2: %02x\n", + devid[0], devid[1], devid[2]); + + /* Check for a valid manufacturer and memory family */ + + if (devid[0] == AT45DB_MANUFACTURER && + (devid[1] & AT45DB_DEVID1_FAMMSK) == AT45DB_DEVID1_DFLASH) + { + /* Okay.. is it a FLASH capacity that we understand? */ + + capacity = devid[1] & AT45DB_DEVID1_CAPMSK; + switch (capacity) + { + case AT45DB_DEVID1_1MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB011 */ + + priv->pageshift = 8; /* Page size = 256 bytes */ + priv->npages = 512; /* 512 pages */ + return OK; + + case AT45DB_DEVID1_2MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB021 */ + + priv->pageshift = 8; /* Page size = 256/264 bytes */ + priv->npages = 1024; /* 1024 pages */ + return OK; + + case AT45DB_DEVID1_4MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB041 */ + + priv->pageshift = 8; /* Page size = 256/264 bytes */ + priv->npages = 2048; /* 2048 pages */ + return OK; + + case AT45DB_DEVID1_8MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB081 */ + + priv->pageshift = 8; /* Page size = 256/264 bytes */ + priv->npages = 4096; /* 4096 pages */ + return OK; + + case AT45DB_DEVID1_16MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB161 */ + + priv->pageshift = 9; /* Page size = 512/528 bytes */ + priv->npages = 4096; /* 4096 pages */ + return OK; + + case AT45DB_DEVID1_32MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB321 */ + + priv->pageshift = 9; /* Page size = 512/528 bytes */ + priv->npages = 8192; /* 8192 pages */ + return OK; + + case AT45DB_DEVID1_64MBIT: + /* Save the FLASH geometry for the 16Mbit AT45DB321 */ + + priv->pageshift = 10; /* Page size = 1024/1056 bytes */ + priv->npages = 8192; /* 8192 pages */ + return OK; + + default: + return -ENODEV; + } + } + + return -ENODEV; +} + +/************************************************************************************ + * Name: at45db_rdsr + ************************************************************************************/ + +static inline uint8_t at45db_rdsr(struct at45db_dev_s *priv) +{ + uint8_t retval; + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SEND(priv->spi, AT45DB_RDSR); + retval = SPI_SEND(priv->spi, 0xff); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + return retval; +} + +/************************************************************************************ + * Name: at45db_waitbusy + ************************************************************************************/ + +static uint8_t at45db_waitbusy(struct at45db_dev_s *priv) +{ + uint8_t sr; + + /* Poll the device, waiting for it to report that it is ready */ + + do + { + up_udelay(10); + sr = (uint8_t)at45db_rdsr(priv); + } + while ((sr & AT45DB_SR_RDY) == 0); + return sr; +} + +/************************************************************************************ + * Name: at45db_pgerase + ************************************************************************************/ + +static inline void at45db_pgerase(struct at45db_dev_s *priv, off_t sector) +{ + uint8_t erasecmd[4]; + off_t offset = sector << priv->pageshift; + + fvdbg("sector: %08lx\n", (long)sector); + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + /* "The Page Erase command can be used to individually erase any page in the main + * memory array allowing the Buffer to Main Memory Page Program to be utilized at a + * later time. ... To perform a page erase in the binary page size ..., the + * opcode 81H must be loaded into the device, followed by three address bytes + * ... When a low-to-high transition occurs on the CS pin, the part will erase the + * selected page (the erased state is a logical 1). ... the status register and the + * RDY/BUSY pin will indicate that the part is busy." + */ + + erasecmd[0] = AT45DB_PGERASE; /* Page erase command */ + erasecmd[1] = (offset >> 16) & 0xff; /* 24-bit offset MS bytes */ + erasecmd[2] = (offset >> 8) & 0xff; /* 24-bit offset middle bytes */ + erasecmd[3] = offset & 0xff; /* 24-bit offset LS bytes */ + + /* Erase the page */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, erasecmd, 4); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + /* Wait for any erase to complete if we are not trying to improve write + * performance. (see comments above). + */ + +#ifndef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + fvdbg("Erased\n"); +} + +/************************************************************************************ + * Name: at32db_chiperase + ************************************************************************************/ + +static inline int at32db_chiperase(struct at45db_dev_s *priv) +{ + fvdbg("priv: %p\n", priv); + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + /* "The entire main memory can be erased at one time by using the Chip Erase + * command. To execute the Chip Erase command, a 4-byte command sequence C7H, 94H, + * 80H and 9AH must be clocked into the device. ... After the last bit of the opcode + * sequence has been clocked in, the CS pin can be deasserted to start the erase + * process. ... the Status Register will indicate that the device is busy. The Chip + * Erase command will not affect sectors that are protected or locked down... + */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, g_chiperase, CHIP_ERASE_SIZE); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + /* Wait for any erase to complete if we are not trying to improve write + * performance. (see comments above). + */ + +#ifndef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + return OK; +} + +/************************************************************************************ + * Name: at45db_pgwrite + ************************************************************************************/ + +static inline void at45db_pgwrite(struct at45db_dev_s *priv, FAR const uint8_t *buffer, + off_t page) +{ + uint8_t wrcmd [4]; + off_t offset = page << priv->pageshift; + + fvdbg("page: %08lx offset: %08lx\n", (long)page, (long)offset); + + /* We assume that sectors are not write protected */ + + wrcmd[0] = AT45DB_MNTHRUBF1; /* To main memory through buffer 1 */ + wrcmd[1] = (offset >> 16) & 0xff; /* 24-bit address MS byte */ + wrcmd[2] = (offset >> 8) & 0xff; /* 24-bit address middle byte */ + wrcmd[3] = offset & 0xff; /* 24-bit address LS byte */ + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, wrcmd, 4); + SPI_SNDBLOCK(priv->spi, buffer, 1 << priv->pageshift); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + /* Wait for any erase to complete if we are not trying to improve write + * performance. (see comments above). + */ + +#ifndef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + fvdbg("Written\n"); +} + +/************************************************************************************ + * Name: at45db_erase + ************************************************************************************/ + +static int at45db_erase(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + size_t pgsleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Take the lock so that we have exclusive access to the bus, then power up the + * FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Then erase each page */ + + while (pgsleft-- > 0) + { + /* Erase each sector */ + + at45db_pgerase(priv, startblock); + startblock++; + } + + at45db_pwrdown(priv); + at45db_unlock(priv); + return (int)nblocks; +} + +/************************************************************************************ + * Name: at45db_bread + ************************************************************************************/ + +static ssize_t at45db_bread(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks, + FAR uint8_t *buffer) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + ssize_t nbytes; + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = at45db_read(mtd, startblock << priv->pageshift, nblocks << priv->pageshift, buffer); + if (nbytes > 0) + { + return nbytes >> priv->pageshift; + } + return nbytes; +} + +/************************************************************************************ + * Name: at45db_bwrite + ************************************************************************************/ + +static ssize_t at45db_bwrite(FAR struct mtd_dev_s *mtd, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + size_t pgsleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Take the lock so that we have exclusive access to the bus, then power up the + * FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Write each page to FLASH */ + + while (pgsleft-- > 0) + { + at45db_pgwrite(priv, buffer, startblock); + startblock++; + } + + at45db_pwrdown(priv); + at45db_unlock(priv); + + return nblocks; +} + +/************************************************************************************ + * Name: at45db_read + ************************************************************************************/ + +static ssize_t at45db_read(FAR struct mtd_dev_s *mtd, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + uint8_t rdcmd [5]; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Set up for the read */ + + rdcmd[0] = AT45DB_RDARRAYHF; /* FAST_READ is safe at all supported SPI speeds. */ + rdcmd[1] = (offset >> 16) & 0xff; /* 24-bit address upper byte */ + rdcmd[2] = (offset >> 8) & 0xff; /* 24-bit address middle byte */ + rdcmd[3] = offset & 0xff; /* 24-bit address least significant byte */ + rdcmd[4] = 0; /* Dummy byte */ + + /* Take the lock so that we have exclusive access to the bus, then power up the + * FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Higher performance write logic: We leave the chip busy after write and erase + * operations. This improves write and erase performance because we do not have + * to wait as long between transactions (other processing can occur while the chip + * is busy) but means that the chip must stay powered and that we must check if + * the chip is still busy on each entry point. + */ + +#ifdef CONFIG_AT45DB_PREWAIT + at45db_waitbusy(priv); +#endif + + /* Perform the read */ + + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, rdcmd, 5); + SPI_RECVBLOCK(priv->spi, buffer, nbytes); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + + at45db_pwrdown(priv); + at45db_unlock(priv); + + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: at45db_ioctl + ************************************************************************************/ + +static int at45db_ioctl(FAR struct mtd_dev_s *mtd, int cmd, unsigned long arg) +{ + FAR struct at45db_dev_s *priv = (FAR struct at45db_dev_s *)mtd; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("cmd: %d \n", cmd); + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = (1 << priv->pageshift); + geo->erasesize = geo->blocksize; + geo->neraseblocks = priv->npages; + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Take the lock so that we have exclusive access to the bus, then + * power up the FLASH device. + */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Erase the entire device */ + + ret = at32db_chiperase(priv); + at45db_pwrdown(priv); + at45db_unlock(priv); + } + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: at45db_initialize + * + * Description: + * Create an initialize MTD device instance. MTD devices are not registered + * in the file system, but are created as instances that can be bound to + * other functions (such as a block or character driver front end). + * + ************************************************************************************/ + +FAR struct mtd_dev_s *at45db_initialize(FAR struct spi_dev_s *spi) +{ + FAR struct at45db_dev_s *priv; + uint8_t sr; + int ret; + + fvdbg("spi: %p\n", spi); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct at45db_dev_s *)kmalloc(sizeof(struct at45db_dev_s)); + if (priv) + { + /* Initialize the allocated structure */ + + priv->mtd.erase = at45db_erase; + priv->mtd.bread = at45db_bread; + priv->mtd.bwrite = at45db_bwrite; + priv->mtd.read = at45db_read; + priv->mtd.ioctl = at45db_ioctl; + priv->spi = spi; + + /* Deselect the FLASH */ + + SPI_SELECT(spi, SPIDEV_FLASH, false); + + /* Lock and configure the SPI bus. */ + + at45db_lock(priv); + at45db_resume(priv); + + /* Identify the FLASH chip and get its capacity */ + + ret = at45db_rdid(priv); + if (ret != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + + fdbg("Unrecognized\n"); + goto errout; + } + + /* Get the value of the status register (as soon as the device is ready) */ + + sr = at45db_waitbusy(priv); + + /* Check if the device is configured as 256, 512 or 1024 bytes-per-page device */ + + if ((sr & AT45DB_SR_PGSIZE) == 0) + { + /* No, re-program it for the binary page size. NOTE: A power cycle + * is required after the device has be re-programmed. + */ + + fdbg("Reprogramming page size\n"); + SPI_SELECT(priv->spi, SPIDEV_FLASH, true); + SPI_SNDBLOCK(priv->spi, g_binpgsize, BINPGSIZE_SIZE); + SPI_SELECT(priv->spi, SPIDEV_FLASH, false); + goto errout; + } + + /* Release the lock and power down the device */ + + at45db_pwrdown(priv); + at45db_unlock(priv); + } + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; + +/* On any failure, we need free memory allocations and release the lock that + * we hold on the SPI bus. On failures, assume that we cannot talk to the + * device to do any more. + */ + +errout: + at45db_unlock(priv); + kfree(priv); + return NULL; +} diff --git a/nuttx/drivers/mtd/flash_eraseall.c b/nuttx/drivers/mtd/flash_eraseall.c new file mode 100644 index 000000000..5f607da35 --- /dev/null +++ b/nuttx/drivers/mtd/flash_eraseall.c @@ -0,0 +1,117 @@ +/**************************************************************************** + * drivers/mtd/flash_eraseall.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <errno.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/ioctl.h> +#include <nuttx/mtd.h> + +/**************************************************************************** + * Private Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: flash_eraseall + * + * Description: + * Call a block driver with the MDIOC_BULKERASE ioctl command. This will + * cause the MTD driver to erase all of the flash. + * + ****************************************************************************/ + +int flash_eraseall(FAR const char *driver) +{ + FAR struct inode *inode; + FAR const struct block_operations *ops; + int ret; + + /* Open the block driver */ + + ret = open_blockdriver(driver ,0, &inode); + if (ret < 0) + { + fdbg("ERROR: Failed to open '%s': %d\n", driver, ret); + return ret; + } + + /* Get the block operations */ + + ops = inode->u.i_bops; + + /* Invoke the block driver ioctl method */ + + ret = -EPERM; + if (ops->ioctl) + { + ret = ops->ioctl(inode, MTDIOC_BULKERASE, 0); + if (ret < 0) + { + fdbg("ERROR: MTD ioctl(%04x) failed: %d\n", MTDIOC_BULKERASE, ret); + } + } + + /* Close the block driver */ + + close_blockdriver(inode); + return ret; +} diff --git a/nuttx/drivers/mtd/ftl.c b/nuttx/drivers/mtd/ftl.c new file mode 100644 index 000000000..f01a19470 --- /dev/null +++ b/nuttx/drivers/mtd/ftl.c @@ -0,0 +1,578 @@ +/**************************************************************************** + * drivers/mtd/ftl.c + * + * Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <debug.h> +#include <errno.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> +#include <nuttx/ioctl.h> +#include <nuttx/mtd.h> +#include <nuttx/rwbuffer.h> + +/**************************************************************************** + * Private Definitions + ****************************************************************************/ + +#if defined(CONFIG_FS_READAHEAD) || (defined(CONFIG_FS_WRITABLE) && defined(CONFIG_FS_WRITEBUFFER)) +# defined CONFIG_FTL_RWBUFFER 1 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct ftl_struct_s +{ + FAR struct mtd_dev_s *mtd; /* Contained MTD interface */ + struct mtd_geometry_s geo; /* Device geometry */ +#ifdef CONFIG_FTL_RWBUFFER + struct rwbuffer_s rwb; /* Read-ahead/write buffer support */ +#endif + uint16_t blkper; /* R/W blocks per erase block */ +#ifdef CONFIG_FS_WRITABLE + FAR uint8_t *eblock; /* One, in-memory erase block */ +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int ftl_open(FAR struct inode *inode); +static int ftl_close(FAR struct inode *inode); +static ssize_t ftl_reload(FAR void *priv, FAR uint8_t *buffer, + off_t startblock, size_t nblocks); +static ssize_t ftl_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#ifdef CONFIG_FS_WRITABLE +static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer, + off_t startblock, size_t nblocks); +static ssize_t ftl_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#endif +static int ftl_geometry(FAR struct inode *inode, struct geometry *geometry); +static int ftl_ioctl(FAR struct inode *inode, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_bops = +{ + ftl_open, /* open */ + ftl_close, /* close */ + ftl_read, /* read */ +#ifdef CONFIG_FS_WRITABLE + ftl_write, /* write */ +#else + NULL, /* write */ +#endif + ftl_geometry, /* geometry */ + ftl_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ftl_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int ftl_open(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: ftl_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int ftl_close(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: ftl_reload + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t ftl_reload(FAR void *priv, FAR uint8_t *buffer, + off_t startblock, size_t nblocks) +{ + struct ftl_struct_s *dev = (struct ftl_struct_s *)priv; + ssize_t nread; + + /* Read the full erase block into the buffer */ + + nread = MTD_BREAD(dev->mtd, startblock, nblocks, buffer); + if (nread != nblocks) + { + fdbg("Read %d blocks starting at block %d failed: %d\n", + nblocks, startblock, nread); + } + return nread; +} + +/**************************************************************************** + * Name: ftl_read + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t ftl_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct ftl_struct_s *dev; + + fvdbg("sector: %d nsectors: %d\n", start_sector, nsectors); + + DEBUGASSERT(inode && inode->i_private); + dev = (struct ftl_struct_s *)inode->i_private; +#ifdef CONFIG_FS_READAHEAD + return rwb_read(&dev->rwb, start_sector, nsectors, buffer); +#else + return ftl_reload(dev, buffer, start_sector, nsectors); +#endif +} + +/**************************************************************************** + * Name: ftl_write + * + * Description: Write the specified number of sectors + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer, + off_t startblock, size_t nblocks) +{ + struct ftl_struct_s *dev = (struct ftl_struct_s *)priv; + off_t alignedblock; + off_t mask; + off_t rwblock; + off_t eraseblock; + off_t offset; + size_t remaining; + size_t nxfrd; + int nbytes; + int ret; + + /* Get the aligned block. Here is is assumed: (1) The number of R/W blocks + * per erase block is a power of 2, and (2) the erase begins with that same + * alignment. + */ + + mask = dev->blkper - 1; + alignedblock = (startblock + mask) & ~mask; + + /* Handle partial erase blocks before the first unaligned block */ + + remaining = nblocks; + if (alignedblock > startblock) + { + /* Read the full erase block into the buffer */ + + rwblock = startblock & ~mask; + nxfrd = MTD_BREAD(dev->mtd, rwblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Read erase block %d failed: %d\n", rwblock, nxfrd); + return -EIO; + } + + /* Then erase the erase block */ + + eraseblock = rwblock / dev->blkper; + ret = MTD_ERASE(dev->mtd, eraseblock, 1); + if (ret < 0) + { + fdbg("Erase block=%d failed: %d\n", eraseblock, ret); + return ret; + } + + /* Copy the user data at the end of the buffered erase block */ + + offset = (startblock & mask) * dev->geo.blocksize; + nbytes = dev->geo.erasesize - offset; + fvdbg("Copy %d bytes into erase block=%d at offset=%d\n", + nbytes, eraseblock, offset); + memcpy(dev->eblock + offset, buffer, nbytes); + + /* And write the erase back to flash */ + + nxfrd = MTD_BWRITE(dev->mtd, rwblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Write erase block %d failed: %d\n", rwblock, nxfrd); + return -EIO; + } + + /* Then update for amount written */ + + remaining -= dev->blkper - (startblock & mask); + buffer += nbytes; + } + + /* How handle full erase pages in the middle */ + + while (remaining >= dev->blkper) + { + /* Erase the erase block */ + + eraseblock = alignedblock / dev->blkper; + ret = MTD_ERASE(dev->mtd, eraseblock, 1); + if (ret < 0) + { + fdbg("Erase block=%d failed: %d\n", eraseblock, ret); + return ret; + } + + /* Write a full erase back to flash */ + + fvdbg("Write %d bytes into erase block=%d at offset=0\n", + dev->geo.erasesize, alignedblock); + nxfrd = MTD_BWRITE(dev->mtd, alignedblock, dev->blkper, buffer); + if (nxfrd != dev->blkper) + { + fdbg("Write erase block %d failed: %d\n", alignedblock, nxfrd); + return -EIO; + } + + /* Then update for amount written */ + + alignedblock += dev->blkper; + remaining -= dev->blkper; + buffer += dev->geo.erasesize; + } + + /* Finally, handler any partial blocks after the last full erase block */ + + if (remaining > 0) + { + /* Read the full erase block into the buffer */ + + nxfrd = MTD_BREAD(dev->mtd, alignedblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Read erase block %d failed: %d\n", alignedblock, nxfrd); + return -EIO; + } + + /* Then erase the erase block */ + + eraseblock = alignedblock / dev->blkper; + ret = MTD_ERASE(dev->mtd, eraseblock, 1); + if (ret < 0) + { + fdbg("Erase block=%d failed: %d\n", eraseblock, ret); + return ret; + } + + /* Copy the user data at the beginning the buffered erase block */ + + nbytes = remaining * dev->geo.blocksize; + fvdbg("Copy %d bytes into erase block=%d at offset=0\n", + nbytes, alignedblock); + memcpy(dev->eblock, buffer, nbytes); + + /* And write the erase back to flash */ + + nxfrd = MTD_BWRITE(dev->mtd, alignedblock, dev->blkper, dev->eblock); + if (nxfrd != dev->blkper) + { + fdbg("Write erase block %d failed: %d\n", alignedblock, nxfrd); + return -EIO; + } + } + + return nblocks; +} +#endif + +/**************************************************************************** + * Name: ftl_write + * + * Description: Write (or buffer) the specified number of sectors + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t ftl_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct ftl_struct_s *dev; + + fvdbg("sector: %d nsectors: %d\n", start_sector, nsectors); + + DEBUGASSERT(inode && inode->i_private); + dev = (struct ftl_struct_s *)inode->i_private; +#ifdef CONFIG_FS_WRITEBUFFER + return rwb_write(&dev->rwb, start_sector, nsectors, buffer); +#else + return ftl_flush(dev, buffer, start_sector, nsectors); +#endif +} +#endif + +/**************************************************************************** + * Name: ftl_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int ftl_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + struct ftl_struct_s *dev; + + fvdbg("Entry\n"); + + DEBUGASSERT(inode); + if (geometry) + { + dev = (struct ftl_struct_s *)inode->i_private; + geometry->geo_available = true; + geometry->geo_mediachanged = false; +#ifdef CONFIG_FS_WRITABLE + geometry->geo_writeenabled = true; +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = dev->geo.neraseblocks * dev->blkper; + geometry->geo_sectorsize = dev->geo.blocksize; + + fvdbg("available: true mediachanged: false writeenabled: %s\n", + geometry->geo_writeenabled ? "true" : "false"); + fvdbg("nsectors: %d sectorsize: %d\n", + geometry->geo_nsectors, geometry->geo_sectorsize); + + return OK; + } + return -EINVAL; +} + +/**************************************************************************** + * Name: ftl_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int ftl_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) +{ + struct ftl_struct_s *dev ; + int ret; + + fvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + /* Only one block driver ioctl command is supported by this driver (and + * that command is just passed on to the MTD driver in a slightly + * different form). + */ + + if (cmd == BIOC_XIPBASE) + { + /* The argument accompanying the BIOC_XIPBASE should be non-NULL. If + * DEBUG is enabled, we will catch it here instead of in the MTD + * driver. + */ + +#ifdef CONFIG_DEBUG + if (arg == 0) + { + fdbg("ERROR: BIOC_XIPBASE argument is NULL\n"); + return -EINVAL; + } +#endif + + /* Just change the BIOC_XIPBASE command to the MTDIOC_XIPBASE command. */ + + cmd = MTDIOC_XIPBASE; + } + + /* No other block driver ioctl commmands are not recognized by this + * driver. Other possible MTD driver ioctl commands are passed through + * to the MTD driver (unchanged). + */ + + dev = (struct ftl_struct_s *)inode->i_private; + ret = MTD_IOCTL(dev->mtd, cmd, arg); + if (ret < 0) + { + fdbg("ERROR: MTD ioctl(%04x) failed: %d\n", cmd, ret); + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ftl_initialize + * + * Description: + * Initialize to provide a block driver wrapper around an MTD interface + * + * Input Parameters: + * minor - The minor device number. The MTD block device will be + * registered as as /dev/mtdblockN where N is the minor number. + * mtd - The MTD device that supports the FLASH interface. + * + ****************************************************************************/ + +int ftl_initialize(int minor, FAR struct mtd_dev_s *mtd) +{ + struct ftl_struct_s *dev; + char devname[16]; + int ret = -ENOMEM; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (minor < 0 || minor > 255 || !mtd) + { + return -EINVAL; + } +#endif + + /* Allocate a FTL device structure */ + + dev = (struct ftl_struct_s *)kmalloc(sizeof(struct ftl_struct_s)); + if (dev) + { + /* Initialize the FTL device structure */ + + dev->mtd = mtd; + + /* Get the device geometry. (casting to uintptr_t first eliminates + * complaints on some architectures where the sizeof long is different + * from the size of a pointer). + */ + + ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&dev->geo)); + if (ret < 0) + { + fdbg("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret); + kfree(dev); + return ret; + } + + /* Allocate one, in-memory erase block buffer */ + +#ifdef CONFIG_FS_WRITABLE + dev->eblock = (FAR uint8_t *)kmalloc(dev->geo.erasesize); + if (!dev->eblock) + { + fdbg("Failed to allocate an erase block buffer\n"); + kfree(dev); + return -ENOMEM; + } +#endif + + /* Get the number of R/W blocks per erase block */ + + dev->blkper = dev->geo.erasesize / dev->geo.blocksize; + DEBUGASSERT(dev->blkper * dev->geo.blocksize == dev->geo.erasesize); + + /* Configure read-ahead/write buffering */ + +#ifdef CONFIG_FTL_RWBUFFER + dev->rwb.blocksize = dev->geo.blocksize; + dev->rwb.nblocks = dev->geo.neraseblocks * dev->blkper; + dev->rwb.dev = (FAR void *)dev; + +#if defined(CONFIG_FS_WRITABLE) && defined(CONFIG_FS_WRITEBUFFER) + dev->rwb.wrmaxblocks = dev->blkper; + dev->rwb.wrflush = ftl_flush; +#endif + +#ifdef CONFIG_FS_READAHEAD + dev->rwb.rhmaxblocks = dev->blkper; + dev->rwb.rhreload = ftl_reload; +#endif + ret = rwb_initialize(&dev->rwb); + if (ret < 0) + { + fdbg("rwb_initialize failed: %d\n", ret); + kfree(dev); + return ret; + } +#endif + + /* Create a MTD block device name */ + + snprintf(devname, 16, "/dev/mtdblock%d", minor); + + /* Inode private data is a reference to the FTL device structure */ + + ret = register_blockdriver(devname, &g_bops, 0, dev); + if (ret < 0) + { + fdbg("register_blockdriver failed: %d\n", -ret); + kfree(dev); + } + } + return ret; +} diff --git a/nuttx/drivers/mtd/m25px.c b/nuttx/drivers/mtd/m25px.c new file mode 100644 index 000000000..69962a7b6 --- /dev/null +++ b/nuttx/drivers/mtd/m25px.c @@ -0,0 +1,710 @@ +/************************************************************************************ + * drivers/mtd/m25px.c + * Driver for SPI-based M25P1 (128Kbit), M25P64 (64Mbit), and M25P128 (128Mbit) FLASH + * + * Copyright (C) 2009-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/ioctl.h> +#include <nuttx/spi.h> +#include <nuttx/mtd.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* Indentification register values */ + +#define M25P_MANUFACTURER 0x20 +#define M25P_MEMORY_TYPE 0x20 +#define M25P_M25P1_CAPACITY 0x11 /* 1 M-bit */ +#define M25P_M25P64_CAPACITY 0x17 /* 64 M-bit */ +#define M25P_M25P128_CAPACITY 0x18 /* 128 M-bit */ + +/* M25P1 capacity is 131,072 bytes: + * (4 sectors) * (32,768 bytes per sector) + * (512 pages) * (256 bytes per page) + */ + +#define M25P_M25P1_SECTOR_SHIFT 15 /* Sector size 1 << 15 = 65,536 */ +#define M25P_M25P1_NSECTORS 4 +#define M25P_M25P1_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_M25P1_NPAGES 512 + +/* M25P64 capacity is 8,338,608 bytes: + * (128 sectors) * (65,536 bytes per sector) + * (32768 pages) * (256 bytes per page) + */ + +#define M25P_M25P64_SECTOR_SHIFT 16 /* Sector size 1 << 16 = 65,536 */ +#define M25P_M25P64_NSECTORS 128 +#define M25P_M25P64_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_M25P64_NPAGES 32768 + +/* M25P128 capacity is 16,777,216 bytes: + * (64 sectors) * (262,144 bytes per sector) + * (65536 pages) * (256 bytes per page) + */ + +#define M25P_M25P128_SECTOR_SHIFT 18 /* Sector size 1 << 18 = 262,144 */ +#define M25P_M25P128_NSECTORS 64 +#define M25P_M25P128_PAGE_SHIFT 8 /* Page size 1 << 8 = 256 */ +#define M25P_M25P128_NPAGES 65536 + +/* Instructions */ +/* Command Value N Description Addr Dummy Data */ +#define M25P_WREN 0x06 /* 1 Write Enable 0 0 0 */ +#define M25P_WRDI 0x04 /* 1 Write Disable 0 0 0 */ +#define M25P_RDID 0x9f /* 1 Read Identification 0 0 1-3 */ +#define M25P_RDSR 0x05 /* 1 Read Status Register 0 0 >=1 */ +#define M25P_WRSR 0x01 /* 1 Write Status Register 0 0 1 */ +#define M25P_READ 0x03 /* 1 Read Data Bytes 3 0 >=1 */ +#define M25P_FAST_READ 0x0b /* 1 Higher speed read 3 1 >=1 */ +#define M25P_PP 0x02 /* 1 Page Program 3 0 1-256 */ +#define M25P_SE 0xd8 /* 1 Sector Erase 3 0 0 */ +#define M25P_BE 0xc7 /* 1 Bulk Erase 0 0 0 */ +#define M25P_RES 0xab /* 2 Read Electronic Signature 0 3 >=1 */ + +/* NOTE 1: Both parts, NOTE 2: M25P64 only */ + +/* Status register bit definitions */ + +#define M25P_SR_WIP (1 << 0) /* Bit 0: Write in progress bit */ +#define M25P_SR_WEL (1 << 1) /* Bit 1: Write enable latch bit */ +#define M25P_SR_BP_SHIFT (2) /* Bits 2-4: Block protect bits */ +#define M25P_SR_BP_MASK (7 << M25P_SR_BP_SHIFT) +# define M25P_SR_BP_NONE (0 << M25P_SR_BP_SHIFT) /* Unprotected */ +# define M25P_SR_BP_UPPER64th (1 << M25P_SR_BP_SHIFT) /* Upper 64th */ +# define M25P_SR_BP_UPPER32nd (2 << M25P_SR_BP_SHIFT) /* Upper 32nd */ +# define M25P_SR_BP_UPPER16th (3 << M25P_SR_BP_SHIFT) /* Upper 16th */ +# define M25P_SR_BP_UPPER8th (4 << M25P_SR_BP_SHIFT) /* Upper 8th */ +# define M25P_SR_BP_UPPERQTR (5 << M25P_SR_BP_SHIFT) /* Upper quarter */ +# define M25P_SR_BP_UPPERHALF (6 << M25P_SR_BP_SHIFT) /* Upper half */ +# define M25P_SR_BP_ALL (7 << M25P_SR_BP_SHIFT) /* All sectors */ +#define M25P_SR_SRWD (1 << 7) /* Bit 7: Status register write protect */ + +#define M25P_DUMMY 0xa5 + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +/* This type represents the state of the MTD device. The struct mtd_dev_s + * must appear at the beginning of the definition so that you can freely + * cast between pointers to struct mtd_dev_s and struct m25p_dev_s. + */ + +struct m25p_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *dev; /* Saved SPI interface instance */ + uint8_t sectorshift; /* 16 or 18 */ + uint8_t pageshift; /* 8 */ + uint16_t nsectors; /* 128 or 64 */ + uint32_t npages; /* 32,768 or 65,536 */ +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Helpers */ + +static void m25p_lock(FAR struct spi_dev_s *dev); +static inline void m25p_unlock(FAR struct spi_dev_s *dev); +static inline int m25p_readid(struct m25p_dev_s *priv); +static void m25p_waitwritecomplete(struct m25p_dev_s *priv); +static void m25p_writeenable(struct m25p_dev_s *priv); +static inline void m25p_sectorerase(struct m25p_dev_s *priv, off_t offset); +static inline int m25p_bulkerase(struct m25p_dev_s *priv); +static inline void m25p_pagewrite(struct m25p_dev_s *priv, FAR const uint8_t *buffer, + off_t offset); + +/* MTD driver methods */ + +static int m25p_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t m25p_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t m25p_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t m25p_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int m25p_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: m25p_lock + ************************************************************************************/ + +static void m25p_lock(FAR struct spi_dev_s *dev) +{ + /* On SPI busses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the busses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusiv access to + * the SPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + SPI_LOCK(dev, true); + + /* After locking the SPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the SPI is properly configured for the device. + * If the SPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + SPI_SETMODE(dev, SPIDEV_MODE3); + SPI_SETBITS(dev, 8); + (void)SPI_SETFREQUENCY(dev, 20000000); +} + +/************************************************************************************ + * Name: m25p_unlock + ************************************************************************************/ + +static inline void m25p_unlock(FAR struct spi_dev_s *dev) +{ + SPI_LOCK(dev, false); +} + +/************************************************************************************ + * Name: m25p_readid + ************************************************************************************/ + +static inline int m25p_readid(struct m25p_dev_s *priv) +{ + uint16_t manufacturer; + uint16_t memory; + uint16_t capacity; + + fvdbg("priv: %p\n", priv); + + /* Lock the SPI bus, configure the bus, and select this FLASH part. */ + + m25p_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Read ID (RDID)" command and read the first three ID bytes */ + + (void)SPI_SEND(priv->dev, M25P_RDID); + manufacturer = SPI_SEND(priv->dev, M25P_DUMMY); + memory = SPI_SEND(priv->dev, M25P_DUMMY); + capacity = SPI_SEND(priv->dev, M25P_DUMMY); + + /* Deselect the FLASH and unlock the bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + m25p_unlock(priv->dev); + + fvdbg("manufacturer: %02x memory: %02x capacity: %02x\n", + manufacturer, memory, capacity); + + /* Check for a valid manufacturer and memory type */ + + if (manufacturer == M25P_MANUFACTURER && memory == M25P_MEMORY_TYPE) + { + /* Okay.. is it a FLASH capacity that we understand? */ + + if (capacity == M25P_M25P1_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_M25P1_SECTOR_SHIFT; + priv->nsectors = M25P_M25P1_NSECTORS; + priv->pageshift = M25P_M25P1_PAGE_SHIFT; + priv->npages = M25P_M25P1_NPAGES; + return OK; + } + else if (capacity == M25P_M25P64_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_M25P64_SECTOR_SHIFT; + priv->nsectors = M25P_M25P64_NSECTORS; + priv->pageshift = M25P_M25P64_PAGE_SHIFT; + priv->npages = M25P_M25P64_NPAGES; + return OK; + } + else if (capacity == M25P_M25P128_CAPACITY) + { + /* Save the FLASH geometry */ + + priv->sectorshift = M25P_M25P128_SECTOR_SHIFT; + priv->nsectors = M25P_M25P128_NSECTORS; + priv->pageshift = M25P_M25P128_PAGE_SHIFT; + priv->npages = M25P_M25P128_NPAGES; + return OK; + } + } + + return -ENODEV; +} + +/************************************************************************************ + * Name: m25p_waitwritecomplete + ************************************************************************************/ + +static void m25p_waitwritecomplete(struct m25p_dev_s *priv) +{ + uint8_t status; + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read Status Register (RDSR)" command */ + + (void)SPI_SEND(priv->dev, M25P_RDSR); + + /* Loop as long as the memory is busy with a write cycle */ + + do + { + /* Send a dummy byte to generate the clock needed to shift out the status */ + + status = SPI_SEND(priv->dev, M25P_DUMMY); + } + while ((status & M25P_SR_WIP) != 0); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Complete\n"); +} + +/************************************************************************************ + * Name: m25p_writeenable + ************************************************************************************/ + +static void m25p_writeenable(struct m25p_dev_s *priv) +{ + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Write Enable (WREN)" command */ + + (void)SPI_SEND(priv->dev, M25P_WREN); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Enabled\n"); +} + +/************************************************************************************ + * Name: m25p_sectorerase + ************************************************************************************/ + +static inline void m25p_sectorerase(struct m25p_dev_s *priv, off_t sector) +{ + off_t offset = sector << priv->sectorshift; + + fvdbg("sector: %08lx\n", (long)sector); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Send write enable instruction */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Sector Erase (SE)" instruction */ + + (void)SPI_SEND(priv->dev, M25P_SE); + + /* Send the sector offset high byte first. For all of the supported + * parts, the sector number is completely contained in the first byte + * and the values used in the following two bytes don't really matter. + */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Erased\n"); +} + +/************************************************************************************ + * Name: m25p_bulkerase + ************************************************************************************/ + +static inline int m25p_bulkerase(struct m25p_dev_s *priv) +{ + fvdbg("priv: %p\n", priv); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Send write enable instruction */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Bulk Erase (BE)" instruction */ + + (void)SPI_SEND(priv->dev, M25P_BE); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Return: OK\n"); + return OK; +} + +/************************************************************************************ + * Name: m25p_pagewrite + ************************************************************************************/ + +static inline void m25p_pagewrite(struct m25p_dev_s *priv, FAR const uint8_t *buffer, + off_t page) +{ + off_t offset = page << priv->pageshift; + + fvdbg("page: %08lx offset: %08lx\n", (long)page, (long)offset); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Enable the write access to the FLASH */ + + m25p_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Page Program (PP)" command */ + + (void)SPI_SEND(priv->dev, M25P_PP); + + /* Send the page offset high byte first. */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Then write the specified number of bytes */ + + SPI_SNDBLOCK(priv->dev, buffer, 1 << priv->pageshift); + + /* Deselect the FLASH: Chip Select high */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Written\n"); +} + +/************************************************************************************ + * Name: m25p_erase + ************************************************************************************/ + +static int m25p_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + size_t blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock access to the SPI bus until we complete the erase */ + + m25p_lock(priv->dev); + while (blocksleft-- > 0) + { + /* Erase each sector */ + + m25p_sectorerase(priv, startblock); + startblock++; + } + m25p_unlock(priv->dev); + return (int)nblocks; +} + +/************************************************************************************ + * Name: m25p_bread + ************************************************************************************/ + +static ssize_t m25p_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buffer) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + ssize_t nbytes; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = m25p_read(dev, startblock << priv->pageshift, nblocks << priv->pageshift, buffer); + if (nbytes > 0) + { + return nbytes >> priv->pageshift; + } + return (int)nbytes; +} + +/************************************************************************************ + * Name: m25p_bwrite + ************************************************************************************/ + +static ssize_t m25p_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + size_t blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock the SPI bus and write each page to FLASH */ + + m25p_lock(priv->dev); + while (blocksleft-- > 0) + { + m25p_pagewrite(priv, buffer, startblock); + startblock++; + } + m25p_unlock(priv->dev); + + return nblocks; +} + +/************************************************************************************ + * Name: m25p_read + ************************************************************************************/ + +static ssize_t m25p_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + m25p_waitwritecomplete(priv); + + /* Lock the SPI bus and select this FLASH part */ + + m25p_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read from Memory " instruction */ + + (void)SPI_SEND(priv->dev, M25P_READ); + + /* Send the page offset high byte first. */ + + (void)SPI_SEND(priv->dev, (offset >> 16) & 0xff); + (void)SPI_SEND(priv->dev, (offset >> 8) & 0xff); + (void)SPI_SEND(priv->dev, offset & 0xff); + + /* Then read all of the requested bytes */ + + SPI_RECVBLOCK(priv->dev, buffer, nbytes); + + /* Deselect the FLASH and unlock the SPI bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + m25p_unlock(priv->dev); + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: m25p_ioctl + ************************************************************************************/ + +static int m25p_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct m25p_dev_s *priv = (FAR struct m25p_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("cmd: %d \n", cmd); + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = (1 << priv->pageshift); + geo->erasesize = (1 << priv->sectorshift); + geo->neraseblocks = priv->nsectors; + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + m25p_lock(priv->dev); + ret = m25p_bulkerase(priv); + m25p_unlock(priv->dev); + } + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: m25p_initialize + * + * Description: + * Create an initialize MTD device instance. MTD devices are not registered + * in the file system, but are created as instances that can be bound to + * other functions (such as a block or character driver front end). + * + ************************************************************************************/ + +FAR struct mtd_dev_s *m25p_initialize(FAR struct spi_dev_s *dev) +{ + FAR struct m25p_dev_s *priv; + int ret; + + fvdbg("dev: %p\n", dev); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct m25p_dev_s *)kmalloc(sizeof(struct m25p_dev_s)); + if (priv) + { + /* Initialize the allocated structure */ + + priv->mtd.erase = m25p_erase; + priv->mtd.bread = m25p_bread; + priv->mtd.bwrite = m25p_bwrite; + priv->mtd.read = m25p_read; + priv->mtd.ioctl = m25p_ioctl; + priv->dev = dev; + + /* Deselect the FLASH */ + + SPI_SELECT(dev, SPIDEV_FLASH, false); + + /* Identify the FLASH chip and get its capacity */ + + ret = m25p_readid(priv); + if (ret != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + + fdbg("Unrecognized\n"); + kfree(priv); + priv = NULL; + } + } + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; +} diff --git a/nuttx/drivers/mtd/rammtd.c b/nuttx/drivers/mtd/rammtd.c new file mode 100644 index 000000000..c50db6a3a --- /dev/null +++ b/nuttx/drivers/mtd/rammtd.c @@ -0,0 +1,417 @@ +/**************************************************************************** + * drivers/mtd/rammtd.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/ioctl.h> +#include <nuttx/mtd.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ + +#ifndef CONFIG_RAMMTD_BLOCKSIZE +# define CONFIG_RAMMTD_BLOCKSIZE 512 +#endif + +#ifndef CONFIG_RAMMTD_ERASESIZE +# define CONFIG_RAMMTD_ERASESIZE 4096 +#endif + +#ifndef CONFIG_RAMMTD_ERASESTATE +# define CONFIG_RAMMTD_ERASESTATE 0xff +#endif + +#if CONFIG_RAMMTD_ERASESTATE != 0xff && CONFIG_RAMMTD_ERASESTATE != 0x00 +# error "Unsupported value for CONFIG_RAMMTD_ERASESTATE" +#endif + +#if CONFIG_RAMMTD_BLOCKSIZE > CONFIG_RAMMTD_ERASESIZE +# error "Must have CONFIG_RAMMTD_BLOCKSIZE <= CONFIG_RAMMTD_ERASESIZE" +#endif + +#undef CONFIG_RAMMTD_BLKPER +#define CONFIG_RAMMTD_BLKPER (CONFIG_RAMMTD_ERASESIZE/CONFIG_RAMMTD_BLOCKSIZE) + +#if CONFIG_RAMMTD_BLKPER*CONFIG_RAMMTD_BLOCKSIZE != CONFIG_RAMMTD_ERASESIZE +# error "CONFIG_RAMMTD_ERASESIZE must be an even multiple of CONFIG_RAMMTD_BLOCKSIZE" +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This type represents the state of the MTD device. The struct mtd_dev_s + * must appear at the beginning of the definition so that you can freely + * cast between pointers to struct mtd_dev_s and struct ram_dev_s. + */ + +struct ram_dev_s +{ + struct mtd_dev_s mtd; /* MTD device */ + FAR uint8_t *start; /* Start of RAM */ + size_t nblocks; /* Number of erase blocks */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +/* The RAM MTD driver may be useful just as it is, but another good use of + * the RAM MTD driver is as a FLASH simulation -- to support testing of FLASH + * based logic without having FLASH. CONFIG_RAMMTD_FLASHSIM will add some + * extra logic to improve the level of FLASH simulation. + */ + +#define ram_read(dest, src, len) memcpy(dest, src, len) +#ifdef CONFIG_RAMMTD_FLASHSIM +static void *ram_write(FAR void *dest, FAR const void *src, size_t len); +#else +# define ram_write(dest, src, len) memcpy(dest, src, len) +#endif + +/* MTD driver methods */ + +static int ram_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t ram_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf); +static ssize_t ram_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buf); +static int ram_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ram_write + ****************************************************************************/ + +#ifdef CONFIG_RAMMTD_FLASHSIM +static void *ram_write(FAR void *dest, FAR const void *src, size_t len) +{ + FAR uint8_t *pout = (FAR uint8_t *)dest; + FAR const uint8_t *pin = (FAR const uint8_t *)src; + + while (len-- > 0) + { + /* Get the source and destination values */ + + uint8_t oldvalue = *pout; + uint8_t srcvalue = *pin++; + uint8_t newvalue; + + /* Get the new destination value, accounting for bits that cannot be + * changes because they are not in the erased state. + */ + +#if CONFIG_RAMMTD_ERASESTATE == 0xff + newvalue = oldvalue & srcvalue; /* We can only clear bits */ +#else /* CONFIG_RAMMTD_ERASESTATE == 0x00 */ + newvalue = oldvalue | srcvalue; /* We can only set bits */ +#endif + + /* Report any attempt to change the value of bits that are not in the + * erased state. + */ + +#ifdef CONFIG_DEBUG + if (newvalue != srcvalue) + { + dbg("ERROR: Bad write: source=%02x dest=%02x result=%02x\n", + srcvalue, oldvalue, newvalue); + } +#endif + + /* Write the modified value to simulated FLASH */ + + *pout++ = newvalue; + } + + return dest; +} +#endif + +/**************************************************************************** + * Name: ram_erase + ****************************************************************************/ + +static int ram_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + off_t offset; + size_t nbytes; + + DEBUGASSERT(dev); + + /* Don't let the erase exceed the size of the ram buffer */ + + if (startblock >= priv->nblocks) + { + return 0; + } + + if (startblock + nblocks > priv->nblocks) + { + nblocks = priv->nblocks - startblock; + } + + /* Convert the erase block to a logical block and the number of blocks + * in logical block numbers + */ + + startblock *= CONFIG_RAMMTD_BLKPER; + nblocks *= CONFIG_RAMMTD_BLKPER; + + /* Get the offset corresponding to the first block and the size + * corresponding to the number of blocks. + */ + + offset = startblock * CONFIG_RAMMTD_BLOCKSIZE; + nbytes = nblocks * CONFIG_RAMMTD_BLOCKSIZE; + + /* Then erase the data in RAM */ + + memset(&priv->start[offset], CONFIG_RAMMTD_ERASESTATE, nbytes); + return OK; +} + +/**************************************************************************** + * Name: ram_bread + ****************************************************************************/ + +static ssize_t ram_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + off_t offset; + off_t maxblock; + size_t nbytes; + + DEBUGASSERT(dev && buf); + + /* Don't let the read exceed the size of the ram buffer */ + + maxblock = priv->nblocks * CONFIG_RAMMTD_BLKPER; + if (startblock >= maxblock) + { + return 0; + } + + if (startblock + nblocks > maxblock) + { + nblocks = maxblock - startblock; + } + + /* Get the offset corresponding to the first block and the size + * corresponding to the number of blocks. + */ + + offset = startblock * CONFIG_RAMMTD_BLOCKSIZE; + nbytes = nblocks * CONFIG_RAMMTD_BLOCKSIZE; + + /* Then read the data frp, RAM */ + + ram_read(buf, &priv->start[offset], nbytes); + return nblocks; +} + +/**************************************************************************** + * Name: ram_bwrite + ****************************************************************************/ + +static ssize_t ram_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + off_t offset; + off_t maxblock; + size_t nbytes; + + DEBUGASSERT(dev && buf); + + /* Don't let the write exceed the size of the ram buffer */ + + maxblock = priv->nblocks * CONFIG_RAMMTD_BLKPER; + if (startblock >= maxblock) + { + return 0; + } + + if (startblock + nblocks > maxblock) + { + nblocks = maxblock - startblock; + } + + /* Get the offset corresponding to the first block and the size + * corresponding to the number of blocks. + */ + + offset = startblock * CONFIG_RAMMTD_BLOCKSIZE; + nbytes = nblocks * CONFIG_RAMMTD_BLOCKSIZE; + + /* Then write the data to RAM */ + + ram_write(&priv->start[offset], buf, nbytes); + return nblocks; +} + +/**************************************************************************** + * Name: ram_ioctl + ****************************************************************************/ + +static int ram_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct ram_dev_s *priv = (FAR struct ram_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + */ + + geo->blocksize = CONFIG_RAMMTD_BLOCKSIZE; + geo->erasesize = CONFIG_RAMMTD_ERASESIZE; + geo->neraseblocks = priv->nblocks; + ret = OK; + } + } + break; + + case MTDIOC_XIPBASE: + { + FAR void **ppv = (FAR void**)((uintptr_t)arg); + if (ppv) + { + /* Return (void*) base address of device memory */ + + *ppv = (FAR void*)priv->start; + ret = OK; + } + } + break; + + case MTDIOC_BULKERASE: + { + size_t size = priv->nblocks * CONFIG_RAMMTD_ERASESIZE; + + /* Erase the entire device */ + + memset(priv->start, CONFIG_RAMMTD_ERASESTATE, size); + ret = OK; + } + break; + + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rammtd_initialize + * + * Description: + * Create and initialize a RAM MTD device instance. + * + * Input Parameters: + * start - Address of the beginning of the allocated RAM regions. + * size - The size in bytes of the allocated RAM region. + * + ****************************************************************************/ + +FAR struct mtd_dev_s *rammtd_initialize(FAR uint8_t *start, size_t size) +{ + FAR struct ram_dev_s *priv; + size_t nblocks; + + /* Create an instance of the RAM MTD device state structure */ + + priv = (FAR struct ram_dev_s *)kmalloc(sizeof(struct ram_dev_s)); + if (!priv) + { + fdbg("Failed to allocate the RAM MTD state structure\n"); + return NULL; + } + + /* Force the size to be an even number of the erase block size */ + + nblocks = size / CONFIG_RAMMTD_ERASESIZE; + if (nblocks <= 0) + { + fdbg("Need to provide at least one full erase block\n"); + return NULL; + } + + /* Perform initialization as necessary */ + + priv->mtd.erase = ram_erase; + priv->mtd.bread = ram_bread; + priv->mtd.bwrite = ram_bwrite; + priv->mtd.ioctl = ram_ioctl; + priv->mtd.erase = ram_erase; + + priv->start = start; + priv->nblocks = nblocks; + return &priv->mtd; +} diff --git a/nuttx/drivers/mtd/ramtron.c b/nuttx/drivers/mtd/ramtron.c new file mode 100644 index 000000000..a59a94bb4 --- /dev/null +++ b/nuttx/drivers/mtd/ramtron.c @@ -0,0 +1,674 @@ +/************************************************************************************ + * drivers/mtd/ramtron.c + * Driver for SPI-based RAMTRON NVRAM Devices FM25V10 and others (not tested) + * + * Copyright (C) 2011 Uros Platise. All rights reserved. + * Copyright (C) 2009-2010 Gregory Nutt. All rights reserved. + * Author: Uros Platise <uros.platise@isotel.eu> + * Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +/* OPTIONS: + * - additional non-jedec standard device: FM25H20 + * must be enabled with the CONFIG_RAMTRON_FRAM_NON_JEDEC=y + * + * NOTE: + * - frequency is fixed to desired max by RAMTRON_INIT_CLK_MAX + * if new devices with different speed arrive, then SETFREQUENCY() + * needs to handle freq changes and INIT_CLK_MAX must be reduced + * to fit all devices. Note that STM32_SPI driver is prone to + * too high freq. parameters and limit it within physical constraints. + * + * TODO: + * - add support for sleep + * - add support for faster read FSTRD command + */ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <errno.h> +#include <debug.h> +#include <assert.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/ioctl.h> +#include <nuttx/spi.h> +#include <nuttx/mtd.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* RAMTRON devices are flat! + * For purpose of the VFAT file system we emulate the following configuration: + */ + +#define RAMTRON_EMULATE_SECTOR_SHIFT 9 +#define RAMTRON_EMULATE_PAGE_SHIFT 9 + +/* RAMTRON Indentification register values */ + +#define RAMTRON_MANUFACTURER 0x7F +#define RAMTRON_MEMORY_TYPE 0xC2 + +/* Instructions: + * Command Value N Description Addr Dummy Data */ +#define RAMTRON_WREN 0x06 /* 1 Write Enable 0 0 0 */ +#define RAMTRON_WRDI 0x04 /* 1 Write Disable 0 0 0 */ +#define RAMTRON_RDSR 0x05 /* 1 Read Status Register 0 0 >=1 */ +#define RAMTRON_WRSR 0x01 /* 1 Write Status Register 0 0 1 */ +#define RAMTRON_READ 0x03 /* 1 Read Data Bytes A 0 >=1 */ +#define RAMTRON_FSTRD 0x0b /* 1 Higher speed read A 1 >=1 */ +#define RAMTRON_WRITE 0x02 /* 1 Write A 0 1-256 */ +#define RAMTRON_SLEEP 0xb9 // TODO: +#define RAMTRON_RDID 0x9f /* 1 Read Identification 0 0 1-3 */ +#define RAMTRON_SN 0xc3 // TODO: + + +/* Status register bit definitions */ + +#define RAMTRON_SR_WIP (1 << 0) /* Bit 0: Write in progress bit */ +#define RAMTRON_SR_WEL (1 << 1) /* Bit 1: Write enable latch bit */ +#define RAMTRON_SR_BP_SHIFT (2) /* Bits 2-4: Block protect bits */ +#define RAMTRON_SR_BP_MASK (7 << RAMTRON_SR_BP_SHIFT) +# define RAMTRON_SR_BP_NONE (0 << RAMTRON_SR_BP_SHIFT) /* Unprotected */ +# define RAMTRON_SR_BP_UPPER64th (1 << RAMTRON_SR_BP_SHIFT) /* Upper 64th */ +# define RAMTRON_SR_BP_UPPER32nd (2 << RAMTRON_SR_BP_SHIFT) /* Upper 32nd */ +# define RAMTRON_SR_BP_UPPER16th (3 << RAMTRON_SR_BP_SHIFT) /* Upper 16th */ +# define RAMTRON_SR_BP_UPPER8th (4 << RAMTRON_SR_BP_SHIFT) /* Upper 8th */ +# define RAMTRON_SR_BP_UPPERQTR (5 << RAMTRON_SR_BP_SHIFT) /* Upper quarter */ +# define RAMTRON_SR_BP_UPPERHALF (6 << RAMTRON_SR_BP_SHIFT) /* Upper half */ +# define RAMTRON_SR_BP_ALL (7 << RAMTRON_SR_BP_SHIFT) /* All sectors */ +#define RAMTRON_SR_SRWD (1 << 7) /* Bit 7: Status register write protect */ + +#define RAMTRON_DUMMY 0xa5 + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +struct ramtron_parts_s +{ + const char *name; + uint8_t id1; + uint8_t id2; + uint32_t size; + uint8_t addr_len; + uint32_t speed; +}; + +/* This type represents the state of the MTD device. The struct mtd_dev_s + * must appear at the beginning of the definition so that you can freely + * cast between pointers to struct mtd_dev_s and struct ramtron_dev_s. + */ + +struct ramtron_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct spi_dev_s *dev; /* Saved SPI interface instance */ + uint8_t sectorshift; + uint8_t pageshift; + uint16_t nsectors; + uint32_t npages; + const struct ramtron_parts_s *part; /* part instance */ +}; + +/************************************************************************************ + * Supported Part Lists + ************************************************************************************/ + +// Defines the initial speed compatible with all devices. In case of RAMTRON +// the defined devices within the part list have all the same speed. +#define RAMTRON_INIT_CLK_MAX 40000000UL + +static struct ramtron_parts_s ramtron_parts[] = +{ + { + "FM25V02", /* name */ + 0x22, /* id1 */ + 0x00, /* id2 */ + 32L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25VN02", /* name */ + 0x22, /* id1 */ + 0x01, /* id2 */ + 32L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25V05", /* name */ + 0x23, /* id1 */ + 0x00, /* id2 */ + 64L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25VN05", /* name */ + 0x23, /* id1 */ + 0x01, /* id2 */ + 64L*1024L, /* size */ + 2, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25V10", /* name */ + 0x24, /* id1 */ + 0x00, /* id2 */ + 128L*1024L, /* size */ + 3, /* addr_len */ + 40000000 /* speed */ + }, + { + "FM25VN10", /* name */ + 0x24, /* id1 */ + 0x01, /* id2 */ + 128L*1024L, /* size */ + 3, /* addr_len */ + 40000000 /* speed */ + }, +#ifdef CONFIG_RAMTRON_FRAM_NON_JEDEC + { + "FM25H20", /* name */ + 0xff, /* id1 */ + 0xff, /* id2 */ + 256L*1024L, /* size */ + 3, /* addr_len */ + 40000000 /* speed */ + }, + { + NULL, /* name */ + 0, /* id1 */ + 0, /* id2 */ + 0, /* size */ + 0, /* addr_len */ + 0 /* speed */ + } +#endif +}; + + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Helpers */ + +static void ramtron_lock(FAR struct spi_dev_s *dev); +static inline void ramtron_unlock(FAR struct spi_dev_s *dev); +static inline int ramtron_readid(struct ramtron_dev_s *priv); +static void ramtron_waitwritecomplete(struct ramtron_dev_s *priv); +static void ramtron_writeenable(struct ramtron_dev_s *priv); +static inline void ramtron_pagewrite(struct ramtron_dev_s *priv, FAR const uint8_t *buffer, + off_t offset); + +/* MTD driver methods */ + +static int ramtron_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t ramtron_bread(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR uint8_t *buf); +static ssize_t ramtron_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, + size_t nblocks, FAR const uint8_t *buf); +static ssize_t ramtron_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int ramtron_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: ramtron_lock + ************************************************************************************/ + +static void ramtron_lock(FAR struct spi_dev_s *dev) +{ + /* On SPI busses where there are multiple devices, it will be necessary to + * lock SPI to have exclusive access to the busses for a sequence of + * transfers. The bus should be locked before the chip is selected. + * + * This is a blocking call and will not return until we have exclusiv access to + * the SPI buss. We will retain that exclusive access until the bus is unlocked. + */ + + SPI_LOCK(dev, true); + + /* After locking the SPI bus, the we also need call the setfrequency, setbits, and + * setmode methods to make sure that the SPI is properly configured for the device. + * If the SPI buss is being shared, then it may have been left in an incompatible + * state. + */ + + SPI_SETMODE(dev, SPIDEV_MODE3); + SPI_SETBITS(dev, 8); + + (void)SPI_SETFREQUENCY(dev, RAMTRON_INIT_CLK_MAX); +} + +/************************************************************************************ + * Name: ramtron_unlock + ************************************************************************************/ + +static inline void ramtron_unlock(FAR struct spi_dev_s *dev) +{ + SPI_LOCK(dev, false); +} + +/************************************************************************************ + * Name: ramtron_readid + ************************************************************************************/ + +static inline int ramtron_readid(struct ramtron_dev_s *priv) +{ + uint16_t manufacturer, memory, capacity, part; + int i; + + fvdbg("priv: %p\n", priv); + + /* Lock the SPI bus, configure the bus, and select this FLASH part. */ + + ramtron_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send the "Read ID (RDID)" command and read the first three ID bytes */ + + (void)SPI_SEND(priv->dev, RAMTRON_RDID); + for (i=0; i<6; i++) manufacturer = SPI_SEND(priv->dev, RAMTRON_DUMMY); + memory = SPI_SEND(priv->dev, RAMTRON_DUMMY); + capacity = SPI_SEND(priv->dev, RAMTRON_DUMMY); // fram.id1 + part = SPI_SEND(priv->dev, RAMTRON_DUMMY); // fram.id2 + + /* Deselect the FLASH and unlock the bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + ramtron_unlock(priv->dev); + + // Select part from the part list + for (priv->part = ramtron_parts; + priv->part->name != NULL && !(priv->part->id1 == capacity && priv->part->id2 == part); + priv->part++); + + if (priv->part->name) { + fvdbg("RAMTRON %s of size %d bytes (mf:%02x mem:%02x cap:%02x part:%02x)\n", + priv->part->name, priv->part->size, manufacturer, memory, capacity, part); + + priv->sectorshift = RAMTRON_EMULATE_SECTOR_SHIFT; + priv->nsectors = priv->part->size / (1 << RAMTRON_EMULATE_SECTOR_SHIFT); + priv->pageshift = RAMTRON_EMULATE_PAGE_SHIFT; + priv->npages = priv->part->size / (1 << RAMTRON_EMULATE_PAGE_SHIFT); + return OK; + } + + fvdbg("RAMTRON device not found\n"); + return -ENODEV; +} + +/************************************************************************************ + * Name: ramtron_waitwritecomplete + ************************************************************************************/ + +static void ramtron_waitwritecomplete(struct ramtron_dev_s *priv) +{ + uint8_t status; + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read Status Register (RDSR)" command */ + + (void)SPI_SEND(priv->dev, RAMTRON_RDSR); + + /* Loop as long as the memory is busy with a write cycle */ + + do + { + /* Send a dummy byte to generate the clock needed to shift out the status */ + + status = SPI_SEND(priv->dev, RAMTRON_DUMMY); + } + while ((status & RAMTRON_SR_WIP) != 0); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Complete\n"); +} + +/************************************************************************************ + * Name: ramtron_writeenable + ************************************************************************************/ + +static void ramtron_writeenable(struct ramtron_dev_s *priv) +{ + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Write Enable (WREN)" command */ + + (void)SPI_SEND(priv->dev, RAMTRON_WREN); + + /* Deselect the FLASH */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Enabled\n"); +} + +/************************************************************************************ + * Name: ramtron_sendaddr + ************************************************************************************/ + +static inline void ramtron_sendaddr(const struct ramtron_dev_s *priv, uint32_t addr) +{ + DEBUGASSERT(priv->part->addr_len == 3 || priv->part->addr_len == 2); + + if (priv->part->addr_len == 3) + (void)SPI_SEND(priv->dev, (addr >> 16) & 0xff); + + (void)SPI_SEND(priv->dev, (addr >> 8) & 0xff); + (void)SPI_SEND(priv->dev, addr & 0xff); +} + +/************************************************************************************ + * Name: ramtron_pagewrite + ************************************************************************************/ + +static inline void ramtron_pagewrite(struct ramtron_dev_s *priv, FAR const uint8_t *buffer, + off_t page) +{ + off_t offset = page << priv->pageshift; + + fvdbg("page: %08lx offset: %08lx\n", (long)page, (long)offset); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + ramtron_waitwritecomplete(priv); + + /* Enable the write access to the FLASH */ + + ramtron_writeenable(priv); + + /* Select this FLASH part */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Page Program (PP)" command */ + + (void)SPI_SEND(priv->dev, RAMTRON_WRITE); + + /* Send the page offset high byte first. */ + + ramtron_sendaddr(priv, offset); + + /* Then write the specified number of bytes */ + + SPI_SNDBLOCK(priv->dev, buffer, 1 << priv->pageshift); + + /* Deselect the FLASH: Chip Select high */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + fvdbg("Written\n"); +} + +/************************************************************************************ + * Name: ramtron_erase + ************************************************************************************/ + +static int ramtron_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + fvdbg("On RAMTRON devices erasing makes no sense, returning as OK\n"); + return (int)nblocks; +} + +/************************************************************************************ + * Name: ramtron_bread + ************************************************************************************/ + +static ssize_t ramtron_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buffer) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + ssize_t nbytes; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* On this device, we can handle the block read just like the byte-oriented read */ + + nbytes = ramtron_read(dev, startblock << priv->pageshift, nblocks << priv->pageshift, buffer); + if (nbytes > 0) + { + return nbytes >> priv->pageshift; + } + return (int)nbytes; +} + +/************************************************************************************ + * Name: ramtron_bwrite + ************************************************************************************/ + +static ssize_t ramtron_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buffer) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + size_t blocksleft = nblocks; + + fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks); + + /* Lock the SPI bus and write each page to FLASH */ + + ramtron_lock(priv->dev); + while (blocksleft-- > 0) + { + ramtron_pagewrite(priv, buffer, startblock); + startblock++; + } + ramtron_unlock(priv->dev); + + return nblocks; +} + +/************************************************************************************ + * Name: ramtron_read + ************************************************************************************/ + +static ssize_t ramtron_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + + fvdbg("offset: %08lx nbytes: %d\n", (long)offset, (int)nbytes); + + /* Wait for any preceding write to complete. We could simplify things by + * perform this wait at the end of each write operation (rather than at + * the beginning of ALL operations), but have the wait first will slightly + * improve performance. + */ + + ramtron_waitwritecomplete(priv); + + /* Lock the SPI bus and select this FLASH part */ + + ramtron_lock(priv->dev); + SPI_SELECT(priv->dev, SPIDEV_FLASH, true); + + /* Send "Read from Memory " instruction */ + + (void)SPI_SEND(priv->dev, RAMTRON_READ); + + /* Send the page offset high byte first. */ + + ramtron_sendaddr(priv, offset); + + /* Then read all of the requested bytes */ + + SPI_RECVBLOCK(priv->dev, buffer, nbytes); + + /* Deselect the FLASH and unlock the SPI bus */ + + SPI_SELECT(priv->dev, SPIDEV_FLASH, false); + ramtron_unlock(priv->dev); + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: ramtron_ioctl + ************************************************************************************/ + +static int ramtron_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct ramtron_dev_s *priv = (FAR struct ramtron_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("cmd: %d \n", cmd); + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = (1 << priv->pageshift); + geo->erasesize = (1 << priv->sectorshift); + geo->neraseblocks = priv->nsectors; + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + fvdbg("BULDERASE: Makes no sense in ramtron. Let's confirm operation as OK\n"); + ret = OK; + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: ramtron_initialize + * + * Description: + * Create an initialize MTD device instance. MTD devices are not registered + * in the file system, but are created as instances that can be bound to + * other functions (such as a block or character driver front end). + * + ************************************************************************************/ + +FAR struct mtd_dev_s *ramtron_initialize(FAR struct spi_dev_s *dev) +{ + FAR struct ramtron_dev_s *priv; + + fvdbg("dev: %p\n", dev); + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct ramtron_dev_s *)kmalloc(sizeof(struct ramtron_dev_s)); + if (priv) + { + /* Initialize the allocated structure */ + + priv->mtd.erase = ramtron_erase; + priv->mtd.bread = ramtron_bread; + priv->mtd.bwrite = ramtron_bwrite; + priv->mtd.read = ramtron_read; + priv->mtd.ioctl = ramtron_ioctl; + priv->dev = dev; + + /* Deselect the FLASH */ + + SPI_SELECT(dev, SPIDEV_FLASH, false); + + /* Identify the FLASH chip and get its capacity */ + + if (ramtron_readid(priv) != OK) + { + /* Unrecognized! Discard all of that work we just did and return NULL */ + kfree(priv); + priv = NULL; + } + } + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return (FAR struct mtd_dev_s *)priv; +} diff --git a/nuttx/drivers/mtd/skeleton.c b/nuttx/drivers/mtd/skeleton.c new file mode 100644 index 000000000..8b09b5c15 --- /dev/null +++ b/nuttx/drivers/mtd/skeleton.c @@ -0,0 +1,275 @@ +/**************************************************************************** + * drivers/mtd/skeleton.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <errno.h> + +#include <nuttx/ioctl.h> +#include <nuttx/mtd.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This type represents the state of the MTD device. The struct mtd_dev_s + * must appear at the beginning of the definition so that you can freely + * cast between pointers to struct mtd_dev_s and struct skel_dev_s. + */ + +struct skel_dev_s +{ + struct mtd_dev_s mtd; + + /* Other implementation specific data may follow here */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* MTD driver methods */ + +static int skel_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); +static ssize_t skel_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf); +static ssize_t skel_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buf); +static ssize_t skel_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int skel_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct skel_dev_s g_skeldev = +{ + { skel_erase, skel_rbead, skel_bwrite, skel_read, skel_ioctl }, + /* Initialization of any other implemenation specific data goes here */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: skel_erase + ****************************************************************************/ + +static int skel_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* The interface definition assumes that all erase blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Erase the specified blocks and return status (OK or a negated errno) */ + + return OK; +} + +/**************************************************************************** + * Name: skel_bread + ****************************************************************************/ + +static ssize_t skel_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR uint8_t *buf) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* The interface definition assumes that all read/write blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Read the specified blocks into the provided user buffer and return status + * (The positive, number of blocks actually read or a negated errno). + */ + + return 0; +} + +/**************************************************************************** + * Name: skel_bwrite + ****************************************************************************/ + +static ssize_t skel_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, + FAR const uint8_t *buf) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* The interface definition assumes that all read/write blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Write the specified blocks from the provided user buffer and return status + * (The positive, number of blocks actually written or a negated errno) + */ + + return 0; +} + +/**************************************************************************** + * Name: skel_read + ****************************************************************************/ + +static ssize_t skel_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + + /* Some devices may support byte oriented read (optional). Byte-oriented + * writing is inherently block oriented on most MTD devices and is not supported. + * It is recommended that low-level drivers not support read() if it requires + * buffering -- let the higher level logic handle that. If the read method is + * not implemented, just set the method pointer to NULL in the struct mtd_dev_s + * instance. + */ + + /* The interface definition assumes that all read/write blocks ar the same size. + * If that is not true for this particular device, then transform the + * start block and nblocks as necessary. + */ + + /* Read the specified blocks into the provided user buffer and return status + * (The positive, number of blocks actually read or a negated errno) + */ + + return 0; +} + +/**************************************************************************** + * Name: skel_ioctl + ****************************************************************************/ + +static int skel_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct skel_dev_s *priv = (FAR struct skel_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FARstruct mtd_geometry_s *)arg; + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = 512; /* Size of one read/write block */ + geo->erasesize = 4096; /* Size of one erase block */ + geo->neraseblocks = 1024; /* Number of erase blocks */ + ret = OK; + } + } + break; + + case MTDIOC_XIPBASE: + { + FAR void **ppv = (FAR void**)arg; + + if (ppv) + { + /* If media is directly acccesible, return (void*) base address + * of device memory. NULL otherwise. It is acceptable to omit + * this case altogether and simply return -ENOTTY. + */ + + *ppv = NULL; + ret = OK; + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + ret = OK; + } + break; + + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: skel_initialize + * + * Description: + * Create and initialize an MTD device instance. MTD devices are not + * registered in the file system, but are created as instances that can + * be bound to other functions (such as a block or character driver front + * end). + * + ****************************************************************************/ + +void skel_initialize(void) +{ + /* Perform initialization as necessary */ + + /* Return the implementation-specific state structure as the MTD device */ + + return (FAR struct mtd_dev_s *)&g_skeldev; +} diff --git a/nuttx/drivers/net/Make.defs b/nuttx/drivers/net/Make.defs new file mode 100644 index 000000000..ab256cd8a --- /dev/null +++ b/nuttx/drivers/net/Make.defs @@ -0,0 +1,71 @@ +############################################################################ +# drivers/net/Make.defs +# +# Copyright (C) 2007, 2010-2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Include nothing if networking is disabled + +ifeq ($(CONFIG_NET),y) + +# Include network interface drivers + +ifeq ($(CONFIG_NET_DM90x0),y) + CSRCS += dm90x0.c +endif + +ifeq ($(CONFIG_NET_CS89x0),y) + CSRCS += cs89x0.c +endif + +ifeq ($(CONFIG_NET_ENC28J60),y) + CSRCS += enc28j60.c +endif + +ifeq ($(CONFIG_NET_VNET),y) + CSRCS += vnet.c +endif + +ifeq ($(CONFIG_NET_E1000),y) + CSRCS += e1000.c +endif + +ifeq ($(CONFIG_NET_SLIP),y) + CSRCS += slip.c +endif + +# Include network build support + +DEPPATH += --dep-path net +VPATH += :net +endif + diff --git a/nuttx/drivers/net/cs89x0.c b/nuttx/drivers/net/cs89x0.c new file mode 100644 index 000000000..5b616233d --- /dev/null +++ b/nuttx/drivers/net/cs89x0.c @@ -0,0 +1,959 @@ +/**************************************************************************** + * drivers/net/cs89x0.c + * + * Copyright (C) 2009-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#if defined(CONFIG_NET) && defined(CONFIG_NET_CS89x0) + +#include <stdint.h> +#include <stdbool.h> +#include <time.h> +#include <string.h> +#include <debug.h> +#include <wdog.h> +#include <errno.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> + +#include <net/uip/uip.h> +#include <net/uip/uip-arp.h> +#include <net/uip/uip-arch.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +#error "Under construction -- do not use" + +/* CONFIG_CS89x0_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_CS89x0_NINTERFACES +# define CONFIG_CS89x0_NINTERFACES 1 +#endif + +/* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per second */ + +#define CS89x0_WDDELAY (1*CLK_TCK) +#define CS89x0_POLLHSEC (1*2) + +/* TX timeout = 1 minute */ + +#define CS89x0_TXTIMEOUT (60*CLK_TCK) + +/* This is a helper pointer for accessing the contents of the Ethernet header */ + +#define BUF ((struct uip_eth_hdr *)cs89x0->cs_dev.d_buf) + +/* If there is only one CS89x0 instance, then mapping the CS89x0 IRQ to + * a driver state instance is trivial. + */ + +#if CONFIG_CS89x0_NINTERFACES == 1 +# define cs89x0_mapirq(irq) g_cs89x0[0] +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static FAR struct cs89x0_driver_s *g_cs89x0[CONFIG_CS89x0_NINTERFACES]; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* CS89x0 register access */ + +static uint16_t cs89x0_getreg(struct cs89x0_driver_s *cs89x0, int offset); +static void cs89x0_putreg(struct cs89x0_driver_s *cs89x0, int offset, + uint16_t value); +static uint16_t cs89x0_getppreg(struct cs89x0_driver_s *cs89x0, int addr); +static void cs89x0_putppreg(struct cs89x0_driver_s *cs89x0, int addr, + uint16_t value); + +/* Common TX logic */ + +static int cs89x0_transmit(struct cs89x0_driver_s *cs89x0); +static int cs89x0_uiptxpoll(struct uip_driver_s *dev); + +/* Interrupt handling */ + +static void cs89x0_receive(struct cs89x0_driver_s *cs89x0); +static void cs89x0_txdone(struct cs89x0_driver_s *cs89x0, uint16_t isq); +#if CONFIG_CS89x0_NINTERFACES > 1 +static inline FAR struct cs89x0_driver_s *cs89x0_mapirq(int irq); +#endif +static int cs89x0_interrupt(int irq, FAR void *context); + +/* Watchdog timer expirations */ + +static void cs89x0_polltimer(int argc, uint32_t arg, ...); +static void cs89x0_txtimeout(int argc, uint32_t arg, ...); + +/* NuttX callback functions */ + +static int cs89x0_ifup(struct uip_driver_s *dev); +static int cs89x0_ifdown(struct uip_driver_s *dev); +static int cs89x0_txavail(struct uip_driver_s *dev); +#ifdef CONFIG_NET_IGMP +static int cs89x0_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +static int cs89x0_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: cs89x0_getreg and cs89x0_putreg + * + * Description: + * Read from and write to a CS89x0 register + * + * Parameters: + * cs89x0 - Reference to the driver state structure + * offset - Offset to the CS89x0 register + * value - Value to be written (cs89x0_putreg only) + * + * Returned Value: + * cs89x0_getreg: The 16-bit value of the register + * cs89x0_putreg: None + * + ****************************************************************************/ + +static uint16_t cs89x0_getreg(struct cs89x0_driver_s *cs89x0, int offset) +{ +#ifdef CONFIG_CS89x0_ALIGN16 + return getreg16(s89x0->cs_base + offset); +#else + return (uint16_t)getreg32(s89x0->cs_base + offset); +#endif +} + +static void cs89x0_putreg(struct cs89x0_driver_s *cs89x0, int offset, uint16_t value) +{ +#ifdef CONFIG_CS89x0_ALIGN16 + return putreg16(value, s89x0->cs_base + offset); +#else + return (uint16_t)putreg32((uint32_t)value, s89x0->cs_base + offset); +#endif +} + +/**************************************************************************** + * Function: cs89x0_getppreg and cs89x0_putppreg + * + * Description: + * Read from and write to a CS89x0 page packet register + * + * Parameters: + * cs89x0 - Reference to the driver state structure + * addr - Address of the CS89x0 page packet register + * value - Value to be written (cs89x0_putppreg only) + * + * Returned Value: + * cs89x0_getppreg: The 16-bit value of the page packet register + * cs89x0_putppreg: None + * + ****************************************************************************/ + +static uint16_t cs89x0_getppreg(struct cs89x0_driver_s *cs89x0, int addr) +{ + /* In memory mode, the CS89x0's internal registers and frame buffers are mapped + * into a contiguous 4kb block providing direct access to the internal registers + * and frame buffers. + */ + +#ifdef CONFIG_CS89x0_MEMMODE + if (cs89x0->cs_memmode) + { +#ifdef CONFIG_CS89x0_ALIGN16 + return getreg16(s89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??)); +#else + return (uint16_t)getreg32(s89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??)); +#endif + } + + /* When configured in I/O mode, the CS89x0 is accessed through eight, 16-bit + * I/O ports that in the host system's I/O space. + */ + + else +#endif + { +#ifdef CONFIG_CS89x0_ALIGN16 + putreg16((uint16_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET); + return getreg16(s89x0->cs_base + CS89x0_PDATA_OFFSET); +#else + putreg32((uint32_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET); + return (uint16_t)getreg32(s89x0->cs_base + CS89x0_PDATA_OFFSET); +#endif + } +} + +static void cs89x0_putppreg(struct cs89x0_driver_s *cs89x0, int addr, uint16_t value) +{ + /* In memory mode, the CS89x0's internal registers and frame buffers are mapped + * into a contiguous 4kb block providing direct access to the internal registers + * and frame buffers. + */ + +#ifdef CONFIG_CS89x0_MEMMODE + if (cs89x0->cs_memmode) + { +#ifdef CONFIG_CS89x0_ALIGN16 + putreg16(value), cs89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??)); +#else + putreg32((uint32_t)value, cs89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??)); +#endif + } + + /* When configured in I/O mode, the CS89x0 is accessed through eight, 16-bit + * I/O ports that in the host system's I/O space. + */ + + else +#endif + { +#ifdef CONFIG_CS89x0_ALIGN16 + putreg16((uint16_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET); + putreg16(value, cs89x0->cs_base + CS89x0_PDATA_OFFSET); +#else + putreg32((uint32_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET); + putreg32((uint32_t)value, cs89x0->cs_base + CS89x0_PDATA_OFFSET); +#endif + } +} + +/**************************************************************************** + * Function: cs89x0_transmit + * + * Description: + * Start hardware transmission. Called either from the txdone interrupt + * handling or from watchdog based polling. + * + * Parameters: + * cs89x0 - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static int cs89x0_transmit(struct cs89x0_driver_s *cs89x0) +{ + /* Verify that the hardware is ready to send another packet */ +#warning "Missing logic" + + /* Increment statistics */ +#warning "Missing logic" + + /* Disable Ethernet interrupts */ +#warning "Missing logic" + + /* Send the packet: address=cs89x0->cs_dev.d_buf, length=cs89x0->cs_dev.d_len */ +#warning "Missing logic" + + /* Restore Ethernet interrupts */ +#warning "Missing logic" + + /* Setup the TX timeout watchdog (perhaps restarting the timer) */ + + (void)wd_start(cs89x0->cs_txtimeout, CS89x0_TXTIMEOUT, cs89x0_txtimeout, 1, (uint32_t)cs89x0); + return OK; +} + +/**************************************************************************** + * Function: cs89x0_uiptxpoll + * + * Description: + * The transmitter is available, check if uIP has any outgoing packets ready + * to send. This is a callback from uip_poll(). uip_poll() may be called: + * + * 1. When the preceding TX packet send is complete, + * 2. When the preceding TX packet send timesout and the interface is reset + * 3. During normal TX polling + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static int cs89x0_uiptxpoll(struct uip_driver_s *dev) +{ + struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + if (cs89x0->cs_dev.d_len > 0) + { + uip_arp_out(&cs89x0->cs_dev); + cs89x0_transmit(cs89x0); + + /* Check if there is room in the CS89x0 to hold another packet. If not, + * return a non-zero value to terminate the poll. + */ +#warning "Missing logic" + } + + /* If zero is returned, the polling will continue until all connections have + * been examined. + */ + + return 0; +} + +/**************************************************************************** + * Function: cs89x0_receive + * + * Description: + * An interrupt was received indicating the availability of a new RX packet + * + * Parameters: + * cs89x0 - Reference to the driver state structure + * isq - Interrupt status queue value read by interrupt handler + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void cs89x0_receive(struct cs89x0_driver_s *cs89x0, uint16_t isq) +{ + uint16_t *dest; + uint16_t rxlength; + int nbytes; + + /* Check for errors and update statistics */ + + rxlength = cs89x0_getreg(PPR_RXLENGTH); + if ((isq & RX_OK) == 0) + { +#ifdef CONFIG_C89x0_STATISTICS + cd89x0->cs_stats.rx_errors++; + if ((isq & RX_RUNT) != 0) + { + cd89x0->cs_stats.rx_lengtherrors++; + } + if ((isq & RX_EXTRA_DATA) != 0) + { + cd89x0->cs_stats.rx_lengtherrors++; + } + if (isq & RX_CRC_ERROR) != 0) + { + if (!(isq & (RX_EXTRA_DATA|RX_RUNT))) + { + cd89x0->cs_stats.rx_crcerrors++; + } + } + if ((isq & RX_DRIBBLE) != 0) + { + cd89x0->cs_stats.rx_frameerrors++; + } +#endif + return; + } + + /* Check if the packet is a valid size for the uIP buffer configuration */ + + if (rxlength > ???) + { +#ifdef CONFIG_C89x0_STATISTICS + cd89x0->cs_stats.rx_errors++; + cd89x0->cs_stats.rx_lengtherrors++; +#endif + return; + } + + /* Copy the data data from the hardware to cs89x0->cs_dev.d_buf. Set + * amount of data in cs89x0->cs_dev.d_len + */ + + dest = (uint16_t*)cs89x0->cs_dev.d_buf; + for (nbytes = 0; nbytes < rxlength; nbytes += sizeof(uint16_t)) + { + *dest++ = cs89x0_getreg(PPR_RXFRAMELOCATION); + } + +#ifdef CONFIG_C89x0_STATISTICS + cd89x0->cs_stats.rx_packets++; +#endif + /* We only accept IP packets of the configured type and ARP packets */ + +#ifdef CONFIG_NET_IPv6 + if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) +#else + if (BUF->type == HTONS(UIP_ETHTYPE_IP)) +#endif + { + uip_arp_ipin(&cs89x0->cs_dev); + uip_input(&cs89x0->cs_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (cs89x0->cs_dev.d_len > 0) + { + uip_arp_out(&cs89x0->cs_dev); + cs89x0_transmit(cs89x0); + } + } + else if (BUF->type == htons(UIP_ETHTYPE_ARP)) + { + uip_arp_arpin(&cs89x0->cs_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (cs89x0->cs_dev.d_len > 0) + { + cs89x0_transmit(cs89x0); + } + } +} + +/**************************************************************************** + * Function: cs89x0_txdone + * + * Description: + * An interrupt was received indicating that the last TX packet(s) is done + * + * Parameters: + * cs89x0 - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void cs89x0_txdone(struct cs89x0_driver_s *cs89x0, uint16_t isq) +{ + /* Check for errors and update statistics. The lower 6-bits of the ISQ + * hold the register address causing the interrupt. We got here because + * those bits indicated */ + +#ifdef CONFIG_C89x0_STATISTICS + cd89x0->cs_stats.tx_packets++; + if ((isq & ISQ_TXEVENT_TXOK) == 0) + { + cd89x0->cs_stats.tx_errors++; + } + if ((isq & ISQ_TXEVENT_LOSSOFCRS) != 0) + { + cd89x0->cs_stats.tx_carriererrors++; + } + if ((isq & ISQ_TXEVENT_SQEERROR) != 0) + { + cd89x0->cs_stats.tx_heartbeaterrors++; + } + if (i(sq & ISQ_TXEVENT_OUTWINDOW) != 0) + { + cd89x0->cs_stats.tx_windowerrors++; + } + if (isq & TX_16_COL) + { + cd89x0->cs_stats.tx_abortederrors++; + } +#endif + + /* If no further xmits are pending, then cancel the TX timeout */ + + wd_cancel(cs89x0->cs_txtimeout); + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&cs89x0->cs_dev, cs89x0_uiptxpoll); +} + +/**************************************************************************** + * Function: cs89x0_mapirq + * + * Description: + * Map an IRQ number to a CS89x0 device state instance. This is only + * necessary to handler the case where the architecture includes more than + * on CS89x0 chip. + * + * Parameters: + * irq - Number of the IRQ that generated the interrupt + * + * Returned Value: + * A reference to device state structure (NULL if irq does not correspond + * to any CS89x0 device). + * + * Assumptions: + * + ****************************************************************************/ + +#if CONFIG_CS89x0_NINTERFACES > 1 +static inline FAR struct cs89x0_driver_s *cs89x0_mapirq(int irq) +{ + int i; + for (i = 0; i < CONFIG_CS89x0_NINTERFACES; i++) + { + if (g_cs89x0[i] && g_cs89x0[i].irq == irq) + { + return g_cs89x0[i]; + } + } + return NULL; +} +#endif + +/**************************************************************************** + * Function: cs89x0_interrupt + * + * Description: + * Hardware interrupt handler + * + * Parameters: + * irq - Number of the IRQ that generated the interrupt + * context - Interrupt register state save info (architecture-specific) + * + * Returned Value: + * OK on success + * + * Assumptions: + * + ****************************************************************************/ + +static int cs89x0_interrupt(int irq, FAR void *context) +{ + register struct cs89x0_driver_s *cs89x0 = s89x0_mapirq(irq); + uint16_t isq; + +#ifdef CONFIG_DEBUG + if (!cs89x0) + { + return -ENODEV; + } +#endif + + /* Read and process all of the events from the ISQ */ + + while ((isq = cs89x0_getreg(dev, CS89x0_ISQ_OFFSET)) != 0) + { + nvdbg("ISQ: %04x\n", isq); + switch (isq & ISQ_EVENTMASK) + { + case ISQ_RXEVENT: + cs89x0_receive(cs89x0); + break; + + case ISQ_TXEVENT: + cs89x0_txdone(cs89x0, isq); + break; + + case ISQ_BUFEVENT: + if ((isq & ISQ_BUFEVENT_TXUNDERRUN) != 0) + { + ndbg("Transmit underrun\n"); +#ifdef CONFIG_CS89x0_XMITEARLY + cd89x0->cs_txunderrun++; + if (cd89x0->cs_txunderrun == 3) + { + cd89x0->cs_txstart = PPR_TXCMD_TXSTART381; + } + else if (cd89x0->cs_txunderrun == 6) + { + cd89x0->cs_txstart = PPR_TXCMD_TXSTARTFULL; + } +#endif + } + break; + + case ISQ_RXMISSEVENT: +#ifdef CONFIG_C89x0_STATISTICS + cd89x0->cs_stats.rx_missederrors += (isq >>6); +#endif + break; + + case ISQ_TXCOLEVENT: +#ifdef CONFIG_C89x0_STATISTICS + cd89x0->cs_stats.collisions += (isq >>6); +#endif + break; + } + } + return OK; +} + +/**************************************************************************** + * Function: cs89x0_txtimeout + * + * Description: + * Our TX watchdog timed out. Called from the timer interrupt handler. + * The last TX never completed. Reset the hardware and start again. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void cs89x0_txtimeout(int argc, uint32_t arg, ...) +{ + struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)arg; + + /* Increment statistics and dump debug info */ +#warning "Missing logic" + + /* Then reset the hardware */ +#warning "Missing logic" + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&cs89x0->cs_dev, cs89x0_uiptxpoll); +} + +/**************************************************************************** + * Function: cs89x0_polltimer + * + * Description: + * Periodic timer handler. Called from the timer interrupt handler. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void cs89x0_polltimer(int argc, uint32_t arg, ...) +{ + struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)arg; + + /* Check if there is room in the send another TXr packet. */ +#warning "Missing logic" + + /* If so, update TCP timing states and poll uIP for new XMIT data */ + + (void)uip_timer(&cs89x0->cs_dev, cs89x0_uiptxpoll, CS89x0_POLLHSEC); + + /* Setup the watchdog poll timer again */ + + (void)wd_start(cs89x0->cs_txpoll, CS89x0_WDDELAY, cs89x0_polltimer, 1, arg); +} + +/**************************************************************************** + * Function: cs89x0_ifup + * + * Description: + * NuttX Callback: Bring up the Ethernet interface when an IP address is + * provided + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int cs89x0_ifup(struct uip_driver_s *dev) +{ + struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private; + + ndbg("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Initialize the Ethernet interface */ +#warning "Missing logic" + + /* Set and activate a timer process */ + + (void)wd_start(cs89x0->cs_txpoll, CS89x0_WDDELAY, cs89x0_polltimer, 1, (uint32_t)cs89x0); + + /* Enable the Ethernet interrupt */ + + cs89x0->cs_bifup = true; + up_enable_irq(CONFIG_CS89x0_IRQ); + return OK; +} + +/**************************************************************************** + * Function: cs89x0_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int cs89x0_ifdown(struct uip_driver_s *dev) +{ + struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private; + irqstate_t flags; + + /* Disable the Ethernet interrupt */ + + flags = irqsave(); + up_disable_irq(CONFIG_CS89x0_IRQ); + + /* Cancel the TX poll timer and TX timeout timers */ + + wd_cancel(cs89x0->cs_txpoll); + wd_cancel(cs89x0->cs_txtimeout); + + /* Reset the device */ + + cs89x0->cs_bifup = false; + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: cs89x0_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static int cs89x0_txavail(struct uip_driver_s *dev) +{ + struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private; + irqstate_t flags; + + flags = irqsave(); + + /* Ignore the notification if the interface is not yet up */ + + if (cs89x0->cs_bifup) + { + /* Check if there is room in the hardware to hold another outgoing packet. */ +#warning "Missing logic" + + /* If so, then poll uIP for new XMIT data */ + + (void)uip_poll(&cs89x0->cs_dev, cs89x0_uiptxpoll); + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: cs89x0_addmac + * + * Description: + * NuttX Callback: Add the specified MAC address to the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be added + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int cs89x0_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct cs89x0_driver_s *priv = (FAR struct cs89x0_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + +#warning "Multicast MAC support not implemented" + return OK; +} +#endif + +/**************************************************************************** + * Function: cs89x0_rmmac + * + * Description: + * NuttX Callback: Remove the specified MAC address from the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be removed + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int cs89x0_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct cs89x0_driver_s *priv = (FAR struct cs89x0_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + +#warning "Multicast MAC support not implemented" + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: cs89x0_initialize + * + * Description: + * Initialize the Ethernet driver + * + * Parameters: + * impl - decribes the implementation of the cs89x00 implementation. + * This reference is retained so so must remain stable throughout the + * life of the driver instance. + * devno - Identifies the device number. This must be a number between + * zero CONFIG_CS89x0_NINTERFACES and the same devno must not be + * initialized twice. The associated network device will be referred + * to with the name "eth" followed by this number (eth0, eth1, etc). + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * + ****************************************************************************/ + +/* Initialize the CS89x0 chip and driver */ + +int cs89x0_initialize(FAR const cs89x0_driver_s *cs89x0, int devno) +{ + /* Sanity checks -- only performed with debug enabled */ + +#ifdef CONFIG_DEBUG + if (!cs89x0 || (unsigned)devno > CONFIG_CS89x0_NINTERFACES || g_cs89x00[devno]) + { + return -EINVAL; + } +#endif + + /* Check if a Ethernet chip is recognized at its I/O base */ + +#warning "Missing logic" + + /* Attach the IRQ to the driver */ + + if (irq_attach(cs89x0->irq, cs89x0_interrupt)) + { + /* We could not attach the ISR to the ISR */ + + return -EAGAIN; + } + + /* Initialize the driver structure */ + + g_cs89x[devno] = cs89x0; /* Used to map IRQ back to instance */ + cs89x0->cs_dev.d_ifup = cs89x0_ifup; /* I/F down callback */ + cs89x0->cs_dev.d_ifdown = cs89x0_ifdown; /* I/F up (new IP address) callback */ + cs89x0->cs_dev.d_txavail = cs89x0_txavail; /* New TX data callback */ +#ifdef CONFIG_NET_IGMP + cs89x0->cs_dev.d_addmac = cs89x0_addmac; /* Add multicast MAC address */ + cs89x0->cs_dev.d_rmmac = cs89x0_rmmac; /* Remove multicast MAC address */ +#endif + cs89x0->cs_dev.d_private = (void*)cs89x0; /* Used to recover private state from dev */ + + /* Create a watchdog for timing polling for and timing of transmisstions */ + + cs89x0->cs_txpoll = wd_create(); /* Create periodic poll timer */ + cs89x0->cs_txtimeout = wd_create(); /* Create TX timeout timer */ + + /* Read the MAC address from the hardware into cs89x0->cs_dev.d_mac.ether_addr_octet */ + + /* Register the device with the OS so that socket IOCTLs can be performed */ + + (void)netdev_register(&cs89x0->cs_dev); + return OK; +} + +#endif /* CONFIG_NET && CONFIG_NET_CS89x0 */ + diff --git a/nuttx/drivers/net/cs89x0.h b/nuttx/drivers/net/cs89x0.h new file mode 100644 index 000000000..c2073eb98 --- /dev/null +++ b/nuttx/drivers/net/cs89x0.h @@ -0,0 +1,326 @@ +/****************************************************************************
+ * drivers/net/cs89x0.h
+ *
+ * Copyright (C) 2009 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <spudmonkey@racsa.co.cr>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. Neither the name NuttX nor the names of its contributors may be
+ * used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+#ifndef __DRIVERS_NET_CS89x0_H
+#define __DRIVERS_NET_CS89x0_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* CONFIG_CS89x0_ALIGN16/32 determines if the 16-bit CS89x0 registers are
+ * aligned to 16-bit or 32-bit address boundaries. NOTE: If there multiple
+ * CS89x00 parts in the board architecture, we assume that the address
+ * alignment is the same for all implementations. If that is not the
+ * case, then it will be necessary to move a shift value into
+ * the cs89x0_driver_s structure and calculate the offsets dynamically in
+ * the putreg and getreg functions.
+ */
+
+#if defined(CONFIG_CS89x0_ALIGN16)
+# define CS89x0_RTDATA_OFFSET (0 << 1)
+# define CS89x0_TxCMD_OFFSET (2 << 1)
+# define CS89x0_TxLEN_OFFSET (3 << 1)
+# define CS89x0_ISQ_OFFSET (4 << 1)
+# define CS89x0_PPTR_OFFSET (5 << 1)
+# define CS89x0_PDATA_OFFSET (6 << 1)
+#elif defined(CONFIG_CS89x0_ALIGN32)
+# define CS89x0_RTDATA_OFFSET (0 << 2)
+# define CS89x0_TxCMD_OFFSET (2 << 2)
+# define CS89x0_TxLEN_OFFSET (3 << 2)
+# define CS89x0_ISQ_OFFSET (4 << 2)
+# define CS89x0_PPTR_OFFSET (5 << 2)
+# define CS89x0_PDATA_OFFSET (6 << 2)
+#else
+# error "CS89x00 address alignment is not defined"
+#endif
+
+/* ISQ register bit definitions */
+
+#define ISQ_EVENTMASK 0x003f /* Bits 0-5 indicate the status register */
+#define ISQ_RXEVENT 0x0004
+#define ISQ_TXEVENT 0x0008
+#define ISQ_BUFEVENT 0x000c
+#define ISQ_RXMISSEVENT 0x0010
+#define ISQ_TXCOLEVENT 0x0012
+
+/* ISQ register TxEVENT bit definitions*/
+
+#define ISQ_RXEVENT_IAHASH (1 << 6)
+#define ISQ_RXEVENT_DRIBBLE (1 << 7)
+#define ISQ_RXEVENT_RXOK (1 << 8)
+#define ISQ_RXEVENT_HASHED (1 << 9)
+#define ISQ_RXEVENT_HASHNDX_SHIFT 10
+#define ISQ_RXEVENT_HASHNDX_MASK (0x3f << ISQ_RXEVENT_HASHNDX_SHIFT)
+
+/* ISQ register TxEVENT bit definitions*/
+
+#define ISQ_TXEVENT_LOSSOFCRS (1 << 6)
+#define ISQ_TXEVENT_SQEERROR (1 << 7)
+#define ISQ_TXEVENT_TXOK (1 << 8)
+#define ISQ_TXEVENT_OUTWINDOW (1 << 9)
+#define ISQ_TXEVENT_JABBER (1 << 10)
+#define ISQ_TXEVENT_NCOLLISION_SHIFT 11
+#define ISQ_TXEVENT_NCOLLISION_MASK (15 << ISQ_TXEVENT_NCOLLISION_SHIFT)
+#define ISQ_TXEVENT_16COLL (1 << 15)
+
+/* ISQ register BufEVENT bit definitions */
+
+#define ISQ_BUFEVENT_SWINT (1 << 6)
+#define ISQ_BUFEVENT_RXDMAFRAME (1 << 7)
+#define ISQ_BUFEVENT_RDY4TX (1 << 8)
+#define ISQ_BUFEVENT_TXUNDERRUN (1 << 9)
+#define ISQ_BUFEVENT_RXMISS (1 << 10)
+#define ISQ_BUFEVENT_RX128 (1 << 11)
+#define ISQ_BUFEVENT_RXDEST (1 << 15)
+
+/* Packet page register offsets *********************************************/
+
+/* 0x0000 Bus interface registers */
+
+#define PPR_CHIPID 0x0000 /* Chip identifier - must be 0x630E */
+#define PPR_CHIPREV 0x0002 /* Chip revision, model codes */
+#define PPR_IOBASEADDRESS 0x0020 /* I/O Base Address */
+#define PPR_INTREG 0x0022 /* Interrupt configuration */
+# define PPR_INTREG_IRQ0 0x0000 /* Use INTR0 pin */
+# define PPR_INTREG_IRQ1 0x0001 /* Use INTR1 pin */
+# define PPR_INTREG_IRQ2 0x0002 /* Use INTR2 pin */
+# define PPR_INTREG_IRQ3 0x0003 /* Use INTR3 pin */
+
+#define PPR_DMACHANNELNUMBER 0x0024 /* DMA Channel Number (0,1, or 2) */
+#define PPR_DMASTARTOFFRAME 0x0026 /* DMA Start of Frame */
+#define PPR_DMAFRAMECOUNT 0x0028 /* DMA Frame Count (12-bits) */
+#define PPR_RXDMABYTECOUNT 0x002a /* Rx DMA Byte Count */
+#define PPR_MEMORYBASEADDRESS 0x002c /* Memory Base Address Register (20-bit) */
+#define PPR_BOOTPROMBASEADDRESS 0x0030 /* Boot PROM Base Address */
+#define PPR_BOOTPROMADDRESSMASK 0x0034 /* Boot PROM Address Mask */
+#define PPR_EEPROMCOMMAND 0x0040 /* EEPROM Command */
+#define PPR_EEPROMDATA 0x0042 /* EEPROM Data */
+#define PPR_RECVFRAMEBYTES 0x0050 /* Received Frame Byte Counter */
+
+/* 0x0100 - Configuration and control registers */
+
+#define PPR_RXCFG 0x0102 /* Receiver configuration */
+# define PPR_RXCFG_SKIP1 (1 << 6) /* Skip (discard) current frame */
+# define PPR_RXCFG_STREAM (1 << 7) /* Enable streaming mode */
+# define PPR_RXCFG_RXOK (1 << 8) /* RxOK interrupt enable */
+# define PPR_RxCFG_RxDMAonly (1 << 9) /* Use RxDMA for all frames */
+# define PPR_RxCFG_AutoRxDMA (1 << 10) /* Select RxDMA automatically */
+# define PPR_RxCFG_BufferCRC (1 << 11) /* Include CRC characters in frame */
+# define PPR_RxCFG_CRC (1 << 12) /* Enable interrupt on CRC error */
+# define PPR_RxCFG_RUNT (1 << 13) /* Enable interrupt on RUNT frames */
+# define PPR_RxCFG_EXTRA (1 << 14) /* Enable interrupt on frames with extra data */
+
+#define PPR_RXCTL 0x0104 /* Receiver control */
+# define PPR_RXCTL_IAHASH (1 << 6) /* Accept frames that match hash */
+# define PPR_RXCTL_PROMISCUOUS (1 << 7) /* Accept any frame */
+# define PPR_RXCTL_RXOK (1 << 8) /* Accept well formed frames */
+# define PPR_RXCTL_MULTICAST (1 << 9) /* Accept multicast frames */
+# define PPR_RXCTL_IA (1 << 10) /* Accept frame that matches IA */
+# define PPR_RXCTL_BROADCAST (1 << 11) /* Accept broadcast frames */
+# define PPR_RXCTL_CRC (1 << 12) /* Accept frames with bad CRC */
+# define PPR_RXCTL_RUNT (1 << 13) /* Accept runt frames */
+# define PPR_RXCTL_EXTRA (1 << 14) /* Accept frames that are too long */
+
+#define PPR_TXCFG 0x0106 /* Transmit configuration */
+# define PPR_TXCFG_CRS (1 << 6) /* Enable interrupt on loss of carrier */
+# define PPR_TXCFG_SQE (1 << 7) /* Enable interrupt on Signal Quality Error */
+# define PPR_TXCFG_TXOK (1 << 8) /* Enable interrupt on successful xmits */
+# define PPR_TXCFG_LATE (1 << 9) /* Enable interrupt on "out of window" */
+# define PPR_TXCFG_JABBER (1 << 10) /* Enable interrupt on jabber detect */
+# define PPR_TXCFG_COLLISION (1 << 11) /* Enable interrupt if collision */
+# define PPR_TXCFG_16COLLISIONS (1 << 15) /* Enable interrupt if > 16 collisions */
+
+#define PPR_TXCMD 0x0108 /* Transmit command status */
+# define PPR_TXCMD_TXSTART5 (0 << 6) /* Start after 5 bytes in buffer */
+# define PPR_TXCMD_TXSTART381 (1 << 6) /* Start after 381 bytes in buffer */
+# define PPR_TXCMD_TXSTART1021 (2 << 6) /* Start after 1021 bytes in buffer */
+# define PPR_TXCMD_TXSTARTFULL (3 << 6) /* Start after all bytes loaded */
+# define PPR_TXCMD_FORCE (1 << 8) /* Discard any pending packets */
+# define PPR_TXCMD_ONECOLLISION (1 << 9) /* Abort after a single collision */
+# define PPR_TXCMD_NOCRC (1 << 12) /* Do not add CRC */
+# define PPR_TXCMD_NOPAD (1 << 13) /* Do not pad short packets */
+
+#define PPR_BUFCFG 0x010a /* Buffer configuration */
+# define PPR_BUFCFG_SWI (1 << 6) /* Force interrupt via software */
+# define PPR_BUFCFG_RXDMA (1 << 7) /* Enable interrupt on Rx DMA */
+# define PPR_BUFCFG_TXRDY (1 << 8) /* Enable interrupt when ready for Tx */
+# define PPR_BUFCFG_TXUE (1 << 9) /* Enable interrupt in Tx underrun */
+# define PPR_BUFCFG_RXMISS (1 << 10) /* Enable interrupt on missed Rx packets */
+# define PPR_BUFCFG_RX128 (1 << 11) /* Enable Rx interrupt after 128 bytes */
+# define PPR_BUFCFG_TXCOL (1 << 12) /* Enable int on Tx collision ctr overflow */
+# define PPR_BUFCFG_MISS (1 << 13) /* Enable int on Rx miss ctr overflow */
+# define PPR_BUFCFG_RXDEST (1 << 15) /* Enable int on Rx dest addr match */
+
+#define PPR_LINECTL 0x0112 /* Line control */
+# define PPR_LINECTL_RX (1 << 6) /* Enable receiver */
+# define PPR_LINECTL_TX (1 << 7) /* Enable transmitter */
+# define PPR_LINECTL_AUIONLY (1 << 8) /* AUI interface only */
+# define PPR_LINECTL_AUTOAUI10BT (1 << 9) /* Autodetect AUI or 10BaseT interface */
+# define PPR_LINECTL_MODBACKOFFE (1 << 11) /* Enable modified backoff algorithm */
+# define PPR_LINECTL_POLARITYDIS (1 << 12) /* Disable Rx polarity autodetect */
+# define PPR_LINECTL_2PARTDEFDIS (1 << 13) /* Disable two-part defferal */
+# define PPR_LINECTL_LORXSQUELCH (1 << 14) /* Reduce receiver squelch threshold */
+
+#define PPR_SELFCTL 0x0114 /* Chip self control */
+# define PPR_SELFCTL_RESET (1 << 6) /* Self-clearing reset */
+# define PPR_SELFCTL_SWSUSPEND (1 << 8) /* Initiate suspend mode */
+# define PPR_SELFCTL_HWSLEEPE (1 << 9) /* Enable SLEEP input */
+# define PPR_SELFCTL_HWSTANDBYE (1 << 10) /* Enable standby mode */
+# define PPR_SELFCTL_HC0E (1 << 12) /* Use HCB0 for LINK LED */
+# define PPR_SELFCTL_HC1E (1 << 13) /* Use HCB1 for BSTATUS LED */
+# define PPR_SELFCTL_HCB0 (1 << 14) /* Control LINK LED if HC0E set */
+# define PPR_SELFCTL_HCB1 (1 << 15) /* Cntrol BSTATUS LED if HC1E set */
+
+#define PPR_BUSCTL 0x0116 /* Bus control */
+# define PPR_BUSCTL_RESETRXDMA (1 << 6) /* Reset RxDMA pointer */
+# define PPR_BUSCTL_DMAEXTEND (1 << 8) /* Extend DMA cycle */
+# define PPR_BUSCTL_USESA (1 << 9) /* Assert MEMCS16 on address decode */
+# define PPR_BUSCTL_MEMORYE (1 << 10) /* Enable memory mode */
+# define PPR_BUSCTL_DMABURST (1 << 11) /* Limit DMA access burst */
+# define PPR_BUSCTL_IOCHRDYE (1 << 12) /* Set IOCHRDY high impedence */
+# define PPR_BUSCTL_RXDMASIZE (1 << 13) /* Set DMA buffer size 64KB */
+# define PPR_BUSCTL_ENABLEIRQ (1 << 15) /* Generate interrupt on interrupt event */
+
+#define PPR_TESTCTL 0x0118 /* Test control */
+# define PPR_TESTCTL_DISABLELT (1 << 7) /* Disable link status */
+# define PPR_TESTCTL_ENDECLOOP (1 << 9) /* Internal loopback */
+# define PPR_TESTCTL_AUILOOP (1 << 10) /* AUI loopback */
+# define PPR_TESTCTL_DISBACKOFF (1 << 11) /* Disable backoff algorithm */
+# define PPR_TESTCTL_FDX (1 << 14) /* Enable full duplex mode */
+
+/* 0x0120 - Status and Event Registers */
+
+#define PPR_ISQ 0x0120 /* Interrupt Status Queue */
+#define PPR_RER 0x0124 /* Receive event */
+# define PPR_RER_IAHASH (1 << 6) /* Frame hash match */
+# define PPR_RER_DRIBBLE (1 << 7) /* Frame had 1-7 extra bits after last byte */
+# define PPR_RER_RXOK (1 << 8) /* Frame received with no errors */
+# define PPR_RER_HASHED (1 << 9) /* Frame address hashed OK */
+# define PPR_RER_IA (1 << 10) /* Frame address matched IA */
+# define PPR_RER_BROADCAST (1 << 11) /* Broadcast frame */
+# define PPR_RER_CRC (1 << 12) /* Frame had CRC error */
+# define PPR_RER_RUNT (1 << 13) /* Runt frame */
+# define PPR_RER_EXTRA (1 << 14) /* Frame was too long */
+
+#define PPR_TER 0x0128 /* Transmit event */
+# define PPR_TER_CRS (1 << 6) /* Carrier lost */
+# define PPR_TER_SQE (1 << 7) /* Signal Quality Error */
+# define PPR_TER_TXOK (1 << 8) /* Packet sent without error */
+# define PPR_TER_LATE (1 << 9) /* Out of window */
+# define PPR_TER_JABBER (1 << 10) /* Stuck transmit? */
+# define PPR_TER_NUMCOLLISIONS_SHIFT 11
+# define PPR_TER_NUMCOLLISIONS_MASK (15 << PPR_TER_NUMCOLLISIONS_SHIFT)
+# define PPR_TER_16COLLISIONS (1 << 15) /* > 16 collisions */
+
+#define PPR_BER 0x012C /* Buffer event */
+# define PPR_BER_SWINT (1 << 6) /* Software interrupt */
+# define PPR_BER_RXDMAFRAME (1 << 7) /* Received framed DMAed */
+# define PPR_BER_RDY4TX (1 << 8) /* Ready for transmission */
+# define PPR_BER_TXUNDERRUN (1 << 9) /* Transmit underrun */
+# define PPR_BER_RXMISS (1 << 10) /* Received frame missed */
+# define PPR_BER_RX128 (1 << 11) /* 128 bytes received */
+# define PPR_BER_RXDEST (1 << 15) /* Received framed passed address filter */
+
+#define PPR_RXMISS 0x0130 /* Receiver miss counter */
+#define PPR_TXCOL 0x0132 /* Transmit collision counter */
+#define PPR_LINESTAT 0x0134 /* Line status */
+# define PPR_LINESTAT_LINKOK (1 << 7) /* Line is connected and working */
+# define PPR_LINESTAT_AUI (1 << 8) /* Connected via AUI */
+# define PPR_LINESTAT_10BT (1 << 9) /* Connected via twisted pair */
+# define PPR_LINESTAT_POLARITY (1 << 12) /* Line polarity OK (10BT only) */
+# define PPR_LINESTAT_CRS (1 << 14) /* Frame being received */
+
+#define PPR_SELFSTAT 0x0136 /* Chip self status */
+# define PPR_SELFSTAT_33VACTIVE (1 << 6) /* supply voltage is 3.3V */
+# define PPR_SELFSTAT_INITD (1 << 7) /* Chip initialization complete */
+# define PPR_SELFSTAT_SIBSY (1 << 8) /* EEPROM is busy */
+# define PPR_SELFSTAT_EEPROM (1 << 9) /* EEPROM present */
+# define PPR_SELFSTAT_EEPROMOK (1 << 10) /* EEPROM checks out */
+# define PPR_SELFSTAT_ELPRESENT (1 << 11) /* External address latch logic available */
+# define PPR_SELFSTAT_EESIZE (1 << 12) /* Size of EEPROM */
+
+#define PPR_BUSSTAT 0x0138 /* Bus status */
+# define PPR_BUSSTAT_TXBID (1 << 7) /* Tx error */
+# define PPR_BUSSTAT_TXRDY (1 << 8) /* Ready for Tx data */
+
+#define PPR_TDR 0x013C /* AUI Time Domain Reflectometer */
+
+/* 0x0144 - Initiate transmit registers */
+
+#define PPR_TXCOMMAND 0x0144 /* Tx Command */
+#define PPR_TXLENGTH 0x0146 /* Tx Length */
+
+/* 0x0150 - Address filter registers */
+
+#define PPR_LAF 0x0150 /* Logical address filter (6 bytes) */
+#define PPR_IA 0x0158 /* Individual address (MAC) */
+
+/* 0x0400 - Frame location registers */
+
+#define PPR_RXSTATUS 0x0400 /* Rx Status */
+#define PPR_RXLENGTH 0x0402 /* Rx Length */
+#define PPR_RXFRAMELOCATION 0x0404 /* Rx Frame Location */
+#define PPR_TXFRAMELOCATION 0x0a00 /* Tx Frame Location */
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C" {
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DRIVERS_NET_CS89x0_H */
diff --git a/nuttx/drivers/net/dm90x0.c b/nuttx/drivers/net/dm90x0.c new file mode 100644 index 000000000..32dc93069 --- /dev/null +++ b/nuttx/drivers/net/dm90x0.c @@ -0,0 +1,1815 @@ +/**************************************************************************** + * drivers/net/dm9x.c + * + * Copyright (C) 2007-2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * References: Davicom data sheets (DM9000-DS-F03-041906.pdf, + * DM9010-DS-F01-103006.pdf) and looking at lots of other DM90x0 + * drivers. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#if defined(CONFIG_NET) && defined(CONFIG_NET_DM90x0) + +/* Only one hardware interface supported at present (although there are + * hooks throughout the design to that extending the support to multiple + * interfaces should not be that difficult) + */ + +#undef CONFIG_DM9X_NINTERFACES +#define CONFIG_DM9X_NINTERFACES 1 + +#include <stdint.h> +#include <stdbool.h> +#include <time.h> +#include <string.h> +#include <debug.h> +#include <wdog.h> +#include <errno.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> + +#include <net/ethernet.h> +#include <net/uip/uip.h> +#include <net/uip/uip-arp.h> +#include <net/uip/uip-arch.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/* DM90000 and DM9010 register offets */ + +#define DM9X_NETC 0x00 /* Network control register */ +#define DM9X_NETS 0x01 /* Network Status register */ +#define DM9X_TXC 0x02 /* TX control register */ +#define DM9X_TXS1 0x03 /* TX status register 1 */ +#define DM9X_TXS2 0x03 /* TX status register 2 */ +#define DM9X_RXC 0x05 /* RX control register */ +#define DM9X_RXS 0x06 /* RX status register */ +#define DM9X_RXOVF 0x07 /* Receive overflow counter register */ +#define DM9X_BPTHRES 0x08 /* Back pressure threshold register */ +#define DM9X_FCTHRES 0x09 /* Flow control threshold register */ +#define DM9X_FC 0x0a /* RX/TX flow control register */ +#define DM9X_EEPHYC 0x0b /* EEPROM & PHY control register */ +#define DM9X_EEPHYA 0x0c /* EEPROM & PHY address register */ +#define DM9X_EEPHYDL 0x0d /* EEPROM & PHY data register (lo) */ +#define DM9X_EEPHYDH 0x0e /* EEPROM & PHY data register (hi) */ +#define DM9X_WAKEUP 0x0f /* Wake-up control register */ +#define DM9X_PAB0 0x10 /* Physical address register (byte 0) */ +#define DM9X_PAB1 0x11 /* Physical address register (byte 1) */ +#define DM9X_PAB2 0x12 /* Physical address register (byte 2) */ +#define DM9X_PAB3 0x13 /* Physical address register (byte 3) */ +#define DM9X_PAB4 0x14 /* Physical address register (byte 4) */ +#define DM9X_PAB5 0x15 /* Physical address register (byte 5) */ +#define DM9X_MAB0 0x16 /* Multicast address register (byte 0) */ +#define DM9X_MAB1 0x17 /* Multicast address register (byte 1) */ +#define DM9X_MAB2 0x18 /* Multicast address register (byte 2) */ +#define DM9X_MAB3 0x19 /* Multicast address register (byte 3) */ +#define DM9X_MAB4 0x1a /* Multicast address register (byte 4) */ +#define DM9X_MAB5 0x1b /* Multicast address register (byte 5) */ +#define DM9X_MAB6 0x1c /* Multicast address register (byte 6) */ +#define DM9X_MAB7 0x1d /* Multicast address register (byte 7) */ +#define DM9X_GPC 0x1e /* General purpose control register */ +#define DM9X_GPD 0x1f /* General purpose register */ + +#define DM9X_TRPAL 0x22 /* TX read pointer address (lo) */ +#define DM9X_TRPAH 0x23 /* TX read pointer address (hi) */ +#define DM9X_RWPAL 0x24 /* RX write pointer address (lo) */ +#define DM9X_RWPAH 0x25 /* RX write pointer address (hi) */ + +#define DM9X_VIDL 0x28 /* Vendor ID (lo) */ +#define DM9X_VIDH 0x29 /* Vendor ID (hi) */ +#define DM9X_PIDL 0x2a /* Product ID (lo) */ +#define DM9X_PIDH 0x2b /* Product ID (hi) */ +#define DM9X_CHIPR 0x2c /* Product ID (lo) */ +#define DM9X_TXC2 0x2d /* Transmit control register 2 (dm9010) */ +#define DM9X_OTC 0x2e /* Operation test control register (dm9010) */ +#define DM9X_SMODEC 0x2f /* Special mode control register */ +#define DM9X_ETXCSR 0x30 /* Early transmit control/status register (dm9010) */ +#define DM9X_TCCR 0x31 /* Transmit checksum control register (dm9010) */ +#define DM9X_RCSR 0x32 /* Receive checksum control/status register (dm9010) */ +#define DM9X_EPHYA 0x33 /* External PHY address register (dm9010) */ +#define DM9X_GPC2 0x34 /* General purpose control register 2 (dm9010) */ +#define DM9X_GPD2 0x35 /* General purpose register 2 */ +#define DM9X_GPC3 0x36 /* General purpose control register 3 (dm9010) */ +#define DM9X_GPD3 0x37 /* General purpose register 3 */ +#define DM9X_PBUSC 0x38 /* Processor bus control register (dm9010) */ +#define DM9X_IPINC 0x39 /* INT pin control register (dm9010) */ + +#define DM9X_MON1 0x40 /* Monitor register 1 (dm9010) */ +#define DM9X_MON2 0x41 /* Monitor register 2 (dm9010) */ + +#define DM9X_SCLKC 0x50 /* System clock turn ON control register (dm9010) */ +#define DM9X_SCLKR 0x51 /* Resume system clock control register (dm9010) */ + +#define DM9X_MRCMDX 0xf0 /* Memory data pre-fetch read command without address increment */ +#define DM9X_MRCMDX1 0xf1 /* memory data read command without address increment (dm9010) */ +#define DM9X_MRCMD 0xf2 /* Memory data read command with address increment */ +#define DM9X_MDRAL 0xf4 /* Memory data read address register (lo) */ +#define DM9X_MDRAH 0xf5 /* Memory data read address register (hi) */ +#define DM9X_MWCMDX 0xf6 /* Memory data write command without address increment */ +#define DM9X_MWCMD 0xf8 /* Memory data write command with address increment */ +#define DM9X_MDWAL 0xfa /* Memory data write address register (lo) */ +#define DM9X_MDWAH 0xfb /* Memory data write address register (lo) */ +#define DM9X_TXPLL 0xfc /* Memory data write address register (lo) */ +#define DM9X_TXPLH 0xfd /* Memory data write address register (hi) */ +#define DM9X_ISR 0xfe /* Interrupt status register */ +#define DM9X_IMR 0xff /* Interrupt mask register */ + +/* Network control register bit definitions */ + +#define DM9X_NETC_RST (1 << 0) /* Software reset */ +#define DM9X_NETC_LBKM (3 << 1) /* Loopback mode mask */ +#define DM9X_NETC_LBK0 (0 << 1) /* 0: Normal */ +#define DM9X_NETC_LBK1 (1 << 1) /* 1: MAC internal loopback */ +#define DM9X_NETC_LBK2 (2 << 1) /* 2: Internal PHY 100M mode loopback */ +#define DM9X_NETC_FDX (1 << 3) /* Full dupliex mode */ +#define DM9X_NETC_FCOL (1 << 4) /* Force collision mode */ +#define DM9X_NETC_WAKEEN (1 << 6) /* Wakeup event enable */ +#define DM9X_NETC_EXTPHY (1 << 7) /* Select external PHY */ + +/* Network status bit definitions */ + +#define DM9X_NETS_RXOV (1 << 1) /* RX Fifo overflow */ +#define DM9X_NETS_TX1END (1 << 2) /* TX packet 1 complete status */ +#define DM9X_NETS_TX2END (1 << 3) /* TX packet 2 complete status */ +#define DM9X_NETS_WAKEST (1 << 5) /* Wakeup event status */ +#define DM9X_NETS_LINKST (1 << 6) /* Link status */ +#define DM9X_NETS_SPEED (1 << 7) /* Media speed */ + +/* IMR/ISR bit definitions */ + +#define DM9X_INT_PR (1 << 0) /* Packet received interrupt */ +#define DM9X_INT_PT (1 << 1) /* Packet transmitted interrupt */ +#define DM9X_INT_RO (1 << 2) /* Receive overflow interrupt */ +#define DM9X_INT_ROO (1 << 3) /* Receive overflow counter overflow int */ +#define DM9X_INT_UDRUN (1 << 4) /* Transmit underrun interrupt */ +#define DM9X_INT_LNKCHG (1 << 5) /* Link status change interrupt */ +#define DM9X_INT_ALL (0x3f) + +#define DM9X_IMR_UNUSED (1 << 6) /* (not used) */ +#define DM9X_IMR_PAR (1 << 7) /* Enable auto R/W pointer reset */ + +#define DM9X_ISR_IOMODEM (3 << 6) /* IO mode mask */ +#define DM9X_ISR_IOMODE8 (2 << 6) /* IO mode = 8 bit */ +#define DM9X_ISR_IOMODE16 (0 << 6) /* IO mode = 16 bit */ +#define DM9X_ISR_IOMODE32 (1 << 6) /* IO mode = 32 bit */ + +#define DM9X_IMRENABLE (DM9X_INT_PR|DM9X_INT_PT|DM9X_INT_LNKCHG|DM9X_IMR_PAR) +#define DM9X_IMRRXDISABLE (DM9X_INT_PT|DM9X_INT_LNKCHG|DM9X_IMR_PAR) +#define DM9X_IMRDISABLE (DM9X_IMR_PAR) + +/* EEPROM/PHY control regiser bits */ + +#define DM9X_EEPHYC_ERRE (1 << 0) /* EEPROM (vs PHY) access status */ +#define DM9X_EEPHYC_ERPRW (1 << 1) /* EEPROM/PHY write access */ +#define DM9X_EEPHYC_ERPRR (1 << 2) /* EEPROM/PHY read access */ +#define DM9X_EEPHYC_EPOS (1 << 3) /* EEPROM/PHY operation select */ +#define DM9X_EEPHYC_WEP (1 << 4) /* Write EEPROM enable */ +#define DM9X_EEPHYC_REEP (1 << 5) /* Reload EEPROM */ + +/* Supported values from the vendor and product ID register */ + +#define DM9X_DAVICOMVID 0x0a46 +#define DM9X_DM9000PID 0x9000 +#define DM9X_DM9010PID 0x9010 + +/* RX control register bit settings */ + +#define DM9X_RXC_RXEN (1 << 0) /* RX enable */ +#define DM9X_RXC_PRMSC (1 << 1) /* Promiscuous mode */ +#define DM9X_RXC_RUNT (1 << 2) /* Pass runt packet */ +#define DM9X_RXC_ALL (1 << 3) /* Pass all multicast */ +#define DM9X_RXC_DISCRC (1 << 4) /* Discard CRC error packets */ +#define DM9X_RXC_DISLONG (1 << 5) /* Discard long packets */ +#define DM9X_RXC_WTDIS (1 << 6) /* Disable watchdog timer */ +#define DM9X_RXC_HASHALL (1 << 7) /* Filter all addresses in hash table */ + +#define DM9X_RXCSETUP (DM9X_RXC_DISCRC|DM9X_RXC_DISLONG) + +/* EEPHY bit settings */ + +#define DM9X_EEPHYA_EROA 0x40 /* PHY register address 0x01 */ + +#define DM9X_PKTRDY 0x01 /* Packet ready to receive */ + +/* The RX interrupt will be disabled if more than the following RX + * interrupts are received back-to-back. + */ + +#define DM9X_CRXTHRES 10 + +/* All access is via an index register and a data regist. Select accecss + * according to user supplied base address and bus width. + */ + +#if defined(CONFIG_DM9X_BUSWIDTH8) +# define DM9X_INDEX *(volatile uint8_t*)(CONFIG_DM9X_BASE) +# define DM9X_DATA *(volatile uint8_t*)(CONFIG_DM9X_BASE + 2) +#elif defined(CONFIG_DM9X_BUSWIDTH16) +# define DM9X_INDEX *(volatile uint16_t*)(CONFIG_DM9X_BASE) +# define DM9X_DATA *(volatile uint16_t*)(CONFIG_DM9X_BASE + 2) +#elif defined(CONFIG_DM9X_BUSWIDTH32) +# define DM9X_INDEX *(volatile uint32_t*)(CONFIG_DM9X_BASE) +# define DM9X_DATA *(volatile uint32_t*)(CONFIG_DM9X_BASE + 2) +#endif + +/* Phy operating mode. Default is AUTO, but this setting can be overridden + * in the NuttX configuration file. + */ + +#define DM9X_MODE_AUTO 0 +#define DM9X_MODE_10MHD 1 +#define DM9X_MODE_100MHD 2 +#define DM9X_MODE_10MFD 3 +#define DM9X_MODE_100MFD 4 + +#ifndef CONFIG_DM9X_MODE +# define CONFIG_DM9X_MODE DM9X_MODE_AUTO +#endif + +/* TX poll deley = 1 seconds. CLK_TCK is the number of clock ticks per second */ + +#define DM6X_WDDELAY (1*CLK_TCK) +#define DM6X_POLLHSEC (1*2) + +/* TX timeout = 1 minute */ + +#define DM6X_TXTIMEOUT (60*CLK_TCK) + +/* This is a helper pointer for accessing the contents of the Ethernet header */ + +#define BUF ((struct uip_eth_hdr *)dm9x->dm_dev.d_buf) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +union rx_desc_u +{ + uint8_t rx_buf[4]; + struct + { + uint8_t rx_byte; + uint8_t rx_status; + uint16_t rx_len; + } desc; +}; + +/* The dm9x_driver_s encapsulates all DM90x0 state information for a single + * DM90x0 hardware interface + */ + +struct dm9x_driver_s +{ + bool dm_bifup; /* true:ifup false:ifdown */ + bool dm_b100M; /* true:speed == 100M; false:speed == 10M */ + WDOG_ID dm_txpoll; /* TX poll timer */ + WDOG_ID dm_txtimeout; /* TX timeout timer */ + uint8_t dm_ntxpending; /* Count of packets pending transmission */ + uint8_t ncrxpackets; /* Number of continuous rx packets */ + + /* Mode-dependent function to move data in 8/16/32 I/O modes */ + + void (*dm_read)(uint8_t *ptr, int len); + void (*dm_write)(const uint8_t *ptr, int len); + void (*dm_discard)(int len); + +#if defined(CONFIG_DM9X_STATS) + uint32_t dm_ntxpackets; /* Count of packets sent */ + uint32_t dm_ntxbytes; /* Count of bytes sent */ + uint32_t dm_ntxerrors; /* Count of TX errors */ + uint32_t dm_nrxpackets; /* Count of packets received */ + uint32_t dm_nrxbytes; /* Count of bytes received */ + uint32_t dm_nrxfifoerrors; /* Count of RX FIFO overflow errors */ + uint32_t dm_nrxcrcerrors; /* Count of RX CRC errors */ + uint32_t dm_nrxlengtherrors; /* Count of RX length errors */ + uint32_t dm_nphyserrors; /* Count of physical layer errors */ + uint32_t dm_nresets; /* Counts number of resets */ + uint32_t dm_ntxtimeouts; /* Counts resets caused by TX timeouts */ +#endif + + /* This holds the information visible to uIP/NuttX */ + + struct uip_driver_s dm_dev; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* At present, only a single DM90x0 device is supported. */ + +static struct dm9x_driver_s g_dm9x[CONFIG_DM9X_NINTERFACES]; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Utility functions */ + +static uint8_t getreg(int reg); +static void putreg(int reg, uint8_t value); +static void read8(uint8_t *ptr, int len); +static void read16(uint8_t *ptr, int len); +static void read32(uint8_t *ptr, int len); +static void discard8(int len); +static void discard16(int len); +static void discard32(int len); +static void write8(const uint8_t *ptr, int len); +static void write16(const uint8_t *ptr, int len); +static void write32(const uint8_t *ptr, int len); + +/* static uint16_t dm9x_readsrom(struct dm9x_driver_s *dm9x, int offset); */ +static uint16_t dm9x_phyread(struct dm9x_driver_s *dm9x, int reg); +static void dm9x_phywrite(struct dm9x_driver_s *dm9x, int reg, uint16_t value); + +#if defined(CONFIG_DM9X_STATS) +static void dm9x_resetstatistics(struct dm9x_driver_s *dm9x); +#else +# define dm9x_resetstatistics(dm9x) +#endif + +#if defined(CONFIG_DM9X_STATS) && defined(CONFIG_DEBUG) +static void dm9x_dumpstatistics(struct dm9x_driver_s *dm9x); +#else +# define dm9x_dumpstatistics(dm9x) +#endif + +#if defined(CONFIG_DM9X_CHECKSUM) +static bool dm9x_rxchecksumready(uint8_t); +#else +# define dm9x_rxchecksumready(a) ((a) == 0x01) +#endif + +/* Common TX logic */ + +static int dm9x_transmit(struct dm9x_driver_s *dm9x); +static int dm9x_uiptxpoll(struct uip_driver_s *dev); + +/* Interrupt handling */ + +static void dm9x_receive(struct dm9x_driver_s *dm9x); +static void dm9x_txdone(struct dm9x_driver_s *dm9x); +static int dm9x_interrupt(int irq, FAR void *context); + +/* Watchdog timer expirations */ + +static void dm9x_polltimer(int argc, uint32_t arg, ...); +static void dm9x_txtimeout(int argc, uint32_t arg, ...); + +/* NuttX callback functions */ + +static int dm9x_ifup(struct uip_driver_s *dev); +static int dm9x_ifdown(struct uip_driver_s *dev); +static int dm9x_txavail(struct uip_driver_s *dev); +#ifdef CONFIG_NET_IGMP +static int dm9x_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +static int dm9x_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +#endif + +/* Initialization functions */ + +static void dm9x_bringup(struct dm9x_driver_s *dm9x); +static void dm9x_reset(struct dm9x_driver_s *dm9x); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: getreg and setreg + * + * Description: + * Access to memory-mapped DM90x0 8-bit registers + * + * Parameters: + * reg - Register number + * value - Value to write to the register (setreg only) + * + * Returned Value: + * Value read from the register (getreg only) + * + * Assumptions: + * + ****************************************************************************/ + +static uint8_t getreg(int reg) +{ + DM9X_INDEX = reg; + return DM9X_DATA & 0xff; +} + +static void putreg(int reg, uint8_t value) +{ + DM9X_INDEX = reg; + DM9X_DATA = value & 0xff; +} + +/**************************************************************************** + * Function: read8, read16, read32 + * + * Description: + * Read packet data from the DM90x0 SRAM based on its current I/O mode + * + * Parameters: + * ptr - Location to write the packet data + * len - The number of bytes to read + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void read8(uint8_t *ptr, int len) +{ + nvdbg("Read %d bytes (8-bit mode)\n", len); + for (; len > 0; len--) + { + *ptr++ = DM9X_DATA; + } +} + +static void read16(uint8_t *ptr, int len) +{ + register uint16_t *ptr16 = (uint16_t*)ptr; + nvdbg("Read %d bytes (16-bit mode)\n", len); + for (; len > 0; len -= sizeof(uint16_t)) + { + *ptr16++ = DM9X_DATA; + } +} + +static void read32(uint8_t *ptr, int len) +{ + register uint32_t *ptr32 = (uint32_t*)ptr; + nvdbg("Read %d bytes (32-bit mode)\n", len); + for (; len > 0; len -= sizeof(uint32_t)) + { + *ptr32++ = DM9X_DATA; + } +} + +/**************************************************************************** + * Function: discard8, discard16, discard32 + * + * Description: + * Read and discard packet data in the DM90x0 SRAM based on its current + * I/O mode + * + * Parameters: + * len - The number of bytes to discard + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void discard8(int len) +{ + nvdbg("Discard %d bytes (8-bit mode)\n", len); + for (; len > 0; len--) + { + DM9X_DATA; + } +} + +static void discard16(int len) +{ + nvdbg("Discard %d bytes (16-bit mode)\n", len); + for (; len > 0; len -= sizeof(uint16_t)) + { + DM9X_DATA; + } +} + +static void discard32(int len) +{ + nvdbg("Discard %d bytes (32-bit mode)\n", len); + for (; len > 0; len -= sizeof(uint32_t)) + { + DM9X_DATA; + } +} + +/**************************************************************************** + * Function: write8, write16, write32 + * + * Description: + * Write packet data into the DM90x0 SRAM based on its current I/O mode + * + * Parameters: + * ptr - Location to write the packet data + * len - The number of bytes to read + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void write8(const uint8_t *ptr, int len) +{ + nvdbg("Write %d bytes (8-bit mode)\n", len); + for (; len > 0; len--) + { + DM9X_DATA = (*ptr++ & 0xff); + } +} + +static void write16(const uint8_t *ptr, int len) +{ + register uint16_t *ptr16 = (uint16_t*)ptr; + nvdbg("Write %d bytes (16-bit mode)\n", len); + + for (; len > 0; len -= sizeof(uint16_t)) + { + DM9X_DATA = *ptr16++; + } +} + +static void write32(const uint8_t *ptr, int len) +{ + register uint32_t *ptr32 = (uint32_t*)ptr; + nvdbg("Write %d bytes (32-bit mode)\n", len); + for (; len > 0; len -= sizeof(uint32_t)) + { + DM9X_DATA = *ptr32++; + } +} + +/**************************************************************************** + * Function: dm9x_readsrom + * + * Description: + * Read a word from SROM + * + * Parameters: + * dm9x - Reference to the driver state structure + * offset - SROM offset to read from + * + * Returned Value: + * SROM content at that offset + * + * Assumptions: + * + ****************************************************************************/ + +#if 0 /* Not used */ +static uint16_t dm9x_readsrom(struct dm9x_driver_s *dm9x, int offset) +{ + putreg(DM9X_EEPHYA, offset); + putreg(DM9X_EEPHYC, DM9X_EEPHYC_ERPRR); + up_udelay(200); + putreg(DM9X_EEPHYC, 0x00); + return (getreg(DM9X_EEPHYDL) + (getreg(DM9X_EEPHYDH) << 8) ); +} +#endif + +/**************************************************************************** + * Function: dm9x_phyread and dm9x_phywrite + * + * Description: + * Read/write data from/to the PHY + * + * Parameters: + * dm9x - Reference to the driver state structure + * reg - PHY register offset + * value - The value to write to the PHY register (dm9x_write only) + * + * Returned Value: + * The value read from the PHY (dm9x_read only) + * + * Assumptions: + * + ****************************************************************************/ + +static uint16_t dm9x_phyread(struct dm9x_driver_s *dm9x, int reg) +{ + /* Setup DM9X_EEPHYA, the EEPROM/PHY address register */ + + putreg(DM9X_EEPHYA, DM9X_EEPHYA_EROA | reg); + + /* Issue PHY read command pulse in the EEPROM/PHY control register */ + + putreg(DM9X_EEPHYC, (DM9X_EEPHYC_ERPRR|DM9X_EEPHYC_EPOS)); + up_udelay(100); + putreg(DM9X_EEPHYC, 0x00); + + /* Return the data from the EEPROM/PHY data register pair */ + + return (((uint16_t)getreg(DM9X_EEPHYDH)) << 8) | (uint16_t)getreg(DM9X_EEPHYDL); +} + +static void dm9x_phywrite(struct dm9x_driver_s *dm9x, int reg, uint16_t value) +{ + /* Setup DM9X_EEPHYA, the EEPROM/PHY address register */ + + putreg(DM9X_EEPHYA, DM9X_EEPHYA_EROA | reg); + + /* Put the data to write in the EEPROM/PHY data register pair */ + + putreg(DM9X_EEPHYDL, (value & 0xff)); + putreg(DM9X_EEPHYDH, ((value >> 8) & 0xff)); + + /* Issue PHY write command pulse in the EEPROM/PHY control register */ + + putreg(DM9X_EEPHYC, (DM9X_EEPHYC_ERPRW|DM9X_EEPHYC_EPOS)); + up_udelay(500); + putreg(DM9X_EEPHYC, 0x0); +} + +/**************************************************************************** + * Function: dm9x_resetstatistics + * + * Description: + * Reset all DM90x0 statistics + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#if defined(CONFIG_DM9X_STATS) +static void dm9x_resetstatistics(struct dm9x_driver_s *dm9x) +{ + dm9x->dm_ntxpackets = 0; /* Count of packets sent */ + dm9x->dm_ntxbytes = 0; /* Count of bytes sent */ + dm9x->dm_ntxerrors = 0; /* Count of TX errors */ + dm9x->dm_nrxpackets = 0; /* Count of packets received */ + dm9x->dm_nrxbytes = 0; /* Count of bytes received */ + dm9x->dm_nrxfifoerrors = 0; /* Count of RX FIFO overflow errors */ + dm9x->dm_nrxcrcerrors = 0; /* Count of RX CRC errors */ + dm9x->dm_nrxlengtherrors = 0; /* Count of RX length errors */ + dm9x->dm_nphyserrors = 0; /* Count of physical layer errors */ + dm9x->dm_nresets = 0; /* Counts number of resets */ + dm9x->dm_ntxtimeouts = 0; /* Counts resets caused by TX timeouts */ +} +#endif + +/**************************************************************************** + * Function: dm9x_dumpstatistics + * + * Description: + * Print the current value of all DM90x0 statistics + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#if defined(CONFIG_DM9X_STATS) && defined(CONFIG_DEBUG) +static void dm9x_dumpstatistics(struct dm9x_driver_s *dm9x) +{ + ndbg("TX packets: %d\n", dm9x->dm_ntxpackets); + ndbg(" bytes: %d\n", dm9x->dm_ntxbytes); + ndbg(" errors: %d\n", dm9x->dm_ntxerrors); + ndbg("RX packets: %d\n", dm9x->dm_nrxpackets); + ndbg(" bytes: %d\n", dm9x->dm_nrxbytes); + ndbg(" FIFO overflows: %d\n", dm9x->dm_nrxfifoerrors); + ndbg(" CRC errors: %d\n", dm9x->dm_nrxcrcerrors); + ndbg(" length errors: %d\n", dm9x->dm_nrxlengtherrors); + ndbg("Physical layer errors: %d\n", dm9x->dm_nphyserrors); + ndbg("Resets: %d\n", dm9x->dm_nresets); + ndbg("TX timeout resets: %d\n", dm9x->dm_ntxtimeouts); +} +#endif + +/**************************************************************************** + * Function: dm9x_rxchecksumready + * + * Description: + * Return true if the RX checksum is available + * + * Parameters: + * rxbyte + * + * Returned Value: + * true: checksum is ready + * + * Assumptions: + * + ****************************************************************************/ + +#if defined(CONFIG_DM9X_CHECKSUM) +static inline bool dm9x_rxchecksumready(uint8_t rxbyte) +{ + if ((rxbyte & 0x01) == 0) + { + return false; + } + + return ((rxbyte >> 4) | 0x01) != 0; +} +#endif + +/**************************************************************************** + * Function: dm9x_transmit + * + * Description: + * Start hardware transmission. Called either from the txdone interrupt + * handling or from watchdog based polling. + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static int dm9x_transmit(struct dm9x_driver_s *dm9x) +{ + /* Check if there is room in the DM90x0 to hold another packet. In 100M mode, + * that can be 2 packets, otherwise it is a single packet. + */ + + if (dm9x->dm_ntxpending < 1 || (dm9x->dm_b100M && dm9x->dm_ntxpending < 2)) + { + /* Increment count of packets transmitted */ + + dm9x->dm_ntxpending++; +#if defined(CONFIG_DM9X_STATS) + dm9x->dm_ntxpackets++; + dm9x->dm_ntxbytes += dm9x->dm_dev.d_len; +#endif + + /* Disable all DM90x0 interrupts */ + + putreg(DM9X_IMR, DM9X_IMRDISABLE); + + /* Set the TX length */ + + putreg(DM9X_TXPLL, (dm9x->dm_dev.d_len & 0xff)); + putreg(DM9X_TXPLH, (dm9x->dm_dev.d_len >> 8) & 0xff); + + /* Move the data to be sent into TX SRAM */ + + DM9X_INDEX = DM9X_MWCMD; + dm9x->dm_write(dm9x->dm_dev.d_buf, dm9x->dm_dev.d_len); + +#if !defined(CONFIG_DM9X_ETRANS) + /* Issue TX polling command */ + + putreg(DM9X_TXC, 0x1); /* Cleared after TX complete*/ +#endif + + /* Clear count of back-to-back RX packet transfers */ + + dm9x->ncrxpackets = 0; + + /* Re-enable DM90x0 interrupts */ + + putreg(DM9X_IMR, DM9X_IMRENABLE); + + /* Setup the TX timeout watchdog (perhaps restarting the timer) */ + + (void)wd_start(dm9x->dm_txtimeout, DM6X_TXTIMEOUT, dm9x_txtimeout, 1, (uint32_t)dm9x); + return OK; + } + return -EBUSY; +} + +/**************************************************************************** + * Function: dm9x_uiptxpoll + * + * Description: + * The transmitter is available, check if uIP has any outgoing packets ready + * to send. This is a callback from uip_poll(). uip_poll() may be called: + * + * 1. When the preceding TX packet send is complete, + * 2. When the preceding TX packet send timesout and the DM90x0 is reset + * 3. During normal TX polling + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static int dm9x_uiptxpoll(struct uip_driver_s *dev) +{ + struct dm9x_driver_s *dm9x = (struct dm9x_driver_s *)dev->d_private; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + if (dm9x->dm_dev.d_len > 0) + { + uip_arp_out(&dm9x->dm_dev); + dm9x_transmit(dm9x); + + /* Check if there is room in the DM90x0 to hold another packet. In 100M mode, + * that can be 2 packets, otherwise it is a single packet. + */ + + if (dm9x->dm_ntxpending > 1 || !dm9x->dm_b100M) + { + /* Returning a non-zero value will terminate the poll operation */ + + return 1; + } + } + + /* If zero is returned, the polling will continue until all connections have + * been examined. + */ + + return 0; +} + +/**************************************************************************** + * Function: dm9x_receive + * + * Description: + * An interrupt was received indicating the availability of a new RX packet + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void dm9x_receive(struct dm9x_driver_s *dm9x) +{ + union rx_desc_u rx; + bool bchecksumready; + uint8_t mdrah; + uint8_t mdral; + uint8_t rxbyte; + + nvdbg("Packet received\n"); + + do + { + /* Store the value of memory data read address register */ + + mdrah = getreg(DM9X_MDRAH); + mdral = getreg(DM9X_MDRAL); + + getreg(DM9X_MRCMDX); /* Dummy read */ + rxbyte = (uint8_t)DM9X_DATA; /* Get the most up-to-date data */ + + /* Packet ready for receive check */ + + bchecksumready = dm9x_rxchecksumready(rxbyte); + if (!bchecksumready) + { + break; + } + + /* A packet is ready now. Get status/length */ + + DM9X_INDEX = DM9X_MRCMD; /* set read ptr ++ */ + + /* Read packet status & length */ + + dm9x->dm_read((uint8_t*)&rx, 4); + + /* Check if any errors were reported by the hardware */ + + if (rx.desc.rx_status & 0xbf) + { + /* Bad RX packet... update statistics */ + +#if defined(CONFIG_DM9X_STATS) + if (rx.desc.rx_status & 0x01) + { + dm9x->dm_nrxfifoerrors++; + ndbg("RX FIFO error: %d\n", dm9x->dm_nrxfifoerrors); + } + + if (rx.desc.rx_status & 0x02) + { + dm9x->dm_nrxcrcerrors++; + ndbg("RX CRC error: %d\n", dm9x->dm_nrxcrcerrors); + } + + if (rx.desc.rx_status & 0x80) + { + dm9x->dm_nrxlengtherrors++; + ndbg("RX length error: %d\n", dm9x->dm_nrxlengtherrors); + } + + if (rx.desc.rx_status & 0x08) + { + dm9x->dm_nphyserrors++; + ndbg("Physical Layer error: %d\n", dm9x->dm_nphyserrors); + } +#else + ndbg("Received packet with errors: %02x\n", rx.desc.rx_status); +#endif + /* Drop this packet and continue to check the next packet */ + + dm9x->dm_discard(rx.desc.rx_len); + } + + /* Also check if the packet is a valid size for the uIP configuration */ + + else if (rx.desc.rx_len < UIP_LLH_LEN || rx.desc.rx_len > (CONFIG_NET_BUFSIZE + 2)) + { +#if defined(CONFIG_DM9X_STATS) + dm9x->dm_nrxlengtherrors++; + ndbg("RX length error: %d\n", dm9x->dm_nrxlengtherrors); +#endif + /* Drop this packet and continue to check the next packet */ + + dm9x->dm_discard(rx.desc.rx_len); + } + else + { + /* Good packet... Copy the packet data out of SRAM and pass it one to uIP */ + + dm9x->dm_dev.d_len = rx.desc.rx_len; + dm9x->dm_read(dm9x->dm_dev.d_buf, rx.desc.rx_len); + + /* We only accept IP packets of the configured type and ARP packets */ + +#ifdef CONFIG_NET_IPv6 + if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) +#else + if (BUF->type == HTONS(UIP_ETHTYPE_IP)) +#endif + { + uip_arp_ipin(&dm9x->dm_dev); + uip_input(&dm9x->dm_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (dm9x->dm_dev.d_len > 0) + { + uip_arp_out(&dm9x->dm_dev); + dm9x_transmit(dm9x); + } + } + else if (BUF->type == htons(UIP_ETHTYPE_ARP)) + { + uip_arp_arpin(&dm9x->dm_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (dm9x->dm_dev.d_len > 0) + { + dm9x_transmit(dm9x); + } + } + } + +#if defined(CONFIG_DM9X_STATS) + dm9x->dm_nrxpackets++; + dm9x->dm_nrxbytes += rx.desc.rx_len; +#endif + dm9x->ncrxpackets++; + } + while ((rxbyte & 0x01) == DM9X_PKTRDY && dm9x->ncrxpackets < DM9X_CRXTHRES); + nvdbg("All RX packets processed\n"); +} + +/**************************************************************************** + * Function: dm9x_txdone + * + * Description: + * An interrupt was received indicating that the last TX packet(s) is done + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void dm9x_txdone(struct dm9x_driver_s *dm9x) +{ + int nsr; + + nvdbg("TX done\n"); + + /* Another packet has completed transmission. Decrement the count of + * of pending TX transmissions. + */ + + nsr = getreg(DM9X_NETS); + if (nsr & DM9X_NETS_TX1END) + { + if (dm9x->dm_ntxpending) + { + dm9x->dm_ntxpending--; + } + else + { + ndbg("Bad TX count (TX1END)\n"); + } + } + + if (nsr & DM9X_NETS_TX2END) + { + if (dm9x->dm_ntxpending) + { + dm9x->dm_ntxpending--; + } + else + { + ndbg("Bad TX count (TX2END)\n"); + } + } + + /* Cancel the TX timeout */ + + if (dm9x->dm_ntxpending == 0) + { + wd_cancel(dm9x->dm_txtimeout); + } + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&dm9x->dm_dev, dm9x_uiptxpoll); +} + +/**************************************************************************** + * Function: dm9x_interrupt + * + * Description: + * DM90x0 interrupt handler + * + * Parameters: + * irq - Number of the IRQ that generated the interrupt + * context - Interrupt register state save info (architecture-specific) + * + * Returned Value: + * OK on success + * + * Assumptions: + * + ****************************************************************************/ + +static int dm9x_interrupt(int irq, FAR void *context) +{ +#if CONFIG_DM9X_NINTERFACES == 1 + register struct dm9x_driver_s *dm9x = &g_dm9x[0]; +#else +# error "Additional logic needed to support multiple interfaces" +#endif + uint8_t isr; + uint8_t save; + int i; + + /* Save previous register address */ + + save = (uint8_t)DM9X_INDEX; + + /* Disable all DM90x0 interrupts */ + + putreg(DM9X_IMR, DM9X_IMRDISABLE); + + /* Get and clear the DM90x0 interrupt status bits */ + + isr = getreg(DM9X_ISR); + putreg(DM9X_ISR, isr); + nvdbg("Interrupt status: %02x\n", isr); + + /* Check for link status change */ + + if (isr & DM9X_INT_LNKCHG) + { + /* Wait up to 0.5s for link OK */ + + for (i = 0; i < 500; i++) + { + dm9x_phyread(dm9x,0x1); + if (dm9x_phyread(dm9x,0x1) & 0x4) /*Link OK*/ + { + /* Wait to get detected speed */ + + for (i = 0; i < 200; i++) + { + up_mdelay(1); + } + + /* Set the new network speed */ + + if (dm9x_phyread(dm9x, 0) & 0x2000) + { + dm9x->dm_b100M = true; + } + else + { + dm9x->dm_b100M = false; + } + break; + } + up_mdelay(1); + } + ndbg("delay: %dmS speed: %s\n", i, dm9x->dm_b100M ? "100M" : "10M"); + } + + /* Check if we received an incoming packet */ + + if (isr & DM9X_INT_PR) + { + dm9x_receive(dm9x); + } + + /* Check if we are able to transmit a packet */ + + if (isr & DM9X_INT_PT) + { + dm9x_txdone(dm9x); + } + + /* If the number of consecutive receive packets exceeds a threshold, + * then disable the RX interrupt. + */ + + if (dm9x->ncrxpackets >= DM9X_CRXTHRES) + { + /* Eanble all DM90x0 interrupts EXCEPT for RX */ + + putreg(DM9X_IMR, DM9X_IMRRXDISABLE); + } + else + { + /* Enable all DM90x0 interrupts */ + + putreg(DM9X_IMR, DM9X_IMRENABLE); + } + + /* Restore previous register address */ + + DM9X_INDEX = save; + return OK; +} + +/**************************************************************************** + * Function: dm9x_txtimeout + * + * Description: + * Our TX watchdog timed out. Called from the timer interrupt handler. + * The last TX never completed. Reset the DM90x0 and start again. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void dm9x_txtimeout(int argc, uint32_t arg, ...) +{ + struct dm9x_driver_s *dm9x = (struct dm9x_driver_s *)arg; + + ndbg("TX timeout\n"); + + /* Increment statistics and dump debug info */ + +#if defined(CONFIG_DM9X_STATS) + dm9x->dm_ntxtimeouts++; + dm9x->dm_ntxerrors++; +#endif + + ndbg(" TX packet count: %d\n", dm9x->dm_ntxpending); +#if defined(CONFIG_DM9X_STATS) + ndbg(" TX timeouts: %d\n", dm9x->dm_ntxtimeouts); +#endif + ndbg(" TX read pointer address: 0x%02x:%02x\n", + getreg(DM9X_TRPAH), getreg(DM9X_TRPAL)); + ndbg(" Memory data write address: 0x%02x:%02x (DM9010)\n", + getreg(DM9X_MDWAH), getreg(DM9X_MDWAL)); + + /* Then reset the DM90x0 */ + + dm9x_reset(dm9x); + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&dm9x->dm_dev, dm9x_uiptxpoll); +} + +/**************************************************************************** + * Function: dm9x_polltimer + * + * Description: + * Periodic timer handler. Called from the timer interrupt handler. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void dm9x_polltimer(int argc, uint32_t arg, ...) +{ + struct dm9x_driver_s *dm9x = (struct dm9x_driver_s *)arg; + + /* If the number of contiguous RX packets exceeds a threshold, reset the counter and + * re-enable RX interrupts + */ + + if (dm9x->ncrxpackets >= DM9X_CRXTHRES) + { + dm9x->ncrxpackets = 0; + putreg(DM9X_IMR, DM9X_IMRENABLE); + } + + /* Check if there is room in the DM90x0 to hold another packet. In 100M mode, + * that can be 2 packets, otherwise it is a single packet. + */ + + if (dm9x->dm_ntxpending < 1 || (dm9x->dm_b100M && dm9x->dm_ntxpending < 2)) + { + /* If so, update TCP timing states and poll uIP for new XMIT data */ + + (void)uip_timer(&dm9x->dm_dev, dm9x_uiptxpoll, DM6X_POLLHSEC); + } + + /* Setup the watchdog poll timer again */ + + (void)wd_start(dm9x->dm_txpoll, DM6X_WDDELAY, dm9x_polltimer, 1, arg); +} + +/**************************************************************************** + * Function: dm9x_phymode + * + * Description: + * Configure the PHY operating mode + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static inline void dm9x_phymode(struct dm9x_driver_s *dm9x) +{ + uint16_t phyreg0; + uint16_t phyreg4; + +#if CONFIG_DM9X_MODE == DM9X_MODE_AUTO + phyreg0 = 0x1200; /* Auto-negotiation & Restart Auto-negotiation */ + phyreg4 = 0x01e1; /* Default flow control disable*/ +#elif CONFIG_DM9X_MODE == DM9X_MODE_10MHD + phyreg4 = 0x21; + phyreg0 = 0x1000; +#elif CONFIG_DM9X_MODE == DM9X_MODE_10MFD + phyreg4 = 0x41; + phyreg0 = 0x1100; +#elif CONFIG_DM9X_MODE == DM9X_MODE_100MHD + phyreg4 = 0x81; + phyreg0 = 0x3000; +#elif CONFIG_DM9X_MODE == DM9X_MODE_100MFD + phyreg4 = 0x101; + phyreg0 = 0x3100; +#else +# error "Recognized PHY mode" +#endif + + dm9x_phywrite(dm9x, 0, phyreg0); + dm9x_phywrite(dm9x, 4, phyreg4); +} + +/**************************************************************************** + * Function: dm9x_ifup + * + * Description: + * NuttX Callback: Bring up the DM90x0 interface when an IP address is + * provided + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int dm9x_ifup(struct uip_driver_s *dev) +{ + struct dm9x_driver_s *dm9x = (struct dm9x_driver_s *)dev->d_private; + uint8_t netstatus; + int i; + + ndbg("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Initilize DM90x0 chip */ + + dm9x_bringup(dm9x); + + /* Check link state and media speed (waiting up to 3s for link OK) */ + + dm9x->dm_b100M = false; + for (i = 0; i < 3000; i++) + { + netstatus = getreg(DM9X_NETS); + if (netstatus & DM9X_NETS_LINKST) + { + /* Link OK... Wait a bit before getting the detected speed */ + + up_mdelay(200); + netstatus = getreg(DM9X_NETS); + if ((netstatus & DM9X_NETS_SPEED) == 0) + { + dm9x->dm_b100M = true; + } + break; + } + i++; + up_mdelay(1); + } + + ndbg("delay: %dmS speed: %s\n", i, dm9x->dm_b100M ? "100M" : "10M"); + + /* Set and activate a timer process */ + + (void)wd_start(dm9x->dm_txpoll, DM6X_WDDELAY, dm9x_polltimer, 1, (uint32_t)dm9x); + + /* Enable the DM9X interrupt */ + + dm9x->dm_bifup = true; + up_enable_irq(CONFIG_DM9X_IRQ); + return OK; +} + +/**************************************************************************** + * Function: dm9x_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int dm9x_ifdown(struct uip_driver_s *dev) +{ + struct dm9x_driver_s *dm9x = (struct dm9x_driver_s *)dev->d_private; + irqstate_t flags; + + ndbg("Stopping\n"); + + /* Disable the DM9X interrupt */ + + flags = irqsave(); + up_disable_irq(CONFIG_DM9X_IRQ); + + /* Cancel the TX poll timer and TX timeout timers */ + + wd_cancel(dm9x->dm_txpoll); + wd_cancel(dm9x->dm_txtimeout); + + /* Reset the device */ + + dm9x_phywrite(dm9x, 0x00, 0x8000); /* PHY reset */ + putreg(DM9X_GPD, 0x01); /* Power-down PHY (GEPIO0=1) */ + putreg(DM9X_IMR, DM9X_IMRDISABLE); /* Disable all interrupts */ + putreg(DM9X_RXC, 0x00); /* Disable RX */ + putreg(DM9X_ISR, DM9X_INT_ALL); /* Clear interrupt status */ + + dm9x->dm_bifup = false; + irqrestore(flags); + + /* Dump statistics */ + + dm9x_dumpstatistics(dm9x); + return OK; +} + +/**************************************************************************** + * Function: dm9x_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static int dm9x_txavail(struct uip_driver_s *dev) +{ + struct dm9x_driver_s *dm9x = (struct dm9x_driver_s *)dev->d_private; + irqstate_t flags; + + ndbg("Polling\n"); + flags = irqsave(); + + /* Ignore the notification if the interface is not yet up */ + + if (dm9x->dm_bifup) + { + + /* Check if there is room in the DM90x0 to hold another packet. In 100M + * mode, that can be 2 packets, otherwise it is a single packet. + */ + + if (dm9x->dm_ntxpending < 1 || (dm9x->dm_b100M && dm9x->dm_ntxpending < 2)) + { + /* If so, then poll uIP for new XMIT data */ + + (void)uip_poll(&dm9x->dm_dev, dm9x_uiptxpoll); + } + } + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: dm9x_addmac + * + * Description: + * NuttX Callback: Add the specified MAC address to the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be added + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int dm9x_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct dm9x_driver_s *priv = (FAR struct dm9x_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + +#warning "Multicast MAC support not implemented" + return OK; +} +#endif + +/**************************************************************************** + * Function: dm9x_rmmac + * + * Description: + * NuttX Callback: Remove the specified MAC address from the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be removed + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int dm9x_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct dm9x_driver_s *priv = (FAR struct dm9x_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + +#warning "Multicast MAC support not implemented" + return OK; +} +#endif + +/**************************************************************************** + * Function: dm9x_bringup + * + * Description: + * Initialize the dm90x0 chip + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void dm9x_bringup(struct dm9x_driver_s *dm9x) +{ + ndbg("Initializing\n"); + + /* Set the internal PHY power-on, GPIOs normal, and wait 2ms */ + + putreg(DM9X_GPD, 0x01); /* Power-down the PHY (GEPIO0=1) */ + up_udelay(500); + putreg(DM9X_GPD, 0x00); /* Preactivate PHY (GPIO0=0 */ + up_udelay(20); /* Wait 20us for PHY power-on ready */ + + /* Do a software reset and wait 20us (twice). The reset autoclears + * in 10us; 20us guarantees completion of the reset + */ + + putreg(DM9X_NETC, (DM9X_NETC_RST|DM9X_NETC_LBK1)); + up_udelay(20); + putreg(DM9X_NETC, (DM9X_NETC_RST|DM9X_NETC_LBK1)); + up_udelay(20); + + /* Configure I/O mode */ + + switch (getreg(DM9X_ISR) & DM9X_ISR_IOMODEM) + { + case DM9X_ISR_IOMODE8: + dm9x->dm_read = read8; + dm9x->dm_write = write8; + dm9x->dm_discard = discard8; + break; + + case DM9X_ISR_IOMODE16: + dm9x->dm_read = read16; + dm9x->dm_write = write16; + dm9x->dm_discard = discard16; + break; + + case DM9X_ISR_IOMODE32: + dm9x->dm_read = read32; + dm9x->dm_write = write32; + dm9x->dm_discard = discard32; + break; + + default: + break; + } + + /* Program PHY operating mode */ + + dm9x_phymode(dm9x); + + /* Program operating mode */ + + putreg(DM9X_NETC, 0x00); /* Network control */ + putreg(DM9X_TXC, 0x00); /* Clear TX Polling */ + putreg(DM9X_BPTHRES, 0x3f); /* Less 3kb, 600us */ + putreg(DM9X_SMODEC, 0x00); /* Special mode */ + putreg(DM9X_NETS, (DM9X_NETS_WAKEST|DM9X_NETS_TX1END|DM9X_NETS_TX2END)); /* Clear TX status */ + putreg(DM9X_ISR, DM9X_INT_ALL); /* Clear interrupt status */ + +#if defined(CONFIG_DM9X_CHECKSUM) + putreg(DM9X_TCCR, 0x07); /* TX UDP/TCP/IP checksum enable */ + putreg(DM9X_RCSR, 0x02); /* Receive checksum enable */ +#endif + +#if defined(CONFIG_DM9X_ETRANS) + putreg(DM9X_ETXCSR, 0x83); +#endif + + /* Initialize statistics */ + + dm9x->ncrxpackets = 0; /* Number of continuous RX packets */ + dm9x->dm_ntxpending = 0; /* Number of pending TX packets */ + dm9x_resetstatistics(dm9x); + + /* Activate DM9000A/DM9010 */ + + putreg(DM9X_RXC, DM9X_RXCSETUP | 1); /* RX enable */ + putreg(DM9X_IMR, DM9X_IMRENABLE); /* Enable TX/RX interrupts */ +} + +/**************************************************************************** + * Function: dm9x_reset + * + * Description: + * Stop, reset, re-initialize, and restart the DM90x0 chip and driver. At + * present, the chip is only reset after a TX timeout. + * + * Parameters: + * dm9x - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void dm9x_reset(struct dm9x_driver_s *dm9x) +{ + uint8_t save; + int i; + + /* Cancel the TX poll timer and TX timeout timers */ + + wd_cancel(dm9x->dm_txpoll); + wd_cancel(dm9x->dm_txtimeout); + + /* Save previous register address */ + + save = (uint8_t)DM9X_INDEX; + +#if defined(CONFIG_DM9X_STATS) + dm9x->dm_nresets++; +#endif + dm9x_bringup(dm9x); + + /* Wait up to 1 second for the link to be OK */ + + dm9x->dm_b100M = false; + for (i = 0; i < 1000; i++) + { + if (dm9x_phyread(dm9x,0x1) & 0x4) + { + if (dm9x_phyread(dm9x, 0) &0x2000) + { + dm9x->dm_b100M = true; + } + break; + } + up_mdelay(1); + } + + /* Restore previous register address */ + + DM9X_INDEX = save; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: dm9x_initialize + * + * Description: + * Initialize the DM90x0 driver + * + * Parameters: + * None + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * + ****************************************************************************/ + +/* Initialize the DM90x0 chip and driver */ + +int dm9x_initialize(void) +{ + uint8_t *mptr; + uint16_t vid; + uint16_t pid; + int i; + int j; + + /* Get the chip vendor ID and product ID */ + + vid = (((uint16_t)getreg(DM9X_VIDH)) << 8) | (uint16_t)getreg(DM9X_VIDL); + pid = (((uint16_t)getreg(DM9X_PIDH)) << 8) | (uint16_t)getreg(DM9X_PIDL); + nlldbg("I/O base: %08x VID: %04x PID: %04x\n", CONFIG_DM9X_BASE, vid, pid); + + /* Check if a DM90x0 chip is recognized at this I/O base */ + + if (vid != DM9X_DAVICOMVID || (pid != DM9X_DM9000PID && pid != DM9X_DM9010PID)) + { + nlldbg("DM90x0 vendor/product ID not found at this base address\n"); + return -ENODEV; + } + + /* Attach the IRQ to the driver */ + + if (irq_attach(CONFIG_DM9X_IRQ, dm9x_interrupt)) + { + /* We could not attach the ISR to the ISR */ + + nlldbg("irq_attach() failed\n"); + return -EAGAIN; + } + + /* Initialize the driver structure */ + + memset(g_dm9x, 0, CONFIG_DM9X_NINTERFACES*sizeof(struct dm9x_driver_s)); + g_dm9x[0].dm_dev.d_ifup = dm9x_ifup; /* I/F down callback */ + g_dm9x[0].dm_dev.d_ifdown = dm9x_ifdown; /* I/F up (new IP address) callback */ + g_dm9x[0].dm_dev.d_txavail = dm9x_txavail; /* New TX data callback */ +#ifdef CONFIG_NET_IGMP + g_dm9x[0].dm_dev.d_addmac = dm9x_addmac; /* Add multicast MAC address */ + g_dm9x[0].dm_dev.d_rmmac = dm9x_rmmac; /* Remove multicast MAC address */ +#endif + g_dm9x[0].dm_dev.d_private = (void*)g_dm9x; /* Used to recover private state from dev */ + + /* Create a watchdog for timing polling for and timing of transmisstions */ + + g_dm9x[0].dm_txpoll = wd_create(); /* Create periodic poll timer */ + g_dm9x[0].dm_txtimeout = wd_create(); /* Create TX timeout timer */ + + /* Read the MAC address */ + + mptr = g_dm9x[0].dm_dev.d_mac.ether_addr_octet; + for (i = 0, j = DM9X_PAB0; i < ETHER_ADDR_LEN; i++, j++) + { + mptr[i] = getreg(j); + } + + nlldbg("MAC: %0x:%0x:%0x:%0x:%0x:%0x\n", + mptr[0], mptr[1], mptr[2], mptr[3], mptr[4], mptr[5]); + + /* Register the device with the OS so that socket IOCTLs can be performed */ + + (void)netdev_register(&g_dm9x[0].dm_dev); + return OK; +} + +#endif /* CONFIG_NET && CONFIG_NET_DM90x0 */ + diff --git a/nuttx/drivers/net/e1000.c b/nuttx/drivers/net/e1000.c new file mode 100644 index 000000000..ce6192ac0 --- /dev/null +++ b/nuttx/drivers/net/e1000.c @@ -0,0 +1,1049 @@ +/**************************************************************************** + * drivers/net/e1000.c + * + * Copyright (C) 2011 Yu Qiang. All rights reserved. + * Author: Yu Qiang <yuq825@gmail.com> + * + * This file is a part of NuttX: + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <nuttx/kmalloc.h> +#include <stdint.h> +#include <stdbool.h> +#include <time.h> +#include <debug.h> +#include <wdog.h> +#include <errno.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> + +#include <net/uip/uip.h> +#include <net/uip/uip-arp.h> +#include <net/uip/uip-arch.h> + +#include <rgmp/pmap.h> +#include <rgmp/string.h> +#include <rgmp/stdio.h> +#include <rgmp/arch/pci.h> +#include <rgmp/memio.h> +#include "e1000.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* TX poll deley = 1 seconds. CLK_TCK is the number of clock ticks per second */ + +#define E1000_WDDELAY (1*CLK_TCK) +#define E1000_POLLHSEC (1*2) + +/* TX timeout = 1 minute */ + +#define E1000_TXTIMEOUT (60*CLK_TCK) + +/* This is a helper pointer for accessing the contents of the Ethernet header */ + +#define BUF ((struct uip_eth_hdr *)e1000->uip_dev.d_buf) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct tx_ring { + struct tx_desc *desc; + char *buf; + int tail; // where to write desc +}; + +struct rx_ring { + struct rx_desc *desc; + char *buf; + int head; // where to read + int tail; // where to release free desc + int free; // number of freed desc +}; + +struct e1000_dev { + uint32_t phy_mem_base; + uint32_t io_mem_base; + uint32_t mem_size; + int pci_dev_id; + unsigned char src_mac[6]; + unsigned char dst_mac[6]; + int irq; + struct irq_action int_desc; + struct tx_ring tx_ring; + struct rx_ring rx_ring; + struct e1000_dev *next; + + // NuttX net data + bool bifup; /* true:ifup false:ifdown */ + WDOG_ID txpoll; /* TX poll timer */ + WDOG_ID txtimeout; /* TX timeout timer */ + + /* This holds the information visible to uIP/NuttX */ + + struct uip_driver_s uip_dev; /* Interface understood by uIP */ +}; + +struct e1000_dev_head { + struct e1000_dev *next; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct e1000_dev_head e1000_list = {0}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Common TX logic */ + +static int e1000_transmit(struct e1000_dev *e1000); +static int e1000_uiptxpoll(struct uip_driver_s *dev); + +/* Interrupt handling */ + +static void e1000_receive(struct e1000_dev *e1000); + +/* Watchdog timer expirations */ + +static void e1000_polltimer(int argc, uint32_t arg, ...); +static void e1000_txtimeout(int argc, uint32_t arg, ...); + +/* NuttX callback functions */ + +static int e1000_ifup(struct uip_driver_s *dev); +static int e1000_ifdown(struct uip_driver_s *dev); +static int e1000_txavail(struct uip_driver_s *dev); +#ifdef CONFIG_NET_IGMP +static int e1000_addmac(struct uip_driver_s *dev, const uint8_t *mac); +static int e1000_rmmac(struct uip_driver_s *dev, const uint8_t *mac); +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline void e1000_outl(struct e1000_dev *dev, int reg, uint32_t val) +{ + writel(dev->io_mem_base+reg, val); +} + +static inline uint32_t e1000_inl(struct e1000_dev *dev, int reg) +{ + return readl(dev->io_mem_base+reg); +} + +/****************************** e1000 driver ********************************/ + +void e1000_reset(struct e1000_dev *dev) +{ + uint32_t dev_control; + + // Reset the network controller hardware + dev_control = 0; + dev_control |= (1<<0); // FD-bit (Full Duplex) + dev_control |= (0<<2); // GIOMD-bit (GIO Master Disable) + dev_control |= (1<<3); // LRST-bit (Link Reset) + dev_control |= (1<<6); // SLU-bit (Set Link Up) + dev_control |= (2<<8); // SPEED=2 (1000Mbps) + dev_control |= (0<<11); // FRCSPD-bit (Force Speed) + dev_control |= (0<<12); // FRCDPLX-bit (Force Duplex) + dev_control |= (0<<20); // ADVD3WUC-bit (Advertise D3 Wake Up Cap) + dev_control |= (1<<26); // RST-bit (Device Reset) + dev_control |= (1<<27); // RFCE-bit (Receive Flow Control Enable) + dev_control |= (1<<28); // TFCE-bit (Transmit Flow Control Enable) + dev_control |= (0<<30); // VME-bit (VLAN Mode Enable) + dev_control |= (0<<31); // PHY_RST-bit (PHY Reset) + + e1000_outl(dev, E1000_IMC, 0xFFFFFFFF); + e1000_outl(dev, E1000_STATUS, 0x00000000); + e1000_outl(dev, E1000_CTRL, dev_control); + dev_control &= ~(1<<26); // clear RST-bit (Device Reset) + e1000_outl(dev, E1000_CTRL, dev_control); + up_mdelay(10); + e1000_outl(dev, E1000_CTRL_EXT, 0x001401C0); + e1000_outl(dev, E1000_IMC, 0xFFFFFFFF); +} + +void e1000_turn_on(struct e1000_dev *dev) +{ + int tx_control, rx_control; + uint32_t ims = 0; + + // turn on the controller's receive engine + rx_control = e1000_inl(dev, E1000_RCTL); + rx_control |= (1<<1); + e1000_outl(dev, E1000_RCTL, rx_control); + + // turn on the controller's transmit engine + tx_control = e1000_inl(dev, E1000_TCTL); + tx_control |= (1<<1); + e1000_outl(dev, E1000_TCTL, tx_control); + + // enable the controller's interrupts + e1000_outl(dev, E1000_ICR, 0xFFFFFFFF); + e1000_outl(dev, E1000_IMC, 0xFFFFFFFF); + + ims |= 1<<0; // TXDW + ims |= 1<<1; // TXQE + ims |= 1<<2; // LSC + ims |= 1<<4; // RXDMT0 + ims |= 1<<7; // RXT0 + e1000_outl(dev, E1000_IMS, ims); +} + +void e1000_turn_off(struct e1000_dev *dev) +{ + int tx_control, rx_control; + + // turn off the controller's receive engine + rx_control = e1000_inl(dev, E1000_RCTL); + rx_control &= ~(1<<1); + e1000_outl(dev, E1000_RCTL, rx_control); + + // turn off the controller's transmit engine + tx_control = e1000_inl(dev, E1000_TCTL); + tx_control &= ~(1<<1); + e1000_outl(dev, E1000_TCTL, tx_control); + + // turn off the controller's interrupts + e1000_outl(dev, E1000_IMC, 0xFFFFFFFF); +} + +void e1000_init(struct e1000_dev *dev) +{ + uint32_t rxd_phys, txd_phys, kmem_phys; + uint32_t rx_control, tx_control; + uint32_t pba; + int i; + + e1000_reset(dev); + + // configure the controller's 'receive' engine + rx_control = 0; + rx_control |= (0<<1); // EN-bit (Enable) + rx_control |= (0<<2); // SPB-bit (Store Bad Packets) + rx_control |= (0<<3); // UPE-bit (Unicast Promiscuous Mode) + rx_control |= (1<<4); // MPE-bit (Multicast Promiscuous Mode) + rx_control |= (0<<5); // LPE-bit (Long Packet Enable) + rx_control |= (0<<6); // LBM=0 (Loop-Back Mode) + rx_control |= (0<<8); // RDMTS=0 (Rx Descriptor Min Threshold Size) + rx_control |= (0<<10); // DTYPE=0 (Descriptor Type) + rx_control |= (0<<12); // MO=0 (Multicast Offset) + rx_control |= (1<<15); // BAM-bit (Broadcast Address Mode) + rx_control |= (0<<16); // BSIZE=0 (Buffer Size = 2048) + rx_control |= (0<<18); // VLE-bit (VLAN filter Enable) + rx_control |= (0<<19); // CFIEN-bit (Canonical Form Indicator Enable) + rx_control |= (0<<20); // CFI-bit (Canonical Form Indicator) + rx_control |= (1<<22); // DPF-bit (Discard Pause Frames) + rx_control |= (0<<23); // PMCF-bit (Pass MAC Control Frames) + rx_control |= (0<<25); // BSEX=0 (Buffer Size EXtension) + rx_control |= (1<<26); // SECRC-bit (Strip Ethernet CRC) + rx_control |= (0<<27); // FLEXBUF=0 (Flexible Buffer size) + e1000_outl(dev, E1000_RCTL, rx_control); + + // configure the controller's 'transmit' engine + tx_control = 0; + tx_control |= (0<<1); // EN-bit (Enable) + tx_control |= (1<<3); // PSP-bit (Pad Short Packets) + tx_control |= (15<<4); // CT=15 (Collision Threshold) + tx_control |= (63<<12); // COLD=63 (Collision Distance) + tx_control |= (0<<22); // SWXOFF-bit (Software XOFF) + tx_control |= (1<<24); // RTLC-bit (Re-Transmit on Late Collision) + tx_control |= (0<<25); // UNORTX-bit (Underrun No Re-Transmit) + tx_control |= (0<<26); // TXCSCMT=0 (TxDesc Mininum Threshold) + tx_control |= (0<<28); // MULR-bit (Multiple Request Support) + e1000_outl(dev, E1000_TCTL, tx_control); + + // hardware flow control + pba = e1000_inl(dev, E1000_PBA); + // get receive FIFO size + pba = (pba & 0x000000ff)<<10; + e1000_outl(dev, E1000_FCAL, 0x00C28001); + e1000_outl(dev, E1000_FCAH, 0x00000100); + e1000_outl(dev, E1000_FCT, 0x00008808); + e1000_outl(dev, E1000_FCTTV, 0x00000680); + e1000_outl(dev, E1000_FCRTL, (pba*8/10)|0x80000000); + e1000_outl(dev, E1000_FCRTH, pba*9/10); + + // setup tx rings + txd_phys = PADDR(dev->tx_ring.desc); + kmem_phys = PADDR(dev->tx_ring.buf); + for (i=0; i<CONFIG_E1000_N_TX_DESC; i++,kmem_phys+=CONFIG_E1000_BUFF_SIZE) { + dev->tx_ring.desc[i].base_address = kmem_phys; + dev->tx_ring.desc[i].packet_length = 0; + dev->tx_ring.desc[i].cksum_offset = 0; + dev->tx_ring.desc[i].cksum_origin = 0; + dev->tx_ring.desc[i].desc_status = 1; + dev->tx_ring.desc[i].desc_command = (1<<0)|(1<<1)|(1<<3); + dev->tx_ring.desc[i].special_info = 0; + } + dev->tx_ring.tail = 0; + e1000_outl(dev, E1000_TDT, 0); + e1000_outl(dev, E1000_TDH, 0); + // tell controller the location, size, and fetch-policy for Tx queue + e1000_outl(dev, E1000_TDBAL, txd_phys); + e1000_outl(dev, E1000_TDBAH, 0x00000000); + e1000_outl(dev, E1000_TDLEN, CONFIG_E1000_N_TX_DESC*16); + e1000_outl(dev, E1000_TXDCTL, 0x01010000); + + // setup rx rings + rxd_phys = PADDR(dev->rx_ring.desc); + kmem_phys = PADDR(dev->rx_ring.buf); + for (i=0; i<CONFIG_E1000_N_RX_DESC; i++,kmem_phys+=CONFIG_E1000_BUFF_SIZE) { + dev->rx_ring.desc[i].base_address = kmem_phys; + dev->rx_ring.desc[i].packet_length = 0; + dev->rx_ring.desc[i].packet_cksum = 0; + dev->rx_ring.desc[i].desc_status = 0; + dev->rx_ring.desc[i].desc_errors = 0; + dev->rx_ring.desc[i].vlan_tag = 0; + } + dev->rx_ring.head = 0; + dev->rx_ring.tail = CONFIG_E1000_N_RX_DESC-1; + dev->rx_ring.free = 0; + // give the controller ownership of all receive descriptors + e1000_outl(dev, E1000_RDH, 0); + e1000_outl(dev, E1000_RDT, CONFIG_E1000_N_RX_DESC-1); + // tell controller the location, size, and fetch-policy for RX queue + e1000_outl(dev, E1000_RDBAL, rxd_phys); + e1000_outl(dev, E1000_RDBAH, 0x00000000); + e1000_outl(dev, E1000_RDLEN, CONFIG_E1000_N_RX_DESC*16); + e1000_outl(dev, E1000_RXDCTL, 0x01010000); + + e1000_turn_on(dev); +} + +/**************************************************************************** + * Function: e1000_transmit + * + * Description: + * Start hardware transmission. Called either from the txdone interrupt + * handling or from watchdog based polling. + * + * Parameters: + * e1000 - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * May or may not be called from an interrupt handler. In either case, + * global interrupts are disabled, either explicitly or indirectly through + * interrupt handling logic. + * + ****************************************************************************/ + +static int e1000_transmit(struct e1000_dev *e1000) +{ + int tail = e1000->tx_ring.tail; + unsigned char *cp = (unsigned char *) + (e1000->tx_ring.buf + tail * CONFIG_E1000_BUFF_SIZE); + int count = e1000->uip_dev.d_len; + + /* Verify that the hardware is ready to send another packet. If we get + * here, then we are committed to sending a packet; Higher level logic + * must have assured that there is not transmission in progress. + */ + + if (!e1000->tx_ring.desc[tail].desc_status) + return -1; + + /* Increment statistics */ + + /* Send the packet: address=skel->sk_dev.d_buf, length=skel->sk_dev.d_len */ + memcpy(cp, e1000->uip_dev.d_buf, e1000->uip_dev.d_len); + + // prepare the transmit-descriptor + e1000->tx_ring.desc[tail].packet_length = count<60 ? 60:count; + e1000->tx_ring.desc[tail].desc_status = 0; + + // give ownership of this descriptor to the network controller + tail = (tail + 1) % CONFIG_E1000_N_TX_DESC; + e1000->tx_ring.tail = tail; + e1000_outl(e1000, E1000_TDT, tail); + + /* Enable Tx interrupts */ + + /* Setup the TX timeout watchdog (perhaps restarting the timer) */ + + wd_start(e1000->txtimeout, E1000_TXTIMEOUT, e1000_txtimeout, 1, (uint32_t)e1000); + return OK; +} + +/**************************************************************************** + * Function: e1000_uiptxpoll + * + * Description: + * The transmitter is available, check if uIP has any outgoing packets ready + * to send. This is a callback from uip_poll(). uip_poll() may be called: + * + * 1. When the preceding TX packet send is complete, + * 2. When the preceding TX packet send timesout and the interface is reset + * 3. During normal TX polling + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * May or may not be called from an interrupt handler. In either case, + * global interrupts are disabled, either explicitly or indirectly through + * interrupt handling logic. + * + ****************************************************************************/ + +static int e1000_uiptxpoll(struct uip_driver_s *dev) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)dev->d_private; + int tail = e1000->tx_ring.tail; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + if (e1000->uip_dev.d_len > 0) { + uip_arp_out(&e1000->uip_dev); + e1000_transmit(e1000); + + /* Check if there is room in the device to hold another packet. If not, + * return a non-zero value to terminate the poll. + */ + if (!e1000->tx_ring.desc[tail].desc_status) + return -1; + } + + /* If zero is returned, the polling will continue until all connections have + * been examined. + */ + + return 0; +} + +/**************************************************************************** + * Function: e1000_receive + * + * Description: + * An interrupt was received indicating the availability of a new RX packet + * + * Parameters: + * e1000 - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by interrupt handling logic. + * + ****************************************************************************/ + +static void e1000_receive(struct e1000_dev *e1000) +{ + int head = e1000->rx_ring.head; + unsigned char *cp = (unsigned char *) + (e1000->rx_ring.buf + head * CONFIG_E1000_BUFF_SIZE); + int cnt; + + while (e1000->rx_ring.desc[head].desc_status) { + + /* Check for errors and update statistics */ + + // Here we do not handle packets that exceed packet-buffer size + if ((e1000->rx_ring.desc[head].desc_status & 3) == 1) { + cprintf("NIC READ: Oversized packet\n"); + goto next; + } + + /* Check if the packet is a valid size for the uIP buffer configuration */ + + // get the number of actual data-bytes in this packet + cnt = e1000->rx_ring.desc[head].packet_length; + + if (cnt > CONFIG_NET_BUFSIZE || cnt < 14) { + cprintf("NIC READ: invalid package size\n"); + goto next; + } + + /* Copy the data data from the hardware to e1000->uip_dev.d_buf. Set + * amount of data in e1000->uip_dev.d_len + */ + + // now we try to copy these data-bytes to the UIP buffer + memcpy(e1000->uip_dev.d_buf, cp, cnt); + e1000->uip_dev.d_len = cnt; + + /* We only accept IP packets of the configured type and ARP packets */ + +#ifdef CONFIG_NET_IPv6 + if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) +#else + if (BUF->type == HTONS(UIP_ETHTYPE_IP)) +#endif + { + uip_arp_ipin(&e1000->uip_dev); + uip_input(&e1000->uip_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (e1000->uip_dev.d_len > 0) { + uip_arp_out(&e1000->uip_dev); + e1000_transmit(e1000); + } + } + else if (BUF->type == htons(UIP_ETHTYPE_ARP)) { + uip_arp_arpin(&e1000->uip_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (e1000->uip_dev.d_len > 0) { + e1000_transmit(e1000); + } + } + + next: + e1000->rx_ring.desc[head].desc_status = 0; + e1000->rx_ring.head = (head + 1) % CONFIG_E1000_N_RX_DESC; + e1000->rx_ring.free++; + head = e1000->rx_ring.head; + cp = (unsigned char *)(e1000->rx_ring.buf + head * CONFIG_E1000_BUFF_SIZE); + } +} + +/**************************************************************************** + * Function: e1000_txtimeout + * + * Description: + * Our TX watchdog timed out. Called from the timer interrupt handler. + * The last TX never completed. Reset the hardware and start again. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void e1000_txtimeout(int argc, uint32_t arg, ...) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)arg; + + /* Increment statistics and dump debug info */ + + /* Then reset the hardware */ + e1000_init(e1000); + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&e1000->uip_dev, e1000_uiptxpoll); +} + +/**************************************************************************** + * Function: e1000_polltimer + * + * Description: + * Periodic timer handler. Called from the timer interrupt handler. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void e1000_polltimer(int argc, uint32_t arg, ...) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)arg; + int tail = e1000->tx_ring.tail; + + /* Check if there is room in the send another TX packet. We cannot perform + * the TX poll if he are unable to accept another packet for transmission. + */ + if (!e1000->tx_ring.desc[tail].desc_status) + return; + + /* If so, update TCP timing states and poll uIP for new XMIT data. Hmmm.. + * might be bug here. Does this mean if there is a transmit in progress, + * we will missing TCP time state updates? + */ + + (void)uip_timer(&e1000->uip_dev, e1000_uiptxpoll, E1000_POLLHSEC); + + /* Setup the watchdog poll timer again */ + + (void)wd_start(e1000->txpoll, E1000_WDDELAY, e1000_polltimer, 1, arg); +} + +/**************************************************************************** + * Function: e1000_ifup + * + * Description: + * NuttX Callback: Bring up the Ethernet interface when an IP address is + * provided + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int e1000_ifup(struct uip_driver_s *dev) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)dev->d_private; + + ndbg("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Initialize PHYs, the Ethernet interface, and setup up Ethernet interrupts */ + e1000_init(e1000); + + /* Set and activate a timer process */ + + (void)wd_start(e1000->txpoll, E1000_WDDELAY, e1000_polltimer, 1, (uint32_t)e1000); + + if (e1000_inl(e1000, E1000_STATUS) & 2) + e1000->bifup = true; + else + e1000->bifup = false; + + return OK; +} + +/**************************************************************************** + * Function: e1000_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int e1000_ifdown(struct uip_driver_s *dev) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)dev->d_private; + irqstate_t flags; + + /* Disable the Ethernet interrupt */ + + flags = irqsave(); + + e1000_turn_off(e1000); + + /* Cancel the TX poll timer and TX timeout timers */ + + wd_cancel(e1000->txpoll); + wd_cancel(e1000->txtimeout); + + /* Put the the EMAC is its reset, non-operational state. This should be + * a known configuration that will guarantee the skel_ifup() always + * successfully brings the interface back up. + */ + //e1000_reset(e1000); + + /* Mark the device "down" */ + + e1000->bifup = false; + irqrestore(flags); + + return OK; +} + +/**************************************************************************** + * Function: e1000_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static int e1000_txavail(struct uip_driver_s *dev) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)dev->d_private; + int tail = e1000->tx_ring.tail; + irqstate_t flags; + + /* Disable interrupts because this function may be called from interrupt + * level processing. + */ + + flags = irqsave(); + + /* Ignore the notification if the interface is not yet up */ + + if (e1000->bifup) { + /* Check if there is room in the hardware to hold another outgoing packet. */ + if (e1000->tx_ring.desc[tail].desc_status) + (void)uip_poll(&e1000->uip_dev, e1000_uiptxpoll); + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: e1000_addmac + * + * Description: + * NuttX Callback: Add the specified MAC address to the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be added + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int e1000_addmac(struct uip_driver_s *dev, const uint8_t *mac) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +/**************************************************************************** + * Function: e1000_rmmac + * + * Description: + * NuttX Callback: Remove the specified MAC address from the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be removed + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int e1000_rmmac(struct uip_driver_s *dev, const uint8_t *mac) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +irqreturn_t e1000_interrupt_handler(struct Trapframe *tf, void *dev_id) +{ + struct e1000_dev *e1000 = (struct e1000_dev *)dev_id; + + /* Get and clear interrupt status bits */ + int intr_cause = e1000_inl(e1000, E1000_ICR); + e1000_outl(e1000, E1000_ICR, intr_cause); + + // not for me + if (intr_cause == 0) + return IRQ_NONE; + + /* Handle interrupts according to status bit settings */ + + // Link status change + if (intr_cause & (1<<2)) { + if (e1000_inl(e1000, E1000_STATUS) & 2) + e1000->bifup = true; + else + e1000->bifup = false; + } + + /* Check if we received an incoming packet, if so, call skel_receive() */ + + // Rx-descriptor Timer expired + if (intr_cause & (1<<7)) + e1000_receive(e1000); + + // Tx queue empty + if (intr_cause & (1<<1)) + wd_cancel(e1000->txtimeout); + + /* Check is a packet transmission just completed. If so, call skel_txdone. + * This may disable further Tx interrupts if there are no pending + * tansmissions. + */ + + // Tx-descriptor Written back + if (intr_cause & (1<<0)) + uip_poll(&e1000->uip_dev, e1000_uiptxpoll); + + + // Rx-Descriptors Low + if (intr_cause & (1<<4)) { + int tail; + tail = e1000->rx_ring.tail + e1000->rx_ring.free; + tail %= CONFIG_E1000_N_RX_DESC; + e1000->rx_ring.tail = tail; + e1000->rx_ring.free = 0; + e1000_outl(e1000, E1000_RDT, tail); + } + + return IRQ_HANDLED; +} + +/******************************* PCI driver *********************************/ + +static pci_id_t e1000_id_table[] = { + {.sep = {INTEL_VENDERID, E1000_82573L}}, + {.sep = {INTEL_VENDERID, E1000_82540EM}}, + {.sep = {INTEL_VENDERID, E1000_82574L}}, + {.sep = {INTEL_VENDERID, E1000_82567LM}}, + {.sep = {INTEL_VENDERID, E1000_82541PI}}, + {.sep = {0,0}} +}; + +static int e1000_probe(uint16_t addr, pci_id_t id) +{ + uint32_t mmio_base, mmio_size; + uint32_t pci_cmd, size; + int err, irq, flags; + void *kmem, *omem; + struct e1000_dev *dev; + + // alloc e1000_dev memory + dev = kzalloc(sizeof(struct e1000_dev)); + if (dev == NULL) + return -1; + + // enable device + err = pci_enable_device(addr, PCI_RESOURCE_MEM); + if (err) + goto error; + + // get e1000 device type + dev->pci_dev_id = id.join; + + // remap the controller's i/o-memory into kernel's address-space + mmio_base = pci_resource_start(addr, 0); + mmio_size = pci_resource_len(addr, 0); + err = rgmp_memmap_nocache(mmio_base, mmio_size, mmio_base); + if (err) + goto error; + dev->phy_mem_base = mmio_base; + dev->io_mem_base = mmio_base; + dev->mem_size = mmio_size; + + // make sure the controller's Bus Master capability is enabled + pci_cmd = pci_config_readl(addr, PCI_COMMAND); + pci_cmd |= (1<<2); + pci_config_writel(addr, PCI_COMMAND, pci_cmd); + + // MAC address + memset(dev->dst_mac, 0xFF, 6); + memcpy(dev->src_mac, (void *)(dev->io_mem_base+E1000_RA), 6); + + // get e1000 IRQ + flags = 0; + irq = pci_enable_msi(addr); + if (irq == 0) { + irq = pci_read_irq(addr); + flags |= IDC_SHARE; + } + dev->irq = irq; + dev->int_desc.handler = e1000_interrupt_handler; + dev->int_desc.dev_id = dev; + err = rgmp_request_irq(irq, &dev->int_desc, flags); + if (err) + goto err0; + + // Here we alloc a big block of memory once and make it + // aligned to page boundary and multiple of page size. This + // is because the memory can be modified by E1000 DMA and + // should be mapped no-cache which will hugely reduce memory + // access performance. The page size alloc will restrict + // this bad effect only within the memory we alloc here. + size = CONFIG_E1000_N_TX_DESC * sizeof(struct tx_desc) + + CONFIG_E1000_N_TX_DESC * CONFIG_E1000_BUFF_SIZE + + CONFIG_E1000_N_RX_DESC * sizeof(struct rx_desc) + + CONFIG_E1000_N_RX_DESC * CONFIG_E1000_BUFF_SIZE; + size = ROUNDUP(size, PGSIZE); + omem = kmem = memalign(PGSIZE, size); + if (kmem == NULL) { + err = -ENOMEM; + goto err1; + } + rgmp_memremap_nocache((uintptr_t)kmem, size); + + // alloc memory for tx ring + dev->tx_ring.desc = (struct tx_desc*)kmem; + kmem += CONFIG_E1000_N_TX_DESC * sizeof(struct tx_desc); + dev->tx_ring.buf = kmem; + kmem += CONFIG_E1000_N_TX_DESC * CONFIG_E1000_BUFF_SIZE; + + // alloc memory for rx rings + dev->rx_ring.desc = (struct rx_desc*)kmem; + kmem += CONFIG_E1000_N_RX_DESC * sizeof(struct rx_desc); + dev->rx_ring.buf = kmem; + + /* Initialize the driver structure */ + + dev->uip_dev.d_ifup = e1000_ifup; /* I/F up (new IP address) callback */ + dev->uip_dev.d_ifdown = e1000_ifdown; /* I/F down callback */ + dev->uip_dev.d_txavail = e1000_txavail; /* New TX data callback */ +#ifdef CONFIG_NET_IGMP + dev->uip_dev.d_addmac = e1000_addmac; /* Add multicast MAC address */ + dev->uip_dev.d_rmmac = e1000_rmmac; /* Remove multicast MAC address */ +#endif + dev->uip_dev.d_private = dev; /* Used to recover private state from dev */ + + /* Create a watchdog for timing polling for and timing of transmisstions */ + + dev->txpoll = wd_create(); /* Create periodic poll timer */ + dev->txtimeout = wd_create(); /* Create TX timeout timer */ + + // Put the interface in the down state. + // e1000 reset + e1000_reset(dev); + + /* Read the MAC address from the hardware */ + memcpy(dev->uip_dev.d_mac.ether_addr_octet, (void *)(dev->io_mem_base+E1000_RA), 6); + + /* Register the device with the OS so that socket IOCTLs can be performed */ + err = netdev_register(&dev->uip_dev); + if (err) + goto err2; + + // insert into e1000_list + dev->next = e1000_list.next; + e1000_list.next = dev; + cprintf("bring up e1000 device: %04x %08x\n", addr, id.join); + + return 0; + + err2: + rgmp_memremap((uintptr_t)omem, size); + free(omem); + err1: + rgmp_free_irq(irq, &dev->int_desc); + err0: + rgmp_memunmap(mmio_base, mmio_size); + error: + kfree(dev); + cprintf("e1000 device probe fail: %d\n", err); + return err; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void e1000_mod_init(void) +{ + pci_probe_device(e1000_id_table, e1000_probe); +} + +void e1000_mod_exit(void) +{ + uint32_t size; + struct e1000_dev *dev; + + size = CONFIG_E1000_N_TX_DESC * sizeof(struct tx_desc) + + CONFIG_E1000_N_TX_DESC * CONFIG_E1000_BUFF_SIZE + + CONFIG_E1000_N_RX_DESC * sizeof(struct rx_desc) + + CONFIG_E1000_N_RX_DESC * CONFIG_E1000_BUFF_SIZE; + size = ROUNDUP(size, PGSIZE); + + for (dev=e1000_list.next; dev!=NULL; dev=dev->next) { + netdev_unregister(&dev->uip_dev); + e1000_reset(dev); + wd_delete(dev->txpoll); + wd_delete(dev->txtimeout); + rgmp_memremap((uintptr_t)dev->tx_ring.desc, size); + free(dev->tx_ring.desc); + rgmp_free_irq(dev->irq, &dev->int_desc); + rgmp_memunmap((uintptr_t)dev->io_mem_base, dev->mem_size); + kfree(dev); + } + + e1000_list.next = NULL; +} diff --git a/nuttx/drivers/net/e1000.h b/nuttx/drivers/net/e1000.h new file mode 100644 index 000000000..6614ad77e --- /dev/null +++ b/nuttx/drivers/net/e1000.h @@ -0,0 +1,123 @@ +/**************************************************************************** + * drivers/net/e1000.h + * + * Copyright (C) 2011 Yu Qiang. All rights reserved. + * Author: Yu Qiang <yuq825@gmail.com> + * + * This file is a part of NuttX: + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_NET_E1000_H +#define __DRIVERS_NET_E1000_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <rgmp/types.h> +#include <rgmp/trap.h> +#include <rgmp/arch/arch.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/************** PCI ID ***************/ + +#define INTEL_VENDERID 0x8086 +#define E1000_82573L 0x109a +#define E1000_82540EM 0x100e +#define E1000_82574L 0x10d3 +#define E1000_82567LM 0x10f5 +#define E1000_82541PI 0x107c + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum e1000_registers { + E1000_CTRL = 0x0000, // Device Control + E1000_STATUS = 0x0008, // Device Status + E1000_CTRL_EXT = 0x0018, // Device Control Extension + E1000_FCAL = 0x0028, // Flow Control Address Low + E1000_FCAH = 0x002C, // Flow Control Address High + E1000_FCT = 0x0030, // Flow Control Type + E1000_ICR = 0x00C0, // Interrupt Cause Read + E1000_ICS = 0x00C8, // Interrupt Cause Set + E1000_IMS = 0x00D0, // Interrupt Mask Set + E1000_IMC = 0x00D8, // Interrupt Mask Clear + E1000_RCTL = 0x0100, // Receive Control + E1000_FCTTV = 0x0170, // Flow Control Transmit Timer Value + E1000_TCTL = 0x0400, // Transmit Control + E1000_PBA = 0x1000, // Packet Buffer Allocation + E1000_FCRTL = 0x2160, // Flow Control Receive Threshold Low + E1000_FCRTH = 0x2168, // Flow Control Receive Threshold High + E1000_RDBAL = 0x2800, // Rx Descriptor Base Address Low + E1000_RDBAH = 0x2804, // Rx Descriptor Base Address High + E1000_RDLEN = 0x2808, // Rx Descriptor Length + E1000_RDH = 0x2810, // Rx Descriptor Head + E1000_RDT = 0x2818, // Rx Descriptor Tail + E1000_RXDCTL = 0x2828, // Rx Descriptor Control + E1000_TDBAL = 0x3800, // Tx Descriptor Base Address Low + E1000_TDBAH = 0x3804, // Tx Descriptor Base Address High + E1000_TDLEN = 0x3808, // Tx Descriptor Length + E1000_TDH = 0x3810, // Tx Descriptor Head + E1000_TDT = 0x3818, // Tx Descriptor Tail + E1000_TXDCTL = 0x3828, // Tx Descriptor Control + E1000_TPR = 0x40D0, // Total Packets Received + E1000_TPT = 0x40D4, // Total Packets Transmitted + E1000_RA = 0x5400, // Receive-filter Array +}; + +/***************** e1000 device structure *****************/ + +struct tx_desc { + uint64_t base_address; + uint16_t packet_length; + uint8_t cksum_offset; + uint8_t desc_command; + uint8_t desc_status; + uint8_t cksum_origin; + uint16_t special_info; +}; + +struct rx_desc { + uint64_t base_address; + uint16_t packet_length; + uint16_t packet_cksum; + uint8_t desc_status; + uint8_t desc_errors; + uint16_t vlan_tag; +}; + +#endif diff --git a/nuttx/drivers/net/enc28j60.c b/nuttx/drivers/net/enc28j60.c new file mode 100644 index 000000000..448decbf8 --- /dev/null +++ b/nuttx/drivers/net/enc28j60.c @@ -0,0 +1,2221 @@ +/**************************************************************************** + * drivers/net/enc28j60.c + * + * Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * References: + * - ENC28J60 Data Sheet, Stand-Alone Ethernet Controller with SPI Interface, + * DS39662C, 2008 Microchip Technology Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#if defined(CONFIG_NET) && defined(CONFIG_NET_ENC28J60) + +#include <stdint.h> +#include <stdbool.h> +#include <stdint.h> +#include <time.h> +#include <string.h> +#include <debug.h> +#include <wdog.h> +#include <errno.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> +#include <nuttx/spi.h> +#include <nuttx/wqueue.h> +#include <nuttx/clock.h> +#include <nuttx/enc28j60.h> + +#include <net/uip/uip.h> +#include <net/uip/uip-arp.h> +#include <net/uip/uip-arch.h> + +#include "enc28j60.h" + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* ENC28J60 Configuration Settings: + * + * CONFIG_NET_ENC28J60 - Enabled ENC28J60 support + * CONFIG_ENC28J60_SPIMODE - Controls the SPI mode + * CONFIG_ENC28J60_FREQUENCY - Define to use a different bus frequency + * CONFIG_ENC28J60_NINTERFACES - Specifies the number of physical ENC28J60 + * devices that will be supported. + * CONFIG_ENC28J60_STATS - Collect network statistics + * CONFIG_ENC28J60_HALFDUPPLEX - Default is full duplex + */ + +/* The ENC28J60 spec says that is supports SPI mode 0,0 only. However, + * somtimes you need to tinker with these things. + */ + +#ifndef CONFIG_ENC28J60_SPIMODE +# define CONFIG_ENC28J60_SPIMODE SPIDEV_MODE2 +#endif + +/* CONFIG_ENC28J60_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_ENC28J60_NINTERFACES +# define CONFIG_ENC28J60_NINTERFACES 1 +#endif + +/* CONFIG_NET_BUFSIZE must always be defined */ + +#if !defined(CONFIG_NET_BUFSIZE) && (CONFIG_NET_BUFSIZE <= MAX_FRAMELEN) +# error "CONFIG_NET_BUFSIZE is not valid for the ENC28J60" +#endif + +/* We need to have the work queue to handle SPI interrupts */ + +#if !defined(CONFIG_SCHED_WORKQUEUE) && !defined(CONFIG_SPI_OWNBUS) +# error "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + +/* CONFIG_ENC28J60_DUMPPACKET will dump the contents of each packet to the console. */ + +#ifdef CONFIG_ENC28J60_DUMPPACKET +# define enc_dumppacket(m,a,n) lib_dumpbuffer(m,a,n) +#else +# define enc_dumppacket(m,a,n) +#endif + +/* Timing *******************************************************************/ + +/* TX poll deley = 1 seconds. CLK_TCK is the number of clock ticks per second */ + +#define ENC_WDDELAY (1*CLK_TCK) +#define ENC_POLLHSEC (1*2) + +/* TX timeout = 1 minute */ + +#define ENC_TXTIMEOUT (60*CLK_TCK) + +/* Poll timeout */ + +#define ENC_POLLTIMEOUT MSEC2TICK(50) + +/* Packet Memory ************************************************************/ + +/* Packet memory layout */ + +#define ALIGNED_BUFSIZE ((CONFIG_NET_BUFSIZE + 255) & ~255) + +#define PKTMEM_TX_START 0x0000 /* Start TX buffer at 0 */ +#define PKTMEM_TX_ENDP1 ALIGNED_BUFSIZE /* Allow TX buffer for one frame + */ +#define PKTMEM_RX_START PKTMEM_TX_ENDP1 /* Followed by RX buffer */ +#define PKTMEM_RX_END PKTMEM_END /* RX buffer goes to the end of SRAM */ + +/* Misc. Helper Macros ******************************************************/ + +#define enc_rdgreg(priv,ctrlreg) \ + enc_rdgreg2(priv, ENC_RCR | GETADDR(ctrlreg)) +#define enc_wrgreg(priv,ctrlreg,wrdata) \ + enc_wrgreg2(priv, ENC_WCR | GETADDR(ctrlreg), wrdata) +#define enc_bfcgreg(priv,ctrlreg,clrbits) \ + enc_wrgreg2(priv, ENC_BFC | GETADDR(ctrlreg), clrbits) +#define enc_bfsgreg(priv,ctrlreg,setbits) \ + enc_wrgreg2(priv, ENC_BFS | GETADDR(ctrlreg), setbits) + +/* This is a helper pointer for accessing the contents of the Ethernet header */ + +#define BUF ((struct uip_eth_hdr *)priv->dev.d_buf) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* The enc_driver_s encapsulates all state information for a single hardware + * interface + */ + +struct enc_driver_s +{ + /* Device control */ + + bool bifup; /* true:ifup false:ifdown */ + uint8_t bank; /* Currently selected bank */ + uint16_t nextpkt; /* Next packet address */ + int irq; /* GPIO IRQ configured for the ENC28J60 */ + + /* Timing */ + + WDOG_ID txpoll; /* TX poll timer */ + WDOG_ID txtimeout; /* TX timeout timer */ + + /* We we don't own the SPI bus, then we cannot do SPI accesses from the + * interrupt handler. + */ + +#ifndef CONFIG_SPI_OWNBUS + struct work_s work; /* Work queue support */ +#endif + + /* This is the contained SPI driver intstance */ + + FAR struct spi_dev_s *spi; + + /* This holds the information visible to uIP/NuttX */ + + struct uip_driver_s dev; /* Interface understood by uIP */ + + /* Statistics */ + +#ifdef CONFIG_ENC28J60_STATS + struct enc_stats_s stats; +#endif +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct enc_driver_s g_enc28j60[CONFIG_ENC28J60_NINTERFACES]; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Low-level SPI helpers */ + +static inline void enc_configspi(FAR struct spi_dev_s *spi); +#ifdef CONFIG_SPI_OWNBUS +static inline void enc_select(FAR struct spi_dev_s *spi); +static inline void enc_deselect(FAR struct spi_dev_s *spi); +#else +static void enc_select(FAR struct spi_dev_s *spi); +static void enc_deselect(FAR struct spi_dev_s *spi); +#endif + +/* SPI control register access */ + +static uint8_t enc_rdgreg2(FAR struct enc_driver_s *priv, uint8_t cmd); +static void enc_wrgreg2(FAR struct enc_driver_s *priv, uint8_t cmd, + uint8_t wrdata); +static void enc_setbank(FAR struct enc_driver_s *priv, uint8_t bank); +static uint8_t enc_rdbreg(FAR struct enc_driver_s *priv, uint8_t ctrlreg); +static void enc_wrbreg(FAR struct enc_driver_s *priv, uint8_t ctrlreg, + uint8_t wrdata); +static int enc_waitbreg(FAR struct enc_driver_s *priv, uint8_t ctrlreg, + uint8_t bits, uint8_t value); + +/* SPI buffer transfers */ + +static void enc_rdbuffer(FAR struct enc_driver_s *priv, FAR uint8_t *buffer, + size_t buflen); +static void enc_wrbuffer(FAR struct enc_driver_s *priv, + FAR const uint8_t *buffer, size_t buflen); + +/* PHY register access */ + +static uint16_t enc_rdphy(FAR struct enc_driver_s *priv, uint8_t phyaddr); +static void enc_wrphy(FAR struct enc_driver_s *priv, uint8_t phyaddr, + uint16_t phydata); + +/* Common TX logic */ + +static int enc_transmit(FAR struct enc_driver_s *priv); +static int enc_uiptxpoll(struct uip_driver_s *dev); + +/* Interrupt handling */ + +static void enc_linkstatus(FAR struct enc_driver_s *priv); +static void enc_txif(FAR struct enc_driver_s *priv); +static void enc_txerif(FAR struct enc_driver_s *priv); +static void enc_txerif(FAR struct enc_driver_s *priv); +static void enc_rxerif(FAR struct enc_driver_s *priv); +static void enc_rxdispath(FAR struct enc_driver_s *priv); +static void enc_pktif(FAR struct enc_driver_s *priv); +static void enc_worker(FAR void *arg); +static int enc_interrupt(int irq, FAR void *context); + +/* Watchdog timer expirations */ + +static void enc_polltimer(int argc, uint32_t arg, ...); +static void enc_txtimeout(int argc, uint32_t arg, ...); + +/* NuttX callback functions */ + +static int enc_ifup(struct uip_driver_s *dev); +static int enc_ifdown(struct uip_driver_s *dev); +static int enc_txavail(struct uip_driver_s *dev); +#ifdef CONFIG_NET_IGMP +static int enc_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +static int enc_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +#endif + +/* Initialization */ + +static void enc_pwrsave(FAR struct enc_driver_s *priv); +static void enc_pwrfull(FAR struct enc_driver_s *priv); +static void enc_setmacaddr(FAR struct enc_driver_s *priv); +static int enc_reset(FAR struct enc_driver_s *priv); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: enc_configspi + * + * Description: + * Configure the SPI for use with the ENC28J60 + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static inline void enc_configspi(FAR struct spi_dev_s *spi) +{ + /* Configure SPI for the ENC28J60. But only if we own the SPI bus. + * Otherwise, don't bother because it might change. + */ + +#ifdef CONFIG_SPI_OWNBUS + SPI_SETMODE(spi, CONFIG_ENC28J60_SPIMODE); + SPI_SETBITS(spi, 8); +#ifdef CONFIG_ENC28J60_FREQUENCY + SPI_SETFREQUENCY(spi, CONFIG_ENC28J60_FREQUENCY) +#endif +#endif +} + +/**************************************************************************** + * Function: enc_select + * + * Description: + * Select the SPI, locking and re-configuring if necessary + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void enc_select(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just select the chip */ + + SPI_SELECT(spi, SPIDEV_ETHERNET, true); +} +#else +static void enc_select(FAR struct spi_dev_s *spi) +{ + /* Select ENC28J60 chip (locking the SPI bus in case there are multiple + * devices competing for the SPI bus + */ + + SPI_LOCK(spi, true); + SPI_SELECT(spi, SPIDEV_ETHERNET, true); + + /* Now make sure that the SPI bus is configured for the ENC28J60 (it + * might have gotten configured for a different device while unlocked) + */ + + SPI_SETMODE(spi, CONFIG_ENC28J60_SPIMODE); + SPI_SETBITS(spi, 8); +#ifdef CONFIG_ENC28J60_FREQUENCY + SPI_SETFREQUENCY(spi, CONFIG_ENC28J60_FREQUENCY); +#endif +} +#endif + +/**************************************************************************** + * Function: enc_deselect + * + * Description: + * De-select the SPI + * + * Parameters: + * spi - Reference to the SPI driver structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_SPI_OWNBUS +static inline void enc_deselect(FAR struct spi_dev_s *spi) +{ + /* We own the SPI bus, so just de-select the chip */ + + SPI_SELECT(spi, SPIDEV_ETHERNET, false); +} +#else +static void enc_deselect(FAR struct spi_dev_s *spi) +{ + /* De-select ENC28J60 chip and relinquish the SPI bus. */ + + SPI_SELECT(spi, SPIDEV_ETHERNET, false); + SPI_LOCK(spi, false); +} +#endif + +/**************************************************************************** + * Function: enc_rdgreg2 + * + * Description: + * Read a global register (EIE, EIR, ESTAT, ECON2, or ECON1). The cmd + * include the CMD 'OR'd with the the global address register. + * + * Parameters: + * priv - Reference to the driver state structure + * cmd - The full command to received (cmd | address) + * + * Returned Value: + * The value read from the register + * + * Assumptions: + * + ****************************************************************************/ + +static uint8_t enc_rdgreg2(FAR struct enc_driver_s *priv, uint8_t cmd) +{ + FAR struct spi_dev_s *spi; + uint8_t rddata; + + DEBUGASSERT(priv && priv->spi); + spi = priv->spi; + + /* Select ENC28J60 chip */ + + enc_select(spi); + + /* Send the read command and collect the data. The sequence requires + * 16-clocks: 8 to clock out the cmd + 8 to clock in the data. + */ + + (void)SPI_SEND(spi, cmd); /* Clock out the command */ + rddata = SPI_SEND(spi, 0); /* Clock in the data */ + + /* De-select ENC28J60 chip */ + + enc_deselect(spi); + return rddata; +} + +/**************************************************************************** + * Function: enc_wrgreg2 + * + * Description: + * Write to a global register (EIE, EIR, ESTAT, ECON2, or ECON1). The cmd + * include the CMD 'OR'd with the the global address register. + * + * Parameters: + * priv - Reference to the driver state structure + * cmd - The full command to received (cmd | address) + * wrdata - The data to send + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_wrgreg2(FAR struct enc_driver_s *priv, uint8_t cmd, + uint8_t wrdata) +{ + FAR struct spi_dev_s *spi; + + DEBUGASSERT(priv && priv->spi); + spi = priv->spi; + + /* Select ENC28J60 chip */ + + enc_select(spi); + + /* Send the write command and data. The sequence requires 16-clocks: + * 8 to clock out the cmd + 8 to clock out the data. + */ + + (void)SPI_SEND(spi, cmd); /* Clock out the command */ + (void)SPI_SEND(spi, wrdata); /* Clock out the data */ + + /* De-select ENC28J60 chip. */ + + enc_deselect(spi); +} + +/**************************************************************************** + * Function: enc_setbank + * + * Description: + * Set the bank for these next control register access. + * + * Assumption: + * The caller has exclusive access to the SPI bus + * + * Parameters: + * priv - Reference to the driver state structure + * bank - The bank to select (0-3) + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_setbank(FAR struct enc_driver_s *priv, uint8_t bank) +{ + /* Check if the bank setting has changed*/ + + if (bank != priv->bank) + { + /* Select bank 0 (just so that all of the bits are cleared) */ + + enc_bfcgreg(priv, ENC_ECON1, ECON1_BSEL_MASK); + + /* Then OR in bits to get the correct bank */ + + if (bank != 0) + { + enc_bfsgreg(priv, ENC_ECON1, (bank << ECON1_BSEL_SHIFT)); + } + + /* Then remember the bank setting */ + + priv->bank = bank; + } +} + +/**************************************************************************** + * Function: enc_rdbreg + * + * Description: + * Read from a banked control register using the RCR command. + * + * Parameters: + * priv - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to read + * + * Returned Value: + * The byte read from the banked register + * + * Assumptions: + * + ****************************************************************************/ + +static uint8_t enc_rdbreg(FAR struct enc_driver_s *priv, uint8_t ctrlreg) +{ + FAR struct spi_dev_s *spi; + uint8_t rddata; + + DEBUGASSERT(priv && priv->spi); + spi = priv->spi; + + /* Select ENC28J60 chip */ + + enc_select(spi); + + /* Set the bank */ + + enc_setbank(priv, GETBANK(ctrlreg)); + + /* Send the RCR command and collect the data. How we collect the data + * depends on if this is a PHY/CAN or not. The normal sequence requires + * 16-clocks: 8 to clock out the cmd and 8 to clock in the data. + */ + + (void)SPI_SEND(spi, ENC_RCR | GETADDR(ctrlreg)); /* Clock out the command */ + if (ISPHYMAC(ctrlreg)) + { + /* The PHY/MAC sequence requires 24-clocks: 8 to clock out the cmd, + * 8 dummy bits, and 8 to clock in the PHY/MAC data. + */ + + (void)SPI_SEND(spi,0); /* Clock in the dummy byte */ + } + rddata = SPI_SEND(spi, 0); /* Clock in the data */ + + /* De-select ENC28J60 chip */ + + enc_deselect(spi); + return rddata; +} + +/**************************************************************************** + * Function: enc_wrbreg + * + * Description: + * Write to a banked control register using the WCR command. Unlike + * reading, this same SPI sequence works for normal, MAC, and PHY + * registers. + * + * Parameters: + * priv - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to write + * wrdata - The data to send + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_wrbreg(FAR struct enc_driver_s *priv, uint8_t ctrlreg, + uint8_t wrdata) +{ + FAR struct spi_dev_s *spi; + + DEBUGASSERT(priv && priv->spi); + spi = priv->spi; + + /* Select ENC28J60 chip */ + + enc_select(spi); + + /* Set the bank */ + + enc_setbank(priv, GETBANK(ctrlreg)); + + /* Send the WCR command and data. The sequence requires 16-clocks: + * 8 to clock out the cmd + 8 to clock out the data. + */ + + (void)SPI_SEND(spi, ENC_WCR | GETADDR(ctrlreg)); /* Clock out the command */ + (void)SPI_SEND(spi, wrdata); /* Clock out the data */ + + /* De-select ENC28J60 chip. */ + + enc_deselect(spi); +} + +/**************************************************************************** + * Function: enc_waitbreg + * + * Description: + * Wait until banked register bit(s) take a specific value (or a timeout + * occurs). + * + * Parameters: + * priv - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to check + * bits - The bits to check (a mask) + * value - The value of the bits to return (value under mask) + * + * Returned Value: + * OK on success, negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static int enc_waitbreg(FAR struct enc_driver_s *priv, uint8_t ctrlreg, + uint8_t bits, uint8_t value) +{ + uint32_t start = clock_systimer(); + uint32_t elapsed; + uint8_t rddata; + + /* Loop until the exit condition is met */ + + do + { + /* Read the byte from the requested banked register */ + + rddata = enc_rdbreg(priv, ctrlreg); + elapsed = clock_systimer() - start; + } + while ((rddata & bits) != value || elapsed > ENC_POLLTIMEOUT); + return (rddata & bits) == value ? -ETIMEDOUT : OK; +} + +/**************************************************************************** + * Function: enc_rdbuffer + * + * Description: + * Read a buffer of data. + * + * Parameters: + * priv - Reference to the driver state structure + * buffer - A pointer to the buffer to read into + * buflen - The number of bytes to read + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +static void enc_rdbuffer(FAR struct enc_driver_s *priv, FAR uint8_t *buffer, + size_t buflen) +{ + FAR struct spi_dev_s *spi; + + DEBUGASSERT(priv && priv->spi); + spi = priv->spi; + + /* Select ENC28J60 chip */ + + enc_select(spi); + + /* Send the read buffer memory command (ignoring the response) */ + + (void)SPI_SEND(spi, ENC_RBM); + + /* Then read the buffer data */ + + SPI_RECVBLOCK(spi, buffer, buflen); + + /* De-select ENC28J60 chip. */ + + enc_deselect(spi); +} + +/**************************************************************************** + * Function: enc_wrbuffer + * + * Description: + * Write a buffer of data. + * + * Parameters: + * priv - Reference to the driver state structure + * buffer - A pointer to the buffer to write from + * buflen - The number of bytes to write + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +static void enc_wrbuffer(FAR struct enc_driver_s *priv, + FAR const uint8_t *buffer, size_t buflen) +{ + FAR struct spi_dev_s *spi; + + DEBUGASSERT(priv && priv->spi); + spi = priv->spi; + + /* Select ENC28J60 chip */ + + enc_select(spi); + + /* Send the write buffer memory command (ignoring the response) */ + + (void)SPI_SEND(spi, ENC_WBM); + + /* Then send the buffer */ + + SPI_SNDBLOCK(spi, buffer, buflen); + + /* De-select ENC28J60 chip. */ + + enc_deselect(spi); +} + +/**************************************************************************** + * Function: enc_rdphy + * + * Description: + * Read 16-bits of PHY data. + * + * Parameters: + * priv - Reference to the driver state structure + * phyaddr - The PHY register address + * + * Returned Value: + * 16-bit value read from the PHY + * + * Assumptions: + * + ****************************************************************************/ + +static uint16_t enc_rdphy(FAR struct enc_driver_s *priv, uint8_t phyaddr) +{ + uint16_t data = 0; + + /* Set the PHY address (and start the PHY read operation) */ + + enc_wrbreg(priv, ENC_MIREGADR, phyaddr); + enc_wrbreg(priv, ENC_MICMD, MICMD_MIIRD); + + /* Wait until the PHY read completes */ + + if (enc_waitbreg(priv, ENC_MISTAT, MISTAT_BUSY, 0x00) == OK); + { + /* Terminate reading */ + + enc_wrbreg(priv, ENC_MICMD, 0x00); + + /* Get the PHY data */ + + data = (uint16_t)enc_rdbreg(priv, ENC_MIRDL); + data |= (uint16_t)enc_rdbreg(priv, ENC_MIRDH) << 8; + } + return data; +} + +/**************************************************************************** + * Function: enc_wrphy + * + * Description: + * write 16-bits of PHY data. + * + * Parameters: + * priv - Reference to the driver state structure + * phyaddr - The PHY register address + * phydata - 16-bit data to write to the PHY + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_wrphy(FAR struct enc_driver_s *priv, uint8_t phyaddr, + uint16_t phydata) +{ + /* Set the PHY register address */ + + enc_wrbreg(priv, ENC_MIREGADR, phyaddr); + + /* Write the PHY data */ + + enc_wrbreg(priv, ENC_MIWRL, phydata); + enc_wrbreg(priv, ENC_MIWRH, phydata >> 8); + + /* Wait until the PHY write completes */ + + enc_waitbreg(priv, ENC_MISTAT, MISTAT_BUSY, 0x00); +} + +/**************************************************************************** + * Function: enc_transmit + * + * Description: + * Start hardware transmission. Called either from: + * + * - pkif interrupt when an application responds to the receipt of data + * by trying to send something, or + * - From watchdog based polling. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static int enc_transmit(FAR struct enc_driver_s *priv) +{ + uint16_t txend; + + /* Increment statistics */ + + nllvdbg("Sending packet, pktlen: %d\n", priv->dev.d_len); +#ifdef CONFIG_ENC28J60_STATS + priv->stats.txrequests++; +#endif + + /* Verify that the hardware is ready to send another packet. The driver + * starts a transmission process by setting ECON1.TXRTS. When the packet is + * finished transmitting or is aborted due to an error/cancellation, the + * ECON1.TXRTS bit will be cleared. + * + * NOTE: If we got here, then we have committed to sending a packet. + * higher level logic must have assured that (1) there is no transmission + * in progress, and that (2) TX-related interrupts are disabled. + */ + + DEBUGASSERT((enc_rdgreg(priv, ENC_ECON1) & ECON1_TXRTS) == 0); + + /* Send the packet: address=priv->dev.d_buf, length=priv->dev.d_len */ + + enc_dumppacket("Transmit Packet", priv->dev.d_buf, priv->dev.d_len); + + /* Reset the write pointer to start of transmit buffer */ + + enc_wrbreg(priv, ENC_EWRPTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(priv, ENC_EWRPTH, PKTMEM_TX_START >> 8); + + /* Set the TX End pointer based on the size of the packet to send */ + + txend = PKTMEM_TX_START + priv->dev.d_len; + enc_wrbreg(priv, ENC_ETXNDL, txend & 0xff); + enc_wrbreg(priv, ENC_ETXNDH, txend >> 8); + + /* Write the per-packet control byte into the transmit buffer */ + + enc_wrgreg(priv, ENC_WBM, 0x00); + + /* Copy the packet itself into the transmit buffer */ + + enc_wrbuffer(priv, priv->dev.d_buf, priv->dev.d_len); + + /* Set TXRTS to send the packet in the transmit buffer */ + + enc_bfsgreg(priv, ENC_ECON1, ECON1_TXRTS); + + /* Setup the TX timeout watchdog (perhaps restarting the timer). Note: + * Is there a race condition. Could the TXIF interrupt occur before + * the timer is started? + */ + + (void)wd_start(priv->txtimeout, ENC_TXTIMEOUT, enc_txtimeout, 1, (uint32_t)priv); + return OK; +} + +/**************************************************************************** + * Function: enc_uiptxpoll + * + * Description: + * The transmitter is available, check if uIP has any outgoing packets ready + * to send. This is a callback from uip_poll(). uip_poll() may be called: + * + * 1. When the preceding TX packet send is complete, + * 2. When the preceding TX packet send timesout and the interface is reset + * 3. During normal TX polling + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static int enc_uiptxpoll(struct uip_driver_s *dev) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)dev->d_private; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + nllvdbg("Poll result: d_len=%d\n", priv->dev.d_len); + if (priv->dev.d_len > 0) + { + uip_arp_out(&priv->dev); + enc_transmit(priv); + + /* Stop the poll now because we can queue only one packet */ + + return -EBUSY; + } + + /* If zero is returned, the polling will continue until all connections have + * been examined. + */ + + return OK; +} + +/**************************************************************************** + * Function: enc_linkstatus + * + * Description: + * The current link status can be obtained from the PHSTAT1.LLSTAT or + * PHSTAT2.LSTAT. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_linkstatus(FAR struct enc_driver_s *priv) +{ +#if 0 + uint16_t regval = enc_rdphy(priv, ENC_PHSTAT2); + priv->duplex = ((regval & PHSTAT2_DPXSTAT) != 0); + priv->carrier = ((regval & PHSTAT2_LSTAT) != 0); +#endif +} + +/**************************************************************************** + * Function: enc_txif + * + * Description: + * An TXIF interrupt was received indicating that the last TX packet(s) is + * done + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_txif(FAR struct enc_driver_s *priv) +{ + /* Update statistics */ + +#ifdef CONFIG_ENC28J60_STATS + priv->stats.txifs++; + if (enc_rdgreg(priv, ENC_ESTAT) & ESTAT_TXABRT) + { + priv->stats.txabrts++; + } +#endif + + /* Clear the request to send bit */ + + enc_bfcgreg(priv, ENC_ECON1, ECON1_TXRTS); + + /* If no further xmits are pending, then cancel the TX timeout */ + + wd_cancel(priv->txtimeout); + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&priv->dev, enc_uiptxpoll); +} + +/**************************************************************************** + * Function: enc_txerif + * + * Description: + * An TXERIF interrupt was received indicating that a TX abort has occurred. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_txerif(FAR struct enc_driver_s *priv) +{ + /* Update statistics */ + +#ifdef CONFIG_ENC28J60_STATS + priv->stats.txerifs++; +#endif + + /* Reset TX */ + + enc_bfsgreg(priv, ENC_ECON1, ECON1_TXRST); + enc_bfcgreg(priv, ENC_ECON1, ECON1_TXRST | ECON1_TXRTS); + + /* Here we really should re-transmit (I fact, if we want half duplex to + * work right, then it is necessary to do this!): + * + * 1. Read the TSV: + * - Read ETXNDL to get the end pointer + * - Read 7 bytes from that pointer + 1 using ENC_RMB. + * 2. Determine if we need to retransmit. Check the LATE COLLISION bit, if + * set, then we need to transmit. + * 3. Retranmit by resetting ECON1_TXRTS. + */ + +#ifdef CONFIG_ENC28J60_HALFDUPLEX +# error "Missing logic for half duplex" +#endif +} + +/**************************************************************************** + * Function: enc_rxerif + * + * Description: + * An RXERIF interrupt was received indicating that the last TX packet(s) is + * done + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_rxerif(FAR struct enc_driver_s *priv) +{ + /* Update statistics */ + +#ifdef CONFIG_ENC28J60_STATS + priv->stats.rxerifs++; +#endif +} + +/**************************************************************************** + * Function: enc_rxdispath + * + * Description: + * Give the newly received packet to uIP. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_rxdispath(FAR struct enc_driver_s *priv) +{ + /* We only accept IP packets of the configured type and ARP packets */ + +#ifdef CONFIG_NET_IPv6 + if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) +#else + if (BUF->type == HTONS(UIP_ETHTYPE_IP)) +#endif + { + nllvdbg("IP packet received (%02x)\n", BUF->type); + uip_arp_ipin(&priv->dev); + uip_input(&priv->dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (priv->dev.d_len > 0) + { + uip_arp_out(&priv->dev); + enc_transmit(priv); + } + } + else if (BUF->type == htons(UIP_ETHTYPE_ARP)) + { + nllvdbg("ARP packet received (%02x)\n", BUF->type); + uip_arp_arpin(&priv->dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (priv->dev.d_len > 0) + { + enc_transmit(priv); + } + } + else + { + nlldbg("Unsupported packet type dropped (%02x)\n", htons(BUF->type)); + } +} + +/**************************************************************************** + * Function: enc_pktif + * + * Description: + * An interrupt was received indicating the availability of a new RX packet + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_pktif(FAR struct enc_driver_s *priv) +{ + uint8_t rsv[6]; + uint16_t pktlen; + uint16_t rxstat; + + /* Update statistics */ + +#ifdef CONFIG_ENC28J60_STATS + priv->stats.pktifs++; +#endif + + /* Set the read pointer to the start of the received packet */ + + DEBUGASSERT(priv->nextpkt <= PKTMEM_RX_END); + enc_wrbreg(priv, ENC_ERDPTL, (priv->nextpkt)); + enc_wrbreg(priv, ENC_ERDPTH, (priv->nextpkt) >> 8); + + /* Read the next packet pointer and the 4 byte read status vector (RSV) + * at the beginning of the received packet + */ + + enc_rdbuffer(priv, rsv, 6); + + /* Decode the new next packet pointer, and the RSV. The + * RSV is encoded as: + * + * Bits 0-15: Indicates length of the received frame. This includes the + * destination address, source address, type/length, data, + * padding and CRC fields. This field is stored in little- + * endian format. + * Bits 16-31: Bit encoded RX status. + */ + + priv->nextpkt = (uint16_t)rsv[1] << 8 | (uint16_t)rsv[0]; + pktlen = (uint16_t)rsv[3] << 8 | (uint16_t)rsv[2]; + rxstat = (uint16_t)rsv[5] << 8 | (uint16_t)rsv[4]; + nllvdbg("Receiving packet, pktlen: %d\n", pktlen); + + /* Check if the packet was received OK */ + + if ((rxstat & RXSTAT_OK) == 0) + { + nlldbg("ERROR: RXSTAT: %04x\n", rxstat); +#ifdef CONFIG_ENC28J60_STATS + priv->stats.rxnotok++; +#endif + } + + /* Check for a usable packet length (4 added for the CRC) */ + + else if (pktlen > (CONFIG_NET_BUFSIZE + 4) || pktlen <= (UIP_LLH_LEN + 4)) + { + nlldbg("Bad packet size dropped (%d)\n", pktlen); +#ifdef CONFIG_ENC28J60_STATS + priv->stats.rxpktlen++; +#endif + } + + /* Otherwise, read and process the packet */ + + else + { + /* Save the packet length (without the 4 byte CRC) in priv->dev.d_len*/ + + priv->dev.d_len = pktlen - 4; + + /* Copy the data data from the receive buffer to priv->dev.d_buf */ + + enc_rdbuffer(priv, priv->dev.d_buf, priv->dev.d_len); + enc_dumppacket("Received Packet", priv->ld_dev.d_buf, priv->ld_dev.d_len); + + /* Dispatch the packet to uIP */ + + enc_rxdispath(priv); + } + + /* Move the RX read pointer to the start of the next received packet. + * This frees the memory we just read. + */ + + enc_wrbreg(priv, ENC_ERXRDPTL, (priv->nextpkt)); + enc_wrbreg(priv, ENC_ERXRDPTH, (priv->nextpkt) >> 8); + + /* Decrement the packet counter indicate we are done with this packet */ + + enc_bfsgreg(priv, ENC_ECON2, ECON2_PKTDEC); +} + +/**************************************************************************** + * Function: enc_worker + * + * Description: + * Perform interrupt handling logic outside of the interrupt handler (on + * the work queue thread). + * + * Parameters: + * arg - The reference to the driver structure (case to void*) + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_worker(FAR void *arg) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)arg; + uint8_t eir; + + DEBUGASSERT(priv); + + /* Disable further interrupts by clearing the global interrup enable bit */ + + enc_bfcgreg(priv, ENC_EIE, EIE_INTIE); + + /* Loop until all interrupts have been processed (EIR==0). Note that + * there is no infinite loop check... if there are always pending interrupts, + * we are just broken. + */ + + while ((eir = enc_rdgreg(priv, ENC_EIR) & EIR_ALLINTS) != 0) + { + /* Handle interrupts according to interrupt register register bit + * settings + * + * DMAIF: The DMA interrupt indicates that the DMA module has completed + * its memory copy or checksum calculation. Additionally, this interrupt + * will be caused if the host controller cancels a DMA operation by + * manually clearing the DMAST bit. Once set, DMAIF can only be cleared + * by the host controller or by a Reset condition. + */ + + if ((eir & EIR_DMAIF) != 0) /* DMA interrupt */ + { + /* Not used by this driver. Just clear the interrupt request. */ + + enc_bfcgreg(priv, ENC_EIR, EIR_DMAIF); + } + + /* LINKIF: The LINKIF indicates that the link status has changed. + * The actual current link status can be obtained from the + * PHSTAT1.LLSTAT or PHSTAT2.LSTAT. Unlike other interrupt sources, the + * link status change interrupt is created in the integrated PHY + * module. + * + * To receive it, the host controller must set the PHIE.PLNKIE and + * PGEIE bits. After setting the two PHY interrupt enable bits, the + * LINKIF bit will then shadow the contents of the PHIR.PGIF bit. + * + * Once LINKIF is set, it can only be cleared by the host controller or + * by a Reset. The LINKIF bit is read-only. Performing an MII read on + * the PHIR register will clear the LINKIF, PGIF and PLNKIF bits + * automatically and allow for future link status change interrupts. + */ + + if ((eir & EIR_LINKIF) != 0) /* Link change interrupt */ + { + enc_linkstatus(priv); /* Get current link status */ + enc_rdphy(priv, ENC_PHIR); /* Clear the LINKIF interrupt */ + } + + /* TXIF: The Transmit Interrupt Flag (TXIF) is used to indicate that + * the requested packet transmission has ended. Upon transmission + * completion, abort or transmission cancellation by the host + * controller, the EIR.TXIF flag will be set to 1. + * + * Once TXIF is set, it can only be cleared by the host controller + * or by a Reset condition. Once processed, the host controller should + * use the BFC command to clear the EIR.TXIF bit. + */ + + if ((eir & EIR_TXIF) != 0) /* Transmit interrupt */ + { + enc_txif(priv); /* Handle TX completion */ + enc_bfcgreg(priv, ENC_EIR, EIR_TXIF); /* Clear the TXIF interrupt */ + } + + /* TXERIF: The Transmit Error Interrupt Flag (TXERIF) is used to + * indicate that a transmit abort has occurred. An abort can occur + * because of any of the following: + * + * 1. Excessive collisions occurred as defined by the Retransmission + * Maximum (RETMAX) bits in the MACLCON1 register. + * 2. A late collision occurred as defined by the Collision Window + * (COLWIN) bits in the MACLCON2 register. + * 3. A collision after transmitting 64 bytes occurred (ESTAT.LATECOL + * set). + * 4. The transmission was unable to gain an opportunity to transmit + * the packet because the medium was constantly occupied for too long. + * The deferral limit (2.4287 ms) was reached and the MACON4.DEFER bit + * was clear. + * 5. An attempt to transmit a packet larger than the maximum frame + * length defined by the MAMXFL registers was made without setting + * the MACON3.HFRMEN bit or per packet POVERRIDE and PHUGEEN bits. + * + * Upon any of these conditions, the EIR.TXERIF flag is set to 1. Once + * set, it can only be cleared by the host controller or by a Reset + * condition. + * + * After a transmit abort, the TXRTS bit will be cleared, the + * ESTAT.TXABRT bit will be set and the transmit status vector will be + * written at ETXND + 1. The MAC will not automatically attempt to + * retransmit the packet. The host controller may wish to read the + * transmit status vector and LATECOL bit to determine the cause of + * the abort. After determining the problem and solution, the host + * controller should clear the LATECOL (if set) and TXABRT bits so + * that future aborts can be detected accurately. + * + * In Full-Duplex mode, condition 5 is the only one that should cause + * this interrupt. Collisions and other problems related to sharing + * the network are not possible on full-duplex networks. The conditions + * which cause the transmit error interrupt meet the requirements of the + * transmit interrupt. As a result, when this interrupt occurs, TXIF + * will also be simultaneously set. + */ + + if ((eir & EIR_TXERIF) != 0) /* Transmit Error Interrupts */ + { + enc_txerif(priv); /* Handle the TX error */ + enc_bfcgreg(priv, ENC_EIR, EIR_TXERIF); /* Clear the TXERIF interrupt */ + } + + /* PKTIF The Receive Packet Pending Interrupt Flag (PKTIF) is used to + * indicate the presence of one or more data packets in the receive + * buffer and to provide a notification means for the arrival of new + * packets. When the receive buffer has at least one packet in it, + * EIR.PKTIF will be set. In other words, this interrupt flag will be + * set anytime the Ethernet Packet Count register (EPKTCNT) is non-zero. + * + * The PKTIF bit can only be cleared by the host controller or by a Reset + * condition. In order to clear PKTIF, the EPKTCNT register must be + * decremented to 0. If the last data packet in the receive buffer is + * processed, EPKTCNT will become zero and the PKTIF bit will automatically + * be cleared. + */ + + /* Ignore PKTIF because is unreliable. Use EPKTCNT instead */ + /* if ((eir & EIR_PKTIF) != 0) */ + { + uint8_t pktcnt = enc_rdbreg(priv, ENC_EPKTCNT); + if (pktcnt > 0) + { +#ifdef CONFIG_ENC28J60_STATS + if (pktcnt > priv->stats.maxpktcnt) + { + priv->stats.maxpktcnt = pktcnt; + } +#endif + /* Handle packet receipt */ + + enc_pktif(priv); + } + } + + /* RXERIF: The Receive Error Interrupt Flag (RXERIF) is used to + * indicate a receive buffer overflow condition. Alternately, this + * interrupt may indicate that too many packets are in the receive + * buffer and more cannot be stored without overflowing the EPKTCNT + * register. When a packet is being received and the receive buffer + * runs completely out of space, or EPKTCNT is 255 and cannot be + * incremented, the packet being received will be aborted (permanently + * lost) and the EIR.RXERIF bit will be set to 1. + * + * Once set, RXERIF can only be cleared by the host controller or by a + * Reset condition. Normally, upon the receive error condition, the + * host controller would process any packets pending from the receive + * buffer and then make additional room for future packets by + * advancing the ERXRDPT registers (low byte first) and decrementing + * the EPKTCNT register. + * + * Once processed, the host controller should use the BFC command to + * clear the EIR.RXERIF bit. + */ + + if ((eir & EIR_RXERIF) != 0) /* Receive Errror Interrupts */ + { + enc_rxerif(priv); /* Handle the RX error */ + enc_bfcgreg(priv, ENC_EIR, EIR_RXERIF); /* Clear the RXERIF interrupt */ + } + + } + + /* Enable Ethernet interrupts */ + + enc_bfsgreg(priv, ENC_EIE, EIE_INTIE); +} + +/**************************************************************************** + * Function: enc_interrupt + * + * Description: + * Hardware interrupt handler + * + * Parameters: + * irq - Number of the IRQ that generated the interrupt + * context - Interrupt register state save info (architecture-specific) + * + * Returned Value: + * OK on success + * + * Assumptions: + * + ****************************************************************************/ + +static int enc_interrupt(int irq, FAR void *context) +{ + register FAR struct enc_driver_s *priv = &g_enc28j60[0]; + + DEBUGASSERT(priv->irq == irq); + +#ifdef CONFIG_SPI_OWNBUS + /* In very simple environments, we own the SPI and can do data transfers + * from the interrupt handler. That is actually a very bad idea in any + * case because it keeps interrupts disabled for a long time. + */ + + enc_worker((FAR void*)priv); + return OK; +#else + /* In complex environments, we cannot do SPI transfers from the interrupt + * handler because semaphores are probably used to lock the SPI bus. In + * this case, we will defer processing to the worker thread. This is also + * much kinder in the use of system resources and is, therefore, probably + * a good thing to do in any event. + */ + + return work_queue(&priv->work, enc_worker, (FAR void *)priv, 0); +#endif +} + +/**************************************************************************** + * Function: enc_txtimeout + * + * Description: + * Our TX watchdog timed out. Called from the timer interrupt handler. + * The last TX never completed. Reset the hardware and start again. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_txtimeout(int argc, uint32_t arg, ...) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)arg; + int ret; + + /* Increment statistics and dump debug info */ + + nlldbg("Tx timeout\n"); +#ifdef CONFIG_ENC28J60_STATS + priv->stats.txtimeouts++; +#endif + + /* Then reset the hardware: Take the interface down, then bring it + * back up + */ + + ret = enc_ifdown(&priv->dev); + DEBUGASSERT(ret == OK); + ret = enc_ifup(&priv->dev); + DEBUGASSERT(ret == OK); + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&priv->dev, enc_uiptxpoll); +} + +/**************************************************************************** + * Function: enc_polltimer + * + * Description: + * Periodic timer handler. Called from the timer interrupt handler. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_polltimer(int argc, uint32_t arg, ...) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)arg; + + /* Verify that the hardware is ready to send another packet. The driver + * start a transmission process by setting ECON1.TXRTS. When the packet is + * finished transmitting or is aborted due to an error/cancellation, the + * ECON1.TXRTS bit will be cleared. + */ + + if ((enc_rdgreg(priv, ENC_ECON1) & ECON1_TXRTS) == 0) + { + /* Yes.. update TCP timing states and poll uIP for new XMIT data. Hmmm.. + * looks like a bug here to me. Does this mean if there is a transmit + * in progress, we will missing TCP time state updates? + */ + + (void)uip_timer(&priv->dev, enc_uiptxpoll, ENC_POLLHSEC); + } + + /* Setup the watchdog poll timer again */ + + (void)wd_start(priv->txpoll, ENC_WDDELAY, enc_polltimer, 1, arg); +} + +/**************************************************************************** + * Function: enc_ifup + * + * Description: + * NuttX Callback: Bring up the Ethernet interface when an IP address is + * provided + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int enc_ifup(struct uip_driver_s *dev) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)dev->d_private; + int ret; + + nlldbg("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Initialize Ethernet interface, set the MAC address, and make sure that + * the ENC28J80 is not in power save mode. + */ + + ret = enc_reset(priv); + if (ret == OK) + { + enc_setmacaddr(priv); + enc_pwrfull(priv); + + /* Enable interrupts at the ENC28J60. Interrupts are still disabled + * at the interrupt controller. + */ + + enc_wrphy(priv, ENC_PHIE, PHIE_PGEIE | PHIE_PLNKIE); + enc_bfsgreg(priv, ENC_EIE, EIE_INTIE | EIE_PKTIE); + enc_bfsgreg(priv, ENC_EIR, EIR_DMAIF | EIR_LINKIF | EIR_TXIF | + EIR_TXERIF | EIR_RXERIF | EIR_PKTIF); + enc_wrgreg(priv, ENC_EIE, EIE_INTIE | EIE_PKTIE | EIE_LINKIE | + EIE_TXIE | EIE_TXERIE | EIE_RXERIE); + + /* Enable the receiver */ + + enc_bfsgreg(priv, ENC_ECON1, ECON1_RXEN); + + /* Set and activate a timer process */ + + (void)wd_start(priv->txpoll, ENC_WDDELAY, enc_polltimer, 1, (uint32_t)priv); + + /* Mark the interface up and enable the Ethernet interrupt at the + * controller + */ + + priv->bifup = true; + up_enable_irq(priv->irq); + } + return ret; +} + +/**************************************************************************** + * Function: enc_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int enc_ifdown(struct uip_driver_s *dev) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)dev->d_private; + irqstate_t flags; + int ret; + + nlldbg("Taking down: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Disable the Ethernet interrupt */ + + flags = irqsave(); + up_disable_irq(priv->irq); + + /* Cancel the TX poll timer and TX timeout timers */ + + wd_cancel(priv->txpoll); + wd_cancel(priv->txtimeout); + + /* Reset the device and leave in the power save state */ + + ret = enc_reset(priv); + enc_pwrsave(priv); + + priv->bifup = false; + irqrestore(flags); + return ret; +} + +/**************************************************************************** + * Function: enc_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static int enc_txavail(struct uip_driver_s *dev) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)dev->d_private; + irqstate_t flags; + + flags = irqsave(); + + /* Ignore the notification if the interface is not yet up */ + + if (priv->bifup) + { + /* Check if the hardware is ready to send another packet. The driver + * starts a transmission process by setting ECON1.TXRTS. When the packet is + * finished transmitting or is aborted due to an error/cancellation, the + * ECON1.TXRTS bit will be cleared. + */ + + if ((enc_rdgreg(priv, ENC_ECON1) & ECON1_TXRTS) == 0) + { + /* The interface is up and TX is idle; poll uIP for new XMIT data */ + + (void)uip_poll(&priv->dev, enc_uiptxpoll); + } + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: enc_addmac + * + * Description: + * NuttX Callback: Add the specified MAC address to the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be added + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int enc_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + +#warning "Multicast MAC support not implemented" + return OK; +} +#endif + +/**************************************************************************** + * Function: enc_rmmac + * + * Description: + * NuttX Callback: Remove the specified MAC address from the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be removed + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int enc_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + +#warning "Multicast MAC support not implemented" + return OK; +} +#endif + +/**************************************************************************** + * Function: enc_pwrsave + * + * Description: + * The ENC28J60 may be commanded to power-down via the SPI interface. + * When powered down, it will no longer be able to transmit and receive + * any packets. To maximize power savings: + * + * 1. Turn off packet reception by clearing ECON1.RXEN. + * 2. Wait for any in-progress packets to finish being received by + * polling ESTAT.RXBUSY. This bit should be clear before proceeding. + * 3. Wait for any current transmissions to end by confirming ECON1.TXRTS + * is clear. + * 4. Set ECON2.VRPS (if not already set). + * 5. Enter Sleep by setting ECON2.PWRSV. All MAC, MII and PHY registers + * become inaccessible as a result. Setting PWRSV also clears + * ESTAT.CLKRDY automatically. + * + * In Sleep mode, all registers and buffer memory will maintain their + * states. The ETH registers and buffer memory will still be accessible + * by the host controller. Additionally, the clock driver will continue + * to operate. The CLKOUT function will be unaffected. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_pwrsave(FAR struct enc_driver_s *priv) +{ + nllvdbg("Set PWRSV\n"); + + /* 1. Turn off packet reception by clearing ECON1.RXEN. */ + + enc_bfcgreg(priv, ENC_ECON1, ECON1_RXEN); + + /* 2. Wait for any in-progress packets to finish being received by + * polling ESTAT.RXBUSY. This bit should be clear before proceeding. + */ + + if (enc_waitbreg(priv, ENC_ESTAT, ESTAT_RXBUSY, 0) == OK) + { + /* 3. Wait for any current transmissions to end by confirming + * ECON1.TXRTS is clear. + */ + + enc_waitbreg(priv, ENC_ECON1, ECON1_TXRTS, 0); + + /* 4. Set ECON2.VRPS (if not already set). */ + /* enc_bfsgreg(priv, ENC_ECON2, ECON2_VRPS); <-- Set in enc_reset() */ + + /* 5. Enter Sleep by setting ECON2.PWRSV. */ + + enc_bfsgreg(priv, ENC_ECON2, ECON2_PWRSV); + } +} + +/**************************************************************************** + * Function: enc_pwrfull + * + * Description: + * When normal operation is desired, the host controller must perform + * a slightly modified procedure: + * + * 1. Wake-up by clearing ECON2.PWRSV. + * 2. Wait at least 300 ìs for the PHY to stabilize. To accomplish the + * delay, the host controller may poll ESTAT.CLKRDY and wait for it + * to become set. + * 3. Restore receive capability by setting ECON1.RXEN. + * + * After leaving Sleep mode, there is a delay of many milliseconds + * before a new link is established (assuming an appropriate link + * partner is present). The host controller may wish to wait until + * the link is established before attempting to transmit any packets. + * The link status can be determined by polling the PHSTAT2.LSTAT bit. + * Alternatively, the link change interrupt may be used if it is + * enabled. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_pwrfull(FAR struct enc_driver_s *priv) +{ + nllvdbg("Clear PWRSV\n"); + + /* 1. Wake-up by clearing ECON2.PWRSV. */ + + enc_bfcgreg(priv, ENC_ECON2, ECON2_PWRSV); + + /* 2. Wait at least 300 ìs for the PHY to stabilize. To accomplish the + * delay, the host controller may poll ESTAT.CLKRDY and wait for it to + * become set. + */ + + enc_waitbreg(priv, ENC_ESTAT, ESTAT_CLKRDY, ESTAT_CLKRDY); + + /* 3. Restore receive capability by setting ECON1.RXEN. + * + * The caller will do this when it is read to receive packets + */ +} + +/**************************************************************************** + * Function: enc_setmacaddr + * + * Description: + * Set the MAC address to the configured value. This is done after ifup + * or after a TX timeout. Note that this means that the interface must + * be down before configuring the MAC addr. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_setmacaddr(FAR struct enc_driver_s *priv) +{ + /* Program the hardware with it's MAC address (for filtering) */ + + enc_wrbreg(priv, ENC_MAADR1, priv->dev.d_mac.ether_addr_octet[5]); + enc_wrbreg(priv, ENC_MAADR2, priv->dev.d_mac.ether_addr_octet[4]); + enc_wrbreg(priv, ENC_MAADR3, priv->dev.d_mac.ether_addr_octet[3]); + enc_wrbreg(priv, ENC_MAADR4, priv->dev.d_mac.ether_addr_octet[2]); + enc_wrbreg(priv, ENC_MAADR5, priv->dev.d_mac.ether_addr_octet[1]); + enc_wrbreg(priv, ENC_MAADR6, priv->dev.d_mac.ether_addr_octet[0]); +} + +/**************************************************************************** + * Function: enc_reset + * + * Description: + * Stop, reset, re-initialize, and restart the ENC28J60. This is done + * initially, on ifup, and after a TX timeout. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int enc_reset(FAR struct enc_driver_s *priv) +{ + uint8_t regval; + + nlldbg("Reset\n"); + + /* Configure SPI for the ENC28J60 */ + + enc_configspi(priv->spi); + + /* Reset the ENC28J60 */ + + enc_wrgreg(priv, ENC_SRC, ENC_SRC); + + /* Check CLKRDY bit to see when the reset is complete. There is an errata + * that says the CLKRDY may be invalid. We'll wait a couple of msec to + * workaround this condition. + */ + + up_mdelay(2); + /* while ((enc_rdgreg(priv, ENC_ESTAT) & ESTAT_CLKRDY) != 0); */ + + /* Initialize ECON1: Clear ECON1 */ + + enc_wrgreg(priv, ENC_ECON1, 0x00); + + /* Initialize ECON2: Enable address auto increment and voltage + * regulator powersave. + */ + + enc_wrgreg(priv, ENC_ECON2, ECON2_AUTOINC | ECON2_VRPS); + + /* Initialize receive buffer. + * First, set the receive buffer start address. + */ + + priv->nextpkt = PKTMEM_RX_START; + enc_wrbreg(priv, ENC_ERXSTL, PKTMEM_RX_START & 0xff); + enc_wrbreg(priv, ENC_ERXSTH, PKTMEM_RX_START >> 8); + + /* Set the receive data pointer */ + + enc_wrbreg(priv, ENC_ERXRDPTL, PKTMEM_RX_START & 0xff); + enc_wrbreg(priv, ENC_ERXRDPTH, PKTMEM_RX_START >> 8); + + /* Set the receive buffer end. */ + + enc_wrbreg(priv, ENC_ERXNDL, PKTMEM_RX_END & 0xff); + enc_wrbreg(priv, ENC_ERXNDH, PKTMEM_RX_END >> 8); + + /* Set transmit buffer start. */ + + enc_wrbreg(priv, ENC_ETXSTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(priv, ENC_ETXSTH, PKTMEM_TX_START >> 8); + + /* Check if we are actually communicating with the ENC28J60. If its + * 0x00 or 0xff, then we are probably not communicating correctly + * via SPI. + */ + + regval = enc_rdbreg(priv, ENC_EREVID); + if (regval == 0x00 || regval == 0xff) + { + nlldbg("Bad Rev ID: %02x\n", regval); + return -ENODEV; + } + nllvdbg("Rev ID: %02x\n", regval); + + /* Set filter mode: unicast OR broadcast AND crc valid */ + + enc_wrbreg(priv, ENC_ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN); + + /* Enable MAC receive */ + + enc_wrbreg(priv, ENC_MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS); + + /* Enable automatic padding and CRC operations */ + +#ifdef CONFIG_ENC28J60_HALFDUPLEX + enc_wrbreg(priv, ENC_MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN); + enc_wrbreg(priv, ENC_MACON4, MACON4_DEFER); /* Defer transmission enable */ + + /* Set Non-Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(priv, ENC_MAIPGL, 0x12); + enc_wrbreg(priv, ENC_MAIPGH, 0x0c); + + /* Set Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(priv, ENC_MABBIPG, 0x12); +#else + /* Set filter mode: unicast OR broadcast AND crc valid AND Full Duplex */ + + enc_wrbreg(priv, ENC_MACON3, + MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN | MACON3_FULDPX); + + /* Set Non-Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(priv, ENC_MAIPGL, 0x12); + + /* Set ack-to-Back Inter-Packet Gap */ + + enc_wrbreg(priv, ENC_MABBIPG, 0x15); +#endif + + /* Set the maximum packet size which the controller will accept */ + + enc_wrbreg(priv, ENC_MAMXFLL, CONFIG_NET_BUFSIZE & 0xff); + enc_wrbreg(priv, ENC_MAMXFLH, CONFIG_NET_BUFSIZE >> 8); + + /* Configure LEDs (No, just use the defaults for now) */ + /* enc_wrphy(priv, ENC_PHLCON, ??); */ + + /* Setup up PHCON1 & 2 */ + +#ifdef CONFIG_ENC28J60_HALFDUPLEX + enc_wrphy(priv, ENC_PHCON1, 0x00); + enc_wrphy(priv, ENC_PHCON2, PHCON2_HDLDIS); +#else + enc_wrphy(priv, ENC_PHCON1, PHCON1_PDPXMD); + enc_wrphy(priv, ENC_PHCON2, 0x00); +#endif + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: enc_initialize + * + * Description: + * Initialize the Ethernet driver. The ENC28J60 device is assumed to be + * in the post-reset state upon entry to this function. + * + * Parameters: + * spi - A reference to the platform's SPI driver for the ENC28J60 + * devno - If more than one ENC28J60 is supported, then this is the + * zero based number that identifies the ENC28J60; + * irq - The fully configured GPIO IRQ that ENC28J60 interrupts will be + * asserted on. This driver will attach and entable this IRQ. + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * + ****************************************************************************/ + +int enc_initialize(FAR struct spi_dev_s *spi, unsigned int devno, unsigned int irq) +{ + FAR struct enc_driver_s *priv ; + int ret; + + DEBUGASSERT(devno < CONFIG_ENC28J60_NINTERFACES); + priv = &g_enc28j60[devno]; + + /* Initialize the driver structure */ + + memset(g_enc28j60, 0, CONFIG_ENC28J60_NINTERFACES*sizeof(struct enc_driver_s)); + priv->dev.d_ifup = enc_ifup; /* I/F down callback */ + priv->dev.d_ifdown = enc_ifdown; /* I/F up (new IP address) callback */ + priv->dev.d_txavail = enc_txavail; /* New TX data callback */ +#ifdef CONFIG_NET_IGMP + priv->dev.d_addmac = enc_addmac; /* Add multicast MAC address */ + priv->dev.d_rmmac = enc_rmmac; /* Remove multicast MAC address */ +#endif + priv->dev.d_private = priv; /* Used to recover private state from dev */ + + /* Create a watchdog for timing polling for and timing of transmisstions */ + + priv->txpoll = wd_create(); /* Create periodic poll timer */ + priv->txtimeout = wd_create(); /* Create TX timeout timer */ + priv->spi = spi; /* Save the SPI instance */ + priv->irq = irq; /* Save the IRQ number */ + + /* Make sure that the interface is in the down state. NOTE: The MAC + * address will not be set up until ifup. That gives the app time to set + * the MAC address before bringing the interface up. + */ + + ret = enc_ifdown(&priv->dev); + if (ret == OK) + { + /* Attach the IRQ to the driver (but don't enable it yet) */ + + if (irq_attach(irq, enc_interrupt)) + { + /* We could not attach the ISR to the interrupt */ + + ret = -EAGAIN; + } + + /* Register the device with the OS so that socket IOCTLs can be performed */ + + (void)netdev_register(&priv->dev); + } + return ret; +} + +/**************************************************************************** + * Function: enc_stats + * + * Description: + * Return accumulated ENC28J60 statistics. Statistics are cleared after + * being returned. + * + * Parameters: + * devno - If more than one ENC28J60 is supported, then this is the + * zero based number that identifies the ENC28J60; + * stats - The user-provided location to return the statistics. + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_ENC28J60_STATS +int enc_stats(unsigned int devno, struct enc_stats_s *stats) +{ + FAR struct enc_driver_s *priv ; + irqstate_t flags; + + DEBUGASSERT(devno < CONFIG_ENC28J60_NINTERFACES); + priv = &g_enc28j60[devno]; + + /* Disable the Ethernet interrupt */ + + flags = irqsave(); + memcpy(stats, &priv->stats, sizeof(struct enc_stats_s)); + memset(&priv->stats, 0, sizeof(struct enc_stats_s)); + irqrestore(flags); + return OK; +} +#endif +#endif /* CONFIG_NET && CONFIG_ENC28J60_NET */ + diff --git a/nuttx/drivers/net/enc28j60.h b/nuttx/drivers/net/enc28j60.h new file mode 100644 index 000000000..408224b22 --- /dev/null +++ b/nuttx/drivers/net/enc28j60.h @@ -0,0 +1,478 @@ +/**************************************************************************** + * drivers/net/enc28j60.h + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * References: + * - ENC28J60 Data Sheet, Stand-Alone Ethernet Controller with SPI Interface, + * DS39662C, 2008 Microchip Technology Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_NET_ENC28J60_H +#define __DRIVERS_NET_ENC28J60_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* ENC28J60 Commands ********************************************************/ +/* A total of seven instructions are implemented on the ENC28J60. Where: + * + * aaaaaa is the 5-bit address of a control register, and + * dddddddd is one or more bytes of data that may accompany the command. + */ + +#define ENC_RCR (0x00) /* Read Control Register + * 000 | aaaaa | (Registe value returned)) */ +#define ENC_RBM (0x3a) /* Read Buffer Memory + * 001 | 11010 | (Read buffer data follows) */ +#define ENC_WCR (0x40) /* Write Control Register + * 010 | aaaaa | dddddddd */ +#define ENC_WBM (0x7a) /* Write Buffer Memory + * 011 | 11010 | (Write buffer data follows) */ +#define ENC_BFS (0x80) /* Bit Field Set + * 100 | aaaaa | dddddddd */ +#define ENC_BFC (0xa0) /* Bit Field Clear + * 101 | aaaaa | dddddddd */ +#define ENC_SRC (0xff) /* System Reset + * 111 | 11111 | (No data) */ + +/* Global Control Registers *************************************************/ +/* Control registers are accessed with the RCR, RBM, WCR, BFS, and BFC + * commands. The following identifies all ENC28J60 control registers. The + * control register memory is partitioned into four banks, selectable by the + * bank select bits, BSEL1:BSEL0, in the ECON1 register. + * + * The last five locations (0x1b to 0x1f) of all banks point to a common set + * of registers: EIE, EIR, ESTAT, ECON2 and ECON1. These are key registers + * usedin controlling and monitoring the operation of the device. Their + * common mapping allows easy access without switching the bank. + * + * Control registers for the ENC28J60 are generically grouped as ETH, MAC and + * MII registers. Register names starting with E belong to the ETH group. + * Similarly, registers names starting with MA belong to the MAC group and + * registers prefixed with MI belong to the MII group. + */ + +#define ENC_EIE (0x1b) /* Ethernet Interrupt Enable Register */ +#define ENC_EIR (0x1c) /* Ethernet Interupt Request Register */ +#define ENC_ESTAT (0x1d) /* Ethernet Status Register */ +#define ENC_ECON2 (0x1e) /* Ethernet Control 2 Register */ +#define ENC_ECON1 (0x1f) /* Ethernet Control 1 Register */ + +/* Ethernet Interrupt Enable Register Bit Definitions */ + +#define EIE_RXERIE (1 << 0) /* Bit 0: Receive Error Interrupt Enable */ +#define EIE_TXERIE (1 << 1) /* Bit 1: Transmit Error Interrupt Enable */ + /* Bit 2: Reserved */ +#define EIE_TXIE (1 << 3) /* Bit 3: Transmit Enable */ +#define EIE_LINKIE (1 << 4) /* Bit 4: Link Status Change Interrupt Enable */ +#define EIE_DMAIE (1 << 5) /* Bit 5: DMA Interrupt Enable */ +#define EIE_PKTIE (1 << 6) /* Bit 6: Receive Packet Pending Interrupt Enable */ +#define EIE_INTIE (1 << 7) /* Bit 7: Global INT Interrupt Enable */ + +/* Ethernet Interrupt Request Register Bit Definitions */ + +#define EIR_RXERIF (1 << 0) /* Bit 0: Receive Error Interrupt */ +#define EIR_TXERIF (1 << 1) /* Bit 1: Transmit Error Interrupt */ + /* Bit 2: Reserved */ +#define EIR_TXIF (1 << 3) /* Bit 3: Transmit Interrupt */ +#define EIR_LINKIF (1 << 4) /* Bit 4: Link Change Interrupt */ +#define EIR_DMAIF (1 << 5) /* Bit 5: DMA Interrupt */ +#define EIR_PKTIF (1 << 6) /* Bit 6: Receive Packet Pending Interrupt */ + /* Bit 7: Reserved */ +#define EIR_ALLINTS (0x7b) /* All interrupts */ + +/* Ethernet Status Register Bit Definitions */ + +#define ESTAT_CLKRDY (1 << 0) /* Bit 0: Clock Ready */ +#define ESTAT_TXABRT (1 << 1) /* Bit 1: Transmit Abort Error */ +#define ESTAT_RXBUSY (1 << 2) /* Bit 2: Receive Busy */ + /* Bit 3: Reserved */ +#define ESTAT_LATECOL (1 << 4) /* Bit 4: Late Collision Error */ + /* Bit 5: Reserved */ +#define ESTAT_BUFER (1 << 6) /* Bit 6: Ethernet Buffer Error Status */ +#define ESTAT_INT (1 << 7) /* Bit 7: INT Interrupt */ + +/* Ethernet Control 1 Register Bit Definitions */ + +#define ECON1_BSEL_SHIFT (0) /* Bits 0-1: Bank select */ +#define ECON1_BSEL_MASK (3 << ECON1_BSEL_SHIFT) +# define ECON1_BSEL_BANK0 (0 << 0) /* Bank 0 */ +# define ECON1_BSEL_BANK1 (1 << 1) /* Bank 1 */ +# define ECON1_BSEL_BANK2 (2 << 0) /* Bank 2 */ +# define ECON1_BSEL_BANK3 (3 << 0) /* Bank 3 */ +#define ECON1_RXEN (1 << 2) /* Bit 2: Receive Enable */ +#define ECON1_TXRTS (1 << 3) /* Bit 3: Transmit Request to Send */ +#define ECON1_CSUMEN (1 << 4) /* Bit 4: DMA Checksum Enable */ +#define ECON1_DMAST (1 << 5) /* Bit 5: DMA Start and Busy Status */ +#define ECON1_RXRST (1 << 6) /* Bit 6: Receive Logic Reset */ +#define ECON1_TXRST (1 << 7) /* Bit 7: Transmit Logic Reset */ + +/* Ethernet Control 2 Register */ + /* Bits 0-2: Reserved */ +#define ECON2_VRPS (1 << 3) /* Bit 3: Voltage Regulator Power Save Enable */ + /* Bit 4: Reserved */ +#define ECON2_PWRSV (1 << 5) /* Bit 5: Power Save Enable */ +#define ECON2_PKTDEC (1 << 6) /* Bit 6: Packet Decrement */ +#define ECON2_AUTOINC (1 << 7) /* Bit 7: Automatic Buffer Pointer Increment Enable */ + +/* Banked Control Registers *************************************************/ +/* The remaining control registers are identified with a a 5 bit address and + * a bank selection. We pack the bank number and an indication if this is + * a MAC/PHY register access together with the control register address + * together to keep the design simpler. + */ + +#define ENC_ADDR_SHIFT (0) /* Bits 0-4: Register address */ +#define ENC_ADDR_MASK (0x1f << ENC_ADDR_SHIFT) +#define ENC_BANK_SHIFT (5) /* Bits 5-6: Bank number */ +#define ENC_BANK_MASK (3 << ENC_BSEL_SHIFT) +# define ENC_BANK0 (0 << ENC_BSEL_SHIFT) +# define ENC_BANK1 (1 << ENC_BSEL_SHIFT) +# define ENC_BANK2 (2 << ENC_BSEL_SHIFT) +# define ENC_BANK3 (3 << ENC_BSEL_SHIFT) +#define ENC_PHYMAC_SHIFT (7) /* Bit 7: This is a PHY/MAC command */ +#define ENC_PHYMAC (1 << ENC_PHYMAC_SHIFT) + +#define REGADDR(a,b,m) ((m) << ENC_PHYMAC_SHIFT | (b) << ENC_BANK_SHIFT | (a)) +#define GETADDR(a) ((a) & ENC_ADDR_MASK) +#define GETBANK(a) (((a) >> ENC_BANK_SHIFT) & 3) +#define ISPHYMAC(a) (((a) & ENC_PHYMAC) != 0) + +/* Bank 0 Control Register Addresses */ + +#define ENC_ERDPTL REGADDR(0x00, 0, 0) /* Read Pointer Low Byte (ERDPT<7:0> */ +#define ENC_ERDPTH REGADDR(0x01, 0, 0) /* Read Pointer High Byte (ERDPT<12:8>) */ +#define ENC_EWRPTL REGADDR(0x02, 0, 0) /* Write Pointer Low Byte (EWRPT<7:0>) */ +#define ENC_EWRPTH REGADDR(0x03, 0, 0) /* Write Pointer High Byte (EWRPT<12:8>) */ +#define ENC_ETXSTL REGADDR(0x04, 0, 0) /* TX Start Low Byte (ETXST<7:0>) */ +#define ENC_ETXSTH REGADDR(0x05, 0, 0) /* TX Start High Byte (ETXST<12:8>) */ +#define ENC_ETXNDL REGADDR(0x06, 0, 0) /* TX End Low Byte (ETXND<7:0>) */ +#define ENC_ETXNDH REGADDR(0x07, 0, 0) /* TX End High Byte (ETXND<12:8>) */ +#define ENC_ERXSTL REGADDR(0x08, 0, 0) /* RX Start Low Byte (ERXST<7:0>) */ +#define ENC_ERXSTH REGADDR(0x09, 0, 0) /* RX Start High Byte (ERXST<12:8>) */ +#define ENC_ERXNDL REGADDR(0x0a, 0, 0) /* RX End Low Byte (ERXND<7:0>) */ +#define ENC_ERXNDH REGADDR(0x0b, 0, 0) /* RX End High Byte (ERXND<12:8>) */ +#define ENC_ERXRDPTL REGADDR(0x0c, 0, 0) /* RX RD Pointer Low Byte (ERXRDPT<7:0>) */ +#define ENC_ERXRDPTH REGADDR(0x0d, 0, 0) /* RX RD Pointer High Byte (ERXRDPT<12:8>) */ +#define ENC_ERXWRPTL REGADDR(0x0e, 0, 0) /* RX WR Pointer Low Byte (ERXWRPT<7:0>) */ +#define ENC_ERXWRPTH REGADDR(0x0f, 0, 0) /* RX WR Pointer High Byte (ERXWRPT<12:8>) */ +#define ENC_EDMASTL REGADDR(0x10, 0, 0) /* DMA Start Low Byte (EDMAST<7:0>) */ +#define ENC_EDMASTH REGADDR(0x11, 0, 0) /* DMA Start High Byte (EDMAST<12:8>) */ +#define ENC_EDMANDL REGADDR(0x12, 0, 0) /* DMA End Low Byte (EDMAND<7:0>) */ +#define ENC_EDMANDH REGADDR(0x13, 0, 0) /* DMA End High Byte (EDMAND<12:8>) */ +#define ENC_EDMADSTL REGADDR(0x14, 0, 0) /* DMA Destination Low Byte (EDMADST<7:0>) */ +#define ENC_EDMADSTH REGADDR(0x15, 0, 0) /* DMA Destination High Byte (EDMADST<12:8>) */ +#define ENC_EDMACSL REGADDR(0x16, 0, 0) /* DMA Checksum Low Byte (EDMACS<7:0>) */ +#define ENC_EDMACSH REGADDR(0x17, 0, 0) /* DMA Checksum High Byte (EDMACS<15:8>) */ + /* 0x18-0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ +/* Bank 1 Control Register Addresses */ + +#define ENC_EHT0 REGADDR(0x00, 1, 0) /* Hash Table Byte 0 (EHT<7:0>) */ +#define ENC_EHT1 REGADDR(0x01, 1, 0) /* Hash Table Byte 1 (EHT<15:8>) */ +#define ENC_EHT2 REGADDR(0x02, 1, 0) /* Hash Table Byte 2 (EHT<23:16>) */ +#define ENC_EHT3 REGADDR(0x03, 1, 0) /* Hash Table Byte 3 (EHT<31:24>) */ +#define ENC_EHT4 REGADDR(0x04, 1, 0) /* Hash Table Byte 4 (EHT<39:32>) */ +#define ENC_EHT5 REGADDR(0x05, 1, 0) /* Hash Table Byte 5 (EHT<47:40>) */ +#define ENC_EHT6 REGADDR(0x06, 1, 0) /* Hash Table Byte 6 (EHT<55:48>) */ +#define ENC_EHT7 REGADDR(0x07, 1, 0) /* Hash Table Byte 7 (EHT<63:56>) */ +#define ENC_EPMM0 REGADDR(0x08, 1, 0) /* Pattern Match Mask Byte 0 (EPMM<7:0>) */ +#define ENC_EPMM1 REGADDR(0x09, 1, 0) /* Pattern Match Mask Byte 1 (EPMM<15:8>) */ +#define ENC_EPMM2 REGADDR(0x0a, 1, 0) /* Pattern Match Mask Byte 2 (EPMM<23:16>) */ +#define ENC_EPMM3 REGADDR(0x0b, 1, 0) /* Pattern Match Mask Byte 3 (EPMM<31:24>) */ +#define ENC_EPMM4 REGADDR(0x0c, 1, 0) /* Pattern Match Mask Byte 4 (EPMM<39:32>) */ +#define ENC_EPMM5 REGADDR(0x0d, 1, 0) /* Pattern Match Mask Byte 5 (EPMM<47:40>) */ +#define ENC_EPMM6 REGADDR(0x0e, 1, 0) /* Pattern Match Mask Byte 6 (EPMM<55:48>) */ +#define ENC_EPMM7 REGADDR(0x0f, 1, 0) /* Pattern Match Mask Byte 7 (EPMM<63:56>) */ +#define ENC_EPMCSL REGADDR(0x10, 1, 0) /* Pattern Match Checksum Low Byte (EPMCS<7:0>) */ +#define ENC_EPMCSH REGADDR(0x11, 1, 0) /* Pattern Match Checksum High Byte (EPMCS<15:0>) */ + /* 0x12-0x13: Reserved */ +#define ENC_EPMOL REGADDR(0x14, 1, 0) /* Pattern Match Offset Low Byte (EPMO<7:0>) */ +#define ENC_EPMOH REGADDR(0x15, 1, 0) /* Pattern Match Offset High Byte (EPMO<12:8>) */ + /* 0x16-0x17: Reserved */ +#define ENC_ERXFCON REGADDR(0x18, 1, 0) /* Receive Filter Configuration */ +#define ENC_EPKTCNT REGADDR(0x19, 1, 0) /* Ethernet Packet Count */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* Receive Filter Configuration Bit Definitions */ + +#define ERXFCON_BCEN (1 << 0) /* Bit 0: Broadcast Filter Enable */ +#define ERXFCON_MCEN (1 << 1) /* Bit 1: Multicast Filter Enable */ +#define ERXFCON_HTEN (1 << 2) /* Bit 2: Hash Table Filter Enable */ +#define ERXFCON_MPEN (1 << 3) /* Bit 3: Magic Packet Filter Enable */ +#define ERXFCON_PMEN (1 << 4) /* Bit 4: Pattern Match Filter Enable */ +#define ERXFCON_CRCEN (1 << 5) /* Bit 5: Post-Filter CRC Check Enable */ +#define ERXFCON_ANDOR (1 << 6) /* Bit 6: AND/OR Filter Select */ +#define ERXFCON_UCEN (1 << 7) /* Bit 7: Unicast Filter Enable */ + +/* Bank 2 Control Register Addresses */ + +#define ENC_MACON1 REGADDR(0x00, 2, 1) /* MAC Control 1 */ + /* 0x01: Reserved */ +#define ENC_MACON3 REGADDR(0x02, 2, 1) /* MAC Control 3 */ +#define ENC_MACON4 REGADDR(0x03, 2, 1) /* MAC Control 4 */ +#define ENC_MABBIPG REGADDR(0x04, 2, 1) /* Back-to-Back Inter-Packet Gap (BBIPG<6:0>) */ + /* 0x05: Reserved */ +#define ENC_MAIPGL REGADDR(0x06, 2, 1) /* Non-Back-to-Back Inter-Packet Gap Low Byte (MAIPGL<6:0>) */ +#define ENC_MAIPGH REGADDR(0x07, 2, 1) /* Non-Back-to-Back Inter-Packet Gap High Byte (MAIPGH<6:0>) */ +#define ENC_MACLCON1 REGADDR(0x08, 2, 1) /* MAC Collision Control 1 */ +#define ENC_MACLCON2 REGADDR(0x09, 2, 1) /* MAC Collision Control 2 */ +#define ENC_MAMXFLL REGADDR(0x0a, 2, 1) /* Maximum Frame Length Low Byte (MAMXFL<7:0>) */ +#define ENC_MAMXFLH REGADDR(0x0b, 2, 1) /* Maximum Frame Length High Byte (MAMXFL<15:8>) */ + /* 0x0c-0x11: Reserved */ +#define ENC_MICMD REGADDR(0x12, 2, 1) /* MII Command Register */ + /* 0x13: Reserved */ +#define ENC_MIREGADR REGADDR(0x14, 2, 1) /* MII Register Address */ + /* 0x15: Reserved */ +#define ENC_MIWRL REGADDR(0x16, 2, 1) /* MII Write Data Low Byte (MIWR<7:0>) */ +#define ENC_MIWRH REGADDR(0x17, 2, 1) /* MII Write Data High Byte (MIWR<15:8>) */ +#define ENC_MIRDL REGADDR(0x18, 2, 1) /* MII Read Data Low Byte (MIRD<7:0>) */ +#define ENC_MIRDH REGADDR(0x19, 2, 1) /* MII Read Data High Byte(MIRD<15:8>) */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON1_MARXEN (1 << 0) /* Bit 0: MAC Receive Enable */ +#define MACON1_PASSALL (1 << 1) /* Bit 1: Pass All Received Frames Enable */ +#define MACON1_RXPAUS (1 << 2) /* Bit 2: Pause Control Frame Reception Enable */ +#define MACON1_TXPAUS (1 << 3) /* Bit 3: Pause Control Frame Transmission Enable */ + /* Bits 4-7: Unimplemented or reserved */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON3_FULDPX (1 << 0) /* Bit 0: MAC Full-Duplex Enable */ +#define MACON3_FRMLNEN (1 << 1) /* Bit 1: Frame Length Checking Enable */ +#define MACON3_HFRMLEN (1 << 2) /* Bit 2: Huge Frame Enable */ +#define MACON3_PHDRLEN (1 << 3) /* Bit 3: Proprietary Header Enable */ +#define MACON3_TXCRCEN (1 << 4) /* Bit 4: Transmit CRC Enable */ +#define MACON3_PADCFG0 (1 << 5) /* Bit 5: Automatic Pad and CRC Configuration */ +#define MACON3_PADCFG1 (1 << 6) /* Bit 6: " " " " " " " " " " */ +#define MACON3_PADCFG2 (1 << 7) /* Bit 7: " " " " " " " " " " */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON4_NOBKOFF (1 << 4) /* Bit 4: No Backoff Enable */ +#define MACON4_BPEN (1 << 5) /* Bit 5: No Backoff During Backpressure Enable */ +#define MACON4_DEFER (1 << 6) /* Bit 6: Defer Transmission Enable bit */ + +/* MII Command Register Bit Definitions */ + +#define MICMD_MIIRD (1 << 0) /* Bit 0: MII Read Enable */ +#define MICMD_MIISCAN (1 << 1) /* Bit 1: MII Scan Enable */ + +/* Bank 3 Control Register Addresses */ + +#define ENC_MAADR5 REGADDR(0x00, 3, 1) /* MAC Address Byte 5 (MAADR<15:8>) */ +#define ENC_MAADR6 REGADDR(0x01, 3, 1) /* MAC Address Byte 6 (MAADR<7:0>) */ +#define ENC_MAADR3 REGADDR(0x02, 3, 1) /* MAC Address Byte 3 (MAADR<31:24>), OUI Byte 3 */ +#define ENC_MAADR4 REGADDR(0x03, 3, 1) /* MAC Address Byte 4 (MAADR<23:16>) */ +#define ENC_MAADR1 REGADDR(0x04, 3, 1) /* MAC Address Byte 1 (MAADR<47:40>), OUI Byte 1 */ +#define ENC_MAADR2 REGADDR(0x05, 3, 1) /* MAC Address Byte 2 (MAADR<39:32>), OUI Byte */ +#define ENC_EBSTSD REGADDR(0x06, 3, 0) /* Built-in Self-Test Fill Seed (EBSTSD<7:0>) */ +#define ENC_EBSTCON REGADDR(0x07, 3, 0) /* Built-in Self-Test Control */ +#define ENC_EBSTCSL REGADDR(0x08, 3, 0) /* Built-in Self-Test Checksum Low Byte (EBSTCS<7:0>) */ +#define ENC_EBSTCSH REGADDR(0x09, 3, 0) /* Built-in Self-Test Checksum High Byte (EBSTCS<15:8>) */ +#define ENC_MISTAT REGADDR(0x0a, 3, 1) /* MII Status Register */ + /* 0x0b-0x11: Reserved */ +#define ENC_EREVID REGADDR(0x12, 3, 0) /* Ethernet Revision ID */ + /* 0x13-0x14: Reserved */ +#define ENC_ECOCON REGADDR(0x15, 3, 0) /* Clock Output Control */ + /* 0x16: Reserved */ +#define ENC_EFLOCON REGADDR(0x17, 3, 0) /* Ethernet Flow Control */ +#define ENC_EPAUSL REGADDR(0x18, 3, 0) /* Pause Timer Value Low Byte (EPAUS<7:0>) */ +#define ENC_EPAUSH REGADDR(0x19, 3, 0) /* Pause Timer Value High Byte (EPAUS<15:8>) */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* Built-in Self-Test Control Register Bit Definitions */ + +#define EBSTCON_BISTST (1 << 0) /* Bit 0: Built-in Self-Test Start/Busy */ +#define EBSTCON_TME (1 << 1) /* Bit 1: Test Mode Enable */ +#define EBSTCON_TMSEL0 (1 << 2) /* Bit 2: Test Mode Select */ +#define EBSTCON_TMSEL1 (1 << 3) /* Bit 3: " " " " " " */ +#define EBSTCON_PSEL (1 << 4) /* Bit 4: Port Select */ +#define EBSTCON_PSV0 (1 << 5) /* Bit 5: Pattern Shift Value */ +#define EBSTCON_PSV1 (1 << 6) /* Bit 6: " " " " " */ +#define EBSTCON_PSV2 (1 << 7) /* Bit 7: " " " " " */ + +/* MII Status Register Register Bit Definitions */ + +#define MISTAT_BUSY (1 << 0) /* Bit 0: MII Management Busy */ +#define MISTAT_SCAN (1 << 1) /* Bit 1: MII Management Scan Operation */ +#define MISTAT_NVALID (1 << 2) /* Bit 2: MII Management Read Data Not Valid */ + /* Bits 3-7: Reserved or unimplemented */ + +/* Ethernet Flow Control Register Bit Definitions */ + +#define EFLOCON_FCEN0 (1 << 0) /* Bit 0: Flow Control Enable */ +#define EFLOCON_FCEN1 (1 << 1) /* Bit 1: " " " " " " */ +#define EFLOCON_FULDPXS (1 << 2) /* Bit 2: Read-Only MAC Full-Duplex Shadow */ + /* Bits 3-7: Reserved or unimplemented */ + +/* PHY Registers ************************************************************/ + +#define ENC_PHCON1 (0x00) /* PHY Control Register 1 */ +#define ENC_PHSTAT1 (0x01) /* PHY Status 1 */ +#define ENC_PHID1 (0x02) /* PHY ID Register 1 */ +#define ENC_PHID2 (0x03) /* PHY ID Register 2 */ +#define ENC_PHCON2 (0x10) /* PHY Control Register 2 */ +#define ENC_PHSTAT2 (0x11) /* PHY Status 2 */ +#define ENC_PHIE (0x12) /* PHY Interrupt Enable Register */ +#define ENC_PHIR (0x13) /* PHY Interrupt Request Register */ +#define ENC_PHLCON (0x14) + +/* PHY Control Register 1 Register Bit Definitions */ + +#define PHCON1_PDPXMD (1 << 8) /* Bit 8: PHY Power-Down */ +#define PHCON1_PPWRSV (1 << 11) /* Bit 11: PHY Power-Down */ +#define PHCON1_PLOOPBK (1 << 14) /* Bit 14: PHY Loopback */ +#define PHCON1_PRST (1 << 15) /* Bit 15: PHY Software Reset */ + +/* HY Status 1 Register Bit Definitions */ + +#define PHSTAT1_JBSTAT (1 << 1) /* Bit 1: PHY Latching Jabber Status */ +#define PHSTAT1_LLSTAT (1 << 2) /* Bit 2: PHY Latching Link Status */ +#define PHSTAT1_PHDPX (1 << 11) /* Bit 11: PHY Half-Duplex Capable */ +#define PHSTAT1_PFDPX (1 << 12) /* Bit 12: PHY Full-Duplex Capable */ + +/* PHY Control Register 2 Register Bit Definitions */ + +#define PHCON2_HDLDIS (1 << 8) /* Bit 8: PHY Half-Duplex Loopback Disable */ +#define PHCON2_JABBER (1 << 10) /* Bit 10: Jabber Correction Disable */ +#define PHCON2_TXDIS (1 << 13) /* Bit 13: Twisted-Pair Transmitter Disable */ +#define PHCON2_FRCLINK (1 << 14) /* Bit 14: PHY Force Linkup */ + +/* PHY Status 2 Register Bit Definitions */ + +#define PHSTAT2_PLRITY (1 << 5) /* Bit 5: Polarity Status */ +#define PHSTAT2_DPXSTAT (1 << 9) /* Bit 9: PHY Duplex Status */ +#define PHSTAT2_LSTAT (1 << 10) /* Bit 10: PHY Link Status */ +#define PHSTAT2_COLSTAT (1 << 11) /* Bit 11: PHY Collision Status */ +#define PHSTAT2_RXSTAT (1 << 12) /* Bit 12: PHY Receive Status */ +#define PHSTAT2_TXSTAT (1 << 13) /* Bit 13: PHY Transmit Status */ + +/* PHY Interrupt Enable Register Bit Definitions */ + +#define PHIE_PGEIE (1 << 1) /* Bit 1: PHY Global Interrupt Enable */ +#define PHIE_PLNKIE (1 << 4) /* Bit 4: PHY Link Change Interrupt Enable */ + +/* PHIR Regiser Bit Definitions */ + +#define PHIR_PGIF (1 << 2) /* Bit 2: PHY Global Interrupt */ +#define PHIR_PLNKIF (1 << 4) /* Bit 4: PHY Link Change Interrupt */ + +/* PHLCON Regiser Bit Definitions */ + + /* Bit 0: Reserved */ +#define PHLCON_STRCH (1 << 1) /* Bit 1: LED Pulse Stretching Enable */ +#define PHLCON_LFRQ0 (1 << 2) /* Bit 2: LED Pulse Stretch Time Configuration */ +#define PHLCON_LFRQ1 (1 << 3) /* Bit 3: " " " " " " " " " */ +#define PHLCON_LBCFG0 (1 << 4) /* Bit 4: LEDB Configuration */ +#define PHLCON_LBCFG1 (1 << 5) /* Bit 5: " " " " */ +#define PHLCON_LBCFG2 (1 << 6) /* Bit 6: " " " " */ +#define PHLCON_LBCFG3 (1 << 7) /* Bit 7: " " " " */ +#define PHLCON_LACFG0 (1 << 8) /* Bit 8: LEDA Configuration */ +#define PHLCON_LACFG1 (1 << 9) /* Bit 9: " " " " */ +#define PHLCON_LACFG2 (1 << 10) /* Bit 10: " " " " */ +#define PHLCON_LACFG3 (1 << 11) /* Bit 11: " " " " */ + +/* Packet Memory ************************************************************/ + +/* 8-Kbyte Transmit/Receive Packet Dual Port SRAM */ + +#define PKTMEM_START 0x0000 +#define PKTMEM_END 0x1fff + +/* Ethernet frames are between 64 and 1518 bytes long */ + +#define MIN_FRAMELEN 64 +#define MAX_FRAMELEN 1518 + +/* Packet Control Bits Definitions ******************************************/ + +#define PKTCTRL_POVERRIDE (1 << 0) /* Bit 0: Per Packet Override */ +#define PKTCTRL_PCRCEN (1 << 1) /* Bit 1: Per Packet CRC Enable */ +#define PKTCTRL_PPADEN (1 << 2) /* Bit 2: Per Packet Padding Enable */ +#define PKTCTRL_PHUGEEN (1 << 3) /* Bit 3: Per Packet Huge Frame Enable */ + +/* RX Status Bit Definitions ************************************************/ + +#define RXSTAT_LDEVENT (1 << 0) /* Bit 0: Long event or pack dropped */ + /* Bit 1: Reserved */ +#define RXSTAT_CEPS (1 << 2) /* Bit 2: Carrier event previously seen */ + /* Bit 3: Reserved */ +#define RXSTAT_CRCERROR (1 << 4) /* Bit 4: Frame CRC field bad */ +#define RXSTAT_LENERROR (1 << 5) /* Bit 5: Packet length != data length */ +#define RXSTAT_LENRANGE (1 << 6) /* Bit 6: Type/length field > 1500 bytes */ +#define RXSTAT_OK (1 << 7) /* Bit 7: Packet with valid CRC and no symbol errors */ +#define RXSTAT_MCAST (1 << 8) /* Bit 8: Packet with multicast address */ +#define RXSTAT_BCAST (1 << 9) /* Bit 9: Packet with broadcast address */ +#define RXSTAT_DRIBBLE (1 << 10) /* Bit 10: Additional bits received after packet */ +#define RXSTAT_CTRLFRAME (1 << 11) /* Bit 11: Control frame with valid type/length */ +#define RXSTAT_PAUSE (1 << 12) /* Bit 12: Control frame with pause frame opcde */ +#define RXSTAT_UNKOPCODE (1 << 13) /* Bit 13: Control frame with unknown opcode */ +#define RXSTAT_VLANTYPE (1 << 14) /* Bit 14: Current frame is a VLAN tagged frame */ + /* Bit 15: Zero */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __DRIVERS_NET_ENC28J60_H */ diff --git a/nuttx/drivers/net/skeleton.c b/nuttx/drivers/net/skeleton.c new file mode 100644 index 000000000..118f0acd1 --- /dev/null +++ b/nuttx/drivers/net/skeleton.c @@ -0,0 +1,692 @@ +/**************************************************************************** + * drivers/net/skeleton.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#if defined(CONFIG_NET) && defined(CONFIG_NET_skeleton) + +#include <stdint.h> +#include <stdbool.h> +#include <time.h> +#include <string.h> +#include <debug.h> +#include <wdog.h> +#include <errno.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> + +#include <net/uip/uip.h> +#include <net/uip/uip-arp.h> +#include <net/uip/uip-arch.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/* CONFIG_skeleton_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_skeleton_NINTERFACES +# define CONFIG_skeleton_NINTERFACES 1 +#endif + +/* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per second */ + +#define skeleton_WDDELAY (1*CLK_TCK) +#define skeleton_POLLHSEC (1*2) + +/* TX timeout = 1 minute */ + +#define skeleton_TXTIMEOUT (60*CLK_TCK) + +/* This is a helper pointer for accessing the contents of the Ethernet header */ + +#define BUF ((struct uip_eth_hdr *)skel->sk_dev.d_buf) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* The skel_driver_s encapsulates all state information for a single hardware + * interface + */ + +struct skel_driver_s +{ + bool sk_bifup; /* true:ifup false:ifdown */ + WDOG_ID sk_txpoll; /* TX poll timer */ + WDOG_ID sk_txtimeout; /* TX timeout timer */ + + /* This holds the information visible to uIP/NuttX */ + + struct uip_driver_s sk_dev; /* Interface understood by uIP */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct skel_driver_s g_skel[CONFIG_skeleton_NINTERFACES]; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Common TX logic */ + +static int skel_transmit(FAR struct skel_driver_s *skel); +static int skel_uiptxpoll(struct uip_driver_s *dev); + +/* Interrupt handling */ + +static void skel_receive(FAR struct skel_driver_s *skel); +static void skel_txdone(FAR struct skel_driver_s *skel); +static int skel_interrupt(int irq, FAR void *context); + +/* Watchdog timer expirations */ + +static void skel_polltimer(int argc, uint32_t arg, ...); +static void skel_txtimeout(int argc, uint32_t arg, ...); + +/* NuttX callback functions */ + +static int skel_ifup(struct uip_driver_s *dev); +static int skel_ifdown(struct uip_driver_s *dev); +static int skel_txavail(struct uip_driver_s *dev); +#ifdef CONFIG_NET_IGMP +static int skel_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +static int skel_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: skel_transmit + * + * Description: + * Start hardware transmission. Called either from the txdone interrupt + * handling or from watchdog based polling. + * + * Parameters: + * skel - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * May or may not be called from an interrupt handler. In either case, + * global interrupts are disabled, either explicitly or indirectly through + * interrupt handling logic. + * + ****************************************************************************/ + +static int skel_transmit(FAR struct skel_driver_s *skel) +{ + /* Verify that the hardware is ready to send another packet. If we get + * here, then we are committed to sending a packet; Higher level logic + * must have assured that there is no transmission in progress. + */ + + /* Increment statistics */ + + /* Send the packet: address=skel->sk_dev.d_buf, length=skel->sk_dev.d_len */ + + /* Enable Tx interrupts */ + + /* Setup the TX timeout watchdog (perhaps restarting the timer) */ + + (void)wd_start(skel->sk_txtimeout, skeleton_TXTIMEOUT, skel_txtimeout, 1, (uint32_t)skel); + return OK; +} + +/**************************************************************************** + * Function: skel_uiptxpoll + * + * Description: + * The transmitter is available, check if uIP has any outgoing packets ready + * to send. This is a callback from uip_poll(). uip_poll() may be called: + * + * 1. When the preceding TX packet send is complete, + * 2. When the preceding TX packet send timesout and the interface is reset + * 3. During normal TX polling + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * May or may not be called from an interrupt handler. In either case, + * global interrupts are disabled, either explicitly or indirectly through + * interrupt handling logic. + * + ****************************************************************************/ + +static int skel_uiptxpoll(struct uip_driver_s *dev) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)dev->d_private; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + if (skel->sk_dev.d_len > 0) + { + uip_arp_out(&skel->sk_dev); + skel_transmit(skel); + + /* Check if there is room in the device to hold another packet. If not, + * return a non-zero value to terminate the poll. + */ + } + + /* If zero is returned, the polling will continue until all connections have + * been examined. + */ + + return 0; +} + +/**************************************************************************** + * Function: skel_receive + * + * Description: + * An interrupt was received indicating the availability of a new RX packet + * + * Parameters: + * skel - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by interrupt handling logic. + * + ****************************************************************************/ + +static void skel_receive(FAR struct skel_driver_s *skel) +{ + do + { + /* Check for errors and update statistics */ + + /* Check if the packet is a valid size for the uIP buffer configuration */ + + /* Copy the data data from the hardware to skel->sk_dev.d_buf. Set + * amount of data in skel->sk_dev.d_len + */ + + /* We only accept IP packets of the configured type and ARP packets */ + +#ifdef CONFIG_NET_IPv6 + if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) +#else + if (BUF->type == HTONS(UIP_ETHTYPE_IP)) +#endif + { + uip_arp_ipin(&skel->sk_dev); + uip_input(&skel->sk_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (skel->sk_dev.d_len > 0) + { + uip_arp_out(&skel->sk_dev); + skel_transmit(skel); + } + } + else if (BUF->type == htons(UIP_ETHTYPE_ARP)) + { + uip_arp_arpin(&skel->sk_dev); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (skel->sk_dev.d_len > 0) + { + skel_transmit(skel); + } + } + } + while (); /* While there are more packets to be processed */ +} + +/**************************************************************************** + * Function: skel_txdone + * + * Description: + * An interrupt was received indicating that the last TX packet(s) is done + * + * Parameters: + * skel - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void skel_txdone(FAR struct skel_driver_s *skel) +{ + /* Check for errors and update statistics */ + + /* If no further xmits are pending, then cancel the TX timeout and + * disable further Tx interrupts. + */ + + wd_cancel(skel->sk_txtimeout); + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&skel->sk_dev, skel_uiptxpoll); +} + +/**************************************************************************** + * Function: skel_interrupt + * + * Description: + * Hardware interrupt handler + * + * Parameters: + * irq - Number of the IRQ that generated the interrupt + * context - Interrupt register state save info (architecture-specific) + * + * Returned Value: + * OK on success + * + * Assumptions: + * + ****************************************************************************/ + +static int skel_interrupt(int irq, FAR void *context) +{ + register FAR struct skel_driver_s *skel = &g_skel[0]; + + /* Get and clear interrupt status bits */ + + /* Handle interrupts according to status bit settings */ + + /* Check if we received an incoming packet, if so, call skel_receive() */ + + skel_receive(skel); + + /* Check if a packet transmission just completed. If so, call skel_txdone. + * This may disable further Tx interrupts if there are no pending + * tansmissions. + */ + + skel_txdone(skel); + + return OK; +} + +/**************************************************************************** + * Function: skel_txtimeout + * + * Description: + * Our TX watchdog timed out. Called from the timer interrupt handler. + * The last TX never completed. Reset the hardware and start again. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void skel_txtimeout(int argc, uint32_t arg, ...) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)arg; + + /* Increment statistics and dump debug info */ + + /* Then reset the hardware */ + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&skel->sk_dev, skel_uiptxpoll); +} + +/**************************************************************************** + * Function: skel_polltimer + * + * Description: + * Periodic timer handler. Called from the timer interrupt handler. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void skel_polltimer(int argc, uint32_t arg, ...) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)arg; + + /* Check if there is room in the send another TX packet. We cannot perform + * the TX poll if he are unable to accept another packet for transmission. + */ + + /* If so, update TCP timing states and poll uIP for new XMIT data. Hmmm.. + * might be bug here. Does this mean if there is a transmit in progress, + * we will missing TCP time state updates? + */ + + (void)uip_timer(&skel->sk_dev, skel_uiptxpoll, skeleton_POLLHSEC); + + /* Setup the watchdog poll timer again */ + + (void)wd_start(skel->sk_txpoll, skeleton_WDDELAY, skel_polltimer, 1, arg); +} + +/**************************************************************************** + * Function: skel_ifup + * + * Description: + * NuttX Callback: Bring up the Ethernet interface when an IP address is + * provided + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int skel_ifup(struct uip_driver_s *dev) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)dev->d_private; + + ndbg("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Initialize PHYs, the Ethernet interface, and setup up Ethernet interrupts */ + + /* Set and activate a timer process */ + + (void)wd_start(skel->sk_txpoll, skeleton_WDDELAY, skel_polltimer, 1, (uint32_t)skel); + + /* Enable the Ethernet interrupt */ + + skel->sk_bifup = true; + up_enable_irq(CONFIG_skeleton_IRQ); + return OK; +} + +/**************************************************************************** + * Function: skel_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int skel_ifdown(struct uip_driver_s *dev) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)dev->d_private; + irqstate_t flags; + + /* Disable the Ethernet interrupt */ + + flags = irqsave(); + up_disable_irq(CONFIG_skeleton_IRQ); + + /* Cancel the TX poll timer and TX timeout timers */ + + wd_cancel(skel->sk_txpoll); + wd_cancel(skel->sk_txtimeout); + + /* Put the EMAC in its reset, non-operational state. This should be + * a known configuration that will guarantee the skel_ifup() always + * successfully brings the interface back up. + */ + + /* Mark the device "down" */ + + skel->sk_bifup = false; + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: skel_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static int skel_txavail(struct uip_driver_s *dev) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)dev->d_private; + irqstate_t flags; + + /* Disable interrupts because this function may be called from interrupt + * level processing. + */ + + flags = irqsave(); + + /* Ignore the notification if the interface is not yet up */ + + if (skel->sk_bifup) + { + /* Check if there is room in the hardware to hold another outgoing packet. */ + + /* If so, then poll uIP for new XMIT data */ + + (void)uip_poll(&skel->sk_dev, skel_uiptxpoll); + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: skel_addmac + * + * Description: + * NuttX Callback: Add the specified MAC address to the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be added + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int skel_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +/**************************************************************************** + * Function: skel_rmmac + * + * Description: + * NuttX Callback: Remove the specified MAC address from the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be removed + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int skel_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct skel_driver_s *skel = (FAR struct skel_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: skel_initialize + * + * Description: + * Initialize the Ethernet controller and driver + * + * Parameters: + * intf - In the case where there are multiple EMACs, this value + * identifies which EMAC is to be initialized. + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * + ****************************************************************************/ + +int skel_initialize(int intf) +{ + struct skel_driver_s *priv; + + /* Get the interface structure associated with this interface number. */ + + DEBUGASSERT(inf < CONFIG_skeleton_NINTERFACES); + priv = &g_skel[intf]; + + /* Check if a Ethernet chip is recognized at its I/O base */ + + /* Attach the IRQ to the driver */ + + if (irq_attach(CONFIG_skeleton_IRQ, skel_interrupt)) + { + /* We could not attach the ISR to the interrupt */ + + return -EAGAIN; + } + + /* Initialize the driver structure */ + + memset(priv, 0, sizeof(struct skel_driver_s)); + priv->sk_dev.d_ifup = skel_ifup; /* I/F up (new IP address) callback */ + priv->sk_dev.d_ifdown = skel_ifdown; /* I/F down callback */ + priv->sk_dev.d_txavail = skel_txavail; /* New TX data callback */ +#ifdef CONFIG_NET_IGMP + priv->sk_dev.d_addmac = skel_addmac; /* Add multicast MAC address */ + priv->sk_dev.d_rmmac = skel_rmmac; /* Remove multicast MAC address */ +#endif + priv->sk_dev.d_private = (void*)g_skel; /* Used to recover private state from dev */ + + /* Create a watchdog for timing polling for and timing of transmisstions */ + + priv->sk_txpoll = wd_create(); /* Create periodic poll timer */ + priv->sk_txtimeout = wd_create(); /* Create TX timeout timer */ + + /* Put the interface in the down state. This usually amounts to resetting + * the device and/or calling skel_ifdown(). + */ + + /* Read the MAC address from the hardware into priv->sk_dev.d_mac.ether_addr_octet */ + + /* Register the device with the OS so that socket IOCTLs can be performed */ + + (void)netdev_register(&priv->sk_dev); + return OK; +} + +#endif /* CONFIG_NET && CONFIG_NET_skeleton */ diff --git a/nuttx/drivers/net/slip.c b/nuttx/drivers/net/slip.c new file mode 100644 index 000000000..a1880db8e --- /dev/null +++ b/nuttx/drivers/net/slip.c @@ -0,0 +1,1017 @@ +/**************************************************************************** + * drivers/net/slip.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Reference: RFC 1055 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <time.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/irq.h> +#include <nuttx/net.h> + +#include <net/uip/uip.h> +#include <net/uip/uip-arch.h> + +#if defined(CONFIG_NET) && defined(CONFIG_NET_SLIP) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* NOTE: Slip requires UART hardware handshake. If hardware handshake is + * not available with your UART, then you might try the 'slattach' option + * -L which enable "3-wire operation." That allows operation without the + * hardware handshake (but with the possibility of data overrun). + */ + +/* Configuration ************************************************************/ + +#if UIP_LLH_LEN > 0 +# error "UIP_LLH_LEN must be set to zero" +#endif + +#ifndef CONFIG_NET_NOINTS +# warning "CONFIG_NET_NOINTS must be set" +#endif + +#ifndef CONFIG_NET_MULTIBUFFER +# warning "CONFIG_NET_MULTIBUFFER must be set" +#endif + +#ifndef CONFIG_SLIP_STACKSIZE +# define CONFIG_SLIP_STACKSIZE 2048 +#endif + +#ifndef CONFIG_SLIP_DEFPRIO +# define CONFIG_SLIP_DEFPRIO 128 +#endif + +/* The Linux slip module hard-codes its MTU size to 296 (40 bytes for the + * IP+TPC headers plus 256 bytes of data). So you might as well set + * CONFIG_NET_BUFSIZE to 296 as well. + * + * There may be an issue with this setting, however. I see that Linux uses + * a MTU of 296 and window of 256, but actually only sends 168 bytes of data: + * 40 + 128. I believe that is to allow for the 2x worst cast packet + * expansion. Ideally we would like to advertise the 256 MSS, but restrict + * uIP to 128 bytes (possibly by modifying the uip_mss() macro). + */ + +#if CONFIG_NET_BUFSIZE < 296 +# error "CONFIG_NET_BUFSIZE >= 296 is required" +#elif CONFIG_NET_BUFSIZE > 296 +# warning "CONFIG_NET_BUFSIZE == 296 is optimal" +#endif + +/* CONFIG_SLIP_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_SLIP_NINTERFACES +# define CONFIG_SLIP_NINTERFACES 1 +#endif + +/* SLIP special character codes *******************************************/ + +#define SLIP_END 0300 /* Indicates end of packet */ +#define SLIP_ESC 0333 /* Indicates byte stuffing */ +#define SLIP_ESC_END 0334 /* ESC ESC_END means SLIP_END data byte */ +#define SLIP_ESC_ESC 0335 /* ESC ESC_ESC means ESC data byte */ + +/* General driver definitions **********************************************/ + +/* TX poll delay = 1 second = 1000000 microseconds. */ + +#define SLIP_WDDELAY (1*1000000) +#define SLIP_POLLHSEC (1*2) + +/* Statistics helper */ + +#ifdef CONFIG_NET_STATISTICS +# define SLIP_STAT(p,f) (p->stats.f)++ +#else +# define SLIP_STAT(p,f) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Driver statistics */ + +#ifdef CONFIG_NET_STATISTICS +struct slip_statistics_s +{ + uint32_t transmitted; /* Number of packets transmitted */ + uint32_t received /* Number of packets received */ +}; +#endif + +/* The slip_driver_s encapsulates all state information for a single hardware + * interface + */ + +struct slip_driver_s +{ + volatile bool bifup; /* true:ifup false:ifdown */ + int fd; /* TTY file descriptor */ + pid_t rxpid; /* Receiver thread ID */ + pid_t txpid; /* Transmitter thread ID */ + sem_t waitsem; /* Mutually exclusive access to uIP */ + uint16_t rxlen; /* The number of bytes in rxbuf */ + + /* Driver statistics */ + +#ifdef CONFIG_NET_STATISTICS + struct slip_statistics_s stats; +#endif + + /* This holds the information visible to uIP/NuttX */ + + struct uip_driver_s dev; /* Interface understood by uIP */ + uint8_t rxbuf[CONFIG_NET_BUFSIZE + 2]; + uint8_t txbuf[CONFIG_NET_BUFSIZE + 2]; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + + /* We really should get rid of CONFIG_SLIP_NINTERFACES and, instead, + * kmalloc() new interface instances as needed. + */ + +static struct slip_driver_s g_slip[CONFIG_SLIP_NINTERFACES]; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void slip_semtake(FAR struct slip_driver_s *priv); + +/* Common TX logic */ + +static void slip_write(FAR struct slip_driver_s *priv, const uint8_t *buffer, int len); +static void slip_putc(FAR struct slip_driver_s *priv, int ch); +static int slip_transmit(FAR struct slip_driver_s *priv); +static int slip_uiptxpoll(struct uip_driver_s *dev); +static void slip_txtask(int argc, char *argv[]); + +/* Packet receiver task */ + +static int slip_getc(FAR struct slip_driver_s *priv); +static inline void slip_receive(FAR struct slip_driver_s *priv); +static int slip_rxtask(int argc, char *argv[]); + +/* NuttX callback functions */ + +static int slip_ifup(struct uip_driver_s *dev); +static int slip_ifdown(struct uip_driver_s *dev); +static int slip_txavail(struct uip_driver_s *dev); +#ifdef CONFIG_NET_IGMP +static int slip_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +static int slip_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: slip_semtake + ****************************************************************************/ + +static void slip_semtake(FAR struct slip_driver_s *priv) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(&priv->waitsem) != 0) + { + /* The only case that an error should occur here is if + * the wait was awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +#define slip_semgive(p) sem_post(&(p)->waitsem); + +/**************************************************************************** + * Function: slip_write + * + * Description: + * Just an inline wrapper around fwrite with error checking. + * + * Parameters: + * priv - Reference to the driver state structure + * buffer - Buffer data to send + * len - Buffer length in bytes + * + ****************************************************************************/ + +static inline void slip_write(FAR struct slip_driver_s *priv, + const uint8_t *buffer, int len) +{ + /* Handle the case where the write is awakened by a signal */ + + while (write(priv->fd, buffer, len) < 0) + { + DEBUGASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Function: slip_putc + * + * Description: + * Just an inline wrapper around putc with error checking. + * + * Parameters: + * priv - Reference to the driver state structure + * ch - The character to send + * + ****************************************************************************/ + +static inline void slip_putc(FAR struct slip_driver_s *priv, int ch) +{ + uint8_t buffer = (uint8_t)ch; + slip_write(priv, &buffer, 1); +} + +/**************************************************************************** + * Function: slip_transmit + * + * Description: + * Start hardware transmission. Called either from the txdone interrupt + * handling or from watchdog based polling. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + ****************************************************************************/ + +static int slip_transmit(FAR struct slip_driver_s *priv) +{ + uint8_t *src; + uint8_t *start; + uint8_t esc; + int remaining; + int len; + + /* Increment statistics */ + + nvdbg("Sending packet size %d\n", priv->dev.d_len); + SLIP_STAT(priv, transmitted); + + /* Send an initial END character to flush out any data that may have + * accumulated in the receiver due to line noise + */ + + slip_putc(priv, SLIP_END); + + /* For each byte in the packet, send the appropriate character sequence */ + + src = priv->dev.d_buf; + remaining = priv->dev.d_len; + start = src; + len = 0; + + while (remaining-- > 0) + { + switch (*src) + { + /* If it's the same code as an END character, we send a special two + * character code so as not to make the receiver think we sent an + * END + */ + + case SLIP_END: + esc = SLIP_ESC_END; + goto escape; + + /* If it's the same code as an ESC character, we send a special two + * character code so as not to make the receiver think we sent an + * ESC + */ + + case SLIP_ESC: + esc = SLIP_ESC_ESC; + + escape: + { + /* Flush any unsent data */ + + if (len > 0) + { + slip_write(priv, start, len); + + /* Reset */ + + start = src + 1; + len = 0; + } + + /* Then send the escape sequence */ + + slip_putc(priv, SLIP_ESC); + slip_putc(priv, esc); + } + break; + + /* otherwise, just bump up the count */ + + default: + len++; + break; + } + + /* Point to the next character in the packet */ + + src++; + } + + /* We have looked at every character in the packet. Now flush any unsent + * data + */ + + if (len > 0) + { + slip_write(priv, start, len); + } + + /* And send the END token */ + + slip_putc(priv, SLIP_END); + return OK; +} + +/**************************************************************************** + * Function: slip_uiptxpoll + * + * Description: + * Check if uIP has any outgoing packets ready to send. This is a + * callback from uip_poll(). uip_poll() may be called: + * + * 1. When the preceding TX packet send is complete, or + * 2. When the preceding TX packet send times o ]ut and the interface is reset + * 3. During normal TX polling + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * The initiator of the poll holds the priv->waitsem; + * + ****************************************************************************/ + +static int slip_uiptxpoll(struct uip_driver_s *dev) +{ + FAR struct slip_driver_s *priv = (FAR struct slip_driver_s *)dev->d_private; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + if (priv->dev.d_len > 0) + { + slip_transmit(priv); + } + + /* If zero is returned, the polling will continue until all connections have + * been examined. + */ + + return 0; +} + +/**************************************************************************** + * Function: slip_txtask + * + * Description: + * Polling and transmission is performed on tx thread. + * + * Parameters: + * arg - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void slip_txtask(int argc, char *argv[]) +{ + FAR struct slip_driver_s *priv; + unsigned int index = *(argv[1]) - '0'; + uip_lock_t flags; + + ndbg("index: %d\n", index); + DEBUGASSERT(index < CONFIG_SLIP_NINTERFACES); + + /* Get our private data structure instance and wake up the waiting + * initialization logic. + */ + + priv = &g_slip[index]; + slip_semgive(priv); + + /* Loop forever */ + + for (;;) + { + /* Wait for the timeout to expire (or until we are signaled by by */ + + usleep(SLIP_WDDELAY); + + /* Is the interface up? */ + + if (priv->bifup) + { + /* Get exclusive access to uIP (if it it is already being used + * slip_rxtask, then we have to wait). + */ + + slip_semtake(priv); + + /* Poll uIP for new XMIT data. BUG: We really need to calculate + * the number of hsecs! When we are awakened by slip_txavail, the + * number will be smaller; when we have to wait for the semaphore + * (above), it may be larger. + */ + + flags = uip_lock(); + priv->dev.d_buf = priv->txbuf; + (void)uip_timer(&priv->dev, slip_uiptxpoll, SLIP_POLLHSEC); + uip_unlock(flags); + slip_semgive(priv); + } + } +} + +/**************************************************************************** + * Function: slip_getc + * + * Description: + * Get one byte from the serial input. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * The returned byte + * + ****************************************************************************/ + +static inline int slip_getc(FAR struct slip_driver_s *priv) +{ + uint8_t ch; + + while (read(priv->fd, &ch, 1) < 0) + { + DEBUGASSERT(errno == EINTR); + } + + return (int)ch; +} + +/**************************************************************************** + * Function: slip_receive + * + * Description: + * Read a packet from the serial input + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline void slip_receive(FAR struct slip_driver_s *priv) +{ + uint8_t ch; + + /* Copy the data data from the hardware to to the RX buffer until we + * put together a whole packet. Make sure not to copy them into the + * packet if we run out of room. + */ + + nvdbg("Receiving packet\n"); + for (;;) + { + /* Get the next character in the stream. */ + + ch = slip_getc(priv); + + /* Handle bytestuffing if necessary */ + + switch (ch) + { + /* If it's an END character then we're done with the packet. + * (OR we are just starting a packet) + */ + + case SLIP_END: + nvdbg("END\n"); + + /* A minor optimization: if there is no data in the packet, ignore + * it. This is meant to avoid bothering IP with all the empty + * packets generated by the duplicate END characters which are in + * turn sent to try to detect line noise. + */ + + if (priv->rxlen > 0) + { + nvdbg("Received packet size %d\n", priv->rxlen); + return; + } + break; + + /* if it's the same code as an ESC character, wait and get another + * character and then figure out what to store in the packet based + * on that. + */ + + case SLIP_ESC: + nvdbg("ESC\n"); + ch = slip_getc(priv); + + /* if "ch" is not one of these two, then we have a protocol + * violation. The best bet seems to be to leave the byte alone + * and just stuff it into the packet + */ + + switch (ch) + { + case SLIP_ESC_END: + nvdbg("ESC-END\n"); + ch = SLIP_END; + break; + case SLIP_ESC_ESC: + nvdbg("ESC-ESC\n"); + ch = SLIP_ESC; + break; + default: + ndbg("ERROR: Protocol violation: %02x\n", ch); + break; + } + + /* Here we fall into the default handler and let it store the + * character for us + */ + + default: + if (priv->rxlen < CONFIG_NET_BUFSIZE+2) + { + priv->rxbuf[priv->rxlen++] = ch; + } + break; + } + } +} + +/**************************************************************************** + * Function: slip_rxtask + * + * Description: + * Wait for incoming data. + * + * Parameters: + * argc + * argv + * + * Returned Value: + * (Does not return) + * + * Assumptions: + * + ****************************************************************************/ + +static int slip_rxtask(int argc, char *argv[]) +{ + FAR struct slip_driver_s *priv; + unsigned int index = *(argv[1]) - '0'; + uip_lock_t flags; + int ch; + + ndbg("index: %d\n", index); + DEBUGASSERT(index < CONFIG_SLIP_NINTERFACES); + + /* Get our private data structure instance and wake up the waiting + * initialization logic. + */ + + priv = &g_slip[index]; + slip_semgive(priv); + + /* Loop forever */ + + for (;;) + { + /* Wait for the next character to be available on the input stream. */ + + nvdbg("Waiting...\n"); + ch = slip_getc(priv); + + /* Ignore any input that we receive before the interface is up. */ + + if (!priv->bifup) + { + continue; + } + + /* We have something... + * + * END characters may appear at packet boundaries BEFORE as well as + * after the beginning of the packet. This is normal and expected. + */ + + if (ch == SLIP_END) + { + priv->rxlen = 0; + } + + /* Otherwise, we are in danger of being out-of-sync. Apparently the + * leading END character is optional. Let's try to continue. + */ + + else + { + priv->rxbuf[0] = (uint8_t)ch; + priv->rxlen = 1; + } + + /* Copy the data data from the hardware to priv->rxbuf until we put + * together a whole packet. + */ + + slip_receive(priv); + SLIP_STAT(priv, received); + + /* All packets are assumed to be IP packets (we don't have a choice.. + * there is no Ethernet header containing the EtherType). So pass the + * received packet on for IP processing -- but only if it is big + * enough to hold an IP header. + */ + + if (priv->rxlen >= UIP_IPH_LEN) + { + /* Handle the IP input. Get exclusive access to uIP. */ + + slip_semtake(priv); + priv->dev.d_buf = priv->rxbuf; + priv->dev.d_len = priv->rxlen; + + flags = uip_lock(); + uip_input(&priv->dev); + + /* If the above function invocation resulted in data that should + * be sent out on the network, the field d_len will set to a + * value > 0. NOTE that we are transmitting using the RX buffer! + */ + + if (priv->dev.d_len > 0) + { + slip_transmit(priv); + } + uip_unlock(flags); + slip_semgive(priv); + } + else + { + SLIP_STAT(priv, rxsmallpacket); + } + } + + /* We won't get here */ + + return OK; +} + +/**************************************************************************** + * Function: slip_ifup + * + * Description: + * NuttX Callback: Bring up the Ethernet interface when an IP address is + * provided + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int slip_ifup(struct uip_driver_s *dev) +{ + FAR struct slip_driver_s *priv = (FAR struct slip_driver_s *)dev->d_private; + + ndbg("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Mark the interface up */ + + priv->bifup = true; + return OK; +} + +/**************************************************************************** + * Function: slip_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int slip_ifdown(struct uip_driver_s *dev) +{ + FAR struct slip_driver_s *priv = (FAR struct slip_driver_s *)dev->d_private; + + /* Mark the device "down" */ + + priv->bifup = false; + return OK; +} + +/**************************************************************************** + * Function: slip_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +static int slip_txavail(struct uip_driver_s *dev) +{ + FAR struct slip_driver_s *priv = (FAR struct slip_driver_s *)dev->d_private; + + /* Ignore the notification if the interface is not yet up */ + + if (priv->bifup) + { + /* Wake up the TX polling thread */ + + kill(priv->txpid, SIGALRM); + } + + return OK; +} + +/**************************************************************************** + * Function: slip_addmac + * + * Description: + * NuttX Callback: Add the specified MAC address to the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be added + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int slip_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct slip_driver_s *priv = (FAR struct slip_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +/**************************************************************************** + * Function: slip_rmmac + * + * Description: + * NuttX Callback: Remove the specified MAC address from the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be removed + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int slip_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct slip_driver_s *priv = (FAR struct slip_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: slip_initialize + * + * Description: + * Instantiate a SLIP network interface. + * + * Parameters: + * intf - In the case where there are multiple SLIP interfaces, this value + * identifies which is to be initialized. The network name will be, + * for example, "/dev/slip5" for intf == 5 + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * + ****************************************************************************/ + +int slip_initialize(int intf, const char *devname) +{ + struct slip_driver_s *priv; + char buffer[8]; + const char *argv[2]; + + /* Get the interface structure associated with this interface number. */ + + DEBUGASSERT(intf < CONFIG_SLIP_NINTERFACES); + priv = &g_slip[intf]; + + /* Initialize the driver structure */ + + memset(priv, 0, sizeof(struct slip_driver_s)); + priv->dev.d_ifup = slip_ifup; /* I/F up (new IP address) callback */ + priv->dev.d_ifdown = slip_ifdown; /* I/F down callback */ + priv->dev.d_txavail = slip_txavail; /* New TX data callback */ +#ifdef CONFIG_NET_IGMP + priv->dev.d_addmac = slip_addmac; /* Add multicast MAC address */ + priv->dev.d_rmmac = slip_rmmac; /* Remove multicast MAC address */ +#endif + priv->dev.d_private = priv; /* Used to recover private state from dev */ + + /* Open the device */ + + priv->fd = open(devname, O_RDWR, 0666); + if (priv->fd < 0) + { + ndbg("ERROR: Failed to open %s: %d\n", devname, errno); + return -errno; + } + + /* Initialize the wait semaphore */ + + sem_init(&priv->waitsem, 0, 0); + + /* Put the interface in the down state. This usually amounts to resetting + * the device and/or calling slip_ifdown(). + */ + + slip_ifdown(&priv->dev); + + /* Start the SLIP receiver task */ + + snprintf(buffer, 8, "%d", intf); + argv[0] = buffer; + argv[1] = NULL; + +#ifndef CONFIG_CUSTOM_STACK + priv->rxpid = task_create("rxslip", CONFIG_SLIP_DEFPRIO, + CONFIG_SLIP_STACKSIZE, (main_t)slip_rxtask, argv); +#else + priv->rxpid = task_create("rxslip", CONFIG_SLIP_DEFPRIO, + (main_t)slip_rxtask, argv); +#endif + if (priv->rxpid < 0) + { + ndbg("ERROR: Failed to start receiver task\n"); + return -errno; + } + + /* Wait and make sure that the receive task is started. */ + + slip_semtake(priv); + + /* Start the SLIP transmitter task */ + +#ifndef CONFIG_CUSTOM_STACK + priv->txpid = task_create("txslip", CONFIG_SLIP_DEFPRIO, + CONFIG_SLIP_STACKSIZE, (main_t)slip_txtask, argv); +#else + priv->txpid = task_create("txslip", CONFIG_SLIP_DEFPRIO, + (main_t)slip_txtask, argv); +#endif + if (priv->txpid < 0) + { + ndbg("ERROR: Failed to start receiver task\n"); + return -errno; + } + + /* Wait and make sure that the transmit task is started. */ + + slip_semtake(priv); + + /* Bump the semaphore count so that it can now be used as a mutex */ + + slip_semgive(priv); + + /* Register the device with the OS so that socket IOCTLs can be performed */ + + (void)netdev_register(&priv->dev); + + /* When the RX and TX tasks were created, the TTY file descriptor was + * dup'ed for each task. This task no longer needs the file descriptor + * and we can safely close it. + */ + + close(priv->fd); + return OK; +} + +#endif /* CONFIG_NET && CONFIG_NET_SLIP */ + diff --git a/nuttx/drivers/net/vnet.c b/nuttx/drivers/net/vnet.c new file mode 100644 index 000000000..c12976a0f --- /dev/null +++ b/nuttx/drivers/net/vnet.c @@ -0,0 +1,673 @@ +/**************************************************************************** + * drivers/net/vnet.c + * + * Copyright (C) 2011 Yu Qiang. All rights reserved. + * Author: Yu Qiang <yuq825@gmail.com> + * + * This file is a part of NuttX: + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#if defined(CONFIG_NET) && defined(CONFIG_NET_VNET) + +#include <stdint.h> +#include <stdbool.h> +#include <time.h> +#include <string.h> +#include <debug.h> +#include <wdog.h> +#include <errno.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> + +#include <net/uip/uip.h> +#include <net/uip/uip-arp.h> +#include <net/uip/uip-arch.h> + +#include <rgmp/vnet.h> +#include <rgmp/stdio.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/* CONFIG_VNET_NINTERFACES determines the number of physical interfaces + * that will be supported. + */ + +#ifndef CONFIG_VNET_NINTERFACES +# define CONFIG_VNET_NINTERFACES 1 +#endif + +/* TX poll deley = 1 seconds. CLK_TCK is the number of clock ticks per second */ + +#define VNET_WDDELAY (1*CLK_TCK) +#define VNET_POLLHSEC (1*2) + +/* TX timeout = 1 minute */ + +#define VNET_TXTIMEOUT (60*CLK_TCK) + +/* This is a helper pointer for accessing the contents of the Ethernet header */ + +#define BUF ((struct uip_eth_hdr *)vnet->sk_dev.d_buf) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* The vnet_driver_s encapsulates all state information for a single hardware + * interface + */ + +struct vnet_driver_s +{ + bool sk_bifup; /* true:ifup false:ifdown */ + WDOG_ID sk_txpoll; /* TX poll timer */ + //WDOG_ID sk_txtimeout; /* TX timeout timer */ + + /* This holds the information visible to uIP/NuttX */ + struct rgmp_vnet *vnet; + struct uip_driver_s sk_dev; /* Interface understood by uIP */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct vnet_driver_s g_vnet[CONFIG_VNET_NINTERFACES]; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Common TX logic */ + +static int vnet_transmit(FAR struct vnet_driver_s *vnet); +static int vnet_uiptxpoll(struct uip_driver_s *dev); + +/* Interrupt handling */ + +static void vnet_txdone(FAR struct vnet_driver_s *vnet); + +/* Watchdog timer expirations */ + +static void vnet_polltimer(int argc, uint32_t arg, ...); +static void vnet_txtimeout(int argc, uint32_t arg, ...); + +/* NuttX callback functions */ + +static int vnet_ifup(struct uip_driver_s *dev); +static int vnet_ifdown(struct uip_driver_s *dev); +static int vnet_txavail(struct uip_driver_s *dev); +#ifdef CONFIG_NET_IGMP +static int vnet_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +static int vnet_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac); +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: vnet_transmit + * + * Description: + * Start hardware transmission. Called either from the txdone interrupt + * handling or from watchdog based polling. + * + * Parameters: + * vnet - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * May or may not be called from an interrupt handler. In either case, + * global interrupts are disabled, either explicitly or indirectly through + * interrupt handling logic. + * + ****************************************************************************/ + +static int vnet_transmit(FAR struct vnet_driver_s *vnet) +{ + int err; + + /* Verify that the hardware is ready to send another packet. If we get + * here, then we are committed to sending a packet; Higher level logic + * must have assured that there is not transmission in progress. + */ + + /* Increment statistics */ + + /* Send the packet: address=vnet->sk_dev.d_buf, length=vnet->sk_dev.d_len */ + err = vnet_xmit(vnet->vnet, (char *)vnet->sk_dev.d_buf, vnet->sk_dev.d_len); + if (err) { + /* Setup the TX timeout watchdog (perhaps restarting the timer) */ + //(void)wd_start(vnet->sk_txtimeout, VNET_TXTIMEOUT, vnet_txtimeout, 1, (uint32_t)vnet); + + // When vnet_xmit fail, it means TX buffer is full. Watchdog + // is of no use here because no TX done INT will happen. So + // we reset the TX buffer directly. +#ifdef CONFIG_DEBUG + cprintf("VNET: TX buffer is full\n"); +#endif + return ERROR; + } + else { + // this step may be unnecessary here + vnet_txdone(vnet); + } + + return OK; +} + +/**************************************************************************** + * Function: vnet_uiptxpoll + * + * Description: + * The transmitter is available, check if uIP has any outgoing packets ready + * to send. This is a callback from uip_poll(). uip_poll() may be called: + * + * 1. When the preceding TX packet send is complete, + * 2. When the preceding TX packet send timesout and the interface is reset + * 3. During normal TX polling + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * May or may not be called from an interrupt handler. In either case, + * global interrupts are disabled, either explicitly or indirectly through + * interrupt handling logic. + * + ****************************************************************************/ + +static int vnet_uiptxpoll(struct uip_driver_s *dev) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)dev->d_private; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + if (vnet->sk_dev.d_len > 0) + { + uip_arp_out(&vnet->sk_dev); + vnet_transmit(vnet); + + /* Check if there is room in the device to hold another packet. If not, + * return a non-zero value to terminate the poll. + */ + if (vnet_is_txbuff_full(vnet->vnet)) + return 1; + } + + /* If zero is returned, the polling will continue until all connections have + * been examined. + */ + + return 0; +} + +/**************************************************************************** + * Function: rtos_vnet_recv + * + * Description: + * An interrupt was received indicating the availability of a new RX packet + * + * Parameters: + * vnet - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by interrupt handling logic. + * + ****************************************************************************/ + +void rtos_vnet_recv(struct rgmp_vnet *vnet_dummy, char *data, int len) +{ + // now only support 1 vnet + struct vnet_driver_s *vnet = &g_vnet[0]; + + do { + /* Check for errors and update statistics */ + + /* Check if the packet is a valid size for the uIP buffer configuration */ + if (len > CONFIG_NET_BUFSIZE || len < 14) { +#ifdef CONFIG_DEBUG + cprintf("VNET: receive invalid packet of size %d\n", len); +#endif + return; + } + + // Copy the data data from the hardware to vnet->sk_dev.d_buf. Set + // amount of data in vnet->sk_dev.d_len + memcpy(vnet->sk_dev.d_buf, data, len); + vnet->sk_dev.d_len = len; + + /* We only accept IP packets of the configured type and ARP packets */ + +#ifdef CONFIG_NET_IPv6 + if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) +#else + if (BUF->type == HTONS(UIP_ETHTYPE_IP)) +#endif + { + uip_arp_ipin(&vnet->sk_dev); + uip_input(&vnet->sk_dev); + + // If the above function invocation resulted in data that should be + // sent out on the network, the field d_len will set to a value > 0. + if (vnet->sk_dev.d_len > 0) { + uip_arp_out(&vnet->sk_dev); + vnet_transmit(vnet); + } + } + else if (BUF->type == htons(UIP_ETHTYPE_ARP)) { + uip_arp_arpin(&vnet->sk_dev); + + // If the above function invocation resulted in data that should be + // sent out on the network, the field d_len will set to a value > 0. + if (vnet->sk_dev.d_len > 0) { + vnet_transmit(vnet); + } + } + } + while (0); /* While there are more packets to be processed */ +} + +/**************************************************************************** + * Function: vnet_txdone + * + * Description: + * An interrupt was received indicating that the last TX packet(s) is done + * + * Parameters: + * vnet - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void vnet_txdone(FAR struct vnet_driver_s *vnet) +{ + /* Check for errors and update statistics */ + + /* If no further xmits are pending, then cancel the TX timeout and + * disable further Tx interrupts. + */ + + //wd_cancel(vnet->sk_txtimeout); + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&vnet->sk_dev, vnet_uiptxpoll); +} + +/**************************************************************************** + * Function: vnet_txtimeout + * + * Description: + * Our TX watchdog timed out. Called from the timer interrupt handler. + * The last TX never completed. Reset the hardware and start again. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void vnet_txtimeout(int argc, uint32_t arg, ...) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)arg; + + /* Increment statistics and dump debug info */ + + /* Then reset the hardware */ + + /* Then poll uIP for new XMIT data */ + + (void)uip_poll(&vnet->sk_dev, vnet_uiptxpoll); +} + +/**************************************************************************** + * Function: vnet_polltimer + * + * Description: + * Periodic timer handler. Called from the timer interrupt handler. + * + * Parameters: + * argc - The number of available arguments + * arg - The first argument + * + * Returned Value: + * None + * + * Assumptions: + * Global interrupts are disabled by the watchdog logic. + * + ****************************************************************************/ + +static void vnet_polltimer(int argc, uint32_t arg, ...) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)arg; + + /* Check if there is room in the send another TX packet. We cannot perform + * the TX poll if he are unable to accept another packet for transmission. + */ + if (vnet_is_txbuff_full(vnet->vnet)) { +#ifdef CONFIG_DEBUG + cprintf("VNET: TX buffer is full\n"); +#endif + return; + } + + /* If so, update TCP timing states and poll uIP for new XMIT data. Hmmm.. + * might be bug here. Does this mean if there is a transmit in progress, + * we will missing TCP time state updates? + */ + + (void)uip_timer(&vnet->sk_dev, vnet_uiptxpoll, VNET_POLLHSEC); + + /* Setup the watchdog poll timer again */ + + (void)wd_start(vnet->sk_txpoll, VNET_WDDELAY, vnet_polltimer, 1, arg); +} + +/**************************************************************************** + * Function: vnet_ifup + * + * Description: + * NuttX Callback: Bring up the Ethernet interface when an IP address is + * provided + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int vnet_ifup(struct uip_driver_s *dev) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)dev->d_private; + + ndbg("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24 ); + + /* Initialize PHYs, the Ethernet interface, and setup up Ethernet interrupts */ + + /* Set and activate a timer process */ + + (void)wd_start(vnet->sk_txpoll, VNET_WDDELAY, vnet_polltimer, 1, (uint32_t)vnet); + + vnet->sk_bifup = true; + return OK; +} + +/**************************************************************************** + * Function: vnet_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int vnet_ifdown(struct uip_driver_s *dev) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)dev->d_private; + irqstate_t flags; + + /* Disable the Ethernet interrupt */ + + flags = irqsave(); + + /* Cancel the TX poll timer and TX timeout timers */ + + wd_cancel(vnet->sk_txpoll); + //wd_cancel(vnet->sk_txtimeout); + + /* Put the the EMAC is its reset, non-operational state. This should be + * a known configuration that will guarantee the vnet_ifup() always + * successfully brings the interface back up. + */ + + /* Mark the device "down" */ + + vnet->sk_bifup = false; + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: vnet_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static int vnet_txavail(struct uip_driver_s *dev) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)dev->d_private; + irqstate_t flags; + + /* Disable interrupts because this function may be called from interrupt + * level processing. + */ + + flags = irqsave(); + + /* Ignore the notification if the interface is not yet up */ + + if (vnet->sk_bifup) + { + /* Check if there is room in the hardware to hold another outgoing packet. */ + if (vnet_is_txbuff_full(vnet->vnet)) { +#ifdef CONFIG_DEBUG + cprintf("VNET: TX buffer is full\n"); +#endif + goto out; + } + + /* If so, then poll uIP for new XMIT data */ + + (void)uip_poll(&vnet->sk_dev, vnet_uiptxpoll); + } + + out: + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Function: vnet_addmac + * + * Description: + * NuttX Callback: Add the specified MAC address to the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be added + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int vnet_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +/**************************************************************************** + * Function: vnet_rmmac + * + * Description: + * NuttX Callback: Remove the specified MAC address from the hardware multicast + * address filtering + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * mac - The MAC address to be removed + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IGMP +static int vnet_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac) +{ + FAR struct vnet_driver_s *vnet = (FAR struct vnet_driver_s *)dev->d_private; + + /* Add the MAC address to the hardware multicast routing table */ + + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Function: vnet_initialize + * + * Description: + * Initialize the Ethernet controller and driver + * + * Parameters: + * intf - In the case where there are multiple EMACs, this value + * identifies which EMAC is to be initialized. + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * + ****************************************************************************/ + +void vnet_initialize(void) +{ + struct vnet_driver_s *priv; + struct rgmp_vnet *vnet = vnet_list.next; + int i; + + for (i=0; i<CONFIG_VNET_NINTERFACES; i++) { + if (vnet == NULL) + break; + priv = &g_vnet[i]; + + /* Initialize the driver structure */ + + memset(priv, 0, sizeof(struct vnet_driver_s)); + priv->sk_dev.d_ifup = vnet_ifup; /* I/F down callback */ + priv->sk_dev.d_ifdown = vnet_ifdown; /* I/F up (new IP address) callback */ + priv->sk_dev.d_txavail = vnet_txavail; /* New TX data callback */ +#ifdef CONFIG_NET_IGMP + priv->sk_dev.d_addmac = vnet_addmac; /* Add multicast MAC address */ + priv->sk_dev.d_rmmac = vnet_rmmac; /* Remove multicast MAC address */ +#endif + priv->sk_dev.d_private = (void*)g_vnet; /* Used to recover private state from dev */ + + /* Create a watchdog for timing polling for and timing of transmisstions */ + + priv->sk_txpoll = wd_create(); /* Create periodic poll timer */ + //priv->sk_txtimeout = wd_create(); /* Create TX timeout timer */ + + priv->vnet = vnet; + + /* Register the device with the OS */ + + (void)netdev_register(&priv->sk_dev); + vnet = vnet->next; + } +} + +#endif /* CONFIG_NET && CONFIG_NET_VNET */ diff --git a/nuttx/drivers/pipes/Make.defs b/nuttx/drivers/pipes/Make.defs new file mode 100644 index 000000000..15930627a --- /dev/null +++ b/nuttx/drivers/pipes/Make.defs @@ -0,0 +1,46 @@ +############################################################################ +# drivers/pipes/Make.defs +# +# Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +ifneq ($(CONFIG_NFILE_DESCRIPTORS),0) + +# Include pipe driver + +CSRCS += pipe.c fifo.c pipe_common.c + +# Include pipe build support + +DEPPATH += --dep-path pipes +VPATH += :pipes +endif diff --git a/nuttx/drivers/pipes/fifo.c b/nuttx/drivers/pipes/fifo.c new file mode 100644 index 000000000..9e663b4a8 --- /dev/null +++ b/nuttx/drivers/pipes/fifo.c @@ -0,0 +1,136 @@ +/**************************************************************************** + * drivers/pipes/fifo.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <nuttx/fs.h> +#include <errno.h> + +#include "pipe_common.h" + +#if CONFIG_DEV_PIPE_SIZE > 0 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations fifo_fops = +{ + pipecommon_open, /* open */ + pipecommon_close, /* close */ + pipecommon_read, /* read */ + pipecommon_write, /* write */ + 0, /* seek */ + 0 /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , pipecommon_poll /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mkfifo + * + * Description: + * mkfifo() makes a FIFO device driver file with name 'pathname.' Unlike + * Linux, a NuttX FIFO is not a special file type but simply a device driver + * instance. 'mode' specifies the FIFO's permissions. + * + * Once the FIFO has been created by mkfifo(), any thread can open it for + * reading or writing, in the same way as an ordinary file. However, it must + * have been opened from both reading and writing before input or output + * can be performed. This FIFO implementation will block all attempts to + * open a FIFO read-only until at least one thread has opened the FIFO for + * writing. + * + * If all threads that write to the FIFO have closed, subsequent calls to + * read() on the FIFO will return 0 (end-of-file). + * + * Inputs: + * pathname - The full path to the FIFO instance to attach to or to create + * (if not already created). + * mode - Ignored for now + * + * Return: + * 0 is returned on success; otherwise, -1 is returned with errno set + * appropriately. + * + ****************************************************************************/ +int mkfifo(FAR const char *pathname, mode_t mode) +{ + struct pipe_dev_s *dev; + int ret; + + /* Allocate and initialize a new device structure instance */ + + dev = pipecommon_allocdev(); + if (!dev) + { + return -ENOMEM; + } + + ret = register_driver(pathname, &fifo_fops, mode, (void*)dev); + if (ret != 0) + { + pipecommon_freedev(dev); + } + return ret; +} +#endif /* CONFIG_DEV_PIPE_SIZE > 0 */ diff --git a/nuttx/drivers/pipes/pipe.c b/nuttx/drivers/pipes/pipe.c new file mode 100644 index 000000000..541f3bda4 --- /dev/null +++ b/nuttx/drivers/pipes/pipe.c @@ -0,0 +1,282 @@ +/**************************************************************************** + * drivers/pipes/pipe.c + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Compilation Switches + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <nuttx/fs.h> +#include <stdio.h> +#include <unistd.h> +#include <semaphore.h> +#include <fcntl.h> +#include <errno.h> + +#include "pipe_common.h" + +#if CONFIG_DEV_PIPE_SIZE > 0 + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +#define MAX_PIPES 32 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int pipe_close(FAR struct file *filep); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations pipe_fops = +{ + pipecommon_open, /* open */ + pipe_close, /* close */ + pipecommon_read, /* read */ + pipecommon_write, /* write */ + 0, /* seek */ + 0 /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , pipecommon_poll /* poll */ +#endif +}; + +static sem_t g_pipesem = { 1 }; +static uint32_t g_pipeset = 0; +static uint32_t g_pipecreated = 0; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pipe_allocate + ****************************************************************************/ + +static inline int pipe_allocate(void) +{ + int pipeno; + int ret = -ENFILE; + + for (pipeno = 0; pipeno < MAX_PIPES; pipeno++) + { + if ((g_pipeset & (1 << pipeno)) == 0) + { + g_pipeset |= (1 << pipeno); + ret = pipeno; + break; + } + } + return ret; +} + +/**************************************************************************** + * Name: pipe_free + ****************************************************************************/ + +static inline void pipe_free(int pipeno) +{ + int ret = sem_wait(&g_pipesem); + if (ret == 0) + { + g_pipeset &= ~(1 << pipeno); + (void)sem_post(&g_pipesem); + } +} + +/**************************************************************************** + * Name: pipe_close + ****************************************************************************/ + +static int pipe_close(FAR struct file *filep) +{ + struct inode *inode = filep->f_inode; + struct pipe_dev_s *dev = inode->i_private; + int ret; + + /* Some sanity checking */ +#if CONFIG_DEBUG + if (!dev) + { + return -EBADF; + } +#endif + + /* Perform common close operations */ + + ret = pipecommon_close(filep); + if (ret == 0 && dev->d_refs == 0) + { + /* Release the pipe when there are no further open references to it. */ + + pipe_free(dev->d_pipeno); + } + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pipe + * + * Description: + * pipe() creates a pair of file descriptors, pointing to a pipe inode, and + * places them in the array pointed to by 'filedes'. filedes[0] is for reading, + * filedes[1] is for writing. + * + * Inputs: + * filedes[2] - The user provided array in which to catch the pipe file + * descriptors + * + * Return: + * 0 is returned on success; otherwise, -1 is returned with errno set + * appropriately. + * + ****************************************************************************/ + +int pipe(int filedes[2]) +{ + struct pipe_dev_s *dev = NULL; + char devname[16]; + int pipeno; + int err; + int ret; + + /* Get exclusive access to the pipe allocation data */ + + ret = sem_wait(&g_pipesem); + if (ret < 0) + { + /* sem_wait() will have already set errno */ + + return ERROR; + } + + /* Allocate a minor number for the pipe device */ + + pipeno = pipe_allocate(); + if (pipeno < 0) + { + (void)sem_post(&g_pipesem); + err = -pipeno; + goto errout; + } + + /* Create a pathname to the pipe device */ + + sprintf(devname, "/dev/pipe%d", pipeno); + + /* Check if the pipe device has already been created */ + + if ((g_pipecreated & (1 << pipeno)) == 0) + { + /* No.. Allocate and initialize a new device structure instance */ + + dev = pipecommon_allocdev(); + if (!dev) + { + (void)sem_post(&g_pipesem); + err = ENOMEM; + goto errout_with_pipe; + } + dev->d_pipeno = pipeno; + + /* Register the pipe device */ + + ret = register_driver(devname, &pipe_fops, 0666, (void*)dev); + if (ret != 0) + { + (void)sem_post(&g_pipesem); + err = -ret; + goto errout_with_dev; + } + + /* Remember that we created this device */ + + g_pipecreated |= (1 << pipeno); + } + (void)sem_post(&g_pipesem); + + /* Get a write file descriptor */ + + filedes[1] = open(devname, O_WRONLY); + if (filedes[1] < 0) + { + err = -filedes[1]; + goto errout_with_driver; + } + + /* Get a read file descriptor */ + + filedes[0] = open(devname, O_RDONLY); + if (filedes[0] < 0) + { + err = -filedes[0]; + goto errout_with_wrfd; + } + + return OK; + +errout_with_wrfd: + close(filedes[1]); +errout_with_driver: + unregister_driver(devname); +errout_with_dev: + pipecommon_freedev(dev); +errout_with_pipe: + pipe_free(pipeno); +errout: + errno = err; + return ERROR; +} + +#endif /* CONFIG_DEV_PIPE_SIZE > 0 */ diff --git a/nuttx/drivers/pipes/pipe_common.c b/nuttx/drivers/pipes/pipe_common.c new file mode 100644 index 000000000..52077beed --- /dev/null +++ b/nuttx/drivers/pipes/pipe_common.c @@ -0,0 +1,679 @@ +/**************************************************************************** + * drivers/pipes/pipe_common.c + * + * Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sched.h> +#include <semaphore.h> +#include <fcntl.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> +#if CONFIG_DEBUG +# include <nuttx/arch.h> +#endif + +#include "pipe_common.h" + +#if CONFIG_DEV_PIPE_SIZE > 0 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* CONFIG_DEV_PIPEDUMP will dump the contents of each transfer into and out + * of the pipe. + */ + +#ifdef CONFIG_DEV_PIPEDUMP +# define pipe_dumpbuffer(m,a,n) lib_dumpbuffer(m,a,n) +#else +# define pipe_dumpbuffer(m,a,n) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void pipecommon_semtake(sem_t *sem); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pipecommon_semtake + ****************************************************************************/ + +static void pipecommon_semtake(sem_t *sem) +{ + while (sem_wait(sem) != 0) + { + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: pipecommon_pollnotify + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static void pipecommon_pollnotify(FAR struct pipe_dev_s *dev, pollevent_t eventset) +{ + int i; + + for (i = 0; i < CONFIG_DEV_PIPE_NPOLLWAITERS; i++) + { + struct pollfd *fds = dev->d_fds[i]; + if (fds) + { + fds->revents |= (fds->events & eventset); + if (fds->revents != 0) + { + fvdbg("Report events: %02x\n", fds->revents); + sem_post(fds->sem); + } + } + } +} +#else +# define pipecommon_pollnotify(dev,event) +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pipecommon_allocdev + ****************************************************************************/ + +FAR struct pipe_dev_s *pipecommon_allocdev(void) +{ + struct pipe_dev_s *dev; + + /* Allocate a private structure to manage the pipe */ + + dev = (struct pipe_dev_s *)kmalloc(sizeof(struct pipe_dev_s)); + if (dev) + { + /* Initialize the private structure */ + + memset(dev, 0, sizeof(struct pipe_dev_s)); + sem_init(&dev->d_bfsem, 0, 1); + sem_init(&dev->d_rdsem, 0, 0); + sem_init(&dev->d_wrsem, 0, 0); + } + return dev; +} + +/**************************************************************************** + * Name: pipecommon_freedev + ****************************************************************************/ + +void pipecommon_freedev(FAR struct pipe_dev_s *dev) +{ + sem_destroy(&dev->d_bfsem); + sem_destroy(&dev->d_rdsem); + sem_destroy(&dev->d_wrsem); + kfree(dev); +} + +/**************************************************************************** + * Name: pipecommon_open + ****************************************************************************/ + +int pipecommon_open(FAR struct file *filep) +{ + struct inode *inode = filep->f_inode; + struct pipe_dev_s *dev = inode->i_private; + int sval; + int ret; + + /* Some sanity checking */ +#if CONFIG_DEBUG + if (!dev) + { + return -EBADF; + } +#endif + /* Make sure that we have exclusive access to the device structure. The + * sem_wait() call should fail only if we are awakened by a signal. + */ + + ret = sem_wait(&dev->d_bfsem); + if (ret != OK) + { + fdbg("sem_wait failed: %d\n", errno); + DEBUGASSERT(errno > 0); + return -errno; + } + + /* If this the first reference on the device, then allocate the buffer */ + + if (dev->d_refs == 0) + { + dev->d_buffer = (uint8_t*)kmalloc(CONFIG_DEV_PIPE_SIZE); + if (!dev->d_buffer) + { + (void)sem_post(&dev->d_bfsem); + return -ENOMEM; + } + } + + /* Increment the reference count on the pipe instance */ + + dev->d_refs++; + + /* If opened for writing, increment the count of writers on on the pipe instance */ + + if ((filep->f_oflags & O_WROK) != 0) + { + dev->d_nwriters++; + + /* If this this is the first writer, then the read semaphore indicates the + * number of readers waiting for the first writer. Wake them all up. + */ + + if (dev->d_nwriters == 1) + { + while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0) + { + sem_post(&dev->d_rdsem); + } + } + } + + /* If opened for read-only, then wait for at least one writer on the pipe */ + + sched_lock(); + (void)sem_post(&dev->d_bfsem); + if ((filep->f_oflags & O_RDWR) == O_RDONLY && dev->d_nwriters < 1) + { + /* NOTE: d_rdsem is normally used when the read logic waits for more + * data to be written. But until the first writer has opened the + * pipe, the meaning is different: it is used prevent O_RDONLY open + * calls from returning until there is at least one writer on the pipe. + * This is required both by spec and also because it prevents + * subsequent read() calls from returning end-of-file because there is + * no writer on the pipe. + */ + + ret = sem_wait(&dev->d_rdsem); + if (ret != OK) + { + /* The sem_wait() call should fail only if we are awakened by + * a signal. + */ + + fdbg("sem_wait failed: %d\n", errno); + DEBUGASSERT(errno > 0); + ret = -errno; + + /* Immediately close the pipe that we just opened */ + + (void)pipecommon_close(filep); + } + } + + sched_unlock(); + return ret; +} + +/**************************************************************************** + * Name: pipecommon_close + ****************************************************************************/ + +int pipecommon_close(FAR struct file *filep) +{ + struct inode *inode = filep->f_inode; + struct pipe_dev_s *dev = inode->i_private; + int sval; + + /* Some sanity checking */ +#if CONFIG_DEBUG + if (!dev) + { + return -EBADF; + } +#endif + + /* Make sure that we have exclusive access to the device structure. + * NOTE: close() is supposed to return EINTR if interrupted, however + * I've never seen anyone check that. + */ + + pipecommon_semtake(&dev->d_bfsem); + + /* Check if the decremented reference count would go to zero */ + + if (dev->d_refs > 1) + { + /* No.. then just decrement the reference count */ + + dev->d_refs--; + + /* If opened for writing, decrement the count of writers on on the pipe instance */ + + if ((filep->f_oflags & O_WROK) != 0) + { + /* If there are no longer any writers on the pipe, then notify all of the + * waiting readers that they must return end-of-file. + */ + + if (--dev->d_nwriters <= 0) + { + while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0) + { + sem_post(&dev->d_rdsem); + } + } + } + } + else + { + /* Yes... deallocate the buffer */ + + kfree(dev->d_buffer); + dev->d_buffer = NULL; + + /* And reset all counts and indices */ + + dev->d_wrndx = 0; + dev->d_rdndx = 0; + dev->d_refs = 0; + dev->d_nwriters = 0; + } + + sem_post(&dev->d_bfsem); + return OK; +} + +/**************************************************************************** + * Name: pipecommon_read + ****************************************************************************/ + +ssize_t pipecommon_read(FAR struct file *filep, FAR char *buffer, size_t len) +{ + struct inode *inode = filep->f_inode; + struct pipe_dev_s *dev = inode->i_private; +#ifdef CONFIG_DEV_PIPEDUMP + FAR uint8_t *start = (uint8_t*)buffer; +#endif + ssize_t nread = 0; + int sval; + int ret; + + /* Some sanity checking */ +#if CONFIG_DEBUG + if (!dev) + { + return -ENODEV; + } +#endif + + /* Make sure that we have exclusive access to the device structure */ + + if (sem_wait(&dev->d_bfsem) < 0) + { + return ERROR; + } + + /* If the pipe is empty, then wait for something to be written to it */ + + while (dev->d_wrndx == dev->d_rdndx) + { + /* If O_NONBLOCK was set, then return EGAIN */ + + if (filep->f_oflags & O_NONBLOCK) + { + sem_post(&dev->d_bfsem); + return -EAGAIN; + } + + /* If there are no writers on the pipe, then return end of file */ + + if (dev->d_nwriters <= 0) + { + sem_post(&dev->d_bfsem); + return 0; + } + + /* Otherwise, wait for something to be written to the pipe */ + + sched_lock(); + sem_post(&dev->d_bfsem); + ret = sem_wait(&dev->d_rdsem); + sched_unlock(); + + if (ret < 0 || sem_wait(&dev->d_bfsem) < 0) + { + return ERROR; + } + } + + /* Then return whatever is available in the pipe (which is at least one byte) */ + + nread = 0; + while (nread < len && dev->d_wrndx != dev->d_rdndx) + { + *buffer++ = dev->d_buffer[dev->d_rdndx]; + if (++dev->d_rdndx >= CONFIG_DEV_PIPE_SIZE) + { + dev->d_rdndx = 0; + } + nread++; + } + + /* Notify all waiting writers that bytes have been removed from the buffer */ + + while (sem_getvalue(&dev->d_wrsem, &sval) == 0 && sval < 0) + { + sem_post(&dev->d_wrsem); + } + + /* Notify all poll/select waiters that they can write to the FIFO */ + + pipecommon_pollnotify(dev, POLLOUT); + + sem_post(&dev->d_bfsem); + pipe_dumpbuffer("From PIPE:", start, nread); + return nread; +} + +/**************************************************************************** + * Name: pipecommon_write + ****************************************************************************/ + +ssize_t pipecommon_write(FAR struct file *filep, FAR const char *buffer, size_t len) +{ + struct inode *inode = filep->f_inode; + struct pipe_dev_s *dev = inode->i_private; + ssize_t nwritten = 0; + ssize_t last; + int nxtwrndx; + int sval; + + /* Some sanity checking */ + +#if CONFIG_DEBUG + if (!dev) + { + return -ENODEV; + } +#endif + pipe_dumpbuffer("To PIPE:", (uint8_t*)buffer, len); + + /* At present, this method cannot be called from interrupt handlers. That is + * because it calls sem_wait (via pipecommon_semtake below) and sem_wait cannot + * be called from interrupt level. This actually happens fairly commonly + * IF dbg() is called from interrupt handlers and stdout is being redirected + * via a pipe. In that case, the debug output will try to go out the pipe + * (interrupt handlers should use the lldbg() APIs). + * + * On the other hand, it would be very valuable to be able to feed the pipe + * from an interrupt handler! TODO: Consider disabling interrupts instead + * of taking semaphores so that pipes can be written from interupt handlers + */ + + DEBUGASSERT(up_interrupt_context() == false) + + /* Make sure that we have exclusive access to the device structure */ + + if (sem_wait(&dev->d_bfsem) < 0) + { + return ERROR; + } + + /* Loop until all of the bytes have been written */ + + last = 0; + for (;;) + { + /* Calculate the write index AFTER the next byte is written */ + + nxtwrndx = dev->d_wrndx + 1; + if (nxtwrndx >= CONFIG_DEV_PIPE_SIZE) + { + nxtwrndx = 0; + } + + /* Would the next write overflow the circular buffer? */ + + if (nxtwrndx != dev->d_rdndx) + { + /* No... copy the byte */ + + dev->d_buffer[dev->d_wrndx] = *buffer++; + dev->d_wrndx = nxtwrndx; + + /* Is the write complete? */ + + if (++nwritten >= len) + { + /* Yes.. Notify all of the waiting readers that more data is available */ + + while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0) + { + sem_post(&dev->d_rdsem); + } + + /* Notify all poll/select waiters that they can write to the FIFO */ + + pipecommon_pollnotify(dev, POLLIN); + + /* Return the number of bytes written */ + + sem_post(&dev->d_bfsem); + return len; + } + } + else + { + /* There is not enough room for the next byte. Was anything written in this pass? */ + + if (last < nwritten) + { + /* Yes.. Notify all of the waiting readers that more data is available */ + + while (sem_getvalue(&dev->d_rdsem, &sval) == 0 && sval < 0) + { + sem_post(&dev->d_rdsem); + } + } + last = nwritten; + + /* If O_NONBLOCK was set, then return partial bytes written or EGAIN */ + + if (filep->f_oflags & O_NONBLOCK) + { + if (nwritten == 0) + { + nwritten = -EAGAIN; + } + sem_post(&dev->d_bfsem); + return nwritten; + } + + /* There is more to be written.. wait for data to be removed from the pipe */ + + sched_lock(); + sem_post(&dev->d_bfsem); + pipecommon_semtake(&dev->d_wrsem); + sched_unlock(); + pipecommon_semtake(&dev->d_bfsem); + } + } +} + +/**************************************************************************** + * Name: pipecommon_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +int pipecommon_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pipe_dev_s *dev = inode->i_private; + pollevent_t eventset; + pipe_ndx_t nbytes; + int ret = OK; + int i; + + /* Some sanity checking */ + +#if CONFIG_DEBUG + if (!dev || !fds) + { + return -ENODEV; + } +#endif + + /* Are we setting up the poll? Or tearing it down? */ + + pipecommon_semtake(&dev->d_bfsem); + if (setup) + { + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference + */ + + for (i = 0; i < CONFIG_DEV_PIPE_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!dev->d_fds[i]) + { + /* Bind the poll structure and this slot */ + + dev->d_fds[i] = fds; + fds->priv = &dev->d_fds[i]; + break; + } + } + + if (i >= CONFIG_DEV_PIPE_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto errout; + } + + /* Should immediately notify on any of the requested events? + * First, determine how many bytes are in the buffer + */ + + if (dev->d_wrndx >= dev->d_rdndx) + { + nbytes = dev->d_wrndx - dev->d_rdndx; + } + else + { + nbytes = (CONFIG_DEV_PIPE_SIZE-1) + dev->d_wrndx - dev->d_rdndx; + } + + /* Notify the POLLOUT event if the pipe is not full */ + + eventset = 0; + if (nbytes < (CONFIG_DEV_PIPE_SIZE-1)) + { + eventset |= POLLOUT; + } + + /* Notify the POLLIN event if the pipe is not empty */ + + if (nbytes > 0) + { + eventset |= POLLIN; + } + + if (eventset) + { + pipecommon_pollnotify(dev, eventset); + } + } + else + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + +#ifdef CONFIG_DEBUG + if (!slot) + { + ret = -EIO; + goto errout; + } +#endif + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +errout: + sem_post(&dev->d_bfsem); + return ret; +} +#endif + +#endif /* CONFIG_DEV_PIPE_SIZE > 0 */ diff --git a/nuttx/drivers/pipes/pipe_common.h b/nuttx/drivers/pipes/pipe_common.h new file mode 100644 index 000000000..16bf285b5 --- /dev/null +++ b/nuttx/drivers/pipes/pipe_common.h @@ -0,0 +1,139 @@ +/**************************************************************************** + * drivers/pipe/pipe_common.h + * + * Copyright (C) 2008-2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_PIPE_COMMON_H +#define __DRIVERS_PIPE_COMMON_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <sys/types.h> + +#include <stdint.h> +#include <stdbool.h> +#include <poll.h> + +#ifndef CONFIG_DEV_PIPE_SIZE +# define CONFIG_DEV_PIPE_SIZE 1024 +#endif + +#if CONFIG_DEV_PIPE_SIZE > 0 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Maximum number of threads than can be waiting for POLL events */ + +#ifndef CONFIG_DEV_PIPE_NPOLLWAITERS +# define CONFIG_DEV_PIPE_NPOLLWAITERS 2 +#endif + +/* Maximum number of open's supported on pipe */ + +#define CONFIG_DEV_PIPE_MAXUSER 255 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Make the buffer index as small as possible for the configured pipe size */ + +#if CONFIG_DEV_PIPE_SIZE > 65535 +typedef uint32_t pipe_ndx_t; /* 32-bit index */ +#elif CONFIG_DEV_PIPE_SIZE > 255 +typedef uint16_t pipe_ndx_t; /* 16-bit index */ +#else +typedef uint8_t pipe_ndx_t; /* 8-bit index */ +#endif + +/* This structure represents the state of one pipe. A reference to this + * structure is retained in the i_private field of the inode whenthe pipe/fifo + * device is registered. + */ + +struct pipe_dev_s +{ + sem_t d_bfsem; /* Used to serialize access to d_buffer and indices */ + sem_t d_rdsem; /* Empty buffer - Reader waits for data write */ + sem_t d_wrsem; /* Full buffer - Writer waits for data read */ + pipe_ndx_t d_wrndx; /* Index in d_buffer to save next byte written */ + pipe_ndx_t d_rdndx; /* Index in d_buffer to return the next byte read */ + uint8_t d_refs; /* References counts on pipe (limited to 255) */ + uint8_t d_nwriters; /* Number of reference counts for write access */ + uint8_t d_pipeno; /* Pipe minor number */ + uint8_t *d_buffer; /* Buffer allocated when device opened */ + + /* The following is a list if poll structures of threads waiting for + * driver events. The 'struct pollfd' reference for each open is also + * retained in the f_priv field of the 'struct file'. + */ + +#ifndef CONFIG_DISABLE_POLL + struct pollfd *d_fds[CONFIG_DEV_PIPE_NPOLLWAITERS]; +#endif +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +# define EXTERN extern "C" +extern "C" { +#else +# define EXTERN extern +#endif + +EXTERN FAR struct pipe_dev_s *pipecommon_allocdev(void); +EXTERN void pipecommon_freedev(FAR struct pipe_dev_s *dev); +EXTERN int pipecommon_open(FAR struct file *filep); +EXTERN int pipecommon_close(FAR struct file *filep); +EXTERN ssize_t pipecommon_read(FAR struct file *, FAR char *, size_t); +EXTERN ssize_t pipecommon_write(FAR struct file *, FAR const char *, size_t); +#ifndef CONFIG_DISABLE_POLL +EXTERN int pipecommon_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_DEV_PIPE_SIZE > 0 */ +#endif /* __DRIVERS_PIPE_COMMON_H */ diff --git a/nuttx/drivers/pm/Make.defs b/nuttx/drivers/pm/Make.defs new file mode 100644 index 000000000..6204973f5 --- /dev/null +++ b/nuttx/drivers/pm/Make.defs @@ -0,0 +1,50 @@ +############################################################################ +# drivers/pm/Make.defs +# +# Copyright (C) 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Has power management support been initialized? + +ifeq ($(CONFIG_PM),y) + +# Include power management sources + +CSRCS += pm_activity.c pm_changestate.c pm_checkstate.c pm_initialize.c pm_register.c pm_update.c + +# Include power management in the build + +DEPPATH += --dep-path pm +VPATH += :pm +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/pm} +endif + diff --git a/nuttx/drivers/pm/pm_activity.c b/nuttx/drivers/pm/pm_activity.c new file mode 100644 index 000000000..dd908b949 --- /dev/null +++ b/nuttx/drivers/pm/pm_activity.c @@ -0,0 +1,166 @@ +/**************************************************************************** + * drivers/pm/pm_activity.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <nuttx/pm.h> +#include <nuttx/clock.h> +#include <arch/irq.h> + +#include "pm_internal.h" + +#ifdef CONFIG_PM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_activity + * + * Description: + * This function is called by a device driver to indicate that it is + * performing meaningful activities (non-idle). This increment an activty + * count and/or will restart a idle timer and prevent entering reduced + * power states. + * + * Input Parameters: + * priority - Activity priority, range 0-9. Larger values correspond to + * higher priorities. Higher priority activity can prevent the system + * from entering reduced power states for a longer period of time. + * + * As an example, a button press might be higher priority activity because + * it means that the user is actively interacting with the device. + * + * Returned Value: + * None. + * + * Assumptions: + * This function may be called from an interrupt handler (this is the ONLY + * PM function that may be called from an interrupt handler!). + * + ****************************************************************************/ + +void pm_activity(int priority) +{ + uint32_t now; + uint32_t accum; + irqstate_t flags; + + /* Just increment the activity count in the current time slice. The priority + * is simply the number of counts that are added. + */ + + if (priority > 0) + { + /* Add the priority to the accumulated counts in a critical section. */ + + flags = irqsave(); + accum = (uint32_t)g_pmglobals.accum + priority; + + /* Make sure that we do not overflow the underlying uint16_t representation */ + + if (accum > INT16_MAX) + { + accum = INT16_MAX; + } + + /* Save the updated count */ + + g_pmglobals.accum = (int16_t)accum; + + /* Check the elapsed time. In periods of low activity, time slicing is + * controlled by IDLE loop polling; in periods of higher activity, time + * slicing is controlled by driver activity. In either case, the duration + * of the time slice is only approximate; during times of heavy activity, + * time slices may be become longer and the activity level may be over- + * estimated. + */ + + now = clock_systimer(); + if (now - g_pmglobals.stime >= TIME_SLICE_TICKS) + { + int16_t tmp; + + /* Sample the count, reset the time and count, and assess the PM + * state. This is an atomic operation because interrupts are + * still disabled. + */ + + tmp = g_pmglobals.accum; + g_pmglobals.stime = now; + g_pmglobals.accum = 0; + + /* Reassessing the PM state may require some computation. However, + * the work will actually be performed on a worker thread at a user- + * controlled priority. + */ + + (void)pm_update(accum); + } + + irqrestore(flags); + } +} + +#endif /* CONFIG_PM */
\ No newline at end of file diff --git a/nuttx/drivers/pm/pm_changestate.c b/nuttx/drivers/pm/pm_changestate.c new file mode 100644 index 000000000..50fa0640f --- /dev/null +++ b/nuttx/drivers/pm/pm_changestate.c @@ -0,0 +1,223 @@ +/**************************************************************************** + * drivers/pm/pm_changestate.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <nuttx/pm.h> +#include <arch/irq.h> + +#include "pm_internal.h" + +#ifdef CONFIG_PM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_prepall + * + * Description: + * Prepare every driver for the state change. + * + * Input Parameters: + * newstate - Identifies the new PM state + * + * Returned Value: + * 0 (OK) means that the callback function for all registered drivers + * returned OK (meaning that they accept the state change). Non-zero + * means that one of the drivers refused the state change. In this case, + * the system will revert to the preceding state. + * + * Assumptions: + * Interrupts are disabled. + * + ****************************************************************************/ + +static int pm_prepall(enum pm_state_e newstate) +{ + FAR sq_entry_t *entry; + int ret = OK; + + /* Visit each registered callback structure. */ + + for (entry = sq_peek(&g_pmglobals.registry); + entry && ret == OK; + entry = sq_next(entry)) + { + /* Is the prepare callback supported? */ + + FAR struct pm_callback_s *cb = (FAR struct pm_callback_s *)entry; + if (cb->prepare) + { + /* Yes.. prepare the driver */ + + ret = cb->prepare(cb, newstate); + } + } + + return ret; +} + +/**************************************************************************** + * Name: pm_changeall + * + * Description: + * Inform all drivers of the state change. + * + * Input Parameters: + * newstate - Identifies the new PM state + * + * Returned Value: + * None + * + * Assumptions: + * Interrupts are disabled. + * + ****************************************************************************/ + +static inline void pm_changeall(enum pm_state_e newstate) +{ + FAR sq_entry_t *entry; + + /* Visit each registered callback structure. */ + + for (entry = sq_peek(&g_pmglobals.registry); entry; entry = sq_next(entry)) + { + /* Is the notification callback supported? */ + + FAR struct pm_callback_s *cb = (FAR struct pm_callback_s *)entry; + if (cb->notify) + { + /* Yes.. notify the driver */ + + cb->notify(cb, newstate); + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_changestate + * + * Description: + * This function is used by platform-specific power management logic. It + * will announce the power management power management state change to all + * drivers that have registered for power management event callbacks. + * + * Input Parameters: + * newstate - Identifies the new PM state + * + * Returned Value: + * 0 (OK) means that the callback function for all registered drivers + * returned OK (meaning that they accept the state change). Non-zero + * means that one of the drivers refused the state change. In this case, + * the system will revert to the preceding state. + * + * Assumptions: + * It is assumed that interrupts are disabled when this function is + * called. This function is probably called from the IDLE loop... the + * lowest priority task in the system. Changing driver power management + * states may result in renewed system activity and, as a result, can + * suspend the IDLE thread before it completes the entire state change + * unless interrupts are disabled throughout the state change. + * + ****************************************************************************/ + +int pm_changestate(enum pm_state_e newstate) +{ + irqstate_t flags; + int ret; + + /* Disable interrupts throught this operation... changing driver states + * could cause additional driver activity that might interfere with the + * state change. When the state change is complete, interrupts will be + * re-enabled. + */ + + flags = irqsave(); + + /* First, prepare the drivers for the state change. In this phase, + * drivers may refuse the state state change. + */ + + ret = pm_prepall(newstate); + if (ret != OK) + { + /* One or more drivers is not ready for this state change. Revert to + * the preceding state. + */ + + newstate = g_pmglobals.state; + (void)pm_prepall(newstate); + } + + /* All drivers have agreed to the state change (or, one or more have + * disagreed and the state has been reverted). Set the new state. + */ + + pm_changeall(newstate); + g_pmglobals.state = newstate; + return ret; +} + +#endif /* CONFIG_PM */ diff --git a/nuttx/drivers/pm/pm_checkstate.c b/nuttx/drivers/pm/pm_checkstate.c new file mode 100644 index 000000000..9ecff862e --- /dev/null +++ b/nuttx/drivers/pm/pm_checkstate.c @@ -0,0 +1,161 @@ +/**************************************************************************** + * drivers/pm/pm_checkstate.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <nuttx/pm.h> +#include <nuttx/clock.h> +#include <arch/irq.h> + +#include "pm_internal.h" + +#ifdef CONFIG_PM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_checkstate + * + * Description: + * This function is called from the MCU-specific IDLE loop to monitor the + * the power management conditions. This function returns the "recommended" + * power management state based on the PM configuration and activity + * reported in the last sampling periods. The power management state is + * not automatically changed, however. The IDLE loop must call + * pm_changestate() in order to make the state change. + * + * These two steps are separated because the plaform-specific IDLE loop may + * have additional situational information that is not available to the + * the PM sub-system. For example, the IDLE loop may know that the + * battery charge level is very low and may force lower power states + * even if there is activity. + * + * NOTE: That these two steps are separated in time and, hence, the IDLE + * loop could be suspended for a long period of time between calling + * pm_checkstate() and pm_changestate(). The IDLE loop may need to make + * these calls atomic by either disabling interrupts until the state change + * is completed. + * + * Input Parameters: + * None + * + * Returned Value: + * The recommended power management state. + * + ****************************************************************************/ + +enum pm_state_e pm_checkstate(void) +{ + uint32_t now; + irqstate_t flags; + + /* Check for the end of the current time slice. This must be performed + * with interrupts disabled so that it does not conflict with the similar + * logic in pm_activity(). + */ + + flags = irqsave(); + + /* Check the elapsed time. In periods of low activity, time slicing is + * controlled by IDLE loop polling; in periods of higher activity, time + * slicing is controlled by driver activity. In either case, the duration + * of the time slice is only approximate; during times of heavy activity, + * time slices may be become longer and the activity level may be over- + * estimated. + */ + + now = clock_systimer(); + if (now - g_pmglobals.stime >= TIME_SLICE_TICKS) + { + int16_t accum; + + /* Sample the count, reset the time and count, and assess the PM + * state. This is an atomic operation because interrupts are + * still disabled. + */ + + accum = g_pmglobals.accum; + g_pmglobals.stime = now; + g_pmglobals.accum = 0; + + /* Reassessing the PM state may require some computation. However, + * the work will actually be performed on a worker thread at a user- + * controlled priority. + */ + + (void)pm_update(accum); + } + irqrestore(flags); + + /* Return the recommended state. Assuming that we are called from the + * IDLE thread at the lowest priority level, any updates scheduled on the + * worker thread above should have already been peformed and the recommended + * state should be current: + */ + + return g_pmglobals.recommended; +} + +#endif /* CONFIG_PM */ diff --git a/nuttx/drivers/pm/pm_initialize.c b/nuttx/drivers/pm/pm_initialize.c new file mode 100644 index 000000000..70cd8d7e6 --- /dev/null +++ b/nuttx/drivers/pm/pm_initialize.c @@ -0,0 +1,112 @@ +/**************************************************************************** + * drivers/pm/pm_initialize.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <semaphore.h> + +#include <nuttx/pm.h> + +#include "pm_internal.h" + +#ifdef CONFIG_PM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* All PM global data: */ + +struct pm_global_s g_pmglobals; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_initialize + * + * Description: + * This function is called by MCU-specific one-time at power on reset in + * order to initialize the power management capabilities. This function + * must be called *very* early in the intialization sequence *before* any + * other device drivers are initialize (since they may attempt to register + * with the power management subsystem). + * + * Input parameters: + * None. + * + * Returned value: + * None. + * + ****************************************************************************/ + +void pm_initialize(void) +{ + /* Initialize the registry and the PM global data structures. The PM + * global data structure resides in .bss which is zeroed at boot time. So + * it is only required to initialize non-zero elements of the PM global + * data structure here. + */ + + sq_init(&g_pmglobals.registry); + sem_init(&g_pmglobals.regsem, 0, 1); +} + +#endif /* CONFIG_PM */
\ No newline at end of file diff --git a/nuttx/drivers/pm/pm_internal.h b/nuttx/drivers/pm/pm_internal.h new file mode 100644 index 000000000..b3e610ba0 --- /dev/null +++ b/nuttx/drivers/pm/pm_internal.h @@ -0,0 +1,210 @@ +/**************************************************************************** + * drivers/pm/pm_internal.h + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_PM_PM_INTERNAL_H +#define __DRIVERS_PM_PM_INTERNAL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <semaphore.h> +#include <queue.h> + +#include <nuttx/pm.h> +#include <nuttx/wqueue.h> + +#ifdef CONFIG_PM + +/**************************************************************************** + * Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ + +#ifndef CONFIG_SCHED_WORKQUEUE +# warning "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + +/* Convert the time slice interval into system clock ticks. + * + * CONFIG_PM_SLICEMS provides the duration of one time slice in milliseconds. + * CLOCKS_PER_SEC provides the number of timer ticks in one second. + * + * slice ticks = (CONFIG_PM_SLICEMS msec / 1000 msec/sec) / + * (CLOCKS_PER_SEC ticks/sec) + */ + +#define TIME_SLICE_TICKS ((CONFIG_PM_SLICEMS * CLOCKS_PER_SEC) / 1000) + +/* Function-like macros *****************************************************/ +/**************************************************************************** + * Name: pm_lock + * + * Descripton: + * Lock the power management registry. NOTE: This function may return + * an error if a signal is received while what (errno == EINTR). + * + ****************************************************************************/ + +#define pm_lock() sem_wait(&g_pmglobals.regsem); + +/**************************************************************************** + * Name: pm_unlock + * + * Descripton: + * Unlock the power management registry. + * + ****************************************************************************/ + +#define pm_unlock() sem_post(&g_pmglobals.regsem); + +/**************************************************************************** + * Public Types + ****************************************************************************/ +/* This structure encapsulates all of the global data used by the PM module */ + +struct pm_global_s +{ + /* state - The current state (as determined by an explicit call to + * pm_changestate() + * recommended - The recommended state based on the PM algorithm in + * function pm_update(). + * mndex - The index to the next slot in the memory[] arry to use. + * mcnt - A tiny counter used only at start up. The actual + * algorithm cannot be applied until CONFIG_PM_MEMORY + * samples have been collected. + */ + + uint8_t state; + uint8_t recommended; + uint8_t mndx; + uint8_t mcnt; + + /* accum - The accumulated counts in this time interval + * thrcnt - The number of below threshold counts seen. + */ + + int16_t accum; + uint16_t thrcnt; + + /* This is the averaging "memory." The averaging algorithm is simply: + * Y = (An*X + SUM(Ai*Yi))/SUM(Aj), where i = 1..n-1 and j= 1..n, n is the + * length of the "memory", Ai is the weight applied to each value, and X is + * the current activity. + * + * CONFIG_PM_MEMORY provides the memory for the algorithm. Default: 2 + * CONFIG_PM_COEFn provides weight for each sample. Default: 1 + */ + +#if CONFIG_PM_MEMORY > 1 + int16_t memory[CONFIG_PM_MEMORY-1]; +#endif + + /* stime - The time (in ticks) at the start of the current time slice */ + + uint32_t stime; + + /* This semaphore manages mutually exclusive access to the power management + * registry. It must be initialized to the value 1. + */ + + sem_t regsem; + + /* For work that has been deferred to the worker thread */ + + struct work_s work; + + /* registry is a singly-linked list of registered power management + * callback structures. To ensure mutually exclusive access, this list + * must be locked by calling pm_lock() before it is accessed. + */ + + sq_queue_t registry; +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +# define EXTERN extern "C" +extern "C" +{ +#else +# define EXTERN extern +#endif + +/* All PM global data: */ + +EXTERN struct pm_global_s g_pmglobals; + +/************************************************************************************ + * Public Function Prototypes + ************************************************************************************/ + +/**************************************************************************** + * Name: pm_update + * + * Description: + * This internal function is called at the end of a time slice in order to + * update driver activity metrics and recommended states. + * + * Input Parameters: + * accum - The value of the activity accumulator at the end of the time + * slice. + * + * Returned Value: + * None. + * + * Assumptions: + * This function may be called from a driver, perhaps even at the interrupt + * level. It may also be called from the IDLE loop at the lowest possible + * priority level. To reconcile these various conditions, all work is + * performed on the worker thread at a user-selectable priority. + * + ****************************************************************************/ + +EXTERN void pm_update(int16_t accum); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* CONFIG_PM */ +#endif /* #define __DRIVERS_PM_PM_INTERNAL_H */ diff --git a/nuttx/drivers/pm/pm_register.c b/nuttx/drivers/pm/pm_register.c new file mode 100644 index 000000000..41d4bb836 --- /dev/null +++ b/nuttx/drivers/pm/pm_register.c @@ -0,0 +1,112 @@ +/**************************************************************************** + * drivers/pm/pm_register.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <queue.h> +#include <assert.h> + +#include <nuttx/pm.h> + +#include "pm_internal.h" + +#ifdef CONFIG_PM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_register + * + * Description: + * This function is called by a device driver in order to register to + * receive power management event callbacks. + * + * Input parameters: + * callbacks - An instance of struct pm_callback_s providing the driver + * callback functions. + * + * Returned value: + * Zero (OK) on success; otherwise a negater errno value is returned. + * + ****************************************************************************/ + +int pm_register(FAR struct pm_callback_s *callbacks) +{ + int ret; + + DEBUGASSERT(callbacks); + + /* Add the new entry to the end of the list of registered callbacks */ + + ret = pm_lock(); + if (ret == OK) + { + sq_addlast(&callbacks->entry, &g_pmglobals.registry); + pm_unlock(); + } + return ret; +} + +#endif /* CONFIG_PM */
\ No newline at end of file diff --git a/nuttx/drivers/pm/pm_update.c b/nuttx/drivers/pm/pm_update.c new file mode 100644 index 000000000..336bee5ef --- /dev/null +++ b/nuttx/drivers/pm/pm_update.c @@ -0,0 +1,334 @@ +/**************************************************************************** + * drivers/pm/pm_update.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <assert.h> + +#include <nuttx/pm.h> +#include <nuttx/wqueue.h> + +#include "pm_internal.h" + +#ifdef CONFIG_PM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ +/* CONFIG_PM_MEMORY is the total number of time slices (including the current + * time slice. The histor or previous values is then CONFIG_PM_MEMORY-1. + */ + +#if CONFIG_PM_MEMORY > 1 +static const int16_t g_pmcoeffs[CONFIG_PM_MEMORY-1] = +{ + CONFIG_PM_COEF1 +#if CONFIG_PM_MEMORY > 2 + , CONFIG_PM_COEF2 +#endif +#if CONFIG_PM_MEMORY > 3 + , CONFIG_PM_COEF3 +#endif +#if CONFIG_PM_MEMORY > 4 + , CONFIG_PM_COEF4 +#endif +#if CONFIG_PM_MEMORY > 5 + , CONFIG_PM_COEF5 +#endif +#if CONFIG_PM_MEMORY > 6 +# warning "This logic needs to be extended" +#endif +}; +#endif + +/* Threshold activity values to enter into the next lower power consumption + * state. Indexing is next state 0:IDLE, 1:STANDBY, 2:SLEEP. + */ + +static const int16_t g_pmenterthresh[3] = +{ + CONFIG_PM_IDLEENTER_THRESH, + CONFIG_PM_STANDBYENTER_THRESH, + CONFIG_PM_SLEEPENTER_THRESH +}; + +/* Threshold activity values to leave the current low power consdumption + * state. Indexing is current state 0:IDLE, 1: STANDBY, 2: SLEEP. + */ + +static const int16_t g_pmexitthresh[3] = +{ + CONFIG_PM_IDLEEXIT_THRESH, + CONFIG_PM_STANDBYEXIT_THRESH, + CONFIG_PM_SLEEPEXIT_THRESH +}; + +/* Threshold time slice count to enter the next low power consdumption + * state. Indexing is next state 0:IDLE, 1: STANDBY, 2: SLEEP. + */ + +static const uint16_t g_pmcount[3] = +{ + CONFIG_PM_IDLEENTER_COUNT, + CONFIG_PM_STANDBYENTER_COUNT, + CONFIG_PM_SLEEPENTER_COUNT +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_worker + * + * Description: + * This worker function is queue at the end of a time slice in order to + * update driver activity metrics and recommended states. + * + * Input Parameters: + * arg - The value of the activity accumulator at the end of the time + * slice. + * + * Returned Value: + * None. + * + * Assumptions: + * This function runs on the worker thread. + * + ****************************************************************************/ + +void pm_worker(FAR void *arg) +{ + int16_t accum = (int16_t)((intptr_t)arg); + int32_t Y; + int index; + +#if CONFIG_PM_MEMORY > 1 + int32_t denom; + int i, j; + + /* We won't bother to do anything until we have accumulated + * CONFIG_PM_MEMORY-1 samples. + */ + + if (g_pmglobals.mcnt < CONFIG_PM_MEMORY-1) + { + g_pmglobals.memory[g_pmglobals.mcnt] = accum; + g_pmglobals.mcnt++; + return; + } + + /* The averaging algorithm is simply: Y = (An*X + SUM(Ai*Yi))/SUM(Aj), where + * i = 1..n-1 and j= 1..n, n is the length of the "memory", Ai is the + * weight applied to each value, and X is the current activity. + * + * CONFIG_PM_MEMORY provides the memory for the algorithm. Default: 2 + * CONFIG_PM_COEFn provides weight for each sample. Default: 1 + * + * First, calclate Y = An*X + */ + + Y = CONFIG_PM_COEFN * accum; + denom = CONFIG_PM_COEFN; + + /* Then calculate Y += SUM(Ai*Yi), i = 1..n-1. The oldest sample will + * reside at g_pmglobals.mndx (and this is the value that we will overwrite + * with the new value). + */ + + for (i = 0, j = g_pmglobals.mndx; i < CONFIG_PM_MEMORY-1; i++, j++) + { + if (j >= CONFIG_PM_MEMORY-1) + { + j = 0; + } + + Y += g_pmcoeffs[i] * g_pmglobals.memory[j]; + denom += g_pmcoeffs[i]; + } + + /* Compute and save the new activity value */ + + Y /= denom; + g_pmglobals.memory[g_pmglobals.mndx] = Y; + g_pmglobals.mndx++; + if (g_pmglobals.mndx >= CONFIG_PM_MEMORY-1) + { + g_pmglobals.mndx = 0; + } + +#else + + /* No smoothing */ + + Y = accum; + +#endif + + /* First check if increased activity should cause us to return to the + * normal operating state. This would be unlikely for the lowest power + * consumption states because the CPU is probably asleep. However this + * probably does apply for the IDLE state. + */ + + if (g_pmglobals.state > PM_NORMAL) + { + /* Get the table index for the current state (which will be the + * current state minus one) + */ + + index = g_pmglobals.state - 1; + + /* Has the threshold to return to normal power consumption state been + * exceeded? + */ + + if (Y > g_pmexitthresh[index]) + { + /* Yes... reset the count and recommend the normal state. */ + + g_pmglobals.thrcnt = 0; + g_pmglobals.recommended = PM_NORMAL; + return; + } + } + + /* Now, compare this new activity level to the thresholds and counts for + * the next lower power consumption state. If we are already in the SLEEP + * state, then there is nothing more to be done (in fact, I would be + * surprised to be executing!). + */ + + if (g_pmglobals.state < PM_SLEEP) + { + unsigned int nextstate; + + /* Get the next state and the table index for the next state (which will + * be the current state) + */ + + index = g_pmglobals.state; + nextstate = g_pmglobals.state + 1; + + /* Has the threshold to enter the next lower power consumption state + * been exceeded? + */ + + if (Y > g_pmenterthresh[index]) + { + /* No... reset the count and recommend the current state */ + + g_pmglobals.thrcnt = 0; + g_pmglobals.recommended = g_pmglobals.state; + } + + /* Yes.. have we already recommended this state? If so, do nothing */ + + else if (g_pmglobals.recommended < nextstate) + { + /* No.. increment the count. Has is passed the the count required + * for a state transition? + */ + + if (++g_pmglobals.thrcnt >= g_pmcount[index]) + { + /* Yes, recommend the new state and set up for the next + * transition. + */ + + g_pmglobals.thrcnt = 0; + g_pmglobals.recommended = nextstate; + } + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pm_update + * + * Description: + * This internal function is called at the end of a time slice in order to + * update driver activity metrics and recommended states. + * + * Input Parameters: + * accum - The value of the activity accumulator at the end of the time + * slice. + * + * Returned Value: + * None. + * + * Assumptions: + * This function may be called from a driver, perhaps even at the interrupt + * level. It may also be called from the IDLE loop at the lowest possible + * priority level. To reconcile these various conditions, all work is + * performed on the worker thread at a user-selectable priority. This will + * also serialize all of the updates and eliminate any need for additional + * protection. + * + ****************************************************************************/ + +void pm_update(int16_t accum) +{ + /* The work will be performed on the worker thread */ + + DEBUGASSERT(g_pmglobals.work.worker == NULL); + (void)work_queue(&g_pmglobals.work, pm_worker, (FAR void*)((intptr_t)accum), 0); +} + +#endif /* CONFIG_PM */
\ No newline at end of file diff --git a/nuttx/drivers/pwm.c b/nuttx/drivers/pwm.c new file mode 100644 index 000000000..c870adfe4 --- /dev/null +++ b/nuttx/drivers/pwm.c @@ -0,0 +1,422 @@ +/**************************************************************************** + * drivers/pwm.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <gnutt@nuttx.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Compilation Switches + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <semaphore.h> +#include <fcntl.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/arch.h> +#include <nuttx/pwm.h> + +#include <arch/irq.h> + +#ifdef CONFIG_PWM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Type Definitions + ****************************************************************************/ + +/* This structure describes the state of the upper half drivere */ + +struct pwm_upperhalf_s +{ + uint8_t crefs; /* The number of times the device has been opened */ + bool started; /* True: pulsed output is being generated */ + sem_t sem; /* Supports mutual exclusion */ + struct pwm_info_s info; /* Pulsed output characteristics */ + FAR struct pwm_lowerhalf_s *dev; /* lower-half state */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int pwm_open(FAR struct file *filep); +static int pwm_close(FAR struct file *filep); +static ssize_t pwm_read(FAR struct file *filep, FAR char *buffer, size_t buflen); +static ssize_t pwm_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); +static int pwm_ioctl(FAR struct file *filep, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_pwmops = +{ + pwm_open, /* open */ + pwm_close, /* close */ + pwm_read, /* read */ + pwm_write, /* write */ + 0, /* seek */ + pwm_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , 0 /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/************************************************************************************ + * Name: pwm_open + * + * Description: + * This function is called whenever the PWM device is opened. + * + ************************************************************************************/ + +static int pwm_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pwm_upperhalf_s *upper = inode->i_private; + uint8_t tmp; + int ret; + + /* Get exclusive access to the device structures */ + + ret = sem_wait(&upper->sem); + if (ret < 0) + { + ret = -errno; + goto errout; + } + + /* Increment the count of references to the device. If this the first + * time that the driver has been opened for this device, then initialize + * the device. + */ + + tmp = upper->crefs + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + goto errout_with_sem; + } + + /* Check if this is the first time that the driver has been opened. */ + + if (tmp == 1) + { + FAR struct pwm_lowerhalf_s *lower = upper->dev; + + /* Yes.. perform one time hardware initialization. */ + + ret = lower->ops->setup(lower); + if (ret < 0) + { + goto errout_with_sem; + } + } + + /* Save the new open count on success */ + + upper->crefs = tmp; + ret = OK; + +errout_with_sem: + sem_post(&upper->sem); + +errout: + return ret; +} + +/************************************************************************************ + * Name: pwm_close + * + * Description: + * This function is called when the PWM device is closed. + * + ************************************************************************************/ + +static int pwm_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pwm_upperhalf_s *upper = inode->i_private; + int ret; + + /* Get exclusive access to the device structures */ + + ret = sem_wait(&upper->sem); + if (ret < 0) + { + ret = -errno; + goto errout; + } + + /* Decrement the references to the driver. If the reference count will + * decrement to 0, then uninitialize the driver. + */ + + if (upper->crefs > 1) + { + upper->crefs--; + } + else + { + FAR struct pwm_lowerhalf_s *lower = upper->dev; + + /* There are no more references to the port */ + + upper->crefs = 0; + + /* Disable the PWM device */ + + lower->ops->shutdown(lower); + } + ret = OK; + +//errout_with_sem: + sem_post(&upper->sem); + +errout: + return ret; +} + +/************************************************************************************ + * Name: pwm_read + * + * Description: + * A dummy read method. This is provided only to satsify the VFS layer. + * + ************************************************************************************/ + +static ssize_t pwm_read(FAR struct file *filep, FAR char *buffer, size_t buflen) +{ + /* Return zero -- usually meaning end-of-file */ + + return 0; +} + +/************************************************************************************ + * Name: pwm_write + * + * Description: + * A dummy write method. This is provided only to satsify the VFS layer. + * + ************************************************************************************/ + +static ssize_t pwm_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) +{ + return 0; +} + +/************************************************************************************ + * Name: pwm_ioctl + * + * Description: + * The standard ioctl method. This is where ALL of the PWM work is done. + * + ************************************************************************************/ + +static int pwm_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pwm_upperhalf_s *upper = inode->i_private; + FAR struct pwm_lowerhalf_s *lower = upper->dev; + int ret = OK; + + /* Handle built-in ioctl commands */ + + switch (cmd) + { + /* PWMIOC_SETCHARACTERISTICS - Set the characteristics of the next pulsed + * output. This command will neither start nor stop the pulsed output. + * It will either setup the configuration that will be used when the + * output is started; or it will change the characteristics of the pulsed + * output on the fly if the timer is already started. + * + * ioctl argument: A read-only reference to struct pwm_info_s that provides + * the characteristics of the pulsed output. + */ + + case PWMIOC_SETCHARACTERISTICS: + { + FAR const struct pwm_info_s *info = (FAR const struct pwm_info_s*)((uintptr_t)arg); + memcpy(&upper->info, info, sizeof(struct pwm_info_s)); + if (upper->started) + { + ret = lower->ops->start(lower, &upper->info); + } + } + break; + + /* PWMIOC_GETCHARACTERISTICS - Get the currently selected characteristics of + * the pulsed output (independent of whether the output is start or stopped). + * + * ioctl argument: A reference to struct pwm_info_s to recevie the + * characteristics of the pulsed output. + */ + + case PWMIOC_GETCHARACTERISTICS: + { + FAR struct pwm_info_s *info = (FAR struct pwm_info_s*)((uintptr_t)arg); + memcpy(info, &upper->info, sizeof(struct pwm_info_s)); + } + break; + + /* PWMIOC_START - Start the pulsed output. The PWMIOC_SETCHARACTERISTICS + * command must have previously been sent. + * + * ioctl argument: None + */ + + case PWMIOC_START: + { + if (!upper->started) + { + ret = lower->ops->start(lower, &upper->info); + upper->started = true; + } + } + break; + + /* PWMIOC_STOP - Stop the pulsed output. + * + * ioctl argument: None + */ + + case PWMIOC_STOP: + { + if (upper->started) + { + ret = lower->ops->stop(lower); + upper->started = false; + } + } + break; + + /* PWMIOC_GETPULSECOUNT - Return the number of pulses generated. + * + * ioctl argument: A pointer to a pwm_count_t variable that will be used to + * receive the pulse count + */ + + case PWMIOC_GETPULSECOUNT: + { + FAR pwm_count_t *count = (FAR pwm_count_t *)((uintptr_t)arg); + ret = lower->ops->pulsecount(lower, count); + } + break; + + /* Any unrecognized IOCTL commands might be platform-specific ioctl commands */ + + default: + { + ret = lower->ops->ioctl(lower, cmd, arg); + } + break; + } + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pwm_register + * + * Description: + * This function binds an instance of a "lower half" timer driver with the + * "upper half" PWM device and registers that device so that can be used + * by application code. + * + * When this function is called, the "lower half" driver should be in the + * reset state (as if the shutdown() method had already been called). + * + * Input parameters: + * path - The full path to the driver to be registers in the NuttX pseudo- + * filesystem. The recommended convention is to name all PWM drivers + * as "/dev/pwm0", "/dev/pwm1", etc. where the driver path differs only + * in the "minor" number at the end of the device name. + * dev - A pointer to an instance of lower half timer driver. This instance + * is bound to the PWM driver and must persists as long as the driver + * persists. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int pwm_register(FAR const char *path, FAR struct pwm_lowerhalf_s *dev) +{ + FAR struct pwm_upperhalf_s *upper; + + /* Allocate the upper-half data structure */ + + upper = (FAR struct pwm_upperhalf_s *)zalloc(sizeof(struct pwm_upperhalf_s)); + if (!upper) + { + return -ENOMEM; + } + + /* Initialize the PWM device structure (it was already zeroed by zalloc()) */ + + sem_init(&upper->sem, 0, 1); + upper->dev = dev; + + /* Register the PWM device */ + + vdbg("Registering %s\n", path); + return register_driver(path, &g_pwmops, 0666, dev); +} + +#endif /* CONFIG_PWM */ diff --git a/nuttx/drivers/ramdisk.c b/nuttx/drivers/ramdisk.c new file mode 100644 index 000000000..bfa0618d6 --- /dev/null +++ b/nuttx/drivers/ramdisk.c @@ -0,0 +1,339 @@ +/**************************************************************************** + * drivers/ramdisk.c + * + * Copyright (C) 2008-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <debug.h> +#include <errno.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> +#include <nuttx/ramdisk.h> + +/**************************************************************************** + * Private Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct rd_struct_s +{ + uint32_t rd_nsectors; /* Number of sectors on device */ + uint16_t rd_sectsize; /* The size of one sector */ +#ifdef CONFIG_FS_WRITABLE + bool rd_writeenabled; /* true: can write to ram disk */ + uint8_t *rd_buffer; /* RAM disk backup memory */ +#else + const uint8_t *rd_buffer; /* ROM disk backup memory */ +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int rd_open(FAR struct inode *inode); +static int rd_close(FAR struct inode *inode); +static ssize_t rd_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#ifdef CONFIG_FS_WRITABLE +static ssize_t rd_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors); +#endif +static int rd_geometry(FAR struct inode *inode, struct geometry *geometry); +static int rd_ioctl(FAR struct inode *inode, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_bops = +{ + rd_open, /* open */ + rd_close, /* close */ + rd_read, /* read */ +#ifdef CONFIG_FS_WRITABLE + rd_write, /* write */ +#else + NULL, /* write */ +#endif + rd_geometry, /* geometry */ + rd_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rd_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int rd_open(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: rd_closel + * + * Description: close the block device + * + ****************************************************************************/ + +static int rd_close(FAR struct inode *inode) +{ + fvdbg("Entry\n"); + return OK; +} + +/**************************************************************************** + * Name: rd_read + * + * Description: Read the specified numer of sectors + * + ****************************************************************************/ + +static ssize_t rd_read(FAR struct inode *inode, unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct rd_struct_s *dev; + + DEBUGASSERT(inode && inode->i_private); + dev = (struct rd_struct_s *)inode->i_private; + + fvdbg("sector: %d nsectors: %d sectorsize: %d\n", + start_sector, dev->rd_sectsize, nsectors); + + if (start_sector < dev->rd_nsectors && + start_sector + nsectors <= dev->rd_nsectors) + { + fvdbg("Transfer %d bytes from %p\n", + nsectors * dev->rd_sectsize, + &dev->rd_buffer[start_sector * dev->rd_sectsize]); + + memcpy(buffer, + &dev->rd_buffer[start_sector * dev->rd_sectsize], + nsectors * dev->rd_sectsize); + return nsectors; + } + return -EINVAL; +} + +/**************************************************************************** + * Name: rd_write + * + * Description: Write the specified number of sectors + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t rd_write(FAR struct inode *inode, const unsigned char *buffer, + size_t start_sector, unsigned int nsectors) +{ + struct rd_struct_s *dev; + + DEBUGASSERT(inode && inode->i_private); + dev = (struct rd_struct_s *)inode->i_private; + + fvdbg("sector: %d nsectors: %d sectorsize: %d\n", + start_sector, dev->rd_sectsize, nsectors); + + if (!dev->rd_writeenabled) + { + return -EACCES; + } + else if (start_sector < dev->rd_nsectors && + start_sector + nsectors <= dev->rd_nsectors) + { + fvdbg("Transfer %d bytes from %p\n", + nsectors * dev->rd_sectsize, + &dev->rd_buffer[start_sector * dev->rd_sectsize]); + + memcpy(&dev->rd_buffer[start_sector * dev->rd_sectsize], + buffer, + nsectors * dev->rd_sectsize); + return nsectors; + } + return -EFBIG; +} +#endif + +/**************************************************************************** + * Name: rd_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int rd_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + struct rd_struct_s *dev; + + fvdbg("Entry\n"); + + DEBUGASSERT(inode); + if (geometry) + { + dev = (struct rd_struct_s *)inode->i_private; + geometry->geo_available = true; + geometry->geo_mediachanged = false; +#ifdef CONFIG_FS_WRITABLE + geometry->geo_writeenabled = dev->rd_writeenabled; +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = dev->rd_nsectors; + geometry->geo_sectorsize = dev->rd_sectsize; + + fvdbg("available: true mediachanged: false writeenabled: %s\n", + geometry->geo_writeenabled ? "true" : "false"); + fvdbg("nsectors: %d sectorsize: %d\n", + geometry->geo_nsectors, geometry->geo_sectorsize); + + return OK; + } + return -EINVAL; +} + +/**************************************************************************** + * Name: rd_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int rd_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) +{ + struct rd_struct_s *dev ; + void **ppv = (void**)((uintptr_t)arg); + + fvdbg("Entry\n"); + + /* Only one ioctl command is supported */ + + DEBUGASSERT(inode && inode->i_private); + if (cmd == BIOC_XIPBASE && ppv) + { + dev = (struct rd_struct_s *)inode->i_private; + *ppv = (void*)dev->rd_buffer; + + fvdbg("ppv: %p\n", *ppv); + return OK; + } + + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ramdisk_register + * + * Description: Register the a ramdisk + + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +int ramdisk_register(int minor, uint8_t *buffer, uint32_t nsectors, + uint16_t sectsize, bool writeenabled) +#else +int romdisk_register(int minor, uint8_t *buffer, uint32_t nsectors, + uint16_t sectsize) +#endif +{ + struct rd_struct_s *dev; + char devname[16]; + int ret = -ENOMEM; + + fvdbg("buffer: %p nsectors: %d sectsize: %d\n", buffer, nsectors, sectsize); + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (minor < 0 || minor > 255 || !buffer || !nsectors || !sectsize) + { + return -EINVAL; + } +#endif + + /* Allocate a ramdisk device structure */ + + dev = (struct rd_struct_s *)kmalloc(sizeof(struct rd_struct_s)); + if (dev) + { + /* Initialize the ramdisk device structure */ + + dev->rd_nsectors = nsectors; /* Number of sectors on device */ + dev->rd_sectsize = sectsize; /* The size of one sector */ +#ifdef CONFIG_FS_WRITABLE + dev->rd_writeenabled = writeenabled; /* true: can write to ram disk */ +#endif + dev->rd_buffer = buffer; /* RAM disk backup memory */ + + /* Create a ramdisk device name */ + + snprintf(devname, 16, "/dev/ram%d", minor); + + /* Inode private data is a reference to the ramdisk device stgructure */ + + ret = register_blockdriver(devname, &g_bops, 0, dev); + if (ret < 0) + { + fdbg("register_blockdriver failed: %d\n", -ret); + kfree(dev); + } + } + return ret; +} diff --git a/nuttx/drivers/rwbuffer.c b/nuttx/drivers/rwbuffer.c new file mode 100644 index 000000000..5f3dbe98c --- /dev/null +++ b/nuttx/drivers/rwbuffer.c @@ -0,0 +1,679 @@ +/**************************************************************************** + * drivers/rwbuffer.c + * + * Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <semaphore.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/wqueue.h> +#include <nuttx/rwbuffer.h> + +#if defined(CONFIG_FS_WRITEBUFFER) || defined(CONFIG_FS_READAHEAD) + +/**************************************************************************** + * Preprocessor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_SCHED_WORKQUEUE +# error "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + +#ifndef CONFIG_FS_WRDELAY +# define CONFIG_FS_WRDELAY 350 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Variables + ****************************************************************************/ + +/**************************************************************************** + * Public Variables + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rwb_semtake + ****************************************************************************/ + +static void rwb_semtake(sem_t *sem) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(sem) != 0) + { + /* The only case that an error should occr here is if + * the wait was awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: rwb_semgive + ****************************************************************************/ + +#define rwb_semgive(s) sem_post(s) + +/**************************************************************************** + * Name: rwb_overlap + ****************************************************************************/ + +static inline bool rwb_overlap(off_t blockstart1, size_t nblocks1, + off_t blockstart2, size_t nblocks2) +{ + off_t blockend1 = blockstart1 + nblocks1; + off_t blockend2 = blockstart2 + nblocks2; + + /* If the buffer 1 is wholly outside of buffer 2, return false */ + + if ((blockend1 < blockstart2) || /* Wholly "below" */ + (blockstart1 > blockend2)) /* Wholly "above" */ + { + return false; + } + else + { + return true; + } +} + +/**************************************************************************** + * Name: rwb_resetwrbuffer + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITEBUFFER +static inline void rwb_resetwrbuffer(struct rwbuffer_s *rwb) +{ + /* We assume that the caller holds the wrsem */ + + rwb->wrnblocks = 0; + rwb->wrblockstart = (off_t)-1; + rwb->wrexpectedblock = (off_t)-1; +} +#endif + +/**************************************************************************** + * Name: rwb_wrflush + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITEBUFFER +static void rwb_wrflush(struct rwbuffer_s *rwb) +{ + int ret; + + fvdbg("Timeout!\n"); + + rwb_semtake(&rwb->wrsem); + if (rwb->wrnblocks) + { + fvdbg("Flushing: blockstart=0x%08lx nblocks=%d from buffer=%p\n", + (long)rwb->wrblockstart, rwb->wrnblocks, rwb->wrbuffer); + + /* Flush cache. On success, the flush method will return the number + * of blocks written. Anything other than the number requested is + * an error. + */ + + ret = rwb->wrflush(rwb->dev, rwb->wrbuffer, rwb->wrblockstart, rwb->wrnblocks); + if (ret != rwb->wrnblocks) + { + fdbg("ERROR: Error flushing write buffer: %d\n", ret); + } + + rwb_resetwrbuffer(rwb); + } + + rwb_semgive(&rwb->wrsem); +} +#endif + +/**************************************************************************** + * Name: rwb_wrtimeout + ****************************************************************************/ + +static void rwb_wrtimeout(FAR void *arg) +{ + /* The following assumes that the size of a pointer is 4-bytes or less */ + + FAR struct rwbuffer_s *rwb = (struct rwbuffer_s *)arg; + DEBUGASSERT(rwb != NULL); + + /* If a timeout elpases with with write buffer activity, this watchdog + * handler function will be evoked on the thread of execution of the + * worker thread. + */ + + rwb_wrflush(rwb); +} + +/**************************************************************************** + * Name: rwb_wrstarttimeout + ****************************************************************************/ + +static void rwb_wrstarttimeout(FAR struct rwbuffer_s *rwb) +{ + /* CONFIG_FS_WRDELAY provides the delay period in milliseconds. CLK_TCK + * provides the clock tick of the system (frequency in Hz). + */ + + int ticks = (CONFIG_FS_WRDELAY + CLK_TCK/2) / CLK_TCK; + (void)work_queue(&rwb->work, rwb_wrtimeout, (FAR void *)rwb, ticks); +} + +/**************************************************************************** + * Name: rwb_wrcanceltimeout + ****************************************************************************/ + +static inline void rwb_wrcanceltimeout(struct rwbuffer_s *rwb) +{ + (void)work_cancel(&rwb->work); +} + +/**************************************************************************** + * Name: rwb_writebuffer + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITEBUFFER +static ssize_t rwb_writebuffer(FAR struct rwbuffer_s *rwb, + off_t startblock, uint32_t nblocks, + FAR const uint8_t *wrbuffer) +{ + int ret; + + /* Write writebuffer Logic */ + + rwb_wrcanceltimeout(rwb); + + /* First: Should we flush out our cache? We would do that if (1) we already + * buffering blocks and the next block writing is not in the same sequence, + * or (2) the number of blocks would exceed our allocated buffer capacity + */ + + if (((startblock != rwb->wrexpectedblock) && (rwb->wrnblocks)) || + ((rwb->wrnblocks + nblocks) > rwb->wrmaxblocks)) + { + fvdbg("writebuffer miss, expected: %08x, given: %08x\n", + rwb->wrexpectedblock, startblock); + + /* Flush the write buffer */ + + ret = rwb->wrflush(rwb, rwb->wrbuffer, rwb->wrblockstart, rwb->wrnblocks); + if (ret < 0) + { + fdbg("ERROR: Error writing multiple from cache: %d\n", -ret); + return ret; + } + + rwb_resetwrbuffer(rwb); + } + + /* writebuffer is empty? Then initialize it */ + + if (!rwb->wrnblocks) + { + fvdbg("Fresh cache starting at block: 0x%08x\n", startblock); + rwb->wrblockstart = startblock; + } + + /* Add data to cache */ + + fvdbg("writebuffer: copying %d bytes from %p to %p\n", + nblocks * wrb->blocksize, wrbuffer, + &rwb->wrbuffer[rwb->wrnblocks * rwb->blocksize]); + memcpy(&rwb->wrbuffer[rwb->wrnblocks * rwb->blocksize], + wrbuffer, nblocks * rwb->blocksize); + + rwb->wrnblocks += nblocks; + rwb->wrexpectedblock = rwb->wrblockstart + rwb->wrnblocks; + rwb_wrstarttimeout(rwb); + return nblocks; +} +#endif + +/**************************************************************************** + * Name: rwb_resetrhbuffer + ****************************************************************************/ + +#ifdef CONFIG_FS_READAHEAD +static inline void rwb_resetrhbuffer(struct rwbuffer_s *rwb) +{ + /* We assume that the caller holds the readAheadBufferSemphore */ + + rwb->rhnblocks = 0; + rwb->rhblockstart = (off_t)-1; +} +#endif + +/**************************************************************************** + * Name: rwb_bufferread + ****************************************************************************/ + +#ifdef CONFIG_FS_READAHEAD +static inline void +rwb_bufferread(struct rwbuffer_s *rwb, off_t startblock, + size_t nblocks, uint8_t **rdbuffer) +{ + /* We assume that (1) the caller holds the readAheadBufferSemphore, and (2) + * that the caller already knows that all of the blocks are in the + * read-ahead buffer. + */ + + /* Convert the units from blocks to bytes */ + + off_t blockoffset = startblock - rwb->rhblockstart; + off_t byteoffset = rwb->blocksize * blockoffset; + size_t nbytes = rwb->blocksize * nblocks; + + /* Get the byte address in the read-ahead buffer */ + + uint8_t *rhbuffer = rwb->rhbuffer + byteoffset; + + /* Copy the data from the read-ahead buffer into the IO buffer */ + + memcpy(*rdbuffer, rhbuffer, nbytes); + + /* Update the caller's copy for the next address */ + + *rdbuffer += nbytes; +} +#endif + +/**************************************************************************** + * Name: rwb_rhreload + ****************************************************************************/ + +#ifdef CONFIG_FS_READAHEAD +static int rwb_rhreload(struct rwbuffer_s *rwb, off_t startblock) +{ + /* Get the block number +1 of the last block that will fit in the + * read-ahead buffer + */ + + off_t endblock = startblock + rwb->rhmaxblocks; + size_t nblocks; + int ret; + + /* Reset the read buffer */ + + rwb_resetrhbuffer(rwb); + + /* Make sure that we don't read past the end of the device */ + + if (endblock > rwb->nblocks) + { + endblock = rwb->nblocks; + } + nblocks = endblock - startblock; + + /* Now perform the read */ + + ret = rwb->rhreload(rwb->dev, rwb->rhbuffer, startblock, nblocks); + if (ret == nblocks) + { + /* Update information about what is in the read-ahead buffer */ + + rwb->rhnblocks = nblocks; + rwb->rhblockstart = startblock; + + /* The return value is not the number of blocks we asked to be loaded. */ + + return nblocks; + } + + return -EIO; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +/**************************************************************************** + * Name: rwb_initialize + ****************************************************************************/ + +int rwb_initialize(FAR struct rwbuffer_s *rwb) +{ + uint32_t allocsize; + + /* Sanity checking */ + + DEBUGASSERT(rwb != NULL); + DEBUGASSERT(rwb->blocksize > 0); + DEBUGASSERT(rwb->nblocks > 0); + DEBUGASSERT(rwb->dev != NULL); + + /* Setup so that rwb_uninitialize can handle a failure */ + +#ifdef CONFIG_FS_WRITEBUFFER + DEBUGASSERT(rwb->wrflush!= NULL); + rwb->wrbuffer = NULL; +#endif +#ifdef CONFIG_FS_READAHEAD + DEBUGASSERT(rwb->rhreload != NULL); + rwb->rhbuffer = NULL; +#endif + +#ifdef CONFIG_FS_WRITEBUFFER + fvdbg("Initialize the write buffer\n"); + + /* Initialize the write buffer access semaphore */ + + sem_init(&rwb->wrsem, 0, 1); + + /* Initialize write buffer parameters */ + + rwb_resetwrbuffer(rwb); + + /* Allocate the write buffer */ + + rwb->wrbuffer = NULL; + if (rwb->wrmaxblocks > 0) + { + allocsize = rwb->wrmaxblocks * rwb->blocksize; + rwb->wrbuffer = kmalloc(allocsize); + if (!rwb->wrbuffer) + { + fdbg("Write buffer kmalloc(%d) failed\n", allocsizee); + return -ENOMEM; + } + } + + fvdbg("Write buffer size: %d bytes\n", allocsize); +#endif /* CONFIG_FS_WRITEBUFFER */ + +#ifdef CONFIG_FS_READAHEAD + fvdbg("Initialize the read-ahead buffer\n"); + + /* Initialize the read-ahead buffer access semaphore */ + + sem_init(&rwb->rhsem, 0, 1); + + /* Initialize read-ahead buffer parameters */ + + rwb_resetrhbuffer(rwb); + + /* Allocate the read-ahead buffer */ + + rwb->rhbuffer = NULL; + if (rwb->rhmaxblocks > 0) + { + allocsize = rwb->rhmaxblocks * rwb->blocksize; + rwb->rhbuffer = kmalloc(allocsize); + if (!rwb->rhbuffer) + { + fdbg("Read-ahead buffer kmalloc(%d) failed\n", allocsize); + return -ENOMEM; + } + } + + fvdbg("Read-ahead buffer size: %d bytes\n", allocsize); +#endif /* CONFIG_FS_READAHEAD */ + return 0; +} + +/**************************************************************************** + * Name: rwb_uninitialize + ****************************************************************************/ + +void rwb_uninitialize(FAR struct rwbuffer_s *rwb) +{ +#ifdef CONFIG_FS_WRITEBUFFER + rwb_wrcanceltimeout(rwb); + sem_destroy(&rwb->wrsem); + if (rwb->wrbuffer) + { + kfree(rwb->wrbuffer); + } +#endif + +#ifdef CONFIG_FS_READAHEAD + sem_destroy(&rwb->rhsem); + if (rwb->rhbuffer) + { + kfree(rwb->rhbuffer); + } +#endif +} + +/**************************************************************************** + * Name: rwb_read + ****************************************************************************/ + +int rwb_read(FAR struct rwbuffer_s *rwb, off_t startblock, uint32_t nblocks, + FAR uint8_t *rdbuffer) +{ + uint32_t remaining; + + fvdbg("startblock=%ld nblocks=%ld rdbuffer=%p\n", + (long)startblock, (long)nblocks, rdbuffer); + +#ifdef CONFIG_FS_WRITEBUFFER + /* If the new read data overlaps any part of the write buffer, then + * flush the write data onto the physical media before reading. We + * could attempt some more exotic handling -- but this simple logic + * is well-suited for simple streaming applications. + */ + + if (rwb->wrmaxblocks > 0) + { + /* If the write buffer overlaps the block(s) requested, then flush the + * write buffer. + */ + + rwb_semtake(&rwb->wrsem); + if (rwb_overlap(rwb->wrblockstart, rwb->wrnblocks, startblock, nblocks)) + { + rwb_wrflush(rwb); + } + rwb_semgive(&rwb->wrsem); + } +#endif + +#ifdef CONFIG_FS_READAHEAD + /* Loop until we have read all of the requested blocks */ + + rwb_semtake(&rwb->rhsem); + for (remaining = nblocks; remaining > 0;) + { + /* Is there anything in the read-ahead buffer? */ + + if (rwb->rhnblocks > 0) + { + off_t startblock = startblock; + size_t nbufblocks = 0; + off_t bufferend; + + /* Loop for each block we find in the read-head buffer. Count the + * number of buffers that we can read from read-ahead buffer. + */ + + bufferend = rwb->rhblockstart + rwb->rhnblocks; + + while ((startblock >= rwb->rhblockstart) && + (startblock < bufferend) && + (remaining > 0)) + { + /* This is one more that we will read from the read ahead buffer */ + + nbufblocks++; + + /* And one less that we will read from the media */ + + startblock++; + remaining--; + } + + /* Then read the data from the read-ahead buffer */ + + rwb_bufferread(rwb, startblock, nbufblocks, &rdbuffer); + } + + /* If we did not get all of the data from the buffer, then we have to refill + * the buffer and try again. + */ + + if (remaining > 0) + { + int ret = rwb_rhreload(rwb, startblock); + if (ret < 0) + { + fdbg("ERROR: Failed to fill the read-ahead buffer: %d\n", -ret); + return ret; + } + } + } + + /* On success, return the number of blocks that we were requested to read. + * This is for compatibility with the normal return of a block driver read + * method + */ + + rwb_semgive(&rwb->rhsem); + return 0; +#else + return rwb->rhreload(rwb->dev, startblock, nblocks, rdbuffer); +#endif +} + +/**************************************************************************** + * Name: rwb_write + ****************************************************************************/ + +int rwb_write(FAR struct rwbuffer_s *rwb, off_t startblock, + size_t nblocks, FAR const uint8_t *wrbuffer) +{ + int ret; + +#ifdef CONFIG_FS_READAHEAD + /* If the new write data overlaps any part of the read buffer, then + * flush the data from the read buffer. We could attempt some more + * exotic handling -- but this simple logic is well-suited for simple + * streaming applications. + */ + + rwb_semtake(&rwb->rhsem); + if (rwb_overlap(rwb->rhblockstart, rwb->rhnblocks, startblock, nblocks)) + { + rwb_resetrhbuffer(rwb); + } + rwb_give(&rwb->rhsem); +#endif + +#ifdef CONFIG_FS_WRITEBUFFER + fvdbg("startblock=%d wrbuffer=%p\n", startblock, wrbuffer); + + /* Use the block cache unless the buffer size is bigger than block cache */ + + if (nblocks > rwb->wrmaxblocks) + { + /* First flush the cache */ + + rwb_semtake(&rwb->wrsem); + rwb_wrflush(rwb); + rwb_semgive(&rwb->wrsem); + + /* Then transfer the data directly to the media */ + + ret = rwb->wrflush(rwb->dev, startblock, nblocks, wrbuffer); + } + else + { + /* Buffer the data in the write buffer */ + + ret = rwb_writebuffer(rwb, startblock, nblocks, wrbuffer); + } + + /* On success, return the number of blocks that we were requested to write. + * This is for compatibility with the normal return of a block driver write + * method + */ + + return ret; + +#else + + return rwb->wrflush(rwb->dev, startblock, nblocks, wrbuffer); + +#endif +} + +/**************************************************************************** + * Name: rwb_mediaremoved + ****************************************************************************/ + +/* The following function is called when media is removed */ + +int rwb_mediaremoved(FAR struct rwbuffer_s *rwb) +{ +#ifdef CONFIG_FS_WRITEBUFFER + rwb_semtake(&rwb->wrsem); + rwb_resetwrbuffer(rwb); + rwb_semgive(&rwb->wrsem); +#endif + +#ifdef CONFIG_FS_READAHEAD + rwb_semtake(&rwb->rhsem); + rwb_resetrhbuffer(rwb); + rwb_semgive(&rwb->rhsem); +#endif + return 0; +} + +#endif /* CONFIG_FS_WRITEBUFFER || CONFIG_FS_READAHEAD */ + diff --git a/nuttx/drivers/sensors/Make.defs b/nuttx/drivers/sensors/Make.defs new file mode 100644 index 000000000..bda3e8ed3 --- /dev/null +++ b/nuttx/drivers/sensors/Make.defs @@ -0,0 +1,51 @@ +############################################################################ +# drivers/sensors/Make.defs +# +# Copyright (C) 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +# Include sensor drivers +# These drivers depend on I2C support + +ifeq ($(CONFIG_I2C),y) + CSRCS += lis331dl.c + +ifeq ($(CONFIG_I2C_LM75),y) + CSRCS += lm75.c +endif +endif + +# Include sensor driver build support + +DEPPATH += --dep-path sensors +VPATH += :sensors +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/sensors} diff --git a/nuttx/drivers/sensors/lis331dl.c b/nuttx/drivers/sensors/lis331dl.c new file mode 100644 index 000000000..2117a7ebd --- /dev/null +++ b/nuttx/drivers/sensors/lis331dl.c @@ -0,0 +1,320 @@ +/**************************************************************************** + * drivers/sensors/lis331dl.c + * + * Copyright (C) 2011 Uros Platise. All rights reserved. + * + * Authors: Uros Platise <uros.platise@isotel.eu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** \file + * \author Uros Platise + * \brief ST LIS331DL I2C Device Driver + **/ + +#include <nuttx/config.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/sensors/lis331dl.h> + +/************************************************************************************ + * LIS331DL Internal Registers + ************************************************************************************/ + +#define ST_LIS331DL_WHOAMI 0x0F /* who am I register */ +#define ST_LIS331DL_WHOAMI_VALUE 0x3B /* Valid result is 0x3B */ + +#define ST_LIS331DL_CTRL_REG1 0x20 +#define ST_LIS331DL_CR1_DR 0x80 /* Data-rate selection 0: 100 Hz, 1: 400 Hz */ +#define ST_LIS331DL_CR1_PD 0x40 /* Active Mode (1) / Power-down (0) */ +#define ST_LIS331DL_CR1_FS 0x20 /* Full Scale (1) +-9g or Normal Scale (0) +-2g */ +#define ST_LIS331DL_CR1_ST 0x18 /* Self test enable */ +#define ST_LIS331DL_CR1_ZEN 0x04 /* Z-Axis Enable */ +#define ST_LIS331DL_CR1_YEN 0x02 /* Y-Axis Enable */ +#define ST_LIS331DL_CR1_XEN 0x01 /* X-Axis Enable */ + +#define ST_LIS331DL_CTRL_REG2 0x21 +#define ST_LIS331DL_CTRL_REG3 0x22 + +#define ST_LIS331DL_HP_FILTER_RESET 0x23 + +#define ST_LIS331DL_STATUS_REG 0x27 /* Status Register */ +#define ST_LIS331DL_SR_ZYXOR 0x80 /* OR'ed X,Y and Z data over-run */ +#define ST_LIS331DL_SR_ZOR 0x40 /* individual data over-run ... */ +#define ST_LIS331DL_SR_YOR 0x20 +#define ST_LIS331DL_SR_XOR 0x10 +#define ST_LIS331DL_SR_ZYXDA 0x08 /* OR'ed X,Y and Z data available */ +#define ST_LIS331DL_SR_ZDA 0x04 /* individual data available ... */ +#define ST_LIS331DL_SR_YDA 0x02 +#define ST_LIS331DL_SR_XDA 0x01 + +#define ST_LIS331DL_OUT_X 0x29 +#define ST_LIS331DL_OUT_Y 0x2B +#define ST_LIS331DL_OUT_Z 0x2D + + +/************************************************************************************ + * Private Data Types + ************************************************************************************/ + +struct lis331dl_dev_s { + struct i2c_dev_s * i2c; + + uint8_t address; + struct lis331dl_vector_s a; + uint8_t cr1; + uint8_t cr2; + uint8_t cr3; +}; + + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/** LIS331DL Access with range check + * + * \param dev LIS331 DL Private Structure + * \param subaddr LIS331 Sub Address + * \param buf Pointer to buffer, either for read or write access + * \param length when >0 it denotes read access, when <0 it denotes write access of -length + * \return OK on success or errno is set. + **/ +int lis331dl_access(struct lis331dl_dev_s * dev, uint8_t subaddr, uint8_t *buf, int length) +{ + uint16_t flags = 0; + int retval; + + if (length > 0) { + flags = I2C_M_READ; + } + else { + flags = I2C_M_NORESTART; + length = -length; + } + + /* Check valid address ranges and set auto address increment flag */ + + if (subaddr == 0x0F) { + if (length > 1) length = 1; + } + else if (subaddr >= 0x20 && subaddr < 0x24) { + if (length > (0x24 - subaddr) ) length = 0x24 - subaddr; + } + else if (subaddr >= 0x27 && subaddr < 0x2E) { + if (length > (0x2E - subaddr) ) length = 0x2E - subaddr; + } + else if (subaddr >= 0x30 && subaddr < 0x40) { + if (length > (0x40 - subaddr) ) length = 0x40 - subaddr; + } + else { + errno = EFAULT; + return ERROR; + } + + if (length > 1) subaddr |= 0x80; + + /* Create message and send */ + + struct i2c_msg_s msgv[2] = { + { + .addr = dev->address, + .flags = 0, + .buffer = &subaddr, + .length = 1 + }, + { + .addr = dev->address, + .flags = flags, + .buffer = buf, + .length = length + } + }; + + if ( (retval = I2C_TRANSFER(dev->i2c, msgv, 2)) == OK ) + return length; + + return retval; +} + + +int lis331dl_readregs(struct lis331dl_dev_s * dev) +{ + if (lis331dl_access(dev, ST_LIS331DL_CTRL_REG1, &dev->cr1, 3) != 3) return ERROR; + return OK; +} + + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct lis331dl_dev_s * lis331dl_init(struct i2c_dev_s * i2c, uint16_t address) +{ + struct lis331dl_dev_s * dev; + uint8_t retval; + + ASSERT(i2c); + ASSERT(address); + + if ( (dev = kmalloc( sizeof(struct lis331dl_dev_s) )) == NULL ) { + errno = ENOMEM; + return NULL; + } + + memset(dev, 0, sizeof(struct lis331dl_dev_s)); + dev->i2c = i2c; + dev->address = address; + + /* Probe device */ + + if (lis331dl_access(dev, ST_LIS331DL_WHOAMI, &retval, 1) > 0) { + + /* Check chip identification, in the future several more compatible parts + * may be added here. + */ + + if (retval == ST_LIS331DL_WHOAMI_VALUE) { + + /* Copy LIS331DL registers to our private structure and power-up device */ + + if (lis331dl_readregs(dev)==OK && lis331dl_powerup(dev)==OK) { + + /* Normal exit point */ + errno = 0; + return dev; + } + retval = errno; + } + + /* Otherwise, we mark an invalid device found at given address */ + retval = ENODEV; + } + else { + /* No response at given address is marked as */ + retval = EFAULT; + } + + /* Error exit */ + kfree(dev); + errno = retval; + return NULL; +} + + +int lis331dl_deinit(struct lis331dl_dev_s * dev) +{ + ASSERT(dev); + + lis331dl_powerdown(dev); + kfree(dev); + + return OK; +} + + +int lis331dl_powerup(struct lis331dl_dev_s * dev) +{ + dev->cr1 = ST_LIS331DL_CR1_PD | + ST_LIS331DL_CR1_ZEN | ST_LIS331DL_CR1_YEN | ST_LIS331DL_CR1_XEN; + dev->cr2 = 0; + dev->cr3 = 0; + + if (lis331dl_access(dev, ST_LIS331DL_CTRL_REG1, &dev->cr1, -3) == 3) return OK; + return ERROR; +} + + +int lis331dl_powerdown(struct lis331dl_dev_s * dev) +{ + dev->cr1 = ST_LIS331DL_CR1_ZEN | ST_LIS331DL_CR1_YEN | ST_LIS331DL_CR1_XEN; + dev->cr2 = 0; + dev->cr3 = 0; + + if (lis331dl_access(dev, ST_LIS331DL_CTRL_REG1, &dev->cr1, -3) == 3) return OK; + return ERROR; +} + + +int lis331dl_setconversion(struct lis331dl_dev_s * dev, bool full, bool fast) +{ + dev->cr1 = ST_LIS331DL_CR1_PD | + (full ? ST_LIS331DL_CR1_FS : 0) | (fast ? ST_LIS331DL_CR1_DR : 0) | + ST_LIS331DL_CR1_ZEN | ST_LIS331DL_CR1_YEN | ST_LIS331DL_CR1_XEN; + + if (lis331dl_access(dev, ST_LIS331DL_CTRL_REG1, &dev->cr1, -1) == 1) return OK; + return ERROR; +} + + +int lis331dl_getprecision(struct lis331dl_dev_s * dev) +{ + if (dev->cr1 & ST_LIS331DL_CR1_FS) + return 9200/127; /* typ. 9.2g full scale */ + return 2300/127; /* typ. 2.3g full scale */ +} + + +int lis331dl_getsamplerate(struct lis331dl_dev_s * dev) +{ + if (dev->cr1 & ST_LIS331DL_CR1_DR) + return 400; + return 100; +} + + +const struct lis331dl_vector_s * lis331dl_getreadings(struct lis331dl_dev_s * dev) +{ + uint8_t retval[7]; + + ASSERT(dev); + + if (lis331dl_access(dev, ST_LIS331DL_STATUS_REG, retval, 7) == 7) { + + /* If result is not yet ready, return NULL */ + + if ( !(retval[0] & ST_LIS331DL_SR_ZYXDA) ) { + errno = EAGAIN; + return NULL; + } + + dev->a.x = retval[2]; + dev->a.y = retval[4]; + dev->a.z = retval[6]; + return &dev->a; + } + + return NULL; +} diff --git a/nuttx/drivers/sensors/lm75.c b/nuttx/drivers/sensors/lm75.c new file mode 100644 index 000000000..397cb9582 --- /dev/null +++ b/nuttx/drivers/sensors/lm75.c @@ -0,0 +1,537 @@ +/**************************************************************************** + * drivers/sensors/lm75.c + * Character driver for the STMicro LM-75 Temperature Sensor + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdlib.h> +#include <fixedmath.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/fs.h> +#include <nuttx/i2c.h> +#include <nuttx/sensors/lm75.h> + +#if defined(CONFIG_I2C) && defined(CONFIG_I2C_LM75) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Centigrade to Fahrenheit conversion: F = 9*C/5 + 32 */ + +#define B16_9DIV5 (9 * 65536 / 5) +#define B16_32 (32 * 65536) + +/* Debug for this file only */ + +#ifdef CONFIG_DEBUG_LM75 +# define lm75dbg dbg +#else +# ifdef CONFIG_CPP_HAVE_VARARGS +# define lm75dbg(x...) +# else +# define lm75dbg (void) +# endif +#endif + +/**************************************************************************** + * Private + ****************************************************************************/ + +struct lm75_dev_s +{ + FAR struct i2c_dev_s *i2c; /* I2C interface */ + uint8_t addr; /* I2C address */ + bool fahrenheit; /* true: temperature will be reported in fahrenheit */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +/* I2C Helpers */ + +static int lm75_readb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, + FAR b16_t *regvalue); +static int lm75_writeb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, + b16_t regval); +static int lm75_readtemp(FAR struct lm75_dev_s *priv, FAR b16_t *temp); +static int lm75_readconf(FAR struct lm75_dev_s *priv, FAR uint8_t *conf); +static int lm75_writeconf(FAR struct lm75_dev_s *priv, uint8_t conf); + +/* Character driver methods */ + +static int lm75_open(FAR struct file *filep); +static int lm75_close(FAR struct file *filep); +static ssize_t lm75_read(FAR struct file *, FAR char *, size_t); +static ssize_t lm75_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); +static int lm75_ioctl(FAR struct file *filep,int cmd,unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations lm75_fops = +{ + lm75_open, + lm75_close, + lm75_read, + lm75_write, + 0, + lm75_ioctl +#ifndef CONFIG_DISABLE_POLL + , 0 +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +/**************************************************************************** + * Name: lm75_readb16 + * + * Description: + * Read a 16-bit register (LM75_TEMP_REG, LM75_THYS_REG, or LM75_TOS_REG) + * + ****************************************************************************/ + +static int lm75_readb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, + FAR b16_t *regvalue) +{ + uint8_t buffer[2]; + int ret; + + /* Write the register address */ + + I2C_SETADDRESS(priv->i2c, priv->addr, 7); + ret = I2C_WRITE(priv->i2c, ®addr, 1); + if (ret < 0) + { + lm75dbg("I2C_WRITE failed: %d\n", ret); + return ret; + } + + /* Restart and read 16-bits from the register (discarding 7) */ + + ret = I2C_READ(priv->i2c, buffer, 2); + if (ret < 0) + { + lm75dbg("I2C_READ failed: %d\n", ret); + return ret; + } + + /* Data format is: TTTTTTTT Txxxxxxx where TTTTTTTTT is a nine-bit, + * signed temperature value with LSB = 0.5 degrees centigrade. So the + * raw data is b8_t + */ + + *regvalue = b8tob16((b8_t)buffer[0] << 8 | (b8_t)buffer[1]); + lm75dbg("addr: %02x value: %08x ret: %d\n", regaddr, *regvalue, ret); + return OK; +} + +/**************************************************************************** + * Name: lm75_writeb16 + * + * Description: + * Write to a 16-bit register (LM75_TEMP_REG, LM75_THYS_REG, or LM75_TOS_REG) + * + ****************************************************************************/ + +static int lm75_writeb16(FAR struct lm75_dev_s *priv, uint8_t regaddr, + b16_t regval) +{ + uint8_t buffer[3]; + b8_t regb8; + + lm75dbg("addr: %02x value: %08x\n", regaddr, regval); + + /* Set up a 3 byte message to send */ + + buffer[0] = regaddr; + + regb8 = b16tob8(regval); + buffer[1] = (uint8_t)(regb8 >> 8); + buffer[2] = (uint8_t)regb8; + + /* Write the register address followed by the data (no RESTART) */ + + I2C_SETADDRESS(priv->i2c, priv->addr, 7); + return I2C_WRITE(priv->i2c, buffer, 3); +} + +/**************************************************************************** + * Name: lm75_readtemp + * + * Description: + * Read the temperature register with special scaling (LM75_TEMP_REG) + * + ****************************************************************************/ + +static int lm75_readtemp(FAR struct lm75_dev_s *priv, FAR b16_t *temp) +{ + b16_t temp16; + int ret; + + /* Read the raw temperature data (b16_t) */ + + ret = lm75_readb16(priv, LM75_TEMP_REG, &temp16); + if (ret < 0) + { + lm75dbg("lm75_readb16 failed: %d\n", ret); + return ret; + } + lm75dbg("Centigrade: %08x\n", temp16); + + /* Was fahrenheit requested? */ + + if (priv->fahrenheit) + { + /* Centigrade to Fahrenheit conversion: F = 9*C/5 + 32 */ + + temp16 = b16mulb16(temp16, B16_9DIV5) + B16_32; + lm75dbg("Fahrenheit: %08x\n", temp16); + } + + *temp = temp16; + return OK; +} + +/**************************************************************************** + * Name: lm75_readconf + * + * Description: + * Read the 8-bit LM75 configuration register + * + ****************************************************************************/ + +static int lm75_readconf(FAR struct lm75_dev_s *priv, FAR uint8_t *conf) +{ + uint8_t buffer; + int ret; + + /* Write the configuration register address */ + + I2C_SETADDRESS(priv->i2c, priv->addr, 7); + + buffer = LM75_CONF_REG; + ret = I2C_WRITE(priv->i2c, &buffer, 1); + if (ret < 0) + { + lm75dbg("I2C_WRITE failed: %d\n", ret); + return ret; + } + + /* Restart and read 8-bits from the register */ + + ret = I2C_READ(priv->i2c, conf, 1); + lm75dbg("conf: %02x ret: %d\n", *conf, ret); + return ret; +} + +/**************************************************************************** + * Name: lm75_writeconf + * + * Description: + * Write to a 8-bit LM75 configuration register. + * + ****************************************************************************/ + +static int lm75_writeconf(FAR struct lm75_dev_s *priv, uint8_t conf) +{ + uint8_t buffer[2]; + + lm75dbg("conf: %02x\n", conf); + + /* Set up a 2 byte message to send */ + + buffer[0] = LM75_CONF_REG; + buffer[1] = conf; + + /* Write the register address followed by the data (no RESTART) */ + + I2C_SETADDRESS(priv->i2c, priv->addr, 7); + return I2C_WRITE(priv->i2c, buffer, 2); +} + +/**************************************************************************** + * Name: lm75_open + * + * Description: + * This function is called whenever the LM-75 device is opened. + * + ****************************************************************************/ + +static int lm75_open(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: lm75_close + * + * Description: + * This routine is called when the LM-75 device is closed. + * + ****************************************************************************/ + +static int lm75_close(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: lm75_read + ****************************************************************************/ + +static ssize_t lm75_read(FAR struct file *filep, FAR char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lm75_dev_s *priv = inode->i_private; + FAR b16_t *ptr; + ssize_t nsamples; + int i; + int ret; + + /* How many samples were requested to get? */ + + nsamples = buflen / sizeof(b16_t); + ptr = (FAR b16_t *)buffer; + + lm75dbg("buflen: %d nsamples: %d\n", buflen, nsamples); + + /* Get the requested number of samples */ + + for (i = 0; i < nsamples; i++) + { + b16_t temp; + + /* Read the next b16_t temperature value */ + + ret = lm75_readtemp(priv, &temp); + if (ret < 0) + { + lm75dbg("lm75_readtemp failed: %d\n",ret); + return (ssize_t)ret; + } + + /* Save the temperature value in the user buffer */ + + *ptr++ = temp; + } + + return nsamples * sizeof(b16_t); +} + +/**************************************************************************** + * Name: lm75_write + ****************************************************************************/ + +static ssize_t lm75_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Name: lm75_ioctl + ****************************************************************************/ + +static int lm75_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lm75_dev_s *priv = inode->i_private; + int ret = OK; + + switch (cmd) + { + /* Read from the configuration register. Arg: uint8_t* pointer */ + + case SNIOC_READCONF: + { + FAR uint8_t *ptr = (FAR uint8_t *)((uintptr_t)arg); + ret = lm75_readconf(priv, ptr); + lm75dbg("conf: %02x ret: %d\n", *ptr, ret); + } + break; + + /* Wrtie to the configuration register. Arg: uint8_t value */ + + case SNIOC_WRITECONF: + ret = lm75_writeconf(priv, (uint8_t)arg); + lm75dbg("conf: %02x ret: %d\n", *(uint8_t*)arg, ret); + break; + + /* Shutdown the LM75, Arg: None */ + + case SNIOC_SHUTDOWN: + { + uint8_t conf; + ret = lm75_readconf(priv, &conf); + if (ret == OK) + { + ret = lm75_writeconf(priv, conf | LM75_CONF_SHUTDOWN); + } + lm75dbg("conf: %02x ret: %d\n", conf | LM75_CONF_SHUTDOWN, ret); + } + break; + + /* Powerup the LM75, Arg: None */ + + case SNIOC_POWERUP: + { + uint8_t conf; + ret = lm75_readconf(priv, &conf); + if (ret == OK) + { + ret = lm75_writeconf(priv, conf & ~LM75_CONF_SHUTDOWN); + } + lm75dbg("conf: %02x ret: %d\n", conf & ~LM75_CONF_SHUTDOWN, ret); + } + break; + + /* Report samples in Fahrenheit */ + + case SNIOC_FAHRENHEIT: + priv->fahrenheit = true; + lm75dbg("Fahrenheit\n"); + break; + + /* Report Samples in Centigrade */ + + case SNIOC_CENTIGRADE: + priv->fahrenheit = false; + lm75dbg("Centigrade\n"); + break; + + /* Read THYS temperature register. Arg: b16_t* pointer */ + + case SNIOC_READTHYS: + { + FAR b16_t *ptr = (FAR b16_t *)((uintptr_t)arg); + ret = lm75_readb16(priv, LM75_THYS_REG, ptr); + lm75dbg("THYS: %08x ret: %d\n", *ptr, ret); + } + break; + + /* Write THYS temperature register. Arg: b16_t value */ + + case SNIOC_WRITETHYS: + ret = lm75_writeb16(priv, LM75_THYS_REG, (b16_t)arg); + lm75dbg("THYS: %08x ret: %d\n", (b16_t)arg, ret); + break; + + /* Read TOS (Over-temp Shutdown Threshold) Register. Arg: b16_t* pointer */ + + case SNIOC_READTOS: + { + FAR b16_t *ptr = (FAR b16_t *)((uintptr_t)arg); + ret = lm75_readb16(priv, LM75_TOS_REG, ptr); + lm75dbg("TOS: %08x ret: %d\n", *ptr, ret); + } + break; + + /* Write TOS (Over-temp Shutdown Threshold) Register. Arg: b16_t value */ + + case SNIOC_WRITRETOS: + ret = lm75_writeb16(priv, LM75_TOS_REG, (b16_t)arg); + lm75dbg("TOS: %08x ret: %d\n", (b16_t)arg, ret); + break; + + default: + lm75dbg("Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lm75_register + * + * Description: + * Register the LM-75 character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/temp0" + * i2c - An instance of the I2C interface to use to communicate with LM75 + * addr - The I2C address of the LM-75. The base I2C address of the LM75 + * is 0x48. Bits 0-3 can be controlled to get 8 unique addresses from 0x48 + * through 0x4f. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int lm75_register(FAR const char *devpath, FAR struct i2c_dev_s *i2c, uint8_t addr) +{ + FAR struct lm75_dev_s *priv; + int ret; + + /* Initialize the LM-75 device structure */ + + priv = (FAR struct lm75_dev_s *)malloc(sizeof(struct lm75_dev_s)); + if (!priv) + { + lm75dbg("Failed to allocate instance\n"); + return -ENOMEM; + } + + priv->i2c = i2c; + priv->addr = addr; + priv->fahrenheit = false; + + /* Register the character driver */ + + ret = register_driver(devpath, &lm75_fops, 0555, priv); + if (ret < 0) + { + lm75dbg("Failed to register driver: %d\n", ret); + free(priv); + } + return ret; +} +#endif /* CONFIG_I2C && CONFIG_I2C_LM75 */ diff --git a/nuttx/drivers/serial/Make.defs b/nuttx/drivers/serial/Make.defs new file mode 100644 index 000000000..b78628334 --- /dev/null +++ b/nuttx/drivers/serial/Make.defs @@ -0,0 +1,50 @@ +############################################################################ +# drivers/serial/Make.defs +# +# Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +ifneq ($(CONFIG_NFILE_DESCRIPTORS),0) + +# Include serial drivers + +CSRCS += serial.c serialirq.c lowconsole.c + +ifeq ($(CONFIG_16550_UART),y) + CSRCS += uart_16550.c +endif + +# Include serial build support + +DEPPATH += --dep-path serial +VPATH += :serial +endif diff --git a/nuttx/drivers/serial/lowconsole.c b/nuttx/drivers/serial/lowconsole.c new file mode 100644 index 000000000..1de76cc06 --- /dev/null +++ b/nuttx/drivers/serial/lowconsole.c @@ -0,0 +1,132 @@ +/**************************************************************************** + * drivers/serial/lowconsole.c + * + * Copyright (C) 2008, 2009 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/fs.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/* The architecture must provide up_putc for this driver */ + +#ifndef CONFIG_ARCH_LOWPUTC +# error "Architecture must provide up_putc() for this driver" +#endif + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static ssize_t lowconsole_read(struct file *filep, char *buffer, size_t buflen); +static ssize_t lowconsole_write(struct file *filep, const char *buffer, size_t buflen); +static int lowconsole_ioctl(struct file *filep, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Variables + ****************************************************************************/ + +static const struct file_operations g_consoleops = +{ + 0, /* open */ + 0, /* close */ + lowconsole_read, /* read */ + lowconsole_write, /* write */ + 0, /* seek */ + lowconsole_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , 0 /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lowconsole_ioctl + ****************************************************************************/ + +static int lowconsole_ioctl(struct file *filep, int cmd, unsigned long arg) +{ + return -ENOTTY; +} + +/**************************************************************************** + * Name: lowconsole_read + ****************************************************************************/ + +static ssize_t lowconsole_read(struct file *filep, char *buffer, size_t buflen) +{ + return 0; +} + +/**************************************************************************** + * Name: lowconsole_write + ****************************************************************************/ + +static ssize_t lowconsole_write(struct file *filep, const char *buffer, size_t buflen) +{ + ssize_t ret = buflen; + + for (; buflen; buflen--) + { + up_putc(*buffer++); + } + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lowconsole_init +****************************************************************************/ + +void lowconsole_init(void) +{ + (void)register_driver("/dev/console", &g_consoleops, 0666, NULL); +} diff --git a/nuttx/drivers/serial/serial.c b/nuttx/drivers/serial/serial.c new file mode 100644 index 000000000..cff810743 --- /dev/null +++ b/nuttx/drivers/serial/serial.c @@ -0,0 +1,763 @@ +/************************************************************************************ + * drivers/serial/serial.c + * + * Copyright (C) 2007-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <semaphore.h> +#include <string.h> +#include <fcntl.h> +#include <poll.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> +#include <nuttx/fs.h> +#include <nuttx/serial.h> + +/************************************************************************************ + * Definitions + ************************************************************************************/ + +/* The architecture must provide up_putc for this driver */ + +#ifndef CONFIG_ARCH_LOWPUTC +# error "Architecture must provide up_putc() for this driver" +#endif + +#define uart_putc(ch) up_putc(ch) + +#define HALF_SECOND_MSEC 500 +#define HALF_SECOND_USEC 500000L + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +static int uart_open(FAR struct file *filep); +static int uart_close(FAR struct file *filep); +static ssize_t uart_read(FAR struct file *filep, FAR char *buffer, size_t buflen); +static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); +static int uart_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +#ifndef CONFIG_DISABLE_POLL +static int uart_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup); +#endif + +/************************************************************************************ + * Private Variables + ************************************************************************************/ + +static const struct file_operations g_serialops = +{ + uart_open, /* open */ + uart_close, /* close */ + uart_read, /* read */ + uart_write, /* write */ + 0, /* seek */ + uart_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , uart_poll /* poll */ +#endif +}; + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: uart_takesem + ************************************************************************************/ + +static void uart_takesem(FAR sem_t *sem) +{ + while (sem_wait(sem) != 0) + { + /* The only case that an error should occur here is if + * the wait was awakened by a signal. + */ + + ASSERT(*get_errno_ptr() == EINTR); + } +} + +/************************************************************************************ + * Name: uart_givesem + ************************************************************************************/ + +#define uart_givesem(sem) (void)sem_post(sem) + +/**************************************************************************** + * Name: uart_pollnotify + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static void uart_pollnotify(FAR uart_dev_t *dev, pollevent_t eventset) +{ + int i; + + for (i = 0; i < CONFIG_DEV_CONSOLE_NPOLLWAITERS; i++) + { + struct pollfd *fds = dev->fds[i]; + if (fds) + { + fds->revents |= (fds->events & eventset); + if (fds->revents != 0) + { + fvdbg("Report events: %02x\n", fds->revents); + sem_post(fds->sem); + } + } + } +} +#else +# define uart_pollnotify(dev,event) +#endif + +/************************************************************************************ + * Name: uart_putxmitchar + ************************************************************************************/ + +static void uart_putxmitchar(FAR uart_dev_t *dev, int ch) +{ + irqstate_t flags; + int nexthead; + + /* Increment to see what the next head pointer will be. We need to use the "next" + * head pointer to determine when the circular buffer would overrun + */ + + nexthead = dev->xmit.head + 1; + if (nexthead >= dev->xmit.size) + { + nexthead = 0; + } + + /* Loop until we are able to add the character to the TX buffer */ + + for (;;) + { + if (nexthead != dev->xmit.tail) + { + dev->xmit.buffer[dev->xmit.head] = ch; + dev->xmit.head = nexthead; + return; + } + else + { + /* Inform the interrupt level logic that we are waiting. + * This and the following steps must be atomic. + */ + + flags = irqsave(); + dev->xmitwaiting = true; + + /* Wait for some characters to be sent from the buffer + * with the TX interrupt enabled. When the TX interrupt + * is enabled, uart_xmitchars should execute and remove + * some of the data from the TX buffer. + */ + + uart_enabletxint(dev); + uart_takesem(&dev->xmitsem); + uart_disabletxint(dev); + irqrestore(flags); + } + } +} + +/************************************************************************************ + * Name: uart_irqwrite + ************************************************************************************/ + +static ssize_t uart_irqwrite(FAR uart_dev_t *dev, FAR const char *buffer, size_t buflen) +{ + ssize_t ret = buflen; + + /* Force each character through the low level interface */ + + for (; buflen; buflen--) + { + int ch = *buffer++; + uart_putc(ch); + + /* If this is the console, then we should replace LF with LF-CR */ + + if (ch == '\n') + { + uart_putc('\r'); + } + } + + return ret; +} + +/************************************************************************************ + * Name: uart_write + ************************************************************************************/ + +static ssize_t uart_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR uart_dev_t *dev = inode->i_private; + ssize_t ret = buflen; + + /* We may receive console writes through this path from + * interrupt handlers and from debug output in the IDLE task! + * In these cases, we will need to do things a little + * differently. + */ + + if (up_interrupt_context() || getpid() == 0) + { + if (dev->isconsole) + { + irqstate_t flags = irqsave(); + ret = uart_irqwrite(dev, buffer, buflen); + irqrestore(flags); + return ret; + } + else + { + return ERROR; + } + } + + /* Only one user can be accessing dev->xmit.head at once */ + + uart_takesem(&dev->xmit.sem); + + /* Loop while we still have data to copy to the transmit buffer. + * we add data to the head of the buffer; uart_xmitchars takes the + * data from the end of the buffer. + */ + + uart_disabletxint(dev); + for (; buflen; buflen--) + { + int ch = *buffer++; + + /* Put the character into the transmit buffer */ + + uart_putxmitchar(dev, ch); + + /* If this is the console, then we should replace LF with LF-CR */ + + if (dev->isconsole && ch == '\n') + { + uart_putxmitchar(dev, '\r'); + } + } + + if (dev->xmit.head != dev->xmit.tail) + { + uart_enabletxint(dev); + } + + uart_givesem(&dev->xmit.sem); + return ret; +} + +/************************************************************************************ + * Name: uart_read + ************************************************************************************/ + +static ssize_t uart_read(FAR struct file *filep, FAR char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR uart_dev_t *dev = inode->i_private; + irqstate_t flags; + ssize_t recvd = 0; + + /* Only one user can be accessing dev->recv.tail at once */ + + uart_takesem(&dev->recv.sem); + + /* Loop while we still have data to copy to the receive buffer. + * we add data to the head of the buffer; uart_xmitchars takes the + * data from the end of the buffer. + */ + + uart_disablerxint(dev); + while (recvd < buflen) + { + /* Check if there is more data to return in the circular buffer */ + + if (dev->recv.head != dev->recv.tail) + { + *buffer++ = dev->recv.buffer[dev->recv.tail]; + recvd++; + + if (++(dev->recv.tail) >= dev->recv.size) + { + dev->recv.tail = 0; + } + } + +#ifdef CONFIG_DEV_SERIAL_FULLBLOCKS + /* No... then we would have to wait to get receive more data. + * If the user has specified the O_NONBLOCK option, then just + * return what we have. + */ + + else if (filep->f_oflags & O_NONBLOCK) + { + /* If nothing was transferred, then return the -EAGAIN + * error (not zero which means end of file). + */ + + if (recvd < 1) + { + recvd = -EAGAIN; + } + break; + } +#else + /* No... the circular buffer is empty. Have we returned anything + * to the caller? + */ + + else if (recvd > 0) + { + /* Yes.. break out of the loop and return the number of bytes + * received up to the wait condition. + */ + + break; + } + + /* No... then we would have to wait to get receive some data. + * If the user has specified the O_NONBLOCK option, then do not + * wait. + */ + + else if (filep->f_oflags & O_NONBLOCK) + { + /* Break out of the loop returning -EAGAIN */ + + recvd = -EAGAIN; + break; + } +#endif + /* Otherwise we are going to have to wait for data to arrive */ + + else + { + /* Wait for some characters to be sent from the buffer with the RX interrupt + * re-enabled. Interrupts are disabled briefly to assure that the following + * operations are atomic. + */ + + flags = irqsave(); + dev->recvwaiting = true; + uart_enablerxint(dev); + uart_takesem(&dev->recvsem); + uart_disablerxint(dev); + irqrestore(flags); + } + } + + uart_enablerxint(dev); + uart_givesem(&dev->recv.sem); + return recvd; +} + +/************************************************************************************ + * Name: uart_ioctl + ************************************************************************************/ + +static int uart_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR uart_dev_t *dev = inode->i_private; + + return dev->ops->ioctl(filep, cmd, arg); +} + +/**************************************************************************** + * Name: uart_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +int uart_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup) +{ + FAR struct inode *inode = filep->f_inode; + FAR uart_dev_t *dev = inode->i_private; + pollevent_t eventset; + int ndx; + int ret = OK; + int i; + + /* Some sanity checking */ + +#if CONFIG_DEBUG + if (!dev || !fds) + { + return -ENODEV; + } +#endif + + /* Are we setting up the poll? Or tearing it down? */ + + uart_takesem(&dev->pollsem); + if (setup) + { + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference + */ + + for (i = 0; i < CONFIG_DEV_CONSOLE_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!dev->fds[i]) + { + /* Bind the poll structure and this slot */ + + dev->fds[i] = fds; + fds->priv = &dev->fds[i]; + break; + } + } + + if (i >= CONFIG_DEV_CONSOLE_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto errout; + } + + /* Should immediately notify on any of the requested events? + * First, check if the xmit buffer is full. + */ + + eventset = 0; + + uart_takesem(&dev->xmit.sem); + ndx = dev->xmit.head + 1; + if (ndx >= dev->xmit.size) + { + ndx = 0; + } + if (ndx != dev->xmit.tail) + { + eventset |= POLLOUT; + } + uart_givesem(&dev->xmit.sem); + + /* Check if the receive buffer is empty */ + + uart_takesem(&dev->recv.sem); + if (dev->recv.head != dev->recv.tail) + { + eventset |= POLLIN; + } + uart_givesem(&dev->recv.sem); + + if (eventset) + { + uart_pollnotify(dev, eventset); + } + + } + else if (fds->priv) + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + +#ifdef CONFIG_DEBUG + if (!slot) + { + ret = -EIO; + goto errout; + } +#endif + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +errout: + uart_givesem(&dev->pollsem); + return ret; +} +#endif + +/************************************************************************************ + * Name: uart_close + * + * Description: + * This routine is called when the serial port gets closed. + * It waits for the last remaining data to be sent. + * + ************************************************************************************/ + +static int uart_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR uart_dev_t *dev = inode->i_private; + irqstate_t flags; + + uart_takesem(&dev->closesem); + if (dev->open_count > 1) + { + dev->open_count--; + uart_givesem(&dev->closesem); + return OK; + } + + /* There are no more references to the port */ + + dev->open_count = 0; + + /* Stop accepting input */ + + uart_disablerxint(dev); + + /* Now we wait for the transmit buffer to clear */ + + while (dev->xmit.head != dev->xmit.tail) + { +#ifndef CONFIG_DISABLE_SIGNALS + usleep(HALF_SECOND_USEC); +#else + up_mdelay(HALF_SECOND_MSEC); +#endif + } + + /* And wait for the TX fifo to drain */ + + while (!uart_txempty(dev)) + { +#ifndef CONFIG_DISABLE_SIGNALS + usleep(HALF_SECOND_USEC); +#else + up_mdelay(HALF_SECOND_MSEC); +#endif + } + + /* Free the IRQ and disable the UART */ + + flags = irqsave(); /* Disable interrupts */ + uart_detach(dev); /* Detach interrupts */ + if (!dev->isconsole) /* Check for the serial console UART */ + { + uart_shutdown(dev); /* Disable the UART */ + } + irqrestore(flags); + + uart_givesem(&dev->closesem); + return OK; + } + +/************************************************************************************ + * Name: uart_open + * + * Description: + * This routine is called whenever a serial port is opened. + * + ************************************************************************************/ + +static int uart_open(FAR struct file *filep) +{ + struct inode *inode = filep->f_inode; + uart_dev_t *dev = inode->i_private; + uint8_t tmp; + int ret = OK; + + /* If the port is the middle of closing, wait until the close is finished */ + + uart_takesem(&dev->closesem); + + /* Start up serial port */ + /* Increment the count of references to the device. */ + + tmp = dev->open_count + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + goto errout_with_sem; + } + + /* Check if this is the first time that the driver has been opened. */ + + if (tmp == 1) + { + irqstate_t flags = irqsave(); + + /* If this is the console, then the UART has already been initialized. */ + + if (!dev->isconsole) + { + /* Perform one time hardware initialization */ + + ret = uart_setup(dev); + if (ret < 0) + { + irqrestore(flags); + goto errout_with_sem; + } + } + + /* In any event, we do have to configure for interrupt driven mode of + * operation. Attach the hardware IRQ(s). Hmm.. should shutdown() the + * the device in the rare case that uart_attach() fails, tmp==1, and + * this is not the console. + */ + + ret = uart_attach(dev); + if (ret < 0) + { + uart_shutdown(dev); + irqrestore(flags); + goto errout_with_sem; + } + + /* Mark the io buffers empty */ + + dev->xmit.head = 0; + dev->xmit.tail = 0; + dev->recv.head = 0; + dev->recv.tail = 0; + + /* Enable the RX interrupt */ + + uart_enablerxint(dev); + irqrestore(flags); + } + + /* Save the new open count on success */ + + dev->open_count = tmp; + +errout_with_sem: + uart_givesem(&dev->closesem); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: uart_register + * + * Description: + * Register serial console and serial ports. + * + ************************************************************************************/ + +int uart_register(FAR const char *path, FAR uart_dev_t *dev) +{ + sem_init(&dev->xmit.sem, 0, 1); + sem_init(&dev->recv.sem, 0, 1); + sem_init(&dev->closesem, 0, 1); + sem_init(&dev->xmitsem, 0, 0); + sem_init(&dev->recvsem, 0, 0); +#ifndef CONFIG_DISABLE_POLL + sem_init(&dev->pollsem, 0, 1); +#endif + + dbg("Registering %s\n", path); + return register_driver(path, &g_serialops, 0666, dev); +} + +/************************************************************************************ + * Name: uart_datareceived + * + * Description: + * This function is called from uart_recvchars when new serial data is place in + * the driver's circular buffer. This function will wake-up any stalled read() + * operations that are waiting for incoming data. + * + ************************************************************************************/ + +void uart_datareceived(FAR uart_dev_t *dev) +{ + /* Awaken any awaiting read() operations */ + + if (dev->recvwaiting) + { + dev->recvwaiting = false; + (void)sem_post(&dev->recvsem); + } + + /* Notify all poll/select waiters that they can read from the recv buffer */ + + uart_pollnotify(dev, POLLIN); + +} + +/************************************************************************************ + * Name: uart_datasent + * + * Description: + * This function is called from uart_xmitchars after serial data has been sent, + * freeing up some space in the driver's circular buffer. This function will + * wake-up any stalled write() operations that was waiting for space to buffer + * outgoing data. + * + ************************************************************************************/ + +void uart_datasent(FAR uart_dev_t *dev) +{ + if (dev->xmitwaiting) + { + dev->xmitwaiting = false; + (void)sem_post(&dev->xmitsem); + } + + /* Notify all poll/select waiters that they can write to xmit buffer */ + + uart_pollnotify(dev, POLLOUT); +} + + diff --git a/nuttx/drivers/serial/serialirq.c b/nuttx/drivers/serial/serialirq.c new file mode 100644 index 000000000..22af6896f --- /dev/null +++ b/nuttx/drivers/serial/serialirq.c @@ -0,0 +1,187 @@ +/************************************************************************************ + * drivers/serial/serialirq.c + * + * Copyright (C) 2007-2009, 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************************/ + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <semaphore.h> +#include <debug.h> +#include <nuttx/serial.h> + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/************************************************************************************ + * Private Variables + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: uart_xmitchars + * + * Description: + * This function is called from the UART interrupt handler when an interrupt + * is received indicating that there is more space in the transmit FIFO. This + * function will send characters from the tail of the xmit buffer while the driver + * write() logic adds data to the head of the xmit buffer. + * + ************************************************************************************/ + +void uart_xmitchars(FAR uart_dev_t *dev) +{ + uint16_t nbytes = 0; + + /* Send while we still have data & room in the fifo */ + + while (dev->xmit.head != dev->xmit.tail && uart_txready(dev)) + { + /* Send the next byte */ + + uart_send(dev, dev->xmit.buffer[dev->xmit.tail]); + nbytes++; + + /* Increment the tail index */ + + if (++(dev->xmit.tail) >= dev->xmit.size) + { + dev->xmit.tail = 0; + } + } + + /* When all of the characters have been sent from the buffer disable the TX + * interrupt. + */ + + if (dev->xmit.head == dev->xmit.tail) + { + uart_disabletxint(dev); + } + + /* If any bytes were removed from the buffer, inform any waiters there there is + * space available. + */ + + if (nbytes) + { + uart_datasent(dev); + } +} + +/************************************************************************************ + * Name: uart_receivechars + * + * Description: + * This function is called from the UART interrupt handler when an interrupt + * is received indicating that are bytes available in the receive fifo. This + * function will add chars to head of receive buffer. Driver read() logic will + * take characters from the tail of the buffer. + * + ************************************************************************************/ + +void uart_recvchars(FAR uart_dev_t *dev) +{ + unsigned int status; + int nexthead = dev->recv.head + 1; + uint16_t nbytes = 0; + + if (nexthead >= dev->recv.size) + { + nexthead = 0; + } + + /* Loop putting characters into the receive buffer until either there are no + * further characters to available. + */ + + while (uart_rxavailable(dev)) + { + char ch = uart_receive(dev, &status); + + /* If the RX buffer becomes full, then the serial data is discarded. This is + * necessary because on most serial hardware, you must read the data in order + * to clear the RX interrupt. An option on some hardware might be to simply + * disable RX interrupts until the RX buffer becomes non-FULL. However, that + * would probably just cause the overrun to occur in hardware (unless it has + * some large internal buffering). + */ + + if (nexthead != dev->recv.tail) + { + /* Add the character to the buffer */ + + dev->recv.buffer[dev->recv.head] = ch; + nbytes++; + + /* Increment the head index */ + + dev->recv.head = nexthead; + if (++nexthead >= dev->recv.size) + { + nexthead = 0; + } + } + } + + /* If any bytes were added to the buffer, inform any waiters there there is new + * incoming data available. + */ + + if (nbytes) + { + uart_datareceived(dev); + } +} + diff --git a/nuttx/drivers/serial/uart_16550.c b/nuttx/drivers/serial/uart_16550.c new file mode 100644 index 000000000..07c0dae36 --- /dev/null +++ b/nuttx/drivers/serial/uart_16550.c @@ -0,0 +1,1162 @@ +/**************************************************************************** + * drivers/serial/uart_16550.c + * Serial driver for 16550 UART + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <semaphore.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/irq.h> +#include <nuttx/arch.h> +#include <nuttx/serial.h> +#include <nuttx/ioctl.h> +#include <nuttx/uart_16550.h> + +#include <arch/board/board.h> + +#ifdef CONFIG_16550_UART + +/**************************************************************************** + * Pre-processor definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct u16550_s +{ + uart_addrwidth_t uartbase; /* Base address of UART registers */ +#ifndef CONFIG_16550_SUPRESS_CONFIG + uint32_t baud; /* Configured baud */ + uint32_t uartclk; /* UART clock frequency */ +#endif +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + uart_datawidth_t ier; /* Saved IER value */ + uint8_t irq; /* IRQ associated with this UART */ +#endif +#ifndef CONFIG_16550_SUPRESS_CONFIG + uint8_t parity; /* 0=none, 1=odd, 2=even */ + uint8_t bits; /* Number of bits (7 or 8) */ + bool stopbits2; /* true: Configure with 2 stop bits instead of 1 */ +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int u16550_setup(struct uart_dev_s *dev); +static void u16550_shutdown(struct uart_dev_s *dev); +static int u16550_attach(struct uart_dev_s *dev); +static void u16550_detach(struct uart_dev_s *dev); +#ifndef CONFIG_SUPPRESS_SERIAL_INTS +static int u16550_interrupt(int irq, void *context); +#endif +static int u16550_ioctl(struct file *filep, int cmd, unsigned long arg); +static int u16550_receive(struct uart_dev_s *dev, uint32_t *status); +static void u16550_rxint(struct uart_dev_s *dev, bool enable); +static bool u16550_rxavailable(struct uart_dev_s *dev); +static void u16550_send(struct uart_dev_s *dev, int ch); +static void u16550_txint(struct uart_dev_s *dev, bool enable); +static bool u16550_txready(struct uart_dev_s *dev); +static bool u16550_txempty(struct uart_dev_s *dev); + +/**************************************************************************** + * Private Variables + ****************************************************************************/ + +struct uart_ops_s g_uart_ops = +{ + .setup = u16550_setup, + .shutdown = u16550_shutdown, + .attach = u16550_attach, + .detach = u16550_detach, + .ioctl = u16550_ioctl, + .receive = u16550_receive, + .rxint = u16550_rxint, + .rxavailable = u16550_rxavailable, + .send = u16550_send, + .txint = u16550_txint, + .txready = u16550_txready, + .txempty = u16550_txempty, +}; + +/* I/O buffers */ + +#ifdef CONFIG_16550_UART0 +static char g_uart0rxbuffer[CONFIG_UART0_RXBUFSIZE]; +static char g_uart0txbuffer[CONFIG_UART0_TXBUFSIZE]; +#endif +#ifdef CONFIG_16550_UART1 +static char g_uart1rxbuffer[CONFIG_UART1_RXBUFSIZE]; +static char g_uart1txbuffer[CONFIG_UART1_TXBUFSIZE]; +#endif +#ifdef CONFIG_16550_UART2 +static char g_uart2rxbuffer[CONFIG_UART1_RXBUFSIZE]; +static char g_uart2txbuffer[CONFIG_UART1_TXBUFSIZE]; +#endif +#ifdef CONFIG_16550_UART3 +static char g_uart3rxbuffer[CONFIG_UART1_RXBUFSIZE]; +static char g_uart3txbuffer[CONFIG_UART1_TXBUFSIZE]; +#endif + +/* This describes the state of the LPC17xx uart0 port. */ + +#ifdef CONFIG_16550_UART0 +static struct u16550_s g_uart0priv = +{ + .uartbase = CONFIG_16550_UART0_BASE, +#ifndef CONFIG_16550_SUPRESS_CONFIG + .baud = CONFIG_UART0_BAUD, + .uartclk = CONFIG_16550_UART0_CLOCK, +#endif +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + .irq = CONFIG_16550_UART0_IRQ, +#endif +#ifndef CONFIG_16550_SUPRESS_CONFIG + .parity = CONFIG_UART0_PARITY, + .bits = CONFIG_UART0_BITS, + .stopbits2 = CONFIG_UART0_2STOP, +#endif +}; + +static uart_dev_t g_uart0port = +{ + .recv = + { + .size = CONFIG_UART0_RXBUFSIZE, + .buffer = g_uart0rxbuffer, + }, + .xmit = + { + .size = CONFIG_UART0_TXBUFSIZE, + .buffer = g_uart0txbuffer, + }, + .ops = &g_uart_ops, + .priv = &g_uart0priv, +}; +#endif + +/* This describes the state of the LPC17xx uart1 port. */ + +#ifdef CONFIG_16550_UART1 +static struct u16550_s g_uart1priv = +{ + .uartbase = CONFIG_16550_UART1_BASE, +#ifndef CONFIG_16550_SUPRESS_CONFIG + .baud = CONFIG_UART1_BAUD, + .uartclk = CONFIG_16550_UART1_CLOCK, +#endif +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + .irq = CONFIG_16550_UART1_IRQ, +#endif +#ifndef CONFIG_16550_SUPRESS_CONFIG + .parity = CONFIG_UART1_PARITY, + .bits = CONFIG_UART1_BITS, + .stopbits2 = CONFIG_UART1_2STOP, +#endif +}; + +static uart_dev_t g_uart1port = +{ + .recv = + { + .size = CONFIG_UART1_RXBUFSIZE, + .buffer = g_uart1rxbuffer, + }, + .xmit = + { + .size = CONFIG_UART1_TXBUFSIZE, + .buffer = g_uart1txbuffer, + }, + .ops = &g_uart_ops, + .priv = &g_uart1priv, +}; +#endif + +/* This describes the state of the LPC17xx uart1 port. */ + +#ifdef CONFIG_16550_UART2 +static struct u16550_s g_uart2priv = +{ + .uartbase = CONFIG_16550_UART2_BASE, +#ifndef CONFIG_16550_SUPRESS_CONFIG + .baud = CONFIG_UART2_BAUD, + .uartclk = CONFIG_16550_UART2_CLOCK, +#endif +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + .irq = CONFIG_16550_UART2_IRQ, +#endif +#ifndef CONFIG_16550_SUPRESS_CONFIG + .parity = CONFIG_UART2_PARITY, + .bits = CONFIG_UART2_BITS, + .stopbits2 = CONFIG_UART2_2STOP, +#endif +}; + +static uart_dev_t g_uart2port = +{ + .recv = + { + .size = CONFIG_UART2_RXBUFSIZE, + .buffer = g_uart2rxbuffer, + }, + .xmit = + { + .size = CONFIG_UART2_TXBUFSIZE, + .buffer = g_uart2txbuffer, + }, + .ops = &g_uart_ops, + .priv = &g_uart2priv, +}; +#endif + +/* This describes the state of the LPC17xx uart1 port. */ + +#ifdef CONFIG_16550_UART3 +static struct u16550_s g_uart3priv = +{ + .uartbase = CONFIG_16550_UART3_BASE, +#ifndef CONFIG_16550_SUPRESS_CONFIG + .baud = CONFIG_UART3_BAUD, + .uartclk = CONFIG_16550_UART3_CLOCK, +#endif +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + .irq = CONFIG_16550_UART3_IRQ, +#endif +#ifndef CONFIG_16550_SUPRESS_CONFIG + .parity = CONFIG_UART3_PARITY, + .bits = CONFIG_UART3_BITS, + .stopbits2 = CONFIG_UART3_2STOP, +#endif +}; + +static uart_dev_t g_uart3port = +{ + .recv = + { + .size = CONFIG_UART3_RXBUFSIZE, + .buffer = g_uart3rxbuffer, + }, + .xmit = + { + .size = CONFIG_UART3_TXBUFSIZE, + .buffer = g_uart3txbuffer, + }, + .ops = &g_uart_ops, + .priv = &g_uart3priv, +}; +#endif + +/* Which UART with be tty0/console and which tty1? tty2? tty3? */ + +#if defined(CONFIG_UART0_SERIAL_CONSOLE) +# define CONSOLE_DEV g_uart0port /* UART0=console */ +# define TTYS0_DEV g_uart0port /* UART0=ttyS0 */ +# ifdef CONFIG_16550_UART1 +# define TTYS1_DEV g_uart1port /* UART0=ttyS0;UART1=ttyS1 */ +# ifdef CONFIG_16550_UART2 +# define TTYS2_DEV g_uart2port /* UART0=ttyS0;UART1=ttyS1;UART2=ttyS2 */ +# ifdef CONFIG_16550_UART3 +# define TTYS3_DEV g_uart3port /* UART0=ttyS0;UART1=ttyS1;UART2=ttyS2;UART3=ttyS3 */ +# else +# undef TTYS3_DEV /* UART0=ttyS0;UART1=ttyS1;UART2=ttyS;No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART3 +# define TTYS2_DEV g_uart3port /* UART0=ttyS0;UART1=ttyS1;UART3=ttys2;No ttyS3 */ +# else +# undef TTYS2_DEV /* UART0=ttyS0;UART1=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART2 +# define TTYS1_DEV g_uart2port /* UART0=ttyS0;UART2=ttyS1;No ttyS3 */ +# ifdef CONFIG_16550_UART3 +# define TTYS2_DEV g_uart3port /* UART0=ttyS0;UART2=ttyS1;UART3=ttyS2;No ttyS3 */ +# else +# undef TTYS2_DEV /* UART0=ttyS0;UART2=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# else +# ifdef CONFIG_16550_UART3 +# define TTYS1_DEV g_uart3port /* UART0=ttyS0;UART3=ttyS1;No ttyS2;No ttyS3 */ +# else +# undef TTYS1_DEV /* UART0=ttyS0;No ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS2_DEV /* No ttyS2 */ +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# endif +#elif defined(CONFIG_UART1_SERIAL_CONSOLE) +# define CONSOLE_DEV g_uart1port /* UART1=console */ +# define TTYS0_DEV g_uart1port /* UART1=ttyS0 */ +# ifdef CONFIG_16550_UART +# define TTYS1_DEV g_uart0port /* UART1=ttyS0;UART0=ttyS1 */ +# ifdef CONFIG_16550_UART2 +# define TTYS2_DEV g_uart2port /* UART1=ttyS0;UART0=ttyS1;UART2=ttyS2 */ +# ifdef CONFIG_16550_UART3 +# define TTYS3_DEV g_uart3port /* UART1=ttyS0;UART0=ttyS1;UART2=ttyS2;UART3=ttyS3 */ +# else +# undef TTYS3_DEV /* UART1=ttyS0;UART0=ttyS1;UART2=ttyS;No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART3 +# define TTYS2_DEV g_uart3port /* UART1=ttyS0;UART0=ttyS1;UART3=ttys2;No ttyS3 */ +# else +# undef TTYS2_DEV /* UART1=ttyS0;UART0=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART2 +# define TTYS1_DEV g_uart2port /* UART1=ttyS0;UART2=ttyS1 */ +# ifdef CONFIG_16550_UART3 +# define TTYS2_DEV g_uart3port /* UART1=ttyS0;UART2=ttyS1;UART3=ttyS2;No ttyS3 */ +# else +# undef TTYS2_DEV /* UART1=ttyS0;UART2=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# else +# ifdef CONFIG_16550_UART3 +# define TTYS1_DEV g_uart3port /* UART1=ttyS0;UART3=ttyS1;No ttyS2;No ttyS3 */ +# else +# undef TTYS1_DEV /* UART1=ttyS0;No ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS2_DEV /* No ttyS2 */ +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# endif +#elif defined(CONFIG_UART2_SERIAL_CONSOLE) +# define CONSOLE_DEV g_uart2port /* UART2=console */ +# define TTYS0_DEV g_uart2port /* UART2=ttyS0 */ +# ifdef CONFIG_16550_UART +# define TTYS1_DEV g_uart0port /* UART2=ttyS0;UART0=ttyS1 */ +# ifdef CONFIG_16550_UART1 +# define TTYS2_DEV g_uart1port /* UART2=ttyS0;UART0=ttyS1;UART1=ttyS2 */ +# ifdef CONFIG_16550_UART3 +# define TTYS3_DEV g_uart3port /* UART2=ttyS0;UART0=ttyS1;UART1=ttyS2;UART3=ttyS3 */ +# else +# undef TTYS3_DEV /* UART2=ttyS0;UART0=ttyS1;UART1=ttyS;No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART3 +# define TTYS2_DEV g_uart3port /* UART2=ttyS0;UART0=ttyS1;UART3=ttys2;No ttyS3 */ +# else +# undef TTYS2_DEV /* UART2=ttyS0;UART0=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART1 +# define TTYS1_DEV g_uart1port /* UART2=ttyS0;UART1=ttyS1 */ +# ifdef CONFIG_16550_UART3 +# define TTYS2_DEV g_uart3port /* UART2=ttyS0;UART1=ttyS1;UART3=ttyS2 */ +# else +# undef TTYS2_DEV /* UART2=ttyS0;UART1=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# else +# ifdef CONFIG_16550_UART3 +# define TTYS1_DEV g_uart3port /* UART2=ttyS0;UART3=ttyS1;No ttyS3 */ +# else +# undef TTYS1_DEV /* UART2=ttyS0;No ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS2_DEV /* No ttyS2 */ +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# endif +#elif defined(CONFIG_UART3_SERIAL_CONSOLE) +# define CONSOLE_DEV g_uart3port /* UART3=console */ +# define TTYS0_DEV g_uart3port /* UART3=ttyS0 */ +# ifdef CONFIG_16550_UART +# define TTYS1_DEV g_uart0port /* UART3=ttyS0;UART0=ttyS1 */ +# ifdef CONFIG_16550_UART1 +# define TTYS2_DEV g_uart1port /* UART3=ttyS0;UART0=ttyS1;UART1=ttyS2 */ +# ifdef CONFIG_16550_UART2 +# define TTYS3_DEV g_uart2port /* UART3=ttyS0;UART0=ttyS1;UART1=ttyS2;UART2=ttyS3 */ +# else +# undef TTYS3_DEV /* UART3=ttyS0;UART0=ttyS1;UART1=ttyS;No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART2 +# define TTYS2_DEV g_uart2port /* UART3=ttyS0;UART0=ttyS1;UART2=ttys2;No ttyS3 */ +# else +# undef TTYS2_DEV /* UART3=ttyS0;UART0=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# else +# ifdef CONFIG_16550_UART1 +# define TTYS1_DEV g_uart1port /* UART3=ttyS0;UART1=ttyS1 */ +# ifdef CONFIG_16550_UART2 +# define TTYS2_DEV g_uart2port /* UART3=ttyS0;UART1=ttyS1;UART2=ttyS2;No ttyS3 */ +# else +# undef TTYS2_DEV /* UART3=ttyS0;UART1=ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS3_DEV /* No ttyS3 */ +# else +# ifdef CONFIG_16550_UART2 +# define TTYS1_DEV g_uart2port /* UART3=ttyS0;UART2=ttyS1;No ttyS3;No ttyS3 */ +# undef TTYS3_DEV /* UART3=ttyS0;UART2=ttyS1;No ttyS2;No ttyS3 */ +# else +# undef TTYS1_DEV /* UART3=ttyS0;No ttyS1;No ttyS2;No ttyS3 */ +# endif +# undef TTYS2_DEV /* No ttyS2 */ +# undef TTYS3_DEV /* No ttyS3 */ +# endif +# endif +#endif + +/************************************************************************************ + * Inline Functions + ************************************************************************************/ + +/**************************************************************************** + * Name: u16550_serialin + ****************************************************************************/ + +static inline uart_datawidth_t u16550_serialin(struct u16550_s *priv, int offset) +{ + return uart_getreg(priv->uartbase, offset); +} + +/**************************************************************************** + * Name: u16550_serialout + ****************************************************************************/ + +static inline void u16550_serialout(struct u16550_s *priv, int offset, uart_datawidth_t value) +{ + uart_putreg(priv->uartbase, offset, value); +} + +/**************************************************************************** + * Name: u16550_disableuartint + ****************************************************************************/ + +#ifndef CONFIG_SUPPRESS_SERIAL_INTS +static inline void u16550_disableuartint(struct u16550_s *priv, uart_datawidth_t *ier) +{ + if (ier) + { + *ier = priv->ier & UART_IER_ALLIE; + } + + priv->ier &= ~UART_IER_ALLIE; + u16550_serialout(priv, UART_IER_OFFSET, priv->ier); +} +#else +# define u16550_disableuartint(priv,ier) +#endif + +/**************************************************************************** + * Name: u16550_restoreuartint + ****************************************************************************/ + +#ifndef CONFIG_SUPPRESS_SERIAL_INTS +static inline void u16550_restoreuartint(struct u16550_s *priv, uint32_t ier) +{ + priv->ier |= ier & UART_IER_ALLIE; + u16550_serialout(priv, UART_IER_OFFSET, priv->ier); +} +#else +# define u16550_restoreuartint(priv,ier) +#endif + +/**************************************************************************** + * Name: u16550_enablebreaks + ****************************************************************************/ + +static inline void u16550_enablebreaks(struct u16550_s *priv, bool enable) +{ + uint32_t lcr = u16550_serialin(priv, UART_LCR_OFFSET); + if (enable) + { + lcr |= UART_LCR_BRK; + } + else + { + lcr &= ~UART_LCR_BRK; + } + u16550_serialout(priv, UART_LCR_OFFSET, lcr); +} + +/************************************************************************************ + * Name: u16550_divisor + * + * Descrption: + * Select a divider to produce the BAUD from the UART_CLK. + * + * BAUD = UART_CLK / (16 * DL), or + * DIV = UART_CLK / BAUD / 16 + * + * Ignoring the fractional divider for now. + * + ************************************************************************************/ + +#ifndef CONFIG_16550_SUPRESS_CONFIG +static inline uint32_t u16550_divisor(struct u16550_s *priv) +{ + return (priv->uartclk + (priv->baud << 3)) / (priv->baud << 4); +} +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: u16550_setup + * + * Description: + * Configure the UART baud, bits, parity, fifos, etc. This + * method is called the first time that the serial port is + * opened. + * + ****************************************************************************/ + +static int u16550_setup(struct uart_dev_s *dev) +{ +#ifndef CONFIG_16550_SUPRESS_CONFIG + struct u16550_s *priv = (struct u16550_s*)dev->priv; + uint16_t div; + uint32_t lcr; + + /* Clear fifos */ + + u16550_serialout(priv, UART_FCR_OFFSET, (UART_FCR_RXRST|UART_FCR_TXRST)); + + /* Set trigger */ + + u16550_serialout(priv, UART_FCR_OFFSET, (UART_FCR_FIFOEN|UART_FCR_RXTRIGGER_8)); + + /* Set up the IER */ + +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + priv->ier = u16550_serialin(priv, UART_IER_OFFSET); +#endif + + /* Set up the LCR */ + + lcr = 0; + switch (priv->bits) + { + case 5 : + lcr |= UART_LCR_WLS_7BIT; + break; + + case 6 : + lcr |= UART_LCR_WLS_7BIT; + break; + + case 7 : + lcr |= UART_LCR_WLS_7BIT; + break; + + default: + case 8 : + lcr |= UART_LCR_WLS_7BIT; + break; + } + + if (priv->stopbits2) + { + lcr |= UART_LCR_STB; + } + + if (priv->parity == 1) + { + lcr |= UART_LCR_PEN; + } + else if (priv->parity == 2) + { + lcr |= (UART_LCR_PEN|UART_LCR_EPS); + } + + /* Enter DLAB=1 */ + + u16550_serialout(priv, UART_LCR_OFFSET, (lcr | UART_LCR_DLAB)); + + /* Set the BAUD divisor */ + + div = u16550_divisor(priv); + u16550_serialout(priv, UART_DLM_OFFSET, div >> 8); + u16550_serialout(priv, UART_DLL_OFFSET, div & 0xff); + + /* Clear DLAB */ + + u16550_serialout(priv, UART_LCR_OFFSET, lcr); + + /* Configure the FIFOs */ + + u16550_serialout(priv, UART_FCR_OFFSET, + (UART_FCR_RXTRIGGER_8|UART_FCR_TXRST|UART_FCR_RXRST|UART_FCR_FIFOEN)); +#endif + return OK; +} + +/**************************************************************************** + * Name: u16550_shutdown + * + * Description: + * Disable the UART. This method is called when the serial + * port is closed + * + ****************************************************************************/ + +static void u16550_shutdown(struct uart_dev_s *dev) +{ + struct u16550_s *priv = (struct u16550_s*)dev->priv; + u16550_disableuartint(priv, NULL); +} + +/**************************************************************************** + * Name: u16550_attach + * + * Description: + * Configure the UART to operation in interrupt driven mode. This method is + * called when the serial port is opened. Normally, this is just after the + * the setup() method is called, however, the serial console may operate in + * a non-interrupt driven mode during the boot phase. + * + * RX and TX interrupts are not enabled when by the attach method (unless the + * hardware supports multiple levels of interrupt enabling). The RX and TX + * interrupts are not enabled until the txint() and rxint() methods are called. + * + ****************************************************************************/ + +static int u16550_attach(struct uart_dev_s *dev) +{ +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + struct u16550_s *priv = (struct u16550_s*)dev->priv; + int ret; + + /* Attach and enable the IRQ */ + + ret = irq_attach(priv->irq, u16550_interrupt); +#ifndef CONFIG_ARCH_NOINTC + if (ret == OK) + { + /* Enable the interrupt (RX and TX interrupts are still disabled + * in the UART + */ + + up_enable_irq(priv->irq); + } +#endif + return ret; +#else + return OK; +#endif +} + +/**************************************************************************** + * Name: u16550_detach + * + * Description: + * Detach UART interrupts. This method is called when the serial port is + * closed normally just before the shutdown method is called. The exception is + * the serial console which is never shutdown. + * + ****************************************************************************/ + +static void u16550_detach(struct uart_dev_s *dev) +{ +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + struct u16550_s *priv = (struct u16550_s*)dev->priv; +#ifndef CONFIG_ARCH_NOINTC + up_disable_irq(priv->irq); +#endif + irq_detach(priv->irq); +#endif +} + +/**************************************************************************** + * Name: u16550_interrupt + * + * Description: + * This is the UART interrupt handler. It will be invoked when an + * interrupt received on the 'irq' It should call uart_transmitchars or + * uart_receivechar to perform the appropriate data transfers. The + * interrupt handling logic must be able to map the 'irq' number into the + * appropriate u16550_s structure in order to call these functions. + * + ****************************************************************************/ + +#ifndef CONFIG_SUPPRESS_SERIAL_INTS +static int u16550_interrupt(int irq, void *context) +{ + struct uart_dev_s *dev = NULL; + struct u16550_s *priv; + uint32_t status; + int passes; + +#ifdef CONFIG_16550_UART0 + if (g_uart0priv.irq == irq) + { + dev = &g_uart0port; + } + else +#endif +#ifdef CONFIG_16550_UART1 + if (g_uart1priv.irq == irq) + { + dev = &g_uart1port; + } + else +#endif +#ifdef CONFIG_16550_UART2 + if (g_uart2priv.irq == irq) + { + dev = &g_uart2port; + } + else +#endif +#ifdef CONFIG_16550_UART3 + if (g_uart3priv.irq == irq) + { + dev = &g_uart3port; + } +#endif + ASSERT(dev != NULL); + priv = (struct u16550_s*)dev->priv; + + /* Loop until there are no characters to be transferred or, + * until we have been looping for a long time. + */ + + for (passes = 0; passes < 256; passes++) + { + /* Get the current UART status and check for loop + * termination conditions + */ + + status = u16550_serialin(priv, UART_IIR_OFFSET); + + /* The UART_IIR_INTSTATUS bit should be zero if there are pending + * interrupts + */ + + if ((status & UART_IIR_INTSTATUS) != 0) + { + /* Break out of the loop when there is no longer a + * pending interrupt + */ + + break; + } + + /* Handle the interrupt by its interrupt ID field */ + + switch (status & UART_IIR_INTID_MASK) + { + /* Handle incoming, receive bytes (with or without timeout) */ + + case UART_IIR_INTID_RDA: + case UART_IIR_INTID_CTI: + { + uart_recvchars(dev); + break; + } + + /* Handle outgoing, transmit bytes */ + + case UART_IIR_INTID_THRE: + { + uart_xmitchars(dev); + break; + } + + /* Just clear modem status interrupts (UART1 only) */ + + case UART_IIR_INTID_MSI: + { + /* Read the modem status register (MSR) to clear */ + + status = u16550_serialin(priv, UART_MSR_OFFSET); + vdbg("MSR: %02x\n", status); + break; + } + + /* Just clear any line status interrupts */ + + case UART_IIR_INTID_RLS: + { + /* Read the line status register (LSR) to clear */ + + status = u16550_serialin(priv, UART_LSR_OFFSET); + vdbg("LSR: %02x\n", status); + break; + } + + /* There should be no other values */ + + default: + { + dbg("Unexpected IIR: %02x\n", status); + break; + } + } + } + return OK; +} +#endif + +/**************************************************************************** + * Name: u16550_ioctl + * + * Description: + * All ioctl calls will be routed through this method + * + ****************************************************************************/ + +static int u16550_ioctl(struct file *filep, int cmd, unsigned long arg) +{ + struct inode *inode = filep->f_inode; + struct uart_dev_s *dev = inode->i_private; + struct u16550_s *priv = (struct u16550_s*)dev->priv; + int ret = OK; + + switch (cmd) + { + case TIOCSERGSTRUCT: + { + struct u16550_s *user = (struct u16550_s*)arg; + if (!user) + { + *get_errno_ptr() = EINVAL; + ret = ERROR; + } + else + { + memcpy(user, dev, sizeof(struct u16550_s)); + } + } + break; + + case TIOCSBRK: /* BSD compatibility: Turn break on, unconditionally */ + { + irqstate_t flags = irqsave(); + u16550_enablebreaks(priv, true); + irqrestore(flags); + } + break; + + case TIOCCBRK: /* BSD compatibility: Turn break off, unconditionally */ + { + irqstate_t flags; + flags = irqsave(); + u16550_enablebreaks(priv, false); + irqrestore(flags); + } + break; + + default: + *get_errno_ptr() = ENOTTY; + ret = ERROR; + break; + } + + return ret; +} + +/**************************************************************************** + * Name: u16550_receive + * + * Description: + * Called (usually) from the interrupt level to receive one + * character from the UART. Error bits associated with the + * receipt are provided in the return 'status'. + * + ****************************************************************************/ + +static int u16550_receive(struct uart_dev_s *dev, uint32_t *status) +{ + struct u16550_s *priv = (struct u16550_s*)dev->priv; + uint32_t rbr; + + *status = u16550_serialin(priv, UART_LSR_OFFSET); + rbr = u16550_serialin(priv, UART_RBR_OFFSET); + return rbr; +} + +/**************************************************************************** + * Name: u16550_rxint + * + * Description: + * Call to enable or disable RX interrupts + * + ****************************************************************************/ + +static void u16550_rxint(struct uart_dev_s *dev, bool enable) +{ +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + struct u16550_s *priv = (struct u16550_s*)dev->priv; + if (enable) + { + priv->ier |= UART_IER_ERBFI; + } + else + { + priv->ier &= ~UART_IER_ERBFI; + } + u16550_serialout(priv, UART_IER_OFFSET, priv->ier); +#endif +} + +/**************************************************************************** + * Name: u16550_rxavailable + * + * Description: + * Return true if the receive fifo is not empty + * + ****************************************************************************/ + +static bool u16550_rxavailable(struct uart_dev_s *dev) +{ + struct u16550_s *priv = (struct u16550_s*)dev->priv; + return ((u16550_serialin(priv, UART_LSR_OFFSET) & UART_LSR_DR) != 0); +} + +/**************************************************************************** + * Name: u16550_send + * + * Description: + * This method will send one byte on the UART + * + ****************************************************************************/ + +static void u16550_send(struct uart_dev_s *dev, int ch) +{ + struct u16550_s *priv = (struct u16550_s*)dev->priv; + u16550_serialout(priv, UART_THR_OFFSET, (uart_datawidth_t)ch); +} + +/**************************************************************************** + * Name: u16550_txint + * + * Description: + * Call to enable or disable TX interrupts + * + ****************************************************************************/ + +static void u16550_txint(struct uart_dev_s *dev, bool enable) +{ +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + struct u16550_s *priv = (struct u16550_s*)dev->priv; + irqstate_t flags; + + flags = irqsave(); + if (enable) + { + priv->ier |= UART_IER_ETBEI; + u16550_serialout(priv, UART_IER_OFFSET, priv->ier); + + /* Fake a TX interrupt here by just calling uart_xmitchars() with + * interrupts disabled (note this may recurse). + */ + + uart_xmitchars(dev); + } + else + { + priv->ier &= ~UART_IER_ETBEI; + u16550_serialout(priv, UART_IER_OFFSET, priv->ier); + } + irqrestore(flags); +#endif +} + +/**************************************************************************** + * Name: u16550_txready + * + * Description: + * Return true if the tranmsit fifo is not full + * + ****************************************************************************/ + +static bool u16550_txready(struct uart_dev_s *dev) +{ + struct u16550_s *priv = (struct u16550_s*)dev->priv; + return ((u16550_serialin(priv, UART_LSR_OFFSET) & UART_LSR_THRE) != 0); +} + +/**************************************************************************** + * Name: u16550_txempty + * + * Description: + * Return true if the transmit fifo is empty + * + ****************************************************************************/ + +static bool u16550_txempty(struct uart_dev_s *dev) +{ + struct u16550_s *priv = (struct u16550_s*)dev->priv; + return ((u16550_serialin(priv, UART_LSR_OFFSET) & UART_LSR_THRE) != 0); +} + +/**************************************************************************** + * Name: u16550_putc + * + * Description: + * Write one character to the UART (polled) + * + ****************************************************************************/ + +static void u16550_putc(struct u16550_s *priv, int ch) +{ + while ((u16550_serialin(priv, UART_LSR_OFFSET) & UART_LSR_THRE) != 0); + u16550_serialout(priv, UART_THR_OFFSET, (uart_datawidth_t)ch); +} + +/**************************************************************************** + * Public Funtions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_earlyserialinit + * + * Description: + * Performs the low level UART initialization early in debug so that the + * serial console will be available during bootup. This must be called + * before uart_serialinit. + * + * NOTE: Configuration of the CONSOLE UART was performed by uart_lowsetup() + * very early in the boot sequence. + * + ****************************************************************************/ + +void up_earlyserialinit(void) +{ + /* Configure all UARTs (except the CONSOLE UART) and disable interrupts */ + +#ifdef CONFIG_16550_UART0 + u16550_disableuartint(&g_uart0priv, NULL); +#endif +#ifdef CONFIG_16550_UART1 + u16550_disableuartint(&g_uart1priv, NULL); +#endif +#ifdef CONFIG_16550_UART2 + u16550_disableuartint(&g_uart2priv, NULL); +#endif +#ifdef CONFIG_16550_UART3 + u16550_disableuartint(&g_uart3priv, NULL); +#endif + + /* Configuration whichever one is the console */ + +#ifdef CONSOLE_DEV + CONSOLE_DEV.isconsole = true; + u16550_setup(&CONSOLE_DEV); +#endif +} + +/**************************************************************************** + * Name: up_serialinit + * + * Description: + * Register serial console and serial ports. This assumes that + * up_earlyserialinit was called previously. + * + ****************************************************************************/ + +void up_serialinit(void) +{ +#ifdef CONSOLE_DEV + (void)uart_register("/dev/console", &CONSOLE_DEV); +#endif +#ifdef TTYS0_DEV + (void)uart_register("/dev/ttyS0", &TTYS0_DEV); +#endif +#ifdef TTYS1_DEV + (void)uart_register("/dev/ttyS1", &TTYS1_DEV); +#endif +#ifdef TTYS2_DEV + (void)uart_register("/dev/ttyS2", &TTYS2_DEV); +#endif +#ifdef TTYS3_DEV + (void)uart_register("/dev/ttyS3", &TTYS3_DEV); +#endif +} + +/**************************************************************************** + * Name: up_putc + * + * Description: + * Provide priority, low-level access to support OS debug writes + * + ****************************************************************************/ + +#ifdef HAVE_16550_CONSOLE +int up_putc(int ch) +{ + struct u16550_s *priv = (struct u16550_s*)CONSOLE_DEV.priv; +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + uart_datawidth_t ier; + + u16550_disableuartint(priv, &ier); +#endif + + /* Check for LF */ + + if (ch == '\n') + { + /* Add CR */ + + u16550_putc(priv, '\r'); + } + + u16550_putc(priv, ch); +#ifndef CONFIG_SUPPRESS_SERIAL_INTS + u16550_restoreuartint(priv, ier); +#endif + return ch; +} +#endif + +#endif /* CONFIG_UART_16550 */ diff --git a/nuttx/drivers/usbdev/Make.defs b/nuttx/drivers/usbdev/Make.defs new file mode 100644 index 000000000..4ae4a49e1 --- /dev/null +++ b/nuttx/drivers/usbdev/Make.defs @@ -0,0 +1,63 @@ +############################################################################ +# drivers/usbdev/Make.defs +# +# Copyright (C) 2008, 2010-2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +ifeq ($(CONFIG_USBDEV),y) + +# Include USB device drivers + +ifeq ($(CONFIG_USBSER),y) + CSRCS += usbdev_serial.c +endif + +ifeq ($(CONFIG_CDCSER),y) + CSRCS += cdc_serial.c cdc_serdesc.c +endif + +ifeq ($(CONFIG_USBSTRG),y) + CSRCS += usbdev_storage.c usbdev_stordesc.c usbdev_scsi.c +endif + +ifeq ($(CONFIG_USBDEV_COMPOSITE),y) + CSRCS += usbdev_composite.c +endif + +CSRCS += usbdev_trace.c usbdev_trprintf.c + +# Include USB device build support + +DEPPATH += --dep-path usbdev +VPATH += :usbdev +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/usbdev} +endif diff --git a/nuttx/drivers/usbdev/cdc_serdesc.c b/nuttx/drivers/usbdev/cdc_serdesc.c new file mode 100644 index 000000000..990704a1b --- /dev/null +++ b/nuttx/drivers/usbdev/cdc_serdesc.c @@ -0,0 +1,590 @@ +/**************************************************************************** + * drivers/usbdev/cdc_serdesc.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/usb/usb.h> +#include <nuttx/usb/cdc.h> +#include <nuttx/usb/cdc_serial.h> +#include <nuttx/usb/usbdev_trace.h> + +#include "cdc_serial.h" + +/**************************************************************************** + * Definitions + ****************************************************************************/ +/* Descriptors **************************************************************/ +/* These settings are not modifiable via the NuttX configuration */ + +#define CDC_VERSIONNO 0x0110 /* CDC version number 1.10 (BCD) */ + +/* Device descriptor values */ + +#define CDCSER_VERSIONNO (0x0101) /* Device version number 1.1 (BCD) */ +#define CDCSER_NCONFIGS (1) /* Number of configurations supported */ + +/* Configuration descriptor values */ + +#define CDCSER_NINTERFACES (2) /* Number of interfaces in the configuration */ + +/* String language */ + +#define CDCSER_STR_LANGUAGE (0x0409) /* en-us */ + +/* Descriptor strings */ + +#define CDCSER_MANUFACTURERSTRID (1) +#define CDCSER_PRODUCTSTRID (2) +#define CDCSER_SERIALSTRID (3) +#define CDCSER_CONFIGSTRID (4) +#define CDCSER_NOTIFSTRID (5) +#define CDCSER_DATAIFSTRID (6) + +/* Number of individual descriptors in the configuration descriptor */ + +#define CDCSER_CFGGROUP_SIZE (9) + +/* The size of the config descriptor: (9 + 2*9 + 3*7 + 4 + 5 + 5) = 62 */ + +#define SIZEOF_CDCSER_CFGDESC \ + (USB_SIZEOF_CFGDESC + 2*USB_SIZEOF_IFDESC + 3*USB_SIZEOF_EPDESC + SIZEOF_ACM_FUNCDESC + SIZEOF_HDR_FUNCDESC + SIZEOF_UNION_FUNCDESC(1)) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Describes one description in the group of descriptors forming the + * total configuration descriptor. + */ + +struct cfgdecsc_group_s +{ + uint16_t descsize; /* Size of the descriptor in bytes */ + uint16_t hsepsize; /* High speed max packet size */ + FAR void *desc; /* A pointer to the descriptor */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* USB descriptor templates these will be copied and modified **************/ + +static const struct usb_devdesc_s g_devdesc = +{ + USB_SIZEOF_DEVDESC, /* len */ + USB_DESC_TYPE_DEVICE, /* type */ + { /* usb */ + LSBYTE(0x0200), + MSBYTE(0x0200) + }, + USB_CLASS_CDC, /* class */ + CDC_SUBCLASS_NONE, /* subclass */ + CDC_PROTO_NONE, /* protocol */ + CONFIG_CDCSER_EP0MAXPACKET, /* maxpacketsize */ + { + LSBYTE(CONFIG_CDCSER_VENDORID), /* vendor */ + MSBYTE(CONFIG_CDCSER_VENDORID) + }, + { + LSBYTE(CONFIG_CDCSER_PRODUCTID), /* product */ + MSBYTE(CONFIG_CDCSER_PRODUCTID) + }, + { + LSBYTE(CDCSER_VERSIONNO), /* device */ + MSBYTE(CDCSER_VERSIONNO) + }, + CDCSER_MANUFACTURERSTRID, /* imfgr */ + CDCSER_PRODUCTSTRID, /* iproduct */ + CDCSER_SERIALSTRID, /* serno */ + CDCSER_NCONFIGS /* nconfigs */ +}; + +/* Configuration descriptor */ + +static const struct usb_cfgdesc_s g_cfgdesc = +{ + USB_SIZEOF_CFGDESC, /* len */ + USB_DESC_TYPE_CONFIG, /* type */ + { + LSBYTE(SIZEOF_CDCSER_CFGDESC), /* LS totallen */ + MSBYTE(SIZEOF_CDCSER_CFGDESC) /* MS totallen */ + }, + CDCSER_NINTERFACES, /* ninterfaces */ + CDCSER_CONFIGID, /* cfgvalue */ + CDCSER_CONFIGSTRID, /* icfg */ + USB_CONFIG_ATTR_ONE|SELFPOWERED|REMOTEWAKEUP, /* attr */ + (CONFIG_USBDEV_MAXPOWER + 1) / 2 /* mxpower */ +}; + +/* Notification interface */ + +static const struct usb_ifdesc_s g_notifdesc = +{ + USB_SIZEOF_IFDESC, /* len */ + USB_DESC_TYPE_INTERFACE, /* type */ + 0, /* ifno */ + 0, /* alt */ + 1, /* neps */ + USB_CLASS_CDC, /* class */ + CDC_SUBCLASS_ACM, /* subclass */ + CDC_PROTO_ATM, /* proto */ +#ifdef CONFIG_CDCSER_NOTIFSTR + CDCSER_NOTIFSTRID /* iif */ +#else + 0 /* iif */ +#endif +}; + +/* Header functional descriptor */ + +static const struct cdc_hdr_funcdesc_s g_funchdr = +{ + SIZEOF_HDR_FUNCDESC, /* size */ + USB_DESC_TYPE_CSINTERFACE, /* type */ + CDC_DSUBTYPE_HDR, /* subtype */ + { + LSBYTE(CDC_VERSIONNO), /* LS cdc */ + MSBYTE(CDC_VERSIONNO) /* MS cdc */ + } +}; + +/* ACM functional descriptor */ + +static const struct cdc_acm_funcdesc_s g_acmfunc = +{ + SIZEOF_ACM_FUNCDESC, /* size */ + USB_DESC_TYPE_CSINTERFACE, /* type */ + CDC_DSUBTYPE_ACM, /* subtype */ + 0x06 /* caps */ +}; + +/* Union functional descriptor */ + +static const struct cdc_union_funcdesc_s g_unionfunc = +{ + SIZEOF_UNION_FUNCDESC(1), /* size */ + USB_DESC_TYPE_CSINTERFACE, /* type */ + CDC_DSUBTYPE_UNION, /* subtype */ + 0, /* master */ + {1} /* slave[0] */ +}; + +/* Interrupt IN endpoint descriptor */ + +static const struct usb_epdesc_s g_epintindesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + CDCSER_EPINTIN_ADDR, /* addr */ + CDCSER_EPINTIN_ATTR, /* attr */ + { + LSBYTE(CONFIG_CDCSER_EPINTIN_FSSIZE), /* maxpacket (full speed) */ + MSBYTE(CONFIG_CDCSER_EPINTIN_FSSIZE) + }, + 0xff /* interval */ +}; + +/* Data interface descriptor */ + +static const struct usb_ifdesc_s g_dataifdesc = +{ + USB_SIZEOF_IFDESC, /* len */ + USB_DESC_TYPE_INTERFACE, /* type */ + 1, /* ifno */ + 0, /* alt */ + 2, /* neps */ + USB_CLASS_CDC_DATA, /* class */ + CDC_DATA_SUBCLASS_NONE, /* subclass */ + CDC_DATA_PROTO_NONE, /* proto */ +#ifdef CONFIG_CDCSER_DATAIFSTR + CDCSER_DATAIFSTRID /* iif */ +#else + 0 /* iif */ +#endif +}; + +/* Bulk OUT endpoint descriptor */ + +static const struct usb_epdesc_s g_epbulkoutdesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + CDCSER_EPOUTBULK_ADDR, /* addr */ + CDCSER_EPOUTBULK_ATTR, /* attr */ + { + LSBYTE(CONFIG_CDCSER_EPBULKOUT_FSSIZE), /* maxpacket (full speed) */ + MSBYTE(CONFIG_CDCSER_EPBULKOUT_FSSIZE) + }, + 1 /* interval */ +}; + +/* Bulk IN endpoint descriptor */ + +static const struct usb_epdesc_s g_epbulkindesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + CDCSER_EPINBULK_ADDR, /* addr */ + CDCSER_EPINBULK_ATTR, /* attr */ + { + LSBYTE(CONFIG_CDCSER_EPBULKIN_FSSIZE), /* maxpacket (full speed) */ + MSBYTE(CONFIG_CDCSER_EPBULKIN_FSSIZE) + }, + 1 /* interval */ +}; + +/* The components of the the configuration descriptor are maintained as + * a collection of separate descriptor structure coordinated by the + * following array. These descriptors could have been combined into + * one larger "super" configuration descriptor structure. However, I + * have concerns about compiler-dependent alignment and packing. Since + * the individual structures consist only of byte types, alignment and + * packing is not an issue. And since the are concatentated at run time + * instead of compile time, there should no issues there either. + */ + +static const struct cfgdecsc_group_s g_cfggroup[CDCSER_CFGGROUP_SIZE] = { + { + USB_SIZEOF_CFGDESC, /* 1. Configuration descriptor */ + 0, + (FAR void *)&g_cfgdesc + }, + { + USB_SIZEOF_IFDESC, /* 2. Notification interface */ + 0, + (FAR void *)&g_notifdesc + }, + { + SIZEOF_HDR_FUNCDESC, /* 3. Header functional descriptor */ + 0, + (FAR void *)&g_funchdr + }, + { + SIZEOF_ACM_FUNCDESC, /* 4. ACM functional descriptor */ + 0, + (FAR void *)&g_acmfunc + }, + { + SIZEOF_UNION_FUNCDESC(1), /* 5. Union functional descriptor */ + 0, + (FAR void *)&g_unionfunc + }, + { + USB_SIZEOF_EPDESC, /* 6. Interrupt IN endpoint descriptor */ + CONFIG_CDCSER_EPINTIN_HSSIZE, + (FAR void *)&g_epintindesc + }, + { + USB_SIZEOF_IFDESC, /* 7. Data interface descriptor */ + 0, + (FAR void *)&g_dataifdesc + }, + { + USB_SIZEOF_EPDESC, /* 8. Bulk OUT endpoint descriptor */ + CONFIG_CDCSER_EPBULKOUT_HSSIZE, + (FAR void *)&g_epbulkoutdesc + }, + { + USB_SIZEOF_EPDESC, /* 9. Bulk OUT endpoint descriptor */ + CONFIG_CDCSER_EPBULKIN_HSSIZE, + (FAR void *)&g_epbulkindesc + } +}; + +#ifdef CONFIG_USBDEV_DUALSPEED +static const struct usb_qualdesc_s g_qualdesc = +{ + USB_SIZEOF_QUALDESC, /* len */ + USB_DESC_TYPE_DEVICEQUALIFIER, /* type */ + { /* usb */ + LSBYTE(0x0200), + MSBYTE(0x0200) + }, + USB_CLASS_VENDOR_SPEC, /* class */ + 0, /* subclass */ + 0, /* protocol */ + CONFIG_CDCSER_EP0MAXPACKET, /* mxpacketsize */ + CDCSER_NCONFIGS, /* nconfigs */ + 0, /* reserved */ +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cdcser_mkstrdesc + * + * Description: + * Construct a string descriptor + * + ****************************************************************************/ + +int cdcser_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc) +{ + const char *str; + int len; + int ndata; + int i; + + switch (id) + { + case 0: + { + /* Descriptor 0 is the language id */ + + strdesc->len = 4; + strdesc->type = USB_DESC_TYPE_STRING; + strdesc->data[0] = LSBYTE(CDCSER_STR_LANGUAGE); + strdesc->data[1] = MSBYTE(CDCSER_STR_LANGUAGE); + return 4; + } + + case CDCSER_MANUFACTURERSTRID: + str = CONFIG_CDCSER_VENDORSTR; + break; + + case CDCSER_PRODUCTSTRID: + str = CONFIG_CDCSER_PRODUCTSTR; + break; + + case CDCSER_SERIALSTRID: + str = CONFIG_CDCSER_SERIALSTR; + break; + + case CDCSER_CONFIGSTRID: + str = CONFIG_CDCSER_CONFIGSTR; + break; + +#ifdef CONFIG_CDCSER_NOTIFSTR + case CDCSER_NOTIFSTRID: + str = CONFIG_CDCSER_NOTIFSTR; + break; +#endif + +#ifdef CONFIG_CDCSER_DATAIFSTR + case CDCSER_DATAIFSTRID: + str = CONFIG_CDCSER_DATAIFSTR; + break; +#endif + + default: + return -EINVAL; + } + + /* The string is utf16-le. The poor man's utf-8 to utf16-le + * conversion below will only handle 7-bit en-us ascii + */ + + len = strlen(str); + for (i = 0, ndata = 0; i < len; i++, ndata += 2) + { + strdesc->data[ndata] = str[i]; + strdesc->data[ndata+1] = 0; + } + + strdesc->len = ndata+2; + strdesc->type = USB_DESC_TYPE_STRING; + return strdesc->len; +} + +/**************************************************************************** + * Name: cdcser_getepdesc + * + * Description: + * Return a pointer to the raw device descriptor + * + ****************************************************************************/ + +FAR const struct usb_devdesc_s *cdcser_getdevdesc(void) +{ + return &g_devdesc; +} + +/**************************************************************************** + * Name: cdcser_getepdesc + * + * Description: + * Return a pointer to the raw endpoint struct (used for configuring + * endpoints) + * + ****************************************************************************/ + +FAR const struct usb_epdesc_s *cdcser_getepdesc(enum cdcser_epdesc_e epid) +{ + switch (epid) + { + case CDCSER_EPINTIN: /* Interrupt IN endpoint */ + return &g_epintindesc; + + case CDCSER_EPBULKOUT: /* Bulk OUT endpoint */ + return &g_epbulkoutdesc; + + case CDCSER_EPBULKIN: /* Bulk IN endpoint */ + return &g_epbulkindesc; + + default: + return NULL; + } +} + +/**************************************************************************** + * Name: cdcser_mkepdesc + * + * Description: + * Construct the endpoint descriptor using the correct max packet size. + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +void cdcser_mkepdesc(num cdcser_epdesc_e epid, uint16_t mxpacket, + FAR struct usb_epdesc_s *outdesc) +{ + FAR const struct usb_epdesc_s *indesc; + + /* Copy the "canned" descriptor */ + + indesc = cdcser_getepdesc(epid) + memcpy(outdesc, indesc, USB_SIZEOF_EPDESC); + + /* Then add the correct max packet size */ + + outdesc->mxpacketsize[0] = LSBYTE(mxpacket); + outdesc->mxpacketsize[1] = MSBYTE(mxpacket); +} +#endif + +/**************************************************************************** + * Name: cdcser_mkcfgdesc + * + * Description: + * Construct the configuration descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +int16_t cdcser_mkcfgdesc(FAR uint8_t *buf, uint8_t speed, uint8_t type) +#else +int16_t cdcser_mkcfgdesc(FAR uint8_t *buf) +#endif +{ + FAR const struct cfgdecsc_group_s *group; + FAR uint8_t *dest = buf; + int i; + +#ifdef CONFIG_USBDEV_DUALSPEED + bool hispeed = (speed == USB_SPEED_HIGH); + + /* Check for switches between high and full speed */ + + if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG) + { + hispeed = !hispeed; + } +#endif + + /* Copy all of the descriptors in the group */ + + for (i = 0, dest = buf; i < CDCSER_CFGGROUP_SIZE; i++) + { + group = &g_cfggroup[i]; + + /* The "canned" descriptors all have full speed endpoint maxpacket + * sizes. If high speed is selected, we will have to change the + * endpoint maxpacket size. + * + * Is there a alternative high speed maxpacket size in the table? + * If so, that is sufficient proof that the descriptor that we + * just copied is an endpoint descriptor and needs the fixup + */ + +#ifdef CONFIG_USBDEV_DUALSPEED + if (highspeed && group->hsepsize != 0) + { + cdcser_mkepdesc(group->desc, group->hsepsize, + (FAR struct usb_epdesc_s*)dest); + } + else +#endif + /* Copy the "canned" descriptor with the full speed max packet + * size + */ + + { + memcpy(dest, group->desc, group->descsize); + } + + /* Advance to the destination location for the next descriptor */ + + dest += group->descsize; + } + + return SIZEOF_CDCSER_CFGDESC; +} + +/**************************************************************************** + * Name: cdcser_getqualdesc + * + * Description: + * Return a pointer to the raw qual descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +FAR const struct usb_qualdesc_s *cdcser_getqualdesc(void) +{ + return &g_qualdesc; +} +#endif diff --git a/nuttx/drivers/usbdev/cdc_serial.c b/nuttx/drivers/usbdev/cdc_serial.c new file mode 100644 index 000000000..c91b451c3 --- /dev/null +++ b/nuttx/drivers/usbdev/cdc_serial.c @@ -0,0 +1,1990 @@ +/**************************************************************************** + * drivers/usbdev/cdc_serial.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <semaphore.h> +#include <string.h> +#include <errno.h> +#include <queue.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/serial.h> + +#include <nuttx/usb/usb.h> +#include <nuttx/usb/cdc.h> +#include <nuttx/usb/usbdev.h> +#include <nuttx/usb/cdc_serial.h> +#include <nuttx/usb/usbdev_trace.h> + +#include "cdc_serial.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Container to support a list of requests */ + +struct cdcser_req_s +{ + FAR struct cdcser_req_s *flink; /* Implements a singly linked list */ + FAR struct usbdev_req_s *req; /* The contained request */ +}; + +/* This structure describes the internal state of the driver */ + +struct cdcser_dev_s +{ + FAR struct uart_dev_s serdev; /* Serial device structure */ + FAR struct usbdev_s *usbdev; /* usbdev driver pointer */ + + uint8_t config; /* Configuration number */ + uint8_t nwrq; /* Number of queue write requests (in reqlist)*/ + uint8_t nrdq; /* Number of queue read requests (in epbulkout) */ + bool rxenabled; /* true: UART RX "interrupts" enabled */ + int16_t rxhead; /* Working head; used when rx int disabled */ + + uint8_t ctrlline; /* Buffered control line state */ + struct cdc_linecoding_s linecoding; /* Buffered line status */ + cdcser_callback_t callback; /* Serial event callback function */ + + FAR struct usbdev_ep_s *epintin; /* Interrupt IN endpoint structure */ + FAR struct usbdev_ep_s *epbulkin; /* Bulk IN endpoint structure */ + FAR struct usbdev_ep_s *epbulkout; /* Bulk OUT endpoint structure */ + FAR struct usbdev_req_s *ctrlreq; /* Control request */ + struct sq_queue_s reqlist; /* List of write request containers */ + + /* Pre-allocated write request containers. The write requests will + * be linked in a free list (reqlist), and used to send requests to + * EPBULKIN; Read requests will be queued in the EBULKOUT. + */ + + struct cdcser_req_s wrreqs[CONFIG_CDCSER_NWRREQS]; + struct cdcser_req_s rdreqs[CONFIG_CDCSER_NWRREQS]; + + /* Serial I/O buffers */ + + char rxbuffer[CONFIG_CDCSER_RXBUFSIZE]; + char txbuffer[CONFIG_CDCSER_TXBUFSIZE]; +}; + +/* The internal version of the class driver */ + +struct cdcser_driver_s +{ + struct usbdevclass_driver_s drvr; + FAR struct cdcser_dev_s *dev; +}; + +/* This is what is allocated */ + +struct cdcser_alloc_s +{ + struct cdcser_dev_s dev; + struct cdcser_driver_s drvr; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Transfer helpers *********************************************************/ + +static uint16_t cdcser_fillrequest(FAR struct cdcser_dev_s *priv, + uint8_t *reqbuf, uint16_t reqlen); +static int cdcser_sndpacket(FAR struct cdcser_dev_s *priv); +static inline int cdcser_recvpacket(FAR struct cdcser_dev_s *priv, + uint8_t *reqbuf, uint16_t reqlen); + +/* Request helpers *********************************************************/ + +static struct usbdev_req_s *cdcser_allocreq(FAR struct usbdev_ep_s *ep, + uint16_t len); +static void cdcser_freereq(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); + +/* Configuration ***********************************************************/ + +static void cdcser_resetconfig(FAR struct cdcser_dev_s *priv); +#ifdef CONFIG_USBDEV_DUALSPEED +static int cdcser_epconfigure(FAR struct usbdev_ep_s *ep, + enum cdcser_epdesc_e epid, uint16_t mxpacket, bool last); +#endif +static int cdcser_setconfig(FAR struct cdcser_dev_s *priv, + uint8_t config); + +/* Completion event handlers ***********************************************/ + +static void cdcser_ep0incomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); +static void cdcser_rdcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); +static void cdcser_wrcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); + +/* USB class device ********************************************************/ + +static int cdcser_bind(FAR struct usbdev_s *dev, + FAR struct usbdevclass_driver_s *driver); +static void cdcser_unbind(FAR struct usbdev_s *dev); +static int cdcser_setup(FAR struct usbdev_s *dev, + const struct usb_ctrlreq_s *ctrl); +static void cdcser_disconnect(FAR struct usbdev_s *dev); + +/* UART Operationt **********************************************************/ + +static int cdcuart_setup(FAR struct uart_dev_s *dev); +static void cdcuart_shutdown(FAR struct uart_dev_s *dev); +static int cdcuart_attach(FAR struct uart_dev_s *dev); +static void cdcuart_detach(FAR struct uart_dev_s *dev); +static int cdcuart_ioctl(FAR struct file *filep,int cmd,unsigned long arg); +static void cdcuart_rxint(FAR struct uart_dev_s *dev, bool enable); +static void cdcuart_txint(FAR struct uart_dev_s *dev, bool enable); +static bool cdcuart_txempty(FAR struct uart_dev_s *dev); + +/**************************************************************************** + * Private Variables + ****************************************************************************/ +/* USB class device *********************************************************/ + +static const struct usbdevclass_driverops_s g_driverops = +{ + cdcser_bind, /* bind */ + cdcser_unbind, /* unbind */ + cdcser_setup, /* setup */ + cdcser_disconnect, /* disconnect */ + NULL, /* suspend */ + NULL, /* resume */ +}; + +/* Serial port **************************************************************/ + +static const struct uart_ops_s g_uartops = +{ + cdcuart_setup, /* setup */ + cdcuart_shutdown, /* shutdown */ + cdcuart_attach, /* attach */ + cdcuart_detach, /* detach */ + cdcuart_ioctl, /* ioctl */ + NULL, /* receive */ + cdcuart_rxint, /* rxinit */ + NULL, /* rxavailable */ + NULL, /* send */ + cdcuart_txint, /* txinit */ + NULL, /* txready */ + cdcuart_txempty /* txempty */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cdcser_fillrequest + * + * Description: + * If there is data to send it is copied to the given buffer. Called + * either to initiate the first write operation, or from the completion + * interrupt handler service consecutive write operations. + * + * NOTE: The USB serial driver does not use the serial drivers + * uart_xmitchars() API. That logic is essentially duplicated here because + * unlike UART hardware, we need to be able to handle writes not byte-by-byte, + * but packet-by-packet. Unfortunately, that decision also exposes some + * internals of the serial driver in the following. + * + ****************************************************************************/ + +static uint16_t cdcser_fillrequest(FAR struct cdcser_dev_s *priv, uint8_t *reqbuf, + uint16_t reqlen) +{ + FAR uart_dev_t *serdev = &priv->serdev; + FAR struct uart_buffer_s *xmit = &serdev->xmit; + irqstate_t flags; + uint16_t nbytes = 0; + + /* Disable interrupts */ + + flags = irqsave(); + + /* Transfer bytes while we have bytes available and there is room in the request */ + + while (xmit->head != xmit->tail && nbytes < reqlen) + { + *reqbuf++ = xmit->buffer[xmit->tail]; + nbytes++; + + /* Increment the tail pointer */ + + if (++(xmit->tail) >= xmit->size) + { + xmit->tail = 0; + } + } + + /* When all of the characters have been sent from the buffer + * disable the "TX interrupt". + */ + + if (xmit->head == xmit->tail) + { + uart_disabletxint(serdev); + } + + /* If any bytes were removed from the buffer, inform any waiters + * there there is space available. + */ + + if (nbytes) + { + uart_datasent(serdev); + } + + irqrestore(flags); + return nbytes; +} + +/**************************************************************************** + * Name: cdcser_sndpacket + * + * Description: + * This function obtains write requests, transfers the TX data into the + * request, and submits the requests to the USB controller. This continues + * untils either (1) there are no further packets available, or (2) thre is + * no further data to send. + * + ****************************************************************************/ + +static int cdcser_sndpacket(FAR struct cdcser_dev_s *priv) +{ + FAR struct usbdev_ep_s *ep; + FAR struct usbdev_req_s *req; + FAR struct cdcser_req_s *reqcontainer; + irqstate_t flags; + int len; + int ret = OK; + +#ifdef CONFIG_DEBUG + if (priv == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -ENODEV; + } +#endif + + flags = irqsave(); + + /* Use our IN endpoint for the transfer */ + + ep = priv->epbulkin; + + /* Loop until either (1) we run out or write requests, or (2) cdcser_fillrequest() + * is unable to fill the request with data (i.e., untilthere is no more data + * to be sent). + */ + + uvdbg("head=%d tail=%d nwrq=%d empty=%d\n", + priv->serdev.xmit.head, priv->serdev.xmit.tail, + priv->nwrq, sq_empty(&priv->reqlist)); + + while (!sq_empty(&priv->reqlist)) + { + /* Peek at the request in the container at the head of the list */ + + reqcontainer = (struct cdcser_req_s *)sq_peek(&priv->reqlist); + req = reqcontainer->req; + + /* Fill the request with serial TX data */ + + len = cdcser_fillrequest(priv, req->buf, req->len); + if (len > 0) + { + /* Remove the empty container from the request list */ + + (void)sq_remfirst(&priv->reqlist); + priv->nwrq--; + + /* Then submit the request to the endpoint */ + + req->len = len; + req->priv = reqcontainer; + req->flags = USBDEV_REQFLAGS_NULLPKT; + ret = EP_SUBMIT(ep, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), (uint16_t)-ret); + break; + } + } + else + { + break; + } + } + + irqrestore(flags); + return ret; +} + +/**************************************************************************** + * Name: cdcser_recvpacket + * + * Description: + * A normal completion event was received by the read completion handler + * at the interrupt level (with interrupts disabled). This function handles + * the USB packet and provides the received data to the uart RX buffer. + * + * Assumptions: + * Called from the USB interrupt handler with interrupts disabled. + * + ****************************************************************************/ + +static inline int cdcser_recvpacket(FAR struct cdcser_dev_s *priv, + uint8_t *reqbuf, uint16_t reqlen) +{ + FAR uart_dev_t *serdev = &priv->serdev; + FAR struct uart_buffer_s *recv = &serdev->recv; + uint16_t currhead; + uint16_t nexthead; + uint16_t nbytes = 0; + + uvdbg("head=%d tail=%d nrdq=%d reqlen=%d\n", + priv->serdev.recv.head, priv->serdev.recv.tail, priv->nrdq, reqlen); + + /* Get the next head index. During the time that RX interrupts are disabled, the + * the serial driver will be extracting data from the circular buffer and modifying + * recv.tail. During this time, we should avoid modifying recv.head; Instead we will + * use a shadow copy of the index. When interrupts are restored, the real recv.head + * will be updated with this indes. + */ + + if (priv->rxenabled) + { + currhead = recv->head; + } + else + { + currhead = priv->rxhead; + } + + /* Pre-calculate the head index and check for wrap around. We need to do this + * so that we can determine if the circular buffer will overrun BEFORE we + * overrun the buffer! + */ + + nexthead = currhead + 1; + if (nexthead >= recv->size) + { + nexthead = 0; + } + + /* Then copy data into the RX buffer until either: (1) all of the data has been + * copied, or (2) the RX buffer is full. NOTE: If the RX buffer becomes full, + * then we have overrun the serial driver and data will be lost. + */ + + while (nexthead != recv->tail && nbytes < reqlen) + { + /* Copy one byte to the head of the circular RX buffer */ + + recv->buffer[currhead] = *reqbuf++; + + /* Update counts and indices */ + + currhead = nexthead; + nbytes++; + + /* Increment the head index and check for wrap around */ + + nexthead = currhead + 1; + if (nexthead >= recv->size) + { + nexthead = 0; + } + } + + /* Write back the head pointer using the shadow index if RX "interrupts" + * are disabled. + */ + + if (priv->rxenabled) + { + recv->head = currhead; + } + else + { + priv->rxhead = currhead; + } + + /* If data was added to the incoming serial buffer, then wake up any + * threads is waiting for incoming data. If we are running in an interrupt + * handler, then the serial driver will not run until the interrupt handler + * returns. + */ + + if (priv->rxenabled && nbytes > 0) + { + uart_datareceived(serdev); + } + + /* Return an error if the entire packet could not be transferred */ + + if (nbytes < reqlen) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RXOVERRUN), 0); + return -ENOSPC; + } + return OK; +} + +/**************************************************************************** + * Name: cdcser_allocreq + * + * Description: + * Allocate a request instance along with its buffer + * + ****************************************************************************/ + +static struct usbdev_req_s *cdcser_allocreq(FAR struct usbdev_ep_s *ep, + uint16_t len) +{ + FAR struct usbdev_req_s *req; + + req = EP_ALLOCREQ(ep); + if (req != NULL) + { + req->len = len; + req->buf = EP_ALLOCBUFFER(ep, len); + if (!req->buf) + { + EP_FREEREQ(ep, req); + req = NULL; + } + } + return req; +} + +/**************************************************************************** + * Name: cdcser_freereq + * + * Description: + * Free a request instance along with its buffer + * + ****************************************************************************/ + +static void cdcser_freereq(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + if (ep != NULL && req != NULL) + { + if (req->buf != NULL) + { + EP_FREEBUFFER(ep, req->buf); + } + EP_FREEREQ(ep, req); + } +} + +/**************************************************************************** + * Name: cdcser_resetconfig + * + * Description: + * Mark the device as not configured and disable all endpoints. + * + ****************************************************************************/ + +static void cdcser_resetconfig(FAR struct cdcser_dev_s *priv) +{ + /* Are we configured? */ + + if (priv->config != CDCSER_CONFIGIDNONE) + { + /* Yes.. but not anymore */ + + priv->config = CDCSER_CONFIGIDNONE; + + /* Disable endpoints. This should force completion of all pending + * transfers. + */ + + EP_DISABLE(priv->epintin); + EP_DISABLE(priv->epbulkin); + EP_DISABLE(priv->epbulkout); + } +} + +/**************************************************************************** + * Name: cdcser_epconfigure + * + * Description: + * Configure one endpoint. + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +static int cdcser_epconfigure(FAR struct usbdev_ep_s *ep, + enum cdcser_epdesc_e epid, uint16_t mxpacket, + bool last) +{ + struct usb_epdesc_s epdesc; + cdcser_mkepdesc(epid, mxpacket, &epdesc); + return EP_CONFIGURE(ep, &epdesc, last); +} +#endif + +/**************************************************************************** + * Name: cdcser_setconfig + * + * Description: + * Set the device configuration by allocating and configuring endpoints and + * by allocating and queue read and write requests. + * + ****************************************************************************/ + +static int cdcser_setconfig(FAR struct cdcser_dev_s *priv, uint8_t config) +{ + FAR struct usbdev_req_s *req; + int i; + int ret = 0; + +#if CONFIG_DEBUG + if (priv == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -EIO; + } +#endif + + if (config == priv->config) + { + /* Already configured -- Do nothing */ + + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0); + return 0; + } + + /* Discard the previous configuration data */ + + cdcser_resetconfig(priv); + + /* Was this a request to simply discard the current configuration? */ + + if (config == CDCSER_CONFIGIDNONE) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0); + return 0; + } + + /* We only accept one configuration */ + + if (config != CDCSER_CONFIGID) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0); + return -EINVAL; + } + + /* Configure the IN interrupt endpoint */ + +#ifdef CONFIG_USBDEV_DUALSPEED + if (priv->usbdev->speed == USB_SPEED_HIGH) + { + ret = cdcser_epconfigure(priv->epintin, CDCSER_EPINTIN, + CONFIG_CDCSER_EPINTIN_HSSIZE, false); + } + else +#endif + { + ret = EP_CONFIGURE(priv->epintin, + cdcser_getepdesc(CDCSER_EPINTIN), false); + } + + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0); + goto errout; + } + priv->epintin->priv = priv; + + /* Configure the IN bulk endpoint */ + +#ifdef CONFIG_USBDEV_DUALSPEED + if (priv->usbdev->speed == USB_SPEED_HIGH) + { + ret = cdcser_epconfigure(priv->epbulkin, CDCSER_EPBULKIN, + CONFIG_CDCSER_EPBULKIN_HSSIZE, false); + } + else +#endif + { + ret = EP_CONFIGURE(priv->epbulkin, + cdcser_getepdesc(CDCSER_EPBULKIN), false); + } + + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0); + goto errout; + } + + priv->epbulkin->priv = priv; + + /* Configure the OUT bulk endpoint */ + +#ifdef CONFIG_USBDEV_DUALSPEED + if (priv->usbdev->speed == USB_SPEED_HIGH) + { + ret = cdcser_epconfigure(priv->epbulkout, CDCSER_EPBULKOUT, + CONFIG_CDCSER_EPBULKOUT_HSSIZE, true); + } + else +#endif + { + ret = EP_CONFIGURE(priv->epbulkout, + cdcser_getepdesc(CDCSER_EPBULKOUT), true); + } + + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0); + goto errout; + } + + priv->epbulkout->priv = priv; + + /* Queue read requests in the bulk OUT endpoint */ + + DEBUGASSERT(priv->nrdq == 0); + for (i = 0; i < CONFIG_CDCSER_NRDREQS; i++) + { + req = priv->rdreqs[i].req; + req->callback = cdcser_rdcomplete; + ret = EP_SUBMIT(priv->epbulkout, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-ret); + goto errout; + } + priv->nrdq++; + } + + priv->config = config; + return OK; + +errout: + cdcser_resetconfig(priv); + return ret; +} + +/**************************************************************************** + * Name: cdcser_ep0incomplete + * + * Description: + * Handle completion of EP0 control operations + * + ****************************************************************************/ + +static void cdcser_ep0incomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + if (req->result || req->xfrd != req->len) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT), (uint16_t)-req->result); + } +} + +/**************************************************************************** + * Name: cdcser_rdcomplete + * + * Description: + * Handle completion of read request on the bulk OUT endpoint. This + * is handled like the receipt of serial data on the "UART" + * + ****************************************************************************/ + +static void cdcser_rdcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + FAR struct cdcser_dev_s *priv; + irqstate_t flags; + int ret; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!ep || !ep->priv || !req) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract references to private data */ + + priv = (FAR struct cdcser_dev_s*)ep->priv; + + /* Process the received data unless this is some unusual condition */ + + flags = irqsave(); + switch (req->result) + { + case 0: /* Normal completion */ + usbtrace(TRACE_CLASSRDCOMPLETE, priv->nrdq); + cdcser_recvpacket(priv, req->buf, req->xfrd); + break; + + case -ESHUTDOWN: /* Disconnection */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0); + priv->nrdq--; + irqrestore(flags); + return; + + default: /* Some other error occurred */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED), (uint16_t)-req->result); + break; + }; + + /* Requeue the read request */ + +#ifdef CONFIG_CDCSER_BULKREQLEN + req->len = max(CONFIG_CDCSER_BULKREQLEN, ep->maxpacket); +#else + req->len = ep->maxpacket; +#endif + + ret = EP_SUBMIT(ep, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-req->result); + } + irqrestore(flags); +} + +/**************************************************************************** + * Name: cdcser_wrcomplete + * + * Description: + * Handle completion of write request. This function probably executes + * in the context of an interrupt handler. + * + ****************************************************************************/ + +static void cdcser_wrcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + FAR struct cdcser_dev_s *priv; + FAR struct cdcser_req_s *reqcontainer; + irqstate_t flags; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!ep || !ep->priv || !req || !req->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract references to our private data */ + + priv = (FAR struct cdcser_dev_s *)ep->priv; + reqcontainer = (FAR struct cdcser_req_s *)req->priv; + + /* Return the write request to the free list */ + + flags = irqsave(); + sq_addlast((sq_entry_t*)reqcontainer, &priv->reqlist); + priv->nwrq++; + irqrestore(flags); + + /* Send the next packet unless this was some unusual termination + * condition + */ + + switch (req->result) + { + case OK: /* Normal completion */ + usbtrace(TRACE_CLASSWRCOMPLETE, priv->nwrq); + cdcser_sndpacket(priv); + break; + + case -ESHUTDOWN: /* Disconnection */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN), priv->nwrq); + break; + + default: /* Some other error occurred */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED), (uint16_t)-req->result); + break; + } +} + +/**************************************************************************** + * USB Class Driver Methods + ****************************************************************************/ + +/**************************************************************************** + * Name: cdcser_bind + * + * Description: + * Invoked when the driver is bound to a USB device driver + * + ****************************************************************************/ + +static int cdcser_bind(FAR struct usbdev_s *dev, FAR struct usbdevclass_driver_s *driver) +{ + FAR struct cdcser_dev_s *priv = ((struct cdcser_driver_s*)driver)->dev; + FAR struct cdcser_req_s *reqcontainer; + irqstate_t flags; + uint16_t reqlen; + int ret; + int i; + + usbtrace(TRACE_CLASSBIND, 0); + + /* Bind the structures */ + + priv->usbdev = dev; + dev->ep0->priv = priv; + + /* Preallocate control request */ + + priv->ctrlreq = cdcser_allocreq(dev->ep0, CDCSER_MXDESCLEN); + if (priv->ctrlreq == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0); + ret = -ENOMEM; + goto errout; + } + priv->ctrlreq->callback = cdcser_ep0incomplete; + + /* Pre-allocate all endpoints... the endpoints will not be functional + * until the SET CONFIGURATION request is processed in cdcser_setconfig. + * This is done here because there may be calls to kmalloc and the SET + * CONFIGURATION processing probably occurrs within interrupt handling + * logic where kmalloc calls will fail. + */ + + /* Pre-allocate the IN interrupt endpoint */ + + priv->epintin = DEV_ALLOCEP(dev, CDCSER_EPINTIN_ADDR, true, USB_EP_ATTR_XFER_INT); + if (!priv->epintin) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epintin->priv = priv; + + /* Pre-allocate the IN bulk endpoint */ + + priv->epbulkin = DEV_ALLOCEP(dev, CDCSER_EPINBULK_ADDR, true, USB_EP_ATTR_XFER_BULK); + if (!priv->epbulkin) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epbulkin->priv = priv; + + /* Pre-allocate the OUT bulk endpoint */ + + priv->epbulkout = DEV_ALLOCEP(dev, CDCSER_EPOUTBULK_ADDR, false, USB_EP_ATTR_XFER_BULK); + if (!priv->epbulkout) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epbulkout->priv = priv; + + /* Pre-allocate read requests */ + +#ifdef CONFIG_CDCSER_BULKREQLEN + reqlen = max(CONFIG_CDCSER_BULKREQLEN, priv->epbulkout->maxpacket); +#else + reqlen = priv->epbulkout->maxpacket; +#endif + + for (i = 0; i < CONFIG_CDCSER_NRDREQS; i++) + { + reqcontainer = &priv->rdreqs[i]; + reqcontainer->req = cdcser_allocreq(priv->epbulkout, reqlen); + if (reqcontainer->req == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM); + ret = -ENOMEM; + goto errout; + } + reqcontainer->req->priv = reqcontainer; + reqcontainer->req->callback = cdcser_rdcomplete; + } + + /* Pre-allocate write request containers and put in a free list */ + +#ifdef CONFIG_CDCSER_BULKREQLEN + reqlen = max(CONFIG_CDCSER_BULKREQLEN, priv->epbulkin->maxpacket); +#else + reqlen = priv->epbulkin->maxpacket; +#endif + + for (i = 0; i < CONFIG_CDCSER_NWRREQS; i++) + { + reqcontainer = &priv->wrreqs[i]; + reqcontainer->req = cdcser_allocreq(priv->epbulkin, reqlen); + if (reqcontainer->req == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM); + ret = -ENOMEM; + goto errout; + } + reqcontainer->req->priv = reqcontainer; + reqcontainer->req->callback = cdcser_wrcomplete; + + flags = irqsave(); + sq_addlast((sq_entry_t*)reqcontainer, &priv->reqlist); + priv->nwrq++; /* Count of write requests available */ + irqrestore(flags); + } + + /* Report if we are selfpowered */ + +#ifdef CONFIG_USBDEV_SELFPOWERED + DEV_SETSELFPOWERED(dev); +#endif + + /* And pull-up the data line for the soft connect function */ + + DEV_CONNECT(dev); + return OK; + +errout: + cdcser_unbind(dev); + return ret; +} + +/**************************************************************************** + * Name: cdcser_unbind + * + * Description: + * Invoked when the driver is unbound from a USB device driver + * + ****************************************************************************/ + +static void cdcser_unbind(FAR struct usbdev_s *dev) +{ + FAR struct cdcser_dev_s *priv; + FAR struct cdcser_req_s *reqcontainer; + irqstate_t flags; + int i; + + usbtrace(TRACE_CLASSUNBIND, 0); + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct cdcser_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); + return; + } +#endif + + /* Make sure that we are not already unbound */ + + if (priv != NULL) + { + /* Make sure that the endpoints have been unconfigured. If + * we were terminated gracefully, then the configuration should + * already have been reset. If not, then calling cdcser_resetconfig + * should cause the endpoints to immediately terminate all + * transfers and return the requests to us (with result == -ESHUTDOWN) + */ + + cdcser_resetconfig(priv); + up_mdelay(50); + + /* Free the interrupt IN endpoint */ + + if (priv->epintin) + { + DEV_FREEEP(dev, priv->epintin); + priv->epintin = NULL; + } + + /* Free the bulk IN endpoint */ + + if (priv->epbulkin) + { + DEV_FREEEP(dev, priv->epbulkin); + priv->epbulkin = NULL; + } + + /* Free the pre-allocated control request */ + + if (priv->ctrlreq != NULL) + { + cdcser_freereq(dev->ep0, priv->ctrlreq); + priv->ctrlreq = NULL; + } + + /* Free pre-allocated read requests (which should all have + * been returned to the free list at this time -- we don't check) + */ + + DEBUGASSERT(priv->nrdq == 0); + for (i = 0; i < CONFIG_CDCSER_NRDREQS; i++) + { + reqcontainer = &priv->rdreqs[i]; + if (reqcontainer->req) + { + cdcser_freereq(priv->epbulkout, reqcontainer->req); + reqcontainer->req = NULL; + } + } + + /* Free the bulk OUT endpoint */ + + if (priv->epbulkout) + { + DEV_FREEEP(dev, priv->epbulkout); + priv->epbulkout = NULL; + } + + /* Free write requests that are not in use (which should be all + * of them + */ + + flags = irqsave(); + DEBUGASSERT(priv->nwrq == CONFIG_CDCSER_NWRREQS); + while (!sq_empty(&priv->reqlist)) + { + reqcontainer = (struct cdcser_req_s *)sq_remfirst(&priv->reqlist); + if (reqcontainer->req != NULL) + { + cdcser_freereq(priv->epbulkin, reqcontainer->req); + priv->nwrq--; /* Number of write requests queued */ + } + } + DEBUGASSERT(priv->nwrq == 0); + irqrestore(flags); + } + + /* Clear out all data in the circular buffer */ + + priv->serdev.xmit.head = 0; + priv->serdev.xmit.tail = 0; +} + +/**************************************************************************** + * Name: cdcser_setup + * + * Description: + * Invoked for ep0 control requests. This function probably executes + * in the context of an interrupt handler. + * + ****************************************************************************/ + +static int cdcser_setup(FAR struct usbdev_s *dev, const struct usb_ctrlreq_s *ctrl) +{ + FAR struct cdcser_dev_s *priv; + FAR struct usbdev_req_s *ctrlreq; + uint16_t value; + uint16_t index; + uint16_t len; + int ret = -EOPNOTSUPP; + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0 || !ctrl) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -EIO; + } +#endif + + /* Extract reference to private data */ + + usbtrace(TRACE_CLASSSETUP, ctrl->req); + priv = (FAR struct cdcser_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv || !priv->ctrlreq) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); + return -ENODEV; + } +#endif + ctrlreq = priv->ctrlreq; + + /* Extract the little-endian 16-bit values to host order */ + + value = GETUINT16(ctrl->value); + index = GETUINT16(ctrl->index); + len = GETUINT16(ctrl->len); + + uvdbg("type=%02x req=%02x value=%04x index=%04x len=%04x\n", + ctrl->type, ctrl->req, value, index, len); + + switch (ctrl->type & USB_REQ_TYPE_MASK) + { + /*********************************************************************** + * Standard Requests + ***********************************************************************/ + + case USB_REQ_TYPE_STANDARD: + { + switch (ctrl->req) + { + case USB_REQ_GETDESCRIPTOR: + { + /* The value field specifies the descriptor type in the MS byte and the + * descriptor index in the LS byte (order is little endian) + */ + + switch (ctrl->value[1]) + { + case USB_DESC_TYPE_DEVICE: + { + ret = USB_SIZEOF_DEVDESC; + memcpy(ctrlreq->buf, cdcser_getdevdesc(), ret); + } + break; + +#ifdef CONFIG_USBDEV_DUALSPEED + case USB_DESC_TYPE_DEVICEQUALIFIER: + { + ret = USB_SIZEOF_QUALDESC; + memcpy(ctrlreq->buf, cdcser_getqualdesc(), ret); + } + break; + + case USB_DESC_TYPE_OTHERSPEEDCONFIG: +#endif /* CONFIG_USBDEV_DUALSPEED */ + + case USB_DESC_TYPE_CONFIG: + { +#ifdef CONFIG_USBDEV_DUALSPEED + ret = cdcser_mkcfgdesc(ctrlreq->buf, dev->speed, ctrl->req); +#else + ret = cdcser_mkcfgdesc(ctrlreq->buf); +#endif + } + break; + + case USB_DESC_TYPE_STRING: + { + /* index == language code. */ + + ret = cdcser_mkstrdesc(ctrl->value[0], (struct usb_strdesc_s *)ctrlreq->buf); + } + break; + + default: + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC), value); + } + break; + } + } + break; + + case USB_REQ_SETCONFIGURATION: + { + if (ctrl->type == 0) + { + ret = cdcser_setconfig(priv, value); + } + } + break; + + case USB_REQ_GETCONFIGURATION: + { + if (ctrl->type == USB_DIR_IN) + { + *(uint8_t*)ctrlreq->buf = priv->config; + ret = 1; + } + } + break; + + case USB_REQ_SETINTERFACE: + { + if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE) + { + if (priv->config == CDCSER_CONFIGID && + index == CDCSER_INTERFACEID && + value == CDCSER_ALTINTERFACEID) + { + cdcser_resetconfig(priv); + cdcser_setconfig(priv, priv->config); + ret = 0; + } + } + } + break; + + case USB_REQ_GETINTERFACE: + { + if (ctrl->type == (USB_DIR_IN|USB_REQ_RECIPIENT_INTERFACE) && + priv->config == CDCSER_CONFIGIDNONE) + { + if (index != CDCSER_INTERFACEID) + { + ret = -EDOM; + } + else + { + *(uint8_t*) ctrlreq->buf = CDCSER_ALTINTERFACEID; + ret = 1; + } + } + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ), ctrl->req); + break; + } + } + break; + + /************************************************************************ + * CDC ACM-Specific Requests + ************************************************************************/ + + /* ACM_GET_LINE_CODING requests current DTE rate, stop-bits, parity, and + * number-of-character bits. (Optional) + */ + + case ACM_GET_LINE_CODING: + { + if (ctrl->type == (USB_DIR_IN|USB_REQ_TYPE_CLASS|USB_REQ_RECIPIENT_INTERFACE)) + { + /* Return the current line status from the private data structure */ + + memcpy(ctrlreq->buf, &priv->linecoding, SIZEOF_CDC_LINECODING); + ret = SIZEOF_CDC_LINECODING; + } + else + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); + } + } + break; + + /* ACM_SET_LINE_CODING configures DTE rate, stop-bits, parity, and + * number-of-character bits. (Optional) + */ + + case ACM_SET_LINE_CODING: + { + if (ctrl->type == (USB_DIR_OUT|USB_REQ_TYPE_CLASS|USB_REQ_RECIPIENT_INTERFACE)) + { + /* Save the new line coding in the private data structure */ + + memcpy(&priv->linecoding, ctrlreq->buf, min(len, 7)); + ret = 0; + + /* If there is a registered callback to receive line status info, then + * callout now. + */ + + if (priv->callback) + { + priv->callback(CDCSER_EVENT_LINECODING); + } + } + else + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); + } + } + break; + + /* ACM_SET_CTRL_LINE_STATE: RS-232 signal used to tell the DCE device the + * DTE device is now present. (Optional) + */ + + case ACM_SET_CTRL_LINE_STATE: + { + if (ctrl->type == (USB_DIR_OUT|USB_REQ_TYPE_CLASS|USB_REQ_RECIPIENT_INTERFACE)) + { + /* Save the control line state in the private data structure. Only bits + * 0 and 1 have meaning. + */ + + priv->ctrlline = value & 3; + ret = 0; + + /* If there is a registered callback to receive control line status info, + * then callout now. + */ + + if (priv->callback) + { + priv->callback(CDCSER_EVENT_CTRLLINE); + } + } + else + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); + } + } + break; + + /* Sends special carrier*/ + + case ACM_SEND_BREAK: + { + if (ctrl->type == (USB_DIR_OUT|USB_REQ_TYPE_CLASS|USB_REQ_RECIPIENT_INTERFACE)) + { + /* If there is a registered callback to handle the SendBreak request, + * then callout now. + */ + + ret = 0; + if (priv->callback) + { + priv->callback(CDCSER_EVENT_SENDBREAK); + } + } + else + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); + } + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE), ctrl->type); + break; + } + + /* Respond to the setup command if data was returned. On an error return + * value (ret < 0), the USB driver will stall. + */ + + if (ret >= 0) + { + ctrlreq->len = min(len, ret); + ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; + ret = EP_SUBMIT(dev->ep0, ctrlreq); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret); + ctrlreq->result = OK; + cdcser_ep0incomplete(dev->ep0, ctrlreq); + } + } + return ret; +} + +/**************************************************************************** + * Name: cdcser_disconnect + * + * Description: + * Invoked after all transfers have been stopped, when the host is + * disconnected. This function is probably called from the context of an + * interrupt handler. + * + ****************************************************************************/ + +static void cdcser_disconnect(FAR struct usbdev_s *dev) +{ + FAR struct cdcser_dev_s *priv; + irqstate_t flags; + + usbtrace(TRACE_CLASSDISCONNECT, 0); + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct cdcser_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); + return; + } +#endif + + /* Reset the configuration */ + + flags = irqsave(); + cdcser_resetconfig(priv); + + /* Clear out all data in the circular buffer */ + + priv->serdev.xmit.head = 0; + priv->serdev.xmit.tail = 0; + irqrestore(flags); + + /* Perform the soft connect function so that we will we can be + * re-enumerated. + */ + + DEV_CONNECT(dev); +} + +/**************************************************************************** + * Serial Device Methods + ****************************************************************************/ + +/**************************************************************************** + * Name: cdcuart_setup + * + * Description: + * This method is called the first time that the serial port is opened. + * + ****************************************************************************/ + +static int cdcuart_setup(FAR struct uart_dev_s *dev) +{ + FAR struct cdcser_dev_s *priv; + + usbtrace(CDCSER_CLASSAPI_SETUP, 0); + + /* Sanity check */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -EIO; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct cdcser_dev_s*)dev->priv; + + /* Check if we have been configured */ + + if (priv->config == CDCSER_CONFIGIDNONE) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SETUPNOTCONNECTED), 0); + return -ENOTCONN; + } + + return OK; +} + +/**************************************************************************** + * Name: cdcuart_shutdown + * + * Description: + * This method is called when the serial port is closed. This operation + * is very simple for the USB serial backend because the serial driver + * has already assured that the TX data has full drained -- it calls + * cdcuart_txempty() until that function returns true before calling this + * function. + * + ****************************************************************************/ + +static void cdcuart_shutdown(FAR struct uart_dev_s *dev) +{ + usbtrace(CDCSER_CLASSAPI_SHUTDOWN, 0); + + /* Sanity check */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + } +#endif +} + +/**************************************************************************** + * Name: cdcuart_attach + * + * Description: + * Does not apply to the USB serial class device + * + ****************************************************************************/ + +static int cdcuart_attach(FAR struct uart_dev_s *dev) +{ + usbtrace(CDCSER_CLASSAPI_ATTACH, 0); + return OK; +} + +/**************************************************************************** + * Name: cdcuart_detach + * + * Description: +* Does not apply to the USB serial class device + * + ****************************************************************************/ + +static void cdcuart_detach(FAR struct uart_dev_s *dev) +{ + usbtrace(CDCSER_CLASSAPI_DETACH, 0); +} + +/**************************************************************************** + * Name: cdcuart_ioctl + * + * Description: + * All ioctl calls will be routed through this method + * + ****************************************************************************/ + +static int cdcuart_ioctl(FAR struct file *filep,int cmd,unsigned long arg) +{ + struct inode *inode = filep->f_inode; + struct cdcser_dev_s *priv = inode->i_private; + int ret = OK; + + switch (cmd) + { + /* CAICO_REGISTERCB + * Register a callback for serial event notification. Argument: + * cdcser_callback_t. See cdcser_callback_t type definition below. + * NOTE: The callback will most likely invoked at the interrupt level. + * The called back function should, therefore, limit its operations to + * invoking some kind of IPC to handle the serial event in some normal + * task environment. + */ + + case CAIOC_REGISTERCB: + { + /* Save the new callback function */ + + priv->callback = (cdcser_callback_t)((uintptr_t)arg); + } + break; + + /* CAIOC_GETLINECODING + * Get current line coding. Argument: struct cdc_linecoding_s*. + * See include/nuttx/usb/cdc.h for structure definition. This IOCTL + * should be called to get the data associated with the + * CDCSER_EVENT_LINECODING event). + */ + + case CAIOC_GETLINECODING: + { + FAR struct cdc_linecoding_s *ptr = (FAR struct cdc_linecoding_s *)((uintptr_t)arg); + if (ptr) + { + memcpy(ptr, &priv->linecoding, sizeof(struct cdc_linecoding_s)); + } + else + { + ret = -EINVAL; + } + } + break; + + /* CAIOC_GETCTRLLINE + * Get control line status bits. Argument FAR int*. See + * include/nuttx/usb/cdc.h for bit definitions. This IOCTL should be + * called to get the data associated CDCSER_EVENT_CTRLLINE event. + */ + + case CAIOC_GETCTRLLINE: + { + FAR int *ptr = (FAR int *)((uintptr_t)arg); + if (ptr) + { + *ptr = priv->ctrlline; + } + else + { + ret = -EINVAL; + } + } + break; + + /* CAIOC_NOTIFY + * Send a serial state to the host via the Interrupt IN endpoint. + * Argument: int. This includes the current state of the carrier detect, + * DSR, break, and ring signal. See "Table 69: UART State Bitmap Values" + * and CDC_UART_definitions in include/nuttx/usb/cdc.h. + */ + + case CAIOC_NOTIFY: + { + /* Not yet implemented. I probably won't bother to implement until + * I comr up with a usage model that needs it. + * + * Here is what the needs to be done: + * + * 1. Format and send a request header with: + * + * bmRequestType: + * USB_REQ_DIR_IN|USB_REQ_TYPE_CLASS|USB_REQ_RECIPIENT_INTERFACE + * bRequest: ACM_SERIAL_STATE + * wValue: 0 + * wIndex: 0 + * wLength: Length of data + * + * 2. Followed by the notification data (in a separate packet) + */ + + ret = -ENOSYS; + } + break; + + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +/**************************************************************************** + * Name: cdcuart_rxint + * + * Description: + * Called by the serial driver to enable or disable RX interrupts. We, of + * course, have no RX interrupts but must behave consistently. This method + * is called under the conditions: + * + * 1. With enable==true when the port is opened (just after cdcuart_setup + * and cdcuart_attach are called called) + * 2. With enable==false while transferring data from the RX buffer + * 2. With enable==true while waiting for more incoming data + * 3. With enable==false when the port is closed (just before cdcuart_detach + * and cdcuart_shutdown are called). + * + ****************************************************************************/ + +static void cdcuart_rxint(FAR struct uart_dev_s *dev, bool enable) +{ + FAR struct cdcser_dev_s *priv; + FAR uart_dev_t *serdev; + irqstate_t flags; + + usbtrace(CDCSER_CLASSAPI_RXINT, (uint16_t)enable); + + /* Sanity check */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct cdcser_dev_s*)dev->priv; + serdev = &priv->serdev; + + /* We need exclusive access to the RX buffer and private structure + * in the following. + */ + + flags = irqsave(); + if (enable) + { + /* RX "interrupts" are enabled. Is this a transition from disabled + * to enabled state? + */ + + if (!priv->rxenabled) + { + /* Yes. During the time that RX interrupts are disabled, the + * the serial driver will be extracting data from the circular + * buffer and modifying recv.tail. During this time, we + * should avoid modifying recv.head; When interrupts are restored, + * we can update the head pointer for all of the data that we + * put into cicular buffer while "interrupts" were disabled. + */ + + if (priv->rxhead != serdev->recv.head) + { + serdev->recv.head = priv->rxhead; + + /* Yes... signal the availability of new data */ + + uart_datareceived(serdev); + } + + /* RX "interrupts are no longer disabled */ + + priv->rxenabled = true; + } + } + + /* RX "interrupts" are disabled. Is this a transition from enabled + * to disabled state? + */ + + else if (priv->rxenabled) + { + /* Yes. During the time that RX interrupts are disabled, the + * the serial driver will be extracting data from the circular + * buffer and modifying recv.tail. During this time, we + * should avoid modifying recv.head; When interrupts are disabled, + * we use a shadow index and continue adding data to the circular + * buffer. + */ + + priv->rxhead = serdev->recv.head; + priv->rxenabled = false; + } + irqrestore(flags); +} + +/**************************************************************************** + * Name: cdcuart_txint + * + * Description: + * Called by the serial driver to enable or disable TX interrupts. We, of + * course, have no TX interrupts but must behave consistently. Initially, + * TX interrupts are disabled. This method is called under the conditions: + * + * 1. With enable==false while transferring data into the TX buffer + * 2. With enable==true when data may be taken from the buffer. + * 3. With enable==false when the TX buffer is empty + * + ****************************************************************************/ + +static void cdcuart_txint(FAR struct uart_dev_s *dev, bool enable) +{ + FAR struct cdcser_dev_s *priv; + + usbtrace(CDCSER_CLASSAPI_TXINT, (uint16_t)enable); + + /* Sanity checks */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract references to private data */ + + priv = (FAR struct cdcser_dev_s*)dev->priv; + + /* If the new state is enabled and if there is data in the XMIT buffer, + * send the next packet now. + */ + + uvdbg("enable=%d head=%d tail=%d\n", + enable, priv->serdev.xmit.head, priv->serdev.xmit.tail); + + if (enable && priv->serdev.xmit.head != priv->serdev.xmit.tail) + { + cdcser_sndpacket(priv); + } +} + +/**************************************************************************** + * Name: cdcuart_txempty + * + * Description: + * Return true when all data has been sent. This is called from the + * serial driver when the driver is closed. It will call this API + * periodically until it reports true. NOTE that the serial driver takes all + * responsibility for flushing TX data through the hardware so we can be + * a bit sloppy about that. + * + ****************************************************************************/ + +static bool cdcuart_txempty(FAR struct uart_dev_s *dev) +{ + FAR struct cdcser_dev_s *priv = (FAR struct cdcser_dev_s*)dev->priv; + + usbtrace(CDCSER_CLASSAPI_TXEMPTY, 0); + +#if CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return true; + } +#endif + + /* When all of the allocated write requests have been returned to the + * reqlist, then there is no longer any TX data in flight. + */ + + return priv->nwrq >= CONFIG_CDCSER_NWRREQS; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cdcser_initialize + * + * Description: + * Register USB serial port (and USB serial console if so configured). + * + * Input Parameter: + * Device minor number. E.g., minor 0 would correspond to /dev/ttyUSB0. + * + * Returned Value: + * Zero (OK) means that the driver was successfully registered. On any + * failure, a negated errno value is retured. + * + ****************************************************************************/ + +int cdcser_initialize(int minor) +{ + FAR struct cdcser_alloc_s *alloc; + FAR struct cdcser_dev_s *priv; + FAR struct cdcser_driver_s *drvr; + char devname[16]; + int ret; + + /* Allocate the structures needed */ + + alloc = (FAR struct cdcser_alloc_s*)kmalloc(sizeof(struct cdcser_alloc_s)); + if (!alloc) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0); + return -ENOMEM; + } + + /* Convenience pointers into the allocated blob */ + + priv = &alloc->dev; + drvr = &alloc->drvr; + + /* Initialize the USB serial driver structure */ + + memset(priv, 0, sizeof(struct cdcser_dev_s)); + sq_init(&priv->reqlist); + + /* Fake line status */ + + priv->linecoding.baud[0] = (115200) & 0xff; /* Baud=115200 */ + priv->linecoding.baud[1] = (115200 >> 8) & 0xff; + priv->linecoding.baud[2] = (115200 >> 16) & 0xff; + priv->linecoding.baud[3] = (115200 >> 24) & 0xff; + priv->linecoding.stop = CDC_CHFMT_STOP1; /* One stop bit */ + priv->linecoding.parity = CDC_PARITY_NONE; /* No parity */ + priv->linecoding.nbits = 8; /* 8 data bits */ + + /* Initialize the serial driver sub-structure */ + + priv->serdev.recv.size = CONFIG_CDCSER_RXBUFSIZE; + priv->serdev.recv.buffer = priv->rxbuffer; + priv->serdev.xmit.size = CONFIG_CDCSER_TXBUFSIZE; + priv->serdev.xmit.buffer = priv->txbuffer; + priv->serdev.ops = &g_uartops; + priv->serdev.priv = priv; + + /* Initialize the USB class driver structure */ + +#ifdef CONFIG_USBDEV_DUALSPEED + drvr->drvr.speed = USB_SPEED_HIGH; +#else + drvr->drvr.speed = USB_SPEED_FULL; +#endif + drvr->drvr.ops = &g_driverops; + drvr->dev = priv; + + /* Register the USB serial class driver */ + + ret = usbdev_register(&drvr->drvr); + if (ret) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER), (uint16_t)-ret); + goto errout_with_alloc; + } + + /* Register the USB serial console */ + +#ifdef CONFIG_CDCSER_CONSOLE + g_usbserialport.isconsole = true; + ret = uart_register("/dev/console", &pri->serdev); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONSOLEREGISTER), (uint16_t)-ret); + goto errout_with_class; + } +#endif + + /* Register the single port supported by this implementation */ + + sprintf(devname, "/dev/ttyUSB%d", minor); + ret = uart_register(devname, &priv->serdev); + if (ret) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTREGISTER), (uint16_t)-ret); + goto errout_with_class; + } + return OK; + +errout_with_class: + usbdev_unregister(&drvr->drvr); +errout_with_alloc: + kfree(alloc); + return ret; +} diff --git a/nuttx/drivers/usbdev/cdc_serial.h b/nuttx/drivers/usbdev/cdc_serial.h new file mode 100644 index 000000000..818cd4935 --- /dev/null +++ b/nuttx/drivers/usbdev/cdc_serial.h @@ -0,0 +1,196 @@ +/**************************************************************************** + * drivers/usbdev/cdc_serial.h + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_USBDEV_USBDEV_CDC_SERIAL_H +#define __DRIVERS_USBDEV_USBDEV_CDC_SERIAL_H 1 + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdint.h> + +#include <nuttx/usb/usbdev.h> +#include <nuttx/usb/usbdev_trace.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ +/* Descriptors **************************************************************/ +/* These settings are not modifiable via the NuttX configuration */ + +#define CDCSER_CONFIGIDNONE (0) /* Config ID means to return to address mode */ +#define CDCSER_INTERFACEID (0) +#define CDCSER_ALTINTERFACEID (0) + +/* Configuration descriptor values */ + +#define CDCSER_CONFIGID (1) /* The only supported configuration ID */ + +/* Endpoint configuration */ + +#define CDCSER_EPINTIN_ADDR (USB_DIR_IN|CONFIG_CDCSER_EPINTIN) +#define CDCSER_EPINTIN_ATTR (USB_EP_ATTR_XFER_INT) + +#define CDCSER_EPOUTBULK_ADDR (CONFIG_CDCSER_EPBULKOUT) +#define CDCSER_EPOUTBULK_ATTR (USB_EP_ATTR_XFER_BULK) + +#define CDCSER_EPINBULK_ADDR (USB_DIR_IN|CONFIG_CDCSER_EPBULKIN) +#define CDCSER_EPINBULK_ATTR (USB_EP_ATTR_XFER_BULK) + +/* Buffer big enough for any of our descriptors (the config descriptor is the + * biggest). + */ + +#define CDCSER_MXDESCLEN (64) + +/* Misc Macros **************************************************************/ +/* min/max macros */ + +#ifndef min +# define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifndef max +# define max(a,b) ((a)>(b)?(a):(b)) +#endif + +/* Trace values *************************************************************/ + +#define CDCSER_CLASSAPI_SETUP TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SETUP) +#define CDCSER_CLASSAPI_SHUTDOWN TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SHUTDOWN) +#define CDCSER_CLASSAPI_ATTACH TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_ATTACH) +#define CDCSER_CLASSAPI_DETACH TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_DETACH) +#define CDCSER_CLASSAPI_IOCTL TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_IOCTL) +#define CDCSER_CLASSAPI_RECEIVE TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RECEIVE) +#define CDCSER_CLASSAPI_RXINT TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXINT) +#define CDCSER_CLASSAPI_RXAVAILABLE TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXAVAILABLE) +#define CDCSER_CLASSAPI_SEND TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SEND) +#define CDCSER_CLASSAPI_TXINT TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXINT) +#define CDCSER_CLASSAPI_TXREADY TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXREADY) +#define CDCSER_CLASSAPI_TXEMPTY TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXEMPTY) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum cdcser_epdesc_e +{ + CDCSER_EPINTIN = 0, /* Interrupt IN endpoint descriptor */ + CDCSER_EPBULKOUT, /* Bulk OUT endpoint descriptor */ + CDCSER_EPBULKIN /* Bulk IN endpoint descriptor */ +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: cdcser_mkstrdesc + * + * Description: + * Construct a string descriptor + * + ****************************************************************************/ + +int cdcser_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc); + +/**************************************************************************** + * Name: cdcser_getepdesc + * + * Description: + * Return a pointer to the raw device descriptor + * + ****************************************************************************/ + +FAR const struct usb_devdesc_s *cdcser_getdevdesc(void); + +/**************************************************************************** + * Name: cdcser_getepdesc + * + * Description: + * Return a pointer to the raw endpoint descriptor (used for configuring + * endpoints) + * + ****************************************************************************/ + +FAR const struct usb_epdesc_s *cdcser_getepdesc(enum cdcser_epdesc_e epid); + +/**************************************************************************** + * Name: cdcser_mkepdesc + * + * Description: + * Construct the endpoint descriptor using the correct max packet size. + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +void cdcser_mkepdesc(enum cdcser_epdesc_e epid, + uint16_t mxpacket, FAR struct usb_epdesc_s *outdesc); +#endif + +/**************************************************************************** + * Name: cdcser_mkcfgdesc + * + * Description: + * Construct the configuration descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +int16_t cdcser_mkcfgdesc(FAR uint8_t *buf, uint8_t speed, uint8_t type); +#else +int16_t cdcser_mkcfgdesc(FAR uint8_t *buf); +#endif + +/**************************************************************************** + * Name: cdcser_getqualdesc + * + * Description: + * Return a pointer to the raw qual descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +FAR const struct usb_qualdesc_s *cdcser_getqualdesc(void); +#endif + +#endif /* __DRIVERS_USBDEV_USBDEV_CDC_SERIAL_H */ diff --git a/nuttx/drivers/usbdev/usbdev_composite.c b/nuttx/drivers/usbdev/usbdev_composite.c new file mode 100644 index 000000000..de531b8a4 --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_composite.c @@ -0,0 +1,85 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_composite.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/usb/usbdev_trace.h> + +#include "usbdev_composite.h" + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/******************************************************************************* + * Name: + * + * Description: + * + * Input Parameters: + * + * Returned Value: + * + *******************************************************************************/ + diff --git a/nuttx/drivers/usbdev/usbdev_composite.h b/nuttx/drivers/usbdev/usbdev_composite.h new file mode 100644 index 000000000..472f523b2 --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_composite.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_composite.h + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_USBDEV_USBDEV_COMPOSITE_H +#define __DRIVERS_USBDEV_USBDEV_COMPOSITE_H 1 + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> + +#include <nuttx/usb/usbdev_trace.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ + +#ifndef CONFIG_USBDEV_TRACE_NRECORDS +# define CONFIG_USBDEV_TRACE_NRECORDS 128 +#endif + +#ifndef CONFIG_USBDEV_TRACE_INITIALIDSET +# define CONFIG_USBDEV_TRACE_INITIALIDSET 0 +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/******************************************************************************* + * Name: + * + * Description: + * + * Input Parameters: + * + * Returned Value: + * + *******************************************************************************/ + +#endif /* __DRIVERS_USBDEV_USBDEV_COMPOSITE_H */ diff --git a/nuttx/drivers/usbdev/usbdev_scsi.c b/nuttx/drivers/usbdev/usbdev_scsi.c new file mode 100644 index 000000000..a0bbf8c2a --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_scsi.c @@ -0,0 +1,2664 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_storage.c + * + * Copyright (C) 2008-2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Mass storage class device. Bulk-only with SCSI subclass. + * + * References: + * "Universal Serial Bus Mass Storage Class, Specification Overview," + * Revision 1.2, USB Implementer's Forum, June 23, 2003. + * + * "Universal Serial Bus Mass Storage Class, Bulk-Only Transport," + * Revision 1.0, USB Implementer's Forum, September 31, 1999. + * + * "SCSI Primary Commands - 3 (SPC-3)," American National Standard + * for Information Technology, May 4, 2005 + * + * "SCSI Primary Commands - 4 (SPC-4)," American National Standard + * for Information Technology, July 19, 2008 + * + * "SCSI Block Commands -2 (SBC-2)," American National Standard + * for Information Technology, November 13, 2004 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <pthread.h> +#include <string.h> +#include <errno.h> +#include <queue.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/scsi.h> +#include <nuttx/usb/storage.h> +#include <nuttx/usb/usbdev.h> +#include <nuttx/usb/usbdev_trace.h> + +#include "usbdev_storage.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Race condition workaround found by David Hewson. This race condition + * "seems to relate to stalling the endpoint when a short response is + * generated which causes a residue to exist when the CSW would be returned. + * I think there's two issues here. The first being if the transfer which + * had just been queued before the stall had not completed then it wouldn’t + * then complete once the endpoint was stalled? The second is that the + * subsequent transfer for the CSW would be dropped on the floor (by the + * epsubmit() function) if the end point was still stalled as the control + * transfer to resume it hadn't occurred." + */ + +#define CONFIG_USBSTRG_RACEWAR 1 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Debug ********************************************************************/ + +#if defined(CONFIG_DEBUG_VERBOSE) && defined (CONFIG_DEBUG_USB) +static void usbstrg_dumpdata(const char *msg, const uint8_t *buf, + int buflen); +#else +# define usbstrg_dumpdata(msg, buf, len) +#endif + +/* Utility Support Functions ************************************************/ + +static uint16_t usbstrg_getbe16(uint8_t *buf); +static uint32_t usbstrg_getbe32(uint8_t *buf); +static void usbstrg_putbe16(uint8_t * buf, uint16_t val); +static void usbstrg_putbe24(uint8_t *buf, uint32_t val); +static void usbstrg_putbe32(uint8_t *buf, uint32_t val); +#if 0 /* not used */ +static uint16_t usbstrg_getle16(uint8_t *buf); +#endif +static uint32_t usbstrg_getle32(uint8_t *buf); +#if 0 /* not used */ +static void usbstrg_putle16(uint8_t * buf, uint16_t val); +#endif +static void usbstrg_putle32(uint8_t *buf, uint32_t val); + +/* SCSI Command Processing **************************************************/ + +static inline int usbstrg_cmdtestunitready(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdrequestsense(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf); +static inline int usbstrg_cmdread6(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdwrite6(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdinquiry(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf); +static inline int usbstrg_cmdmodeselect6(FAR struct usbstrg_dev_s *priv); +static int usbstrg_modepage(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf, uint8_t pcpgcode, int *mdlen); +static inline int usbstrg_cmdmodesense6(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf); +static inline int usbstrg_cmdstartstopunit(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdpreventmediumremoval(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdreadformatcapacity(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf); +static inline int usbstrg_cmdreadcapacity10(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf); +static inline int usbstrg_cmdread10(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdwrite10(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdverify10(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdsynchronizecache10(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdmodeselect10(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdmodesense10(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf); +static inline int usbstrg_cmdread12(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_cmdwrite12(FAR struct usbstrg_dev_s *priv); +static inline int usbstrg_setupcmd(FAR struct usbstrg_dev_s *priv, + uint8_t cdblen, uint8_t flags); + +/* SCSI Worker Thread *******************************************************/ + +static int usbstrg_idlestate(FAR struct usbstrg_dev_s *priv); +static int usbstrg_cmdparsestate(FAR struct usbstrg_dev_s *priv); +static int usbstrg_cmdreadstate(FAR struct usbstrg_dev_s *priv); +static int usbstrg_cmdwritestate(FAR struct usbstrg_dev_s *priv); +static int usbstrg_cmdfinishstate(FAR struct usbstrg_dev_s *priv); +static int usbstrg_cmdstatusstate(FAR struct usbstrg_dev_s *priv); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Debug + ****************************************************************************/ + +/**************************************************************************** + * Name: usbstrg_dumpdata + ****************************************************************************/ + +#if defined(CONFIG_DEBUG_VERBOSE) && defined (CONFIG_DEBUG_USB) +static void usbstrg_dumpdata(const char *msg, const uint8_t *buf, int buflen) +{ + int i; + + dbgprintf("%s:", msg); + for (i = 0; i < buflen; i++) + { + dbgprintf(" %02x", buf[i]); + } + dbgprintf("\n"); +} +#endif + +/**************************************************************************** + * Utility Support Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbstrg_getbe16 + * + * Description: + * Get a 16-bit big-endian value reference by the byte pointer + * + ****************************************************************************/ + +static uint16_t usbstrg_getbe16(uint8_t *buf) +{ + return ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]); +} + +/**************************************************************************** + * Name: usbstrg_getbe32 + * + * Description: + * Get a 32-bit big-endian value reference by the byte pointer + * + ****************************************************************************/ + +static uint32_t usbstrg_getbe32(uint8_t *buf) +{ + return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3]); +} + +/**************************************************************************** + * Name: usbstrg_putbe16 + * + * Description: + * Store a 16-bit value in big-endian order to the location specified by + * a byte pointer + * + ****************************************************************************/ + +static void usbstrg_putbe16(uint8_t * buf, uint16_t val) +{ + buf[0] = val >> 8; + buf[1] = val; +} + +/**************************************************************************** + * Name: usbstrg_putbe24 + * + * Description: + * Store a 32-bit value in big-endian order to the location specified by + * a byte pointer + * + ****************************************************************************/ + +static void usbstrg_putbe24(uint8_t *buf, uint32_t val) +{ + buf[0] = val >> 16; + buf[1] = val >> 8; + buf[2] = val; +} + +/**************************************************************************** + * Name: usbstrg_putbe32 + * + * Description: + * Store a 32-bit value in big-endian order to the location specified by + * a byte pointer + * + ****************************************************************************/ + +static void usbstrg_putbe32(uint8_t *buf, uint32_t val) +{ + buf[0] = val >> 24; + buf[1] = val >> 16; + buf[2] = val >> 8; + buf[3] = val; +} + +/**************************************************************************** + * Name: usbstrg_getle16 + * + * Description: + * Get a 16-bit little-endian value reference by the byte pointer + * + ****************************************************************************/ + +#if 0 /* not used */ +static uint16_t usbstrg_getle16(uint8_t *buf) +{ + return ((uint16_t)buf[1] << 8) | ((uint16_t)buf[0]); +} +#endif + +/**************************************************************************** + * Name: usbstrg_getle32 + * + * Description: + * Get a 32-bit little-endian value reference by the byte pointer + * + ****************************************************************************/ + +static uint32_t usbstrg_getle32(uint8_t *buf) +{ + return ((uint32_t)buf[3] << 24) | ((uint32_t)buf[2] << 16) | + ((uint32_t)buf[1] << 8) | ((uint32_t)buf[0]); +} + +/**************************************************************************** + * Name: usbstrg_putle16 + * + * Description: + * Store a 16-bit value in little-endian order to the location specified by + * a byte pointer + * + ****************************************************************************/ + +#if 0 /* not used */ +static void usbstrg_putle16(uint8_t * buf, uint16_t val) +{ + buf[0] = val; + buf[1] = val >> 8; +} +#endif + +/**************************************************************************** + * Name: usbstrg_putle32 + * + * Description: + * Store a 32-bit value in little-endian order to the location specified by + * a byte pointer + * + ****************************************************************************/ + +static void usbstrg_putle32(uint8_t *buf, uint32_t val) +{ + buf[0] = val; + buf[1] = val >> 8; + buf[2] = val >> 16; + buf[3] = val >> 24; +} + +/**************************************************************************** + * SCSI Worker Thread + ****************************************************************************/ + +/**************************************************************************** + * Name: usbstrg_cmdtestunitready + * + * Description: + * Handle the SCSI_CMD_TESTUNITREADY command + * + ****************************************************************************/ + +static inline int usbstrg_cmdtestunitready(FAR struct usbstrg_dev_s *priv) +{ + int ret; + + priv->u.alloclen = 0; + ret = usbstrg_setupcmd(priv, 6, USBSTRG_FLAGS_DIRNONE); + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdrequestsense + * + * Description: + * Handle the SCSI_CMD_REQUESTSENSE command + * + ****************************************************************************/ + +static inline int usbstrg_cmdrequestsense(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf) +{ + FAR struct scsicmd_requestsense_s *request = (FAR struct scsicmd_requestsense_s *)priv->cdb; + FAR struct scsiresp_fixedsensedata_s *response = (FAR struct scsiresp_fixedsensedata_s *)buf; + FAR struct usbstrg_lun_s *lun; + uint32_t sd; + uint32_t sdinfo; + uint8_t cdblen; + int ret; + + /* Extract the host allocation length */ + + priv->u.alloclen = request->alloclen; + + /* Get the expected length of the command (with hack for MS-Windows 12-byte + * REQUEST SENSE command. + */ + + cdblen = SCSICMD_REQUESTSENSE_SIZEOF; + if (cdblen != priv->cdblen) + { + /* Try MS-Windows REQUEST SENSE with cbw->cdblen == 12 */ + + cdblen = SCSICMD_REQUESTSENSE_MSSIZEOF; + } + + ret = usbstrg_setupcmd(priv, cdblen, + USBSTRG_FLAGS_DIRDEVICE2HOST|USBSTRG_FLAGS_LUNNOTNEEDED| + USBSTRG_FLAGS_UACOKAY|USBSTRG_FLAGS_RETAINSENSEDATA); + if (ret == OK) + { + lun = priv->lun; + if (!lun) + { + sd = SCSI_KCQIR_INVALIDLUN; + sdinfo = 0; + } + else + { + /* Get the saved sense data from the LUN structure */ + + sd = lun->sd; + sdinfo = lun->sdinfo; + + /* Discard the sense data */ + + lun->sd = SCSI_KCQ_NOSENSE; + lun->sdinfo = 0; + } + + /* Create the fixed sense data response */ + + memset(response, 0, SCSIRESP_FIXEDSENSEDATA_SIZEOF); + + response->code = SCSIRESP_SENSEDATA_RESPVALID|SCSIRESP_SENSEDATA_CURRENTFIXED; + response->flags = (uint8_t)(sd >> 16); + usbstrg_putbe32(response->info, sdinfo); + response->len = SCSIRESP_FIXEDSENSEDATA_SIZEOF - 7; + response->code2 = (uint8_t)(sd >> 8); + response->qual2 = (uint8_t)sd; + + priv->nreqbytes = SCSIRESP_FIXEDSENSEDATA_SIZEOF; + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdread6 + * + * Description: + * Handle the SCSI_CMD_READ6 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdread6(FAR struct usbstrg_dev_s *priv) +{ + FAR struct scsicmd_read6_s *read6 = (FAR struct scsicmd_read6_s*)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; + int ret; + + priv->u.xfrlen = (uint16_t)read6->xfrlen; + if (priv->u.xfrlen == 0) + { + priv->u.xfrlen = 256; + } + + ret = usbstrg_setupcmd(priv, SCSICMD_READ6_SIZEOF, USBSTRG_FLAGS_DIRDEVICE2HOST|USBSTRG_FLAGS_BLOCKXFR); + if (ret == OK) + { + /* Get the Logical Block Address (LBA) from cdb[] as the starting sector */ + + priv->sector = (uint32_t)(read6->mslba & SCSICMD_READ6_MSLBAMASK) << 16 | (uint32_t)usbstrg_getbe16(read6->lslba); + + /* Verify that a block driver has been bound to the LUN */ + + if (!lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ6MEDIANOTPRESENT), 0); + lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + + /* Verify that sector lies in the range supported by the block driver */ + + else if (priv->sector >= lun->nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ6LBARANGE), 0); + lun->sd = SCSI_KCQIR_LBAOUTOFRANGE; + ret = -EINVAL; + } + + /* Looks like we are good to go */ + + else + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDPARSECMDREAD6), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDREAD; + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdwrite6 + * + * Description: + * Handle the SCSI_CMD_WRITE6 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdwrite6(FAR struct usbstrg_dev_s *priv) +{ + FAR struct scsicmd_write6_s *write6 = (FAR struct scsicmd_write6_s *)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; + int ret; + + priv->u.xfrlen = (uint16_t)write6->xfrlen; + if (priv->u.xfrlen == 0) + { + priv->u.xfrlen = 256; + } + + ret = usbstrg_setupcmd(priv, SCSICMD_WRITE6_SIZEOF, USBSTRG_FLAGS_DIRHOST2DEVICE|USBSTRG_FLAGS_BLOCKXFR); + if (ret == OK) + { + /* Get the Logical Block Address (LBA) from cdb[] as the starting sector */ + + priv->sector = (uint32_t)(write6->mslba & SCSICMD_WRITE6_MSLBAMASK) << 16 | (uint32_t)usbstrg_getbe16(write6->lslba); + + /* Verify that a block driver has been bound to the LUN */ + + if (!lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE6MEDIANOTPRESENT), 0); + lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + + /* Check for attempts to write to a read-only device */ + + else if (lun->readonly) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE6READONLY), 0); + lun->sd = SCSI_KCQWP_COMMANDNOTALLOWED; + ret = -EINVAL; + } + + /* Verify that sector lies in the range supported by the block driver */ + + else if (priv->sector >= lun->nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE6LBARANGE), 0); + lun->sd = SCSI_KCQIR_LBAOUTOFRANGE; + ret = -EINVAL; + } + + /* Looks like we are good to go */ + + else + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDPARSECMDWRITE6), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDWRITE; + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdinquiry + * + * Description: + * Handle SCSI_CMD_INQUIRY command + * + ****************************************************************************/ + +static inline int usbstrg_cmdinquiry(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf) +{ + FAR struct scscicmd_inquiry_s *inquiry = (FAR struct scscicmd_inquiry_s *)priv->cdb; + FAR struct scsiresp_inquiry_s *response = (FAR struct scsiresp_inquiry_s *)buf; + int len; + int ret; + + priv->u.alloclen = usbstrg_getbe16(inquiry->alloclen); + ret = usbstrg_setupcmd(priv, SCSICMD_INQUIRY_SIZEOF, + USBSTRG_FLAGS_DIRDEVICE2HOST|USBSTRG_FLAGS_LUNNOTNEEDED|USBSTRG_FLAGS_UACOKAY); + if (ret == OK) + { + if (!priv->lun) + { + response->qualtype = SCSIRESP_INQUIRYPQ_NOTCAPABLE|SCSIRESP_INQUIRYPD_UNKNOWN; + } + else if ((inquiry->flags != 0) || (inquiry->pagecode != 0)) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INQUIRYFLAGS), 0); + priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + else + { + memset(response, 0, SCSIRESP_INQUIRY_SIZEOF); + priv->nreqbytes = SCSIRESP_INQUIRY_SIZEOF; + +#ifdef CONFIG_USBSTRG_REMOVABLE + response->flags1 = SCSIRESP_INQUIRYFLAGS1_RMB; +#endif + response->version = 2; /* SCSI-2 */ + response->flags2 = 2; /* SCSI-2 INQUIRY response data format */ + response->len = SCSIRESP_INQUIRY_SIZEOF - 5; + + /* Strings */ + + memset(response->vendorid, ' ', 8+16+4); + + len = strlen(g_vendorstr); + DEBUGASSERT(len <= 8); + memcpy(response->vendorid, g_vendorstr, len); + + len = strlen(g_productstr); + DEBUGASSERT(len <= 16); + memcpy(response->productid, g_productstr, len); + + len = strlen(g_serialstr); + DEBUGASSERT(len <= 4); + memcpy(response->productid, g_serialstr, len); + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdmodeselect6 + * + * Description: + * Handle SCSI_CMD_MODESELECT6 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdmodeselect6(FAR struct usbstrg_dev_s *priv) +{ + FAR struct scsicmd_modeselect6_s *modeselect = (FAR struct scsicmd_modeselect6_s *)priv->cdb; + + priv->u.alloclen = modeselect->plen; + (void)usbstrg_setupcmd(priv, SCSICMD_MODESELECT6_SIZEOF, USBSTRG_FLAGS_DIRHOST2DEVICE); + + /* Not supported */ + + priv->lun->sd = SCSI_KCQIR_INVALIDCOMMAND; + return -EINVAL; +} + +/**************************************************************************** + * Name: usbstrg_modepage + * + * Description: + * Common logic for usbstrg_cmdmodesense6() and usbstrg_cmdmodesense10() + * + ****************************************************************************/ + +static int usbstrg_modepage(FAR struct usbstrg_dev_s *priv, FAR uint8_t *buf, + uint8_t pcpgcode, int *mdlen) +{ + FAR struct scsiresp_cachingmodepage_s *cmp = (FAR struct scsiresp_cachingmodepage_s *)buf; + + /* Saving parms not supported */ + + if ((pcpgcode & SCSICMD_MODESENSE_PCMASK) == SCSICMD_MODESENSE_PCSAVED) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_PCSAVED), 0); + priv->lun->sd = SCSI_KCQIR_SAVINGPARMSNOTSUPPORTED; + return -EINVAL; + } + + /* Only the caching mode page is supported: */ + + if ((pcpgcode & SCSICMD_MODESENSE_PGCODEMASK) == SCSIRESP_MODESENSE_PGCCODE_CACHING || + (pcpgcode & SCSICMD_MODESENSE_PGCODEMASK) == SCSIRESP_MODESENSE_PGCCODE_RETURNALL) + { + memset(cmp, 0, 12); + cmp->pgcode = SCSIRESP_MODESENSE_PGCCODE_CACHING; + cmp->len = 10; /* n-2 */ + + /* None of the fields are changeable */ + + if (((pcpgcode & SCSICMD_MODESENSE_PCMASK) != SCSICMD_MODESENSE_PCCHANGEABLE)) + { + cmp->flags1 = SCSIRESP_CACHINGMODEPG_WCE; /* Write cache enable */ + cmp->dpflen[0] = 0xff; /* Disable prefetch transfer length = 0xffffffff */ + cmp->dpflen[1] = 0xff; + cmp->maxpf[0] = 0xff; /* Maximum pre-fetch = 0xffffffff */ + cmp->maxpf[1] = 0xff; + cmp->maxpfc[0] = 0xff; /* Maximum pref-fetch ceiling = 0xffffffff */ + cmp->maxpfc[1] = 0xff; + } + + /* Return the mode data length */ + + *mdlen = 12; /* Only the first 12-bytes of caching mode page sent */ + return OK; + } + else + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_MODEPAGEFLAGS), pcpgcode); + priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + return -EINVAL; + } +} + +/**************************************************************************** + * Name: usbstrg_cmdmodesense6 + * + * Description: + * Handle SCSI_CMD_MODESENSE6 command + * + ****************************************************************************/ + +static int inline usbstrg_cmdmodesense6(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf) +{ + FAR struct scsicmd_modesense6_s *modesense = (FAR struct scsicmd_modesense6_s *)priv->cdb; + FAR struct scsiresp_modeparameterhdr6_s *mph = (FAR struct scsiresp_modeparameterhdr6_s *)buf; + int mdlen; + int ret; + + priv->u.alloclen = modesense->alloclen; + ret = usbstrg_setupcmd(priv, SCSICMD_MODESENSE6_SIZEOF, USBSTRG_FLAGS_DIRDEVICE2HOST); + if (ret == OK) + { + if ((modesense->flags & ~SCSICMD_MODESENSE6_DBD) != 0 || modesense->subpgcode != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_MODESENSE6FLAGS), 0); + priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + else + { + /* The response consists of: + * + * (1) A MODESENSE6-specific mode parameter header, + * (2) A variable length list of block descriptors, and + * (3) A variable length list of mode page formats + */ + + mph->type = 0; /* Medium type */ + mph->param = (priv->lun->readonly ? SCSIRESP_MODEPARMHDR_DAPARM_WP : 0x00); + mph->bdlen = 0; /* Block descriptor length */ + + /* There are no block descriptors, only the following mode page: */ + + ret = usbstrg_modepage(priv, &buf[SCSIRESP_MODEPARAMETERHDR6_SIZEOF], modesense->pcpgcode, &mdlen); + if (ret == OK) + { + /* Store the mode data length and return the total message size */ + + mph->mdlen = mdlen - 1; + priv->nreqbytes = mdlen + SCSIRESP_MODEPARAMETERHDR6_SIZEOF; + } + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdstartstopunit + * + * Description: + * Handle SCSI_CMD_STARTSTOPUNIT command + * + ****************************************************************************/ + +static inline int usbstrg_cmdstartstopunit(FAR struct usbstrg_dev_s *priv) +{ + int ret; + + priv->u.alloclen = 0; + ret = usbstrg_setupcmd(priv, SCSICMD_STARTSTOPUNIT_SIZEOF, USBSTRG_FLAGS_DIRNONE); + if (ret == OK) + { +#ifndef CONFIG_USBSTRG_REMOVABLE + /* This command is not valid if the media is not removable */ + + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_NOTREMOVABLE), 0); + lun->sd = SCSI_KCQIR_INVALIDCOMMAND; + ret = -EINVAL; +#endif + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdpreventmediumremoval + * + * Description: + * Handle SCSI_CMD_PREVENTMEDIAREMOVAL command + * + ****************************************************************************/ + +static inline int usbstrg_cmdpreventmediumremoval(FAR struct usbstrg_dev_s *priv) +{ +#ifdef CONFIG_USBSTRG_REMOVABLE + FAR struct scsicmd_preventmediumremoval_s *pmr = (FAR struct scsicmd_preventmediumremoval_s *)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; +#endif + int ret; + + priv->u.alloclen = 0; + ret = usbstrg_setupcmd(priv, SCSICMD_PREVENTMEDIUMREMOVAL_SIZEOF, USBSTRG_FLAGS_DIRNONE); + if (ret == OK) + { +#ifndef CONFIG_USBSTRG_REMOVABLE + lun->sd = SCSI_KCQIR_INVALIDCOMMAND; + ret = -EINVAL; +#else + if ((pmr->prevent & ~SCSICMD_PREVENTMEDIUMREMOVAL_TRANSPORT) != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_PREVENTMEDIUMREMOVALPREVENT), 0); + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + + lun->locked = pmr->prevent & SCSICMD_PREVENTMEDIUMREMOVAL_TRANSPORT; +#endif + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdreadformatcapacity + * + * Description: + * Handle SCSI_CMD_READFORMATCAPACITIES command (MMC) + * + ****************************************************************************/ + +static inline int usbstrg_cmdreadformatcapacity(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf) +{ + FAR struct scsicmd_readformatcapcacities_s *rfc = (FAR struct scsicmd_readformatcapcacities_s *)priv->cdb; + FAR struct scsiresp_readformatcapacities_s *hdr; + FAR struct usbstrg_lun_s *lun = priv->lun; + int ret; + + priv->u.alloclen = usbstrg_getbe16(rfc->alloclen); + ret = usbstrg_setupcmd(priv, SCSICMD_READFORMATCAPACITIES_SIZEOF, USBSTRG_FLAGS_DIRDEVICE2HOST); + if (ret == OK) + { + hdr = (FAR struct scsiresp_readformatcapacities_s *)buf; + memset(hdr, 0, SCSIRESP_READFORMATCAPACITIES_SIZEOF); + hdr->listlen = SCSIRESP_CURRCAPACITYDESC_SIZEOF; + + /* Only the Current/Maximum Capacity Descriptor follows the header */ + + usbstrg_putbe32(hdr->nblocks, lun->nsectors); + hdr->type = SCIRESP_RDFMTCAPACITIES_FORMATED; + usbstrg_putbe24(hdr->blocklen, lun->sectorsize); + priv->nreqbytes = SCSIRESP_READFORMATCAPACITIES_SIZEOF; + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdreadcapacity10 + * + * Description: + * Handle SCSI_CMD_READCAPACITY10 command + * + ****************************************************************************/ + +static int inline usbstrg_cmdreadcapacity10(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf) +{ + FAR struct scsicmd_readcapacity10_s *rcc = (FAR struct scsicmd_readcapacity10_s *)priv->cdb; + FAR struct scsiresp_readcapacity10_s *rcr = (FAR struct scsiresp_readcapacity10_s *)buf; + FAR struct usbstrg_lun_s *lun = priv->lun; + uint32_t lba; + int ret; + + priv->u.alloclen = SCSIRESP_READCAPACITY10_SIZEOF; /* Fake the allocation length */ + ret = usbstrg_setupcmd(priv, SCSICMD_READCAPACITY10_SIZEOF, USBSTRG_FLAGS_DIRDEVICE2HOST); + if (ret == OK) + { + /* Check the PMI and LBA fields */ + + lba = usbstrg_getbe32(rcc->lba); + + if (rcc->pmi > 1 || (rcc->pmi == 0 && lba != 0)) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READCAPACITYFLAGS), 0); + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + else + { + usbstrg_putbe32(rcr->lba, lun->nsectors - 1); + usbstrg_putbe32(&buf[4], lun->sectorsize); + priv->nreqbytes = SCSIRESP_READCAPACITY10_SIZEOF; + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdread10 + * + * Description: + * Handle SCSI_CMD_READ10 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdread10(FAR struct usbstrg_dev_s *priv) +{ + struct scsicmd_read10_s *read10 = (struct scsicmd_read10_s*)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; + int ret; + + priv->u.xfrlen = usbstrg_getbe16(read10->xfrlen); + ret = usbstrg_setupcmd(priv, SCSICMD_READ10_SIZEOF, USBSTRG_FLAGS_DIRDEVICE2HOST|USBSTRG_FLAGS_BLOCKXFR); + if (ret == OK) + { + /* Get the Logical Block Address (LBA) from cdb[] as the starting sector */ + + priv->sector = usbstrg_getbe32(read10->lba); + + /* Verify that we can support this read command */ + + if ((read10->flags & ~(SCSICMD_READ10FLAGS_DPO|SCSICMD_READ10FLAGS_FUA)) != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ10FLAGS), 0); + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + + /* Verify that a block driver has been bound to the LUN */ + + else if (!lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ10MEDIANOTPRESENT), 0); + lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + + /* Verify that LBA lies in the range supported by the block driver */ + + else if (priv->sector >= lun->nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ10LBARANGE), 0); + lun->sd = SCSI_KCQIR_LBAOUTOFRANGE; + ret = -EINVAL; + } + + /* Looks like we are good to go */ + + else + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDPARSECMDREAD10), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDREAD; + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdwrite10 + * + * Description: + * Handle SCSI_CMD_WRITE10 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdwrite10(FAR struct usbstrg_dev_s *priv) +{ + struct scsicmd_write10_s *write10 = (struct scsicmd_write10_s *)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; + int ret; + + priv->u.xfrlen = usbstrg_getbe16(write10->xfrlen); + ret = usbstrg_setupcmd(priv, SCSICMD_WRITE10_SIZEOF, USBSTRG_FLAGS_DIRHOST2DEVICE|USBSTRG_FLAGS_BLOCKXFR); + if (ret == OK) + { + /* Get the Logical Block Address (LBA) from cdb[] as the starting sector */ + + priv->sector = usbstrg_getbe32(write10->lba); + + /* Verify that we can support this write command */ + + if ((write10->flags & ~(SCSICMD_WRITE10FLAGS_DPO|SCSICMD_WRITE10FLAGS_FUA)) != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE10FLAGS), 0); + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + + /* Verify that a block driver has been bound to the LUN */ + + else if (!lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE10MEDIANOTPRESENT), 0); + lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + + /* Check for attempts to write to a read-only device */ + + else if (lun->readonly) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE10READONLY), 0); + lun->sd = SCSI_KCQWP_COMMANDNOTALLOWED; + ret = -EINVAL; + } + + /* Verify that LBA lies in the range supported by the block driver */ + + else if (priv->sector >= lun->nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE10LBARANGE), 0); + lun->sd = SCSI_KCQIR_LBAOUTOFRANGE; + ret = -EINVAL; + } + + /* Looks like we are good to go */ + + else + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDPARSECMDWRITE10), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDWRITE; + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdverify10 + * + * Description: + * Handle SCSI_CMD_VERIFY10 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdverify10(FAR struct usbstrg_dev_s *priv) +{ + FAR struct scsicmd_verify10_s *verf = (FAR struct scsicmd_verify10_s *)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; + uint32_t lba; + uint16_t blocks; + size_t sector; + ssize_t nread; + int ret; + int i; + + priv->u.alloclen = 0; + ret = usbstrg_setupcmd(priv, SCSICMD_VERIFY10_SIZEOF, USBSTRG_FLAGS_DIRNONE); + if (ret == OK) + { + /* Verify the starting and ending LBA */ + + lba = usbstrg_getbe32(verf->lba); + blocks = usbstrg_getbe16(verf->len); + + if ((verf->flags & ~SCSICMD_VERIFY10_DPO) != 0 || verf->groupno != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_VERIFY10FLAGS), 0); + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + else if (blocks == 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_VERIFY10NOBLOCKS), 0); + ret = -EIO; /* No reply */ + } + + /* Verify that a block driver has been bound to the LUN */ + + else if (!lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_VERIFY10MEDIANOTPRESENT), 0); + lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + + /* Verify that LBA lies in the range supported by the block driver */ + + else if (lba >= lun->nsectors || lba + blocks > lun->nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_VERIFY10LBARANGE), 0); + lun->sd = SCSI_KCQIR_LBAOUTOFRANGE; + ret = -EINVAL; + } + else + { + /* Try to read the requested blocks */ + + for (i = 0, sector = lba + lun->startsector; i < blocks; i++, sector++) + { + nread = USBSTRG_DRVR_READ(lun, priv->iobuffer, sector, 1); + if (nread < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_VERIFY10READFAIL), i); + lun->sd = SCSI_KCQME_UNRRE1; + lun->sdinfo = sector; + ret = -EIO; + break; + } + } + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdsynchronizecache10 + * + * Description: + * Handle SCSI_CMD_SYNCHCACHE10 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdsynchronizecache10(FAR struct usbstrg_dev_s *priv) +{ + int ret; + + priv->u.alloclen = 0; + + /* Verify that we have the LUN structure and the block driver has been bound */ + + if (!priv->lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SYNCCACHEMEDIANOTPRESENT), 0); + priv->lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + else + { + ret = usbstrg_setupcmd(priv, SCSICMD_SYNCHRONIZECACHE10_SIZEOF, USBSTRG_FLAGS_DIRNONE); + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdmodeselect10 + * + * Description: + * Handle SCSI_CMD_MODESELECT10 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdmodeselect10(FAR struct usbstrg_dev_s *priv) +{ + FAR struct scsicmd_modeselect10_s *modeselect = (FAR struct scsicmd_modeselect10_s *)priv->cdb; + + priv->u.alloclen = usbstrg_getbe16(modeselect->parmlen); + (void)usbstrg_setupcmd(priv, SCSICMD_MODESELECT10_SIZEOF, USBSTRG_FLAGS_DIRHOST2DEVICE); + + /* Not supported */ + + priv->lun->sd = SCSI_KCQIR_INVALIDCOMMAND; + return -EINVAL; +} + +/**************************************************************************** + * Name: usbstrg_cmdmodesense10 + * + * Description: + * Handle SCSI_CMD_MODESENSE10 command + * + ****************************************************************************/ + +static int inline usbstrg_cmdmodesense10(FAR struct usbstrg_dev_s *priv, + FAR uint8_t *buf) +{ + FAR struct scsicmd_modesense10_s *modesense = (FAR struct scsicmd_modesense10_s *)priv->cdb; + FAR struct scsiresp_modeparameterhdr10_s *mph = (FAR struct scsiresp_modeparameterhdr10_s *)buf; + int mdlen; + int ret; + + priv->u.alloclen = usbstrg_getbe16(modesense->alloclen); + ret = usbstrg_setupcmd(priv, SCSICMD_MODESENSE10_SIZEOF, USBSTRG_FLAGS_DIRDEVICE2HOST); + if (ret == OK) + { + if ((modesense->flags & ~SCSICMD_MODESENSE10_DBD) != 0 || modesense->subpgcode != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_MODESENSE10FLAGS), 0); + priv->lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + else + { + /* The response consists of: + * + * (1) A MODESENSE6-specific mode parameter header, + * (2) A variable length list of block descriptors, and + * (3) A variable lengtth list of mode page formats + */ + + memset(mph, 0, SCSIRESP_MODEPARAMETERHDR10_SIZEOF); + mph->param = (priv->lun->readonly ? SCSIRESP_MODEPARMHDR_DAPARM_WP : 0x00); + + /* There are no block descriptors, only the following mode page: */ + + ret = usbstrg_modepage(priv, &buf[SCSIRESP_MODEPARAMETERHDR10_SIZEOF], modesense->pcpgcode, &mdlen); + if (ret == OK) + { + /* Store the mode data length and return the total message size */ + + usbstrg_putbe16(mph->mdlen, mdlen - 2); + priv->nreqbytes = mdlen + SCSIRESP_MODEPARAMETERHDR10_SIZEOF; + } + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdread12 + * + * Description: + * Handle SCSI_CMD_READ12 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdread12(FAR struct usbstrg_dev_s *priv) +{ + struct scsicmd_read12_s *read12 = (struct scsicmd_read12_s*)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; + int ret; + + priv->u.xfrlen = usbstrg_getbe32(read12->xfrlen); + ret = usbstrg_setupcmd(priv, SCSICMD_READ12_SIZEOF, USBSTRG_FLAGS_DIRDEVICE2HOST|USBSTRG_FLAGS_BLOCKXFR); + if (ret == OK) + { + /* Get the Logical Block Address (LBA) from cdb[] as the starting sector */ + + priv->sector = usbstrg_getbe32(read12->lba); + + /* Verify that we can support this read command */ + + if ((read12->flags & ~(SCSICMD_READ12FLAGS_DPO|SCSICMD_READ12FLAGS_FUA)) != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ12FLAGS), 0); + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + ret = -EINVAL; + } + + /* Verify that a block driver has been bound to the LUN */ + + else if (!lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ12MEDIANOTPRESENT), 0); + lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + + /* Verify that LBA lies in the range supported by the block driver */ + + else if (priv->sector >= lun->nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_READ12LBARANGE), 0); + lun->sd = SCSI_KCQIR_LBAOUTOFRANGE; + ret = -EINVAL; + } + + /* Looks like we are good to go */ + + else + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDPARSECMDREAD12), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDREAD; + } + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdwrite12 + * + * Description: + * Handle SCSI_CMD_WRITE12 command + * + ****************************************************************************/ + +static inline int usbstrg_cmdwrite12(FAR struct usbstrg_dev_s *priv) +{ + struct scsicmd_write12_s *write12 = (struct scsicmd_write12_s *)priv->cdb; + FAR struct usbstrg_lun_s *lun = priv->lun; + int ret; + + priv->u.xfrlen = usbstrg_getbe32(write12->xfrlen); + ret = usbstrg_setupcmd(priv, SCSICMD_WRITE12_SIZEOF, USBSTRG_FLAGS_DIRHOST2DEVICE|USBSTRG_FLAGS_BLOCKXFR); + if (ret == OK) + { + /* Get the Logical Block Address (LBA) from cdb[] as the starting sector */ + + priv->sector = usbstrg_getbe32(write12->lba); + + /* Verify that we can support this write command */ + + if ((write12->flags & ~(SCSICMD_WRITE12FLAGS_DPO|SCSICMD_WRITE12FLAGS_FUA)) != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE12FLAGS), 0); + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + } + + /* Verify that a block driver has been bound to the LUN */ + + else if (!lun->inode) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE12MEDIANOTPRESENT), 0); + lun->sd = SCSI_KCQNR_MEDIANOTPRESENT; + ret = -EINVAL; + } + + /* Check for attempts to write to a read-only device */ + + else if (lun->readonly) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE12READONLY), 0); + lun->sd = SCSI_KCQWP_COMMANDNOTALLOWED; + } + + /* Verify that LBA lies in the range supported by the block driver */ + + else if (priv->sector >= lun->nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRITE12LBARANGE), 0); + lun->sd = SCSI_KCQIR_LBAOUTOFRANGE; + } + + /* Looks like we are good to go */ + + else + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDPARSECMDWRITE12), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDWRITE; + return OK; + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbstrg_setupcmd + * + * Description: + * Called after each SCSI command is identified in order to perform setup + * and verification operations that are common to all SCSI commands. This + * function performs the following common setup operations: + * + * 1. Determine the direction of the response + * 2. Verify lengths + * 3. Setup and verify the LUN + * + * Includes special logic for INQUIRY and REQUESTSENSE commands + * + ****************************************************************************/ + +static int inline usbstrg_setupcmd(FAR struct usbstrg_dev_s *priv, uint8_t cdblen, uint8_t flags) +{ + FAR struct usbstrg_lun_s *lun = NULL; + uint32_t datlen; + uint8_t dir = flags & USBSTRG_FLAGS_DIRMASK; + int ret = OK; + + /* Verify the LUN and set up the current LUN reference in the + * device structure + */ + + if (priv->cbwlun < priv->nluns) + { + /* LUN number is valid in a valid range, but the LUN is not necessarily + * bound to a block driver. That will be checked as necessary in each + * individual command. + */ + + lun = &priv->luntab[priv->cbwlun]; + priv->lun = lun; + } + + /* Only a few commands may specify unsupported LUNs */ + + else if ((flags & USBSTRG_FLAGS_LUNNOTNEEDED) == 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDBADLUN), priv->cbwlun); + ret = -EINVAL; + } + + /* Extract the direction and data transfer length */ + + dir = flags & USBSTRG_FLAGS_DIRMASK; /* Expected data direction */ + datlen = 0; + if ((flags & USBSTRG_FLAGS_BLOCKXFR) == 0) + { + /* Non-block transfer. Data length: Host allocation to receive data + * (only for device-to-host transfers. At present, alloclen is set + * to zero for all host-to-device, non-block transfers. + */ + + datlen = priv->u.alloclen; + } + else if (lun) + { + /* Block transfer: Calculate the total size of all sectors to be transferred */ + + datlen = priv->u.alloclen * lun->sectorsize; + } + + /* Check the data direction. This was set up at the end of the + * IDLE state when the CBW was parsed, but if there is no data, + * then the direction is none. + */ + + if (datlen == 0) + { + /* No data.. then direction is none */ + + dir = USBSTRG_FLAGS_DIRNONE; + } + + /* Compare the expected data length in the command to the data length + * in the CBW. + */ + + else if (priv->cbwlen < datlen) + { + /* Clip to the length in the CBW and declare a phase error */ + + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_PHASEERROR1), priv->cdb[0]); + if ((flags & USBSTRG_FLAGS_BLOCKXFR) != 0) + { + priv->u.alloclen = priv->cbwlen; + } + else + { + priv->u.xfrlen = priv->cbwlen / lun->sectorsize; + } + + priv->phaseerror = 1; + } + + /* Initialize the residue */ + + priv->residue = priv->cbwlen; + + /* Conflicting data directions is a phase error */ + + if (priv->cbwdir != dir && datlen > 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_PHASEERROR2), priv->cdb[0]); + priv->phaseerror = 1; + ret = -EINVAL; + } + + /* Compare the length of data in the cdb[] with the expected length + * of the command. + */ + + if (cdblen != priv->cdblen) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_PHASEERROR3), priv->cdb[0]); + priv->phaseerror = 1; + ret = -EINVAL; + } + + if (lun) + { + /* Retain the sense data for the REQUEST SENSE command */ + + if ((flags & USBSTRG_FLAGS_RETAINSENSEDATA) == 0) + { + /* Discard the sense data */ + + lun->sd = SCSI_KCQ_NOSENSE; + lun->sdinfo = 0; + } + + /* If a unit attention condition exists, then only a restricted set of + * commands is permitted. + */ + + if (lun->uad != SCSI_KCQ_NOSENSE && (flags & USBSTRG_FLAGS_UACOKAY) != 0) + { + /* Command not permitted */ + + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDUNEVIOLATION), priv->cbwlun); + lun->sd = lun->uad; + lun->uad = SCSI_KCQ_NOSENSE; + ret = -EINVAL; + } + } + + /* The final, 1-byte field of every SCSI command is the Control field which + * must be zero + */ + + if (priv->cdb[cdblen-1] != 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SCSICMDCONTROL), 0); + if (lun) + { + lun->sd = SCSI_KCQIR_INVALIDFIELDINCBA; + } + ret = -EINVAL; + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_idlestate + * + * Description: + * Called from the worker thread in the USBSTRG_STATE_IDLE state. Checks + * for the receipt of a bulk CBW. + * + * Returned value: + * If no new, valid CBW is available, this function returns a negated errno. + * Otherwise, when a new CBW is successfully parsed, this function sets + * priv->thstate to USBSTRG_STATE_CMDPARSE and returns OK. + * + ****************************************************************************/ + +static int usbstrg_idlestate(FAR struct usbstrg_dev_s *priv) +{ + FAR struct usbstrg_req_s *privreq; + FAR struct usbdev_req_s *req; + FAR struct usbstrg_cbw_s *cbw; + irqstate_t flags; + int ret = -EINVAL; + + /* Take a request from the rdreqlist */ + + flags = irqsave(); + privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->rdreqlist); + irqrestore(flags); + + /* Has anything been received? If not, just return an error. + * This will cause us to remain in the IDLE state. When a USB request is + * received, the worker thread will be awakened in the USBSTRG_STATE_IDLE + * and we will be called again. + */ + + if (!privreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_IDLERDREQLISTEMPTY), 0); + return -ENOMEM; + } + + req = privreq->req; + cbw = (FAR struct usbstrg_cbw_s *)req->buf; + + /* Handle the CBW */ + + usbstrg_dumpdata("SCSCI CBW", (uint8_t*)cbw, USBSTRG_CBW_SIZEOF - USBSTRG_MAXCDBLEN); + usbstrg_dumpdata(" CDB", cbw->cdb, min(cbw->cdblen, USBSTRG_MAXCDBLEN)); + + /* Check for properly formatted CBW? */ + + if (req->xfrd != USBSTRG_CBW_SIZEOF || + cbw->signature[0] != 'U' || + cbw->signature[1] != 'S' || + cbw->signature[2] != 'B' || + cbw->signature[3] != 'C') + { + /* CBS BAD: Stall the bulk endpoints. If the CBW is bad we must stall the + * bulk IN endpoint and either (1) stall the bulk OUT endpoint, or + * (2) discard data from the endpoint. + */ + + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INVALIDCBWSIGNATURE), 0); + EP_STALL(priv->epbulkout); + EP_STALL(priv->epbulkin); + } + + /* Is the CBW meaningful? */ + + else if (cbw->lun >= priv->nluns || (cbw->flags & ~USBSTRG_CBWFLAG_IN) != 0 || + cbw->cdblen < 6 || cbw->cdblen > USBSTRG_MAXCDBLEN) + { + /* CBS BAD: Stall the bulk endpoints. If the CBW is bad we must stall the + * bulk IN endpoint and either (1) stall the bulk OUT endpoint, or + * (2) discard data from the endpoint. + */ + + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INVALIDCBWCONTENT), 0); + EP_STALL(priv->epbulkout); + EP_STALL(priv->epbulkin); + } + + /* Save the information from the CBW */ + + else + { + /* Extract the CDB and copy the whole CBD[] for later use */ + + priv->cdblen = cbw->cdblen; + memcpy(priv->cdb, cbw->cdb, priv->cdblen); + + /* Extract the data direction */ + + if ((cbw->flags & USBSTRG_CBWFLAG_IN) != 0) + { + /* IN: Device-to-host */ + + priv->cbwdir = USBSTRG_FLAGS_DIRDEVICE2HOST; + } + else + { + /* OUT: Host-to-device */ + + priv->cbwdir = USBSTRG_FLAGS_DIRHOST2DEVICE; + } + + /* Get the max size of the data response */ + + priv->cbwlen = usbstrg_getle32(cbw->datlen); + if (priv->cbwlen == 0) + { + /* No length? Then no direction either */ + + priv->cbwdir = USBSTRG_FLAGS_DIRNONE; + } + + /* Extract and save the LUN index and TAG value */ + + priv->cbwlun = cbw->lun; + priv->cbwtag = usbstrg_getle32(cbw->tag); + + /* Return the read request to the bulk out endpoint for re-filling */ + + req = privreq->req; + req->len = CONFIG_USBSTRG_BULKOUTREQLEN; + req->priv = privreq; + req->callback = usbstrg_rdcomplete; + + if (EP_SUBMIT(priv->epbulkout, req) != OK) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_IDLERDSUBMIT), (uint16_t)-ret); + } + + /* Change to the CMDPARSE state and return success */ + + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_IDLECMDPARSE), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDPARSE; + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdparsestate + * + * Description: + * Called from the worker thread in the USBSTRG_STATE_CMDPARSE state. + * This state is entered when usbstrg_idlestate obtains a valid CBW + * containing SCSI commands. This function processes those SCSI commands. + * + * Returned value: + * If no write request is available or certain other errors occur, this + * function returns a negated errno and stays in the USBSTRG_STATE_CMDPARSE + * state. Otherwise, when the new CBW is successfully process, this + * function sets priv->thstate to one of USBSTRG_STATE_CMDREAD, + * USBSTRG_STATE_CMDWRITE, or USBSTRG_STATE_CMDFINISH and returns OK. + * + ****************************************************************************/ + +static int usbstrg_cmdparsestate(FAR struct usbstrg_dev_s *priv) +{ + FAR struct usbstrg_req_s *privreq; + FAR uint8_t *buf; + int ret = -EINVAL; + + usbstrg_dumpdata("SCSCI CDB", priv->cdb, priv->cdblen); + + /* Check if there is a request in the wrreqlist that we will be able to + * use for data or status. + */ + + privreq = (FAR struct usbstrg_req_s *)sq_peek(&priv->wrreqlist); + + /* If there no request structures available, then just return an error. + * This will cause us to remain in the CMDPARSE state. When a request is + * returned, the worker thread will be awakened in the USBSTRG_STATE_CMDPARSE + * and we will be called again. + */ + + if (!privreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDPARSEWRREQLISTEMPTY), 0); + return -ENOMEM; + } + DEBUGASSERT(privreq->req && privreq->req->buf); + buf = privreq->req->buf; + + /* Assume that no errors will be encountered */ + + priv->phaseerror = 0; + priv->shortpacket = 0; + + /* No data is buffered */ + + priv->nsectbytes = 0; + priv->nreqbytes = 0; + + /* Get exclusive access to the block driver */ + + pthread_mutex_lock(&priv->mutex); + switch (priv->cdb[0]) + { + case SCSI_CMD_TESTUNITREADY: /* 0x00 Mandatory */ + ret = usbstrg_cmdtestunitready(priv); + break; + + /* case SCSI_CMD_REZEROUNIT: * 0x01 Obsolete + * * 0x02 Vendor-specific */ + + case SCSI_CMD_REQUESTSENSE: /* 0x03 Mandatory */ + ret = usbstrg_cmdrequestsense(priv, buf); + break; + + /* case SCSI_CMD_FORMAT_UNIT: * 0x04 Mandatory, but not implemented + * * 0x05 Vendor specific + * * 0x06 Vendor specific + * case SCSI_CMD_REASSIGNBLOCKS: * 0x07 Optional */ + + case SCSI_CMD_READ6: /* 0x08 Mandatory */ + ret = usbstrg_cmdread6(priv); + break; + + /* * 0x09 Vendor specific */ + + case SCSI_CMD_WRITE6: /* 0x0a Optional */ + ret = usbstrg_cmdwrite6(priv); + break; + + /* case SCSI_CMD_SEEK6: * 0x0b Obsolete + * * 0x0c-0x10 Vendor specific + * case SCSI_CMD_SPACE6: * 0x11 Vendor specific */ + + case SCSI_CMD_INQUIRY: /* 0x12 Mandatory */ + ret = usbstrg_cmdinquiry(priv, buf); + break; + + /* * 0x13-0x14 Vendor specific */ + + case SCSI_CMD_MODESELECT6: /* 0x15 Optional */ + ret = usbstrg_cmdmodeselect6(priv); + break; + + /* case SCSI_CMD_RESERVE6: * 0x16 Obsolete + * case SCSI_CMD_RELEASE6: * 0x17 Obsolete + * case SCSI_CMD_COPY: * 0x18 Obsolete + * * 0x19 Vendor specific */ + + case SCSI_CMD_MODESENSE6: /* 0x1a Optional */ + ret = usbstrg_cmdmodesense6(priv, buf); + break; + + case SCSI_CMD_STARTSTOPUNIT: /* 0x1b Optional */ + ret = usbstrg_cmdstartstopunit(priv); + break; + + /* case SCSI_CMD_RECEIVEDIAGNOSTICRESULTS: * 0x1c Optional + * case SCSI_CMD_SENDDIAGNOSTIC: * 0x1d Mandatory, but not implemented */ + + case SCSI_CMD_PREVENTMEDIAREMOVAL: /* 0x1e Optional */ + ret = usbstrg_cmdpreventmediumremoval(priv); + break; + + /* * 0x20-22 Vendor specific */ + case SCSI_CMD_READFORMATCAPACITIES: /* 0x23 Vendor-specific (defined in MMC spec) */ + ret = usbstrg_cmdreadformatcapacity(priv, buf); + break; + /* * 0x24 Vendor specific */ + + case SCSI_CMD_READCAPACITY10: /* 0x25 Mandatory */ + ret = usbstrg_cmdreadcapacity10(priv, buf); + break; + + /* * 0x26-27 Vendor specific */ + case SCSI_CMD_READ10: /* 0x28 Mandatory */ + ret = usbstrg_cmdread10(priv); + break; + + /* * 0x29 Vendor specific */ + + case SCSI_CMD_WRITE10: /* 0x2a Optional */ + ret = usbstrg_cmdwrite10(priv); + break; + + /* case SCSI_CMD_SEEK10: * 0x2b Obsolete + * * 0x2c-2d Vendor specific + * case SCSI_CMD_WRITEANDVERIFY: * 0x2e Optional */ + + case SCSI_CMD_VERIFY10: /* 0x2f Optional, but needed my MS Windows */ + ret = usbstrg_cmdverify10(priv); + break; + + /* case SCSI_CMD_SEARCHDATAHIGH: * 0x30 Obsolete + * case SCSI_CMD_SEARCHDATAEQUAL: * 0x31 Obsolete + * case SCSI_CMD_SEARCHDATALOW: * 0x32 Obsolete + * case SCSI_CMD_SETLIMITS10: * 0x33 Obsolete + * case SCSI_CMD_PREFETCH10: * 0x34 Optional */ + + case SCSI_CMD_SYNCHCACHE10: /* 0x35 Optional */ + ret = usbstrg_cmdsynchronizecache10(priv); + break; + + /* case SCSI_CMD_LOCKCACHE: * 0x36 Obsolete + * case SCSI_CMD_READDEFECTDATA10: * 0x37 Optional + * case SCSI_CMD_COMPARE: * 0x39 Obsolete + * case SCSI_CMD_COPYANDVERIFY: * 0x3a Obsolete + * case SCSI_CMD_WRITEBUFFER: * 0x3b Optional + * case SCSI_CMD_READBUFFER: * 0x3c Optional + * case SCSI_CMD_READLONG10: * 0x3e Optional + * case SCSI_CMD_WRITELONG10: * 0x3f Optional + * case SCSI_CMD_CHANGEDEFINITION: * 0x40 Obsolete + * case SCSI_CMD_WRITESAME10: * 0x41 Optional + * case SCSI_CMD_LOGSELECT: * 0x4c Optional + * case SCSI_CMD_LOGSENSE: * 0x4d Optional + * case SCSI_CMD_XDWRITE10: * 0x50 Optional + * case SCSI_CMD_XPWRITE10: * 0x51 Optional + * case SCSI_CMD_XDREAD10: * 0x52 Optional */ + + case SCSI_CMD_MODESELECT10: /* 0x55 Optional */ + ret = usbstrg_cmdmodeselect10(priv); + break; + + /* case SCSI_CMD_RESERVE10: * 0x56 Obsolete + * case SCSI_CMD_RELEASE10: * 0x57 Obsolete */ + + case SCSI_CMD_MODESENSE10: /* 0x5a Optional */ + ret = usbstrg_cmdmodesense10(priv, buf); + break; + + /* case SCSI_CMD_PERSISTENTRESERVEIN: * 0x5e Optional + * case SCSI_CMD_PERSISTENTRESERVEOUT: * 0x5f Optional + * case SCSI_CMD_32: * 0x7f Optional + * case SCSI_CMD_XDWRITEEXTENDED: * 0x80 Obsolete + * case SCSI_CMD_REBUILD: * 0x81 Obsolete + * case SCSI_CMD_REGENERATE: * 0x82 Obsolete + * case SCSI_CMD_EXTENDEDCOPY: * 0x83 Optional + * case SCSI_CMD_COPYRESULTS: * 0x84 Optional + * case SCSI_CMD_ACCESSCONTROLIN: * 0x86 Optional + * case SCSI_CMD_ACCESSCONTROLOUT: * 0x87 Optional + * case SCSI_CMD_READ16: * 0x88 Optional + * case SCSI_CMD_WRITE16: * 0x8a Optional + * case SCSI_CMD_READATTRIBUTE: * 0x8c Optional + * case SCSI_CMD_WRITEATTRIBUTE: * 0x8d Optional + * case SCSI_CMD_WRITEANDVERIFY16: * 0x8e Optional + * case SCSI_CMD_SYNCHCACHE16: * 0x91 Optional + * case SCSI_CMD_LOCKUNLOCKACACHE: * 0x92 Optional + * case SCSI_CMD_WRITESAME16: * 0x93 Optional + * case SCSI_CMD_READCAPACITY16: * 0x9e Optional + * case SCSI_CMD_READLONG16: * 0x9e Optional + * case SCSI_CMD_WRITELONG16 * 0x9f Optional + * case SCSI_CMD_REPORTLUNS: * 0xa0 Mandatory, but not implemented + * case SCSI_CMD_MAINTENANCEIN: * 0xa3 Optional (SCCS==0) + * case SCSI_CMD_MAINTENANCEOUT: * 0xa4 Optional (SCCS==0) + * case SCSI_CMD_MOVEMEDIUM: * 0xa5 ? + * case SCSI_CMD_MOVEMEDIUMATTACHED: * 0xa7 Optional (MCHNGR==0) */ + + case SCSI_CMD_READ12: /* 0xa8 Optional */ + ret = usbstrg_cmdread12(priv); + break; + + case SCSI_CMD_WRITE12: /* 0xaa Optional */ + ret = usbstrg_cmdwrite12(priv); + break; + + /* case SCSI_CMD_READMEDIASERIALNUMBER: * 0xab Optional + * case SCSI_CMD_WRITEANDVERIFY12: * 0xae Optional + * case SCSI_CMD_VERIFY12: * 0xaf Optional + * case SCSI_CMD_SETLIMITS12 * 0xb3 Obsolete + * case SCSI_CMD_READELEMENTSTATUS: * 0xb4 Optional (MCHNGR==0) + * case SCSI_CMD_READDEFECTDATA12: * 0xb7 Optional + * case SCSI_CMD_REDUNDANCYGROUPIN: * 0xba Optional + * case SCSI_CMD_REDUNDANCYGROUPOUT: * 0xbb Optional + * case SCSI_CMD_SPAREIN: * 0xbc Optional (SCCS==0) + * case SCSI_CMD_SPAREOUT: * 0xbd Optional (SCCS==0) + * case SCSI_CMD_VOLUMESETIN: * 0xbe Optional (SCCS==0) + * case SCSI_CMD_VOLUMESETOUT: * 0xbe Optional (SCCS==0) + * * 0xc0-0xff Vendor specific */ + + default: + priv->u.alloclen = 0; + if (ret == OK) + { + priv->lun->sd = SCSI_KCQIR_INVALIDCOMMAND; + ret = -EINVAL; + } + break; + } + pthread_mutex_unlock(&priv->mutex); + + /* Is a response required? (Not for read6/10/12 and write6/10/12). */ + + if (priv->thstate == USBSTRG_STATE_CMDPARSE) + { + /* All commands come through this path (EXCEPT read6/10/12 and write6/10/12). + * For all other commands, the following setup is expected for the response + * based on data direction: + * + * For direction NONE: + * 1. priv->u.alloclen == 0 + * 2. priv->nreqbytes == 0 + * + * For direction = device-to-host: + * 1. priv->u.alloclen == allocation length; space set aside by the + * host to receive the device data. The size of the response + * cannot exceed this value. + * 2. response data is in the request currently at the head of + * the priv->wrreqlist queue. priv->nreqbytes is set to the length + * of data in the response. + * + * For direction host-to-device + * At present, there are no supported commands that should have host-to-device + * transfers (except write6/10/12 and that command logic does not take this + * path. The 'residue' is left at the full host-to-device data size. + * + * For all: + * ret set to <0 if an error occurred in parsing the commands. + */ + + /* For from device-to-hose operations, the residue is the expected size + * (the initial value of 'residue') minus the amount actually returned + * in the response: + */ + + if (priv->cbwdir == USBSTRG_FLAGS_DIRDEVICE2HOST) + { + /* The number of bytes in the response cannot exceed the host + * 'allocation length' in the command. + */ + + if (priv->nreqbytes > priv->u.alloclen) + { + priv->nreqbytes = priv->u.alloclen; + } + + /* The residue is then the number of bytes that were not sent */ + + priv->residue -= priv->nreqbytes; + } + + /* On return, we need the following: + * + * 1. Setup for CMDFINISH state if appropriate + * 2. priv->thstate set to either CMDPARSE if no buffer was available or + * CMDFINISH to send the response + * 3. Return OK to continue; <0 to wait for the next event + */ + + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDPARSECMDFINISH), priv->cdb[0]); + priv->thstate = USBSTRG_STATE_CMDFINISH; + ret = OK; + } + return ret; +} + +/**************************************************************************** + * Name: usbstrg_cmdreadstate + * + * Description: + * Called from the worker thread in the USBSTRG_STATE_CMDREAD state. + * The USBSTRG_STATE_CMDREAD state is entered when usbstrg_cmdparsestate + * obtains a valid SCSI read command. This state is really a continuation + * of the USBSTRG_STATE_CMDPARSE state that handles extended SCSI read + * command handling. + * + * Returned value: + * If no USBDEV write request is available or certain other errors occur, this + * function returns a negated errno and stays in the USBSTRG_STATE_CMDREAD + * state. Otherwise, when the new SCSI read command is fully processed, + * this function sets priv->thstate to USBSTRG_STATE_CMDFINISH and returns OK. + * + * State variables: + * xfrlen - holds the number of sectors read to be read. + * sector - holds the sector number of the next sector to be read + * nsectbytes - holds the number of bytes buffered for the current sector + * nreqbytes - holds the number of bytes currently buffered in the request + * at the head of the wrreqlist. + * + ****************************************************************************/ + +static int usbstrg_cmdreadstate(FAR struct usbstrg_dev_s *priv) +{ + FAR struct usbstrg_lun_s *lun = priv->lun; + FAR struct usbstrg_req_s *privreq; + FAR struct usbdev_req_s *req; + irqstate_t flags; + ssize_t nread; + uint8_t *src; + uint8_t *dest; + int nbytes; + int ret; + + /* Loop transferring data until either (1) all of the data has been + * transferred, or (2) we have used up all of the write requests that we have + * available. + */ + + while (priv->u.xfrlen > 0 || priv->nsectbytes > 0) + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDREAD), priv->u.xfrlen); + + /* Is the I/O buffer empty? */ + + if (priv->nsectbytes <= 0) + { + /* Yes.. read the next sector */ + + nread = USBSTRG_DRVR_READ(lun, priv->iobuffer, priv->sector, 1); + if (nread < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDREADREADFAIL), -nread); + lun->sd = SCSI_KCQME_UNRRE1; + lun->sdinfo = priv->sector; + break; + } + + priv->nsectbytes = lun->sectorsize; + priv->u.xfrlen--; + priv->sector++; + } + + /* Check if there is a request in the wrreqlist that we will be able to + * use for data transfer. + */ + + privreq = (FAR struct usbstrg_req_s *)sq_peek(&priv->wrreqlist); + + /* If there no request structures available, then just return an error. + * This will cause us to remain in the CMDREAD state. When a request is + * returned, the worker thread will be awakened in the USBSTRG_STATE_CMDREAD + * and we will be called again. + */ + + if (!privreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDREADWRRQEMPTY), 0); + priv->nreqbytes = 0; + return -ENOMEM; + } + req = privreq->req; + + /* Transfer all of the data that will (1) fit into the request buffer, OR (2) + * all of the data available in the sector buffer. + */ + + src = &priv->iobuffer[lun->sectorsize - priv->nsectbytes]; + dest = &req->buf[priv->nreqbytes]; + + nbytes = min(CONFIG_USBSTRG_BULKINREQLEN - priv->nreqbytes, priv->nsectbytes); + + /* Copy the data from the sector buffer to the USB request and update counts */ + + memcpy(dest, src, nbytes); + priv->nreqbytes += nbytes; + priv->nsectbytes -= nbytes; + + /* If (1) the request buffer is full OR (2) this is the final request full of data, + * then submit the request + */ + + if (priv->nreqbytes >= CONFIG_USBSTRG_BULKINREQLEN || + (priv->u.xfrlen <= 0 && priv->nsectbytes <= 0)) + { + /* Remove the request that we just filled from wrreqlist (we've already checked + * that is it not NULL + */ + + flags = irqsave(); + privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist); + irqrestore(flags); + + /* And submit the request to the bulk IN endpoint */ + + req->len = priv->nreqbytes; + req->priv = privreq; + req->callback = usbstrg_wrcomplete; + req->flags = 0; + + ret = EP_SUBMIT(priv->epbulkin, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDREADSUBMIT), (uint16_t)-ret); + lun->sd = SCSI_KCQME_UNRRE1; + lun->sdinfo = priv->sector; + break; + } + + /* Assume success... residue should probably really be decremented in + * wrcomplete when we know that the transfer completed successfully. + */ + + priv->residue -= priv->nreqbytes; + priv->nreqbytes = 0; + } + } + + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDREADCMDFINISH), priv->u.xfrlen); + priv->thstate = USBSTRG_STATE_CMDFINISH; + return OK; +} + +/**************************************************************************** + * Name: usbstrg_cmdwritestate + * + * Description: + * Called from the worker thread in the USBSTRG_STATE_CMDWRITE state. + * The USBSTRG_STATE_CMDWRITE state is entered when usbstrg_cmdparsestate + * obtains a valid SCSI write command. This state is really a continuation + * of the USBSTRG_STATE_CMDPARSE state that handles extended SCSI write + * command handling. + * + * Returned value: + * If no USBDEV write request is available or certain other errors occur, this + * function returns a negated errno and stays in the USBSTRG_STATE_CMDWRITE + * state. Otherwise, when the new SCSI write command is fully processed, + * this function sets priv->thstate to USBSTRG_STATE_CMDFINISH and returns OK. + * + * State variables: + * xfrlen - holds the number of sectors read to be written. + * sector - holds the sector number of the next sector to write + * nsectbytes - holds the number of bytes buffered for the current sector + * nreqbytes - holds the number of untransferred bytes currently in the + * request at the head of the rdreqlist. + * + ****************************************************************************/ + +static int usbstrg_cmdwritestate(FAR struct usbstrg_dev_s *priv) +{ + FAR struct usbstrg_lun_s *lun = priv->lun; + FAR struct usbstrg_req_s *privreq; + FAR struct usbdev_req_s *req; + ssize_t nwritten; + uint16_t xfrd; + uint8_t *src; + uint8_t *dest; + int nbytes; + int ret; + + /* Loop transferring data until either (1) all of the data has been + * transferred, or (2) we have written all of the data in the available + * read requests. + */ + + while (priv->u.xfrlen > 0) + { + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDWRITE), priv->u.xfrlen); + + /* Check if there is a request in the rdreqlist containing additional + * data to be written. + */ + + privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->rdreqlist); + + /* If there no request data available, then just return an error. + * This will cause us to remain in the CMDWRITE state. When a filled request is + * received, the worker thread will be awakened in the USBSTRG_STATE_CMDWRITE + * and we will be called again. + */ + + if (!privreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDWRITERDRQEMPTY), 0); + priv->nreqbytes = 0; + return -ENOMEM; + } + + req = privreq->req; + xfrd = req->xfrd; + priv->nreqbytes = xfrd; + + /* Now loop until all of the data in the read request has been tranferred + * to the block driver OR all of the request data has been transferred. + */ + + while (priv->nreqbytes > 0 && priv->u.xfrlen > 0) + { + /* Copy the data received in the read request into the sector I/O buffer */ + + src = &req->buf[xfrd - priv->nreqbytes]; + dest = &priv->iobuffer[priv->nsectbytes]; + + nbytes = min(lun->sectorsize - priv->nsectbytes, priv->nreqbytes); + + /* Copy the data from the sector buffer to the USB request and update counts */ + + memcpy(dest, src, nbytes); + priv->nsectbytes += nbytes; + priv->nreqbytes -= nbytes; + + /* Is the I/O buffer full? */ + + if (priv->nsectbytes >= lun->sectorsize) + { + /* Yes.. Write the next sector */ + + nwritten = USBSTRG_DRVR_WRITE(lun, priv->iobuffer, priv->sector, 1); + if (nwritten < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDWRITEWRITEFAIL), -nwritten); + lun->sd = SCSI_KCQME_WRITEFAULTAUTOREALLOCFAILED; + lun->sdinfo = priv->sector; + goto errout; + } + + priv->nsectbytes = 0; + priv->residue -= lun->sectorsize; + priv->u.xfrlen--; + priv->sector++; + } + } + + /* In either case, we are finished with this read request and can return it + * to the endpoint. Then we will go back to the top of the top and attempt + * to get the next read request. + */ + + req->len = CONFIG_USBSTRG_BULKOUTREQLEN; + req->priv = privreq; + req->callback = usbstrg_rdcomplete; + + ret = EP_SUBMIT(priv->epbulkout, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDWRITERDSUBMIT), (uint16_t)-ret); + } + + /* Did the host decide to stop early? */ + + if (xfrd != CONFIG_USBSTRG_BULKOUTREQLEN) + { + priv->shortpacket = 1; + goto errout; + } + } + +errout: + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDWRITECMDFINISH), priv->u.xfrlen); + priv->thstate = USBSTRG_STATE_CMDFINISH; + return OK; +} + +/**************************************************************************** + * Name: usbstrg_cmdfinishstate + * + * Description: + * Called from the worker thread in the USBSTRG_STATE_CMDFINISH state. + * The USBSTRG_STATE_CMDFINISH state is entered when processing of a + * command has finished but before status has been returned. + * + * Returned value: + * If no USBDEV write request is available or certain other errors occur, this + * function returns a negated errno and stays in the USBSTRG_STATE_CMDFINISH + * state. Otherwise, when the command is fully processed, this function + * sets priv->thstate to USBSTRG_STATE_CMDSTATUS and returns OK. + * + ****************************************************************************/ + +static int usbstrg_cmdfinishstate(FAR struct usbstrg_dev_s *priv) +{ + FAR struct usbstrg_req_s *privreq; + irqstate_t flags; + int ret = OK; + + /* Check if there is a request in the wrreqlist that we will be able to + * use for data transfer. + */ + + privreq = (FAR struct usbstrg_req_s *)sq_peek(&priv->wrreqlist); + + /* If there no request structures available, then just return an error. + * This will cause us to remain in the CMDREAD state. When a request is + * returned, the worker thread will be awakened in the USBSTRG_STATE_CMDREAD + * and we will be called again. + */ + + if (!privreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHRQEMPTY), 0); + return -ENOMEM; + } + + /* Finish the final stages of the reply */ + + switch (priv->cbwdir) + { + /* Device-to-host: All but the last data buffer has been sent */ + + case USBSTRG_FLAGS_DIRDEVICE2HOST: + if (priv->cbwlen > 0) + { + /* On most commands (the exception is outgoing, write commands), + * the data has not not yet been sent. + */ + + if (priv->nreqbytes > 0) + { + struct usbdev_req_s *req; + + /* Take a request from the wrreqlist (we've already checked + * that is it not NULL) + */ + + flags = irqsave(); + privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist); + irqrestore(flags); + + /* Send the write request */ + + req = privreq->req; + req->len = priv->nreqbytes; + req->callback = usbstrg_wrcomplete; + req->priv = privreq; + req->flags = USBDEV_REQFLAGS_NULLPKT; + + ret = EP_SUBMIT(priv->epbulkin, privreq->req); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHSUBMIT), (uint16_t)-ret); + } + } + + /* Stall the BULK In endpoint if there is a residue */ + + if (priv->residue > 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHRESIDUE), (uint16_t)priv->residue); + +#ifdef CONFIG_USBSTRG_RACEWAR + /* (See description of the workaround at the top of the file). + * First, wait for the transfer to complete, then stall the endpoint + */ + + usleep (100000); + (void)EP_STALL(priv->epbulkin); + + /* now wait for stall to go away .... */ + + usleep (100000); +#else + (void)EP_STALL(priv->epbulkin); +#endif + } + } + break; + + /* Host-to-device: We have processed all we want of the host data. */ + + case USBSTRG_FLAGS_DIRHOST2DEVICE: + if (priv->residue > 0) + { + /* Did the host stop sending unexpectedly early? */ + + flags = irqsave(); + if (priv->shortpacket) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINISHSHORTPKT), (uint16_t)priv->residue); + } + + /* Unprocessed incoming data: STALL and cancel requests. */ + + else + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINSHSUBMIT), (uint16_t)priv->residue); + EP_STALL(priv->epbulkout); + } + + priv->theventset |= USBSTRG_EVENT_ABORTBULKOUT; + irqrestore(flags); + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDFINSHDIR), priv->cbwdir); + case USBSTRG_FLAGS_DIRNONE: /* Nothing to send */ + break; + } + + /* Return to the IDLE state */ + + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDFINISHCMDSTATUS), 0); + priv->thstate = USBSTRG_STATE_CMDSTATUS; + return OK; +} + +/**************************************************************************** + * Name: usbstrg_cmdstatusstate + * + * Description: + * Called from the worker thread in the USBSTRG_STATE_CMDSTATUS state. + * That state is after a CBW has been fully processed. This function sends + * the concluding CSW. + * + * Returned value: + * If no write request is available or certain other errors occur, this + * function returns a negated errno and stays in the USBSTRG_STATE_CMDSTATUS + * state. Otherwise, when the SCSI statis is successfully returned, this + * function sets priv->thstate to USBSTRG_STATE_IDLE and returns OK. + * + ****************************************************************************/ + +static int usbstrg_cmdstatusstate(FAR struct usbstrg_dev_s *priv) +{ + FAR struct usbstrg_lun_s *lun = priv->lun; + FAR struct usbstrg_req_s *privreq; + FAR struct usbdev_req_s *req; + FAR struct usbstrg_csw_s *csw; + irqstate_t flags; + uint32_t sd; + uint8_t status = USBSTRG_CSWSTATUS_PASS; + int ret; + + /* Take a request from the wrreqlist */ + + flags = irqsave(); + privreq = (FAR struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist); + irqrestore(flags); + + /* If there no request structures available, then just return an error. + * This will cause us to remain in the CMDSTATUS status. When a request is + * returned, the worker thread will be awakened in the USBSTRG_STATE_CMDSTATUS + * and we will be called again. + */ + + if (!privreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CMDSTATUSRDREQLISTEMPTY), 0); + return -ENOMEM; + } + + req = privreq->req; + csw = (struct usbstrg_csw_s*)req->buf; + + /* Extract the sense data from the LUN structure */ + + if (lun) + { + sd = lun->sd; + } + else + { + sd = SCSI_KCQIR_INVALIDLUN; + } + + /* Determine the CSW status: PASS, PHASEERROR, or FAIL */ + + if (priv->phaseerror) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDPHERROR), 0); + status = USBSTRG_CSWSTATUS_PHASEERROR; + sd = SCSI_KCQIR_INVALIDCOMMAND; + } + else if (sd != SCSI_KCQ_NOSENSE) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDCSWFAIL), 0); + status = USBSTRG_CSWSTATUS_FAIL; + } + + /* Format and submit the CSW */ + + csw->signature[0] = 'U'; + csw->signature[1] = 'S'; + csw->signature[2] = 'B'; + csw->signature[3] = 'S'; + usbstrg_putle32(csw->tag, priv->cbwtag); + usbstrg_putle32(csw->residue, priv->residue); + csw->status = status; + + usbstrg_dumpdata("SCSCI CSW", (uint8_t*)csw, USBSTRG_CSW_SIZEOF); + + req->len = USBSTRG_CSW_SIZEOF; + req->callback = usbstrg_wrcomplete; + req->priv = privreq; + req->flags = USBDEV_REQFLAGS_NULLPKT; + + ret = EP_SUBMIT(priv->epbulkin, req); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SNDSTATUSSUBMIT), (uint16_t)-ret); + flags = irqsave(); + (void)sq_addlast((sq_entry_t*)privreq, &priv->wrreqlist); + irqrestore(flags); + } + + /* Return to the IDLE state */ + + usbtrace(TRACE_CLASSSTATE(USBSTRG_CLASSSTATE_CMDSTATUSIDLE), 0); + priv->thstate = USBSTRG_STATE_IDLE; + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbstrg_workerthread + * + * Description: + * This is the main function of the USB storage worker thread. It loops + * until USB-related events occur, then processes those events accordingly + * + ****************************************************************************/ + +void *usbstrg_workerthread(void *arg) +{ + struct usbstrg_dev_s *priv = (struct usbstrg_dev_s *)arg; + irqstate_t flags; + uint16_t eventset; + int ret; + + /* This thread is started before the USB storage class is fully initialized. + * wait here until we are told to begin. Start in the NOTINITIALIZED state + */ + + pthread_mutex_lock(&priv->mutex); + priv->thstate = USBSTRG_STATE_STARTED; + while ((priv->theventset & USBSTRG_EVENT_READY) != 0 && + (priv->theventset & USBSTRG_EVENT_TERMINATEREQUEST) != 0) + { + pthread_cond_wait(&priv->cond, &priv->mutex); + } + + /* Transition to the INITIALIZED/IDLE state */ + + priv->thstate = USBSTRG_STATE_IDLE; + eventset = priv->theventset; + priv->theventset = USBSTRG_EVENT_NOEVENTS; + pthread_mutex_unlock(&priv->mutex); + + /* Then loop until we are asked to terminate */ + + while ((eventset & USBSTRG_EVENT_TERMINATEREQUEST) == 0) + { + /* Wait for some interesting event. Note that we must both take the + * lock (to eliminate race conditions with other threads) and disable + * interrupts (to eliminate race conditions with USB interrupt handling. + */ + + pthread_mutex_lock(&priv->mutex); + flags = irqsave(); + if (priv->theventset == USBSTRG_EVENT_NOEVENTS) + { + pthread_cond_wait(&priv->cond, &priv->mutex); + } + + /* Sample any events before re-enabling interrupts. Any events that + * occur after re-enabling interrupts will have to be handled in the + * next time through the loop. + */ + + eventset = priv->theventset; + priv->theventset = USBSTRG_EVENT_NOEVENTS; + pthread_mutex_unlock(&priv->mutex); + + /* Were we awakened by some event that requires immediate action? + * + * - The USBSTRG_EVENT_DISCONNECT is signalled from the disconnect method + * after all transfers have been stopped, when the host is disconnected. + * + * - The CUSBSTRG_EVENT_RESET is signalled when the bulk-storage-specific + * USBSTRG_REQ_MSRESET EP0 setup received. We must stop the current + * operation and reinialize state. + * + * - The USBSTRG_EVENT_CFGCHANGE is signaled when the EP0 setup logic + * receives a valid USB_REQ_SETCONFIGURATION request + * + * - The USBSTRG_EVENT_IFCHANGE is signaled when the EP0 setup logic + * receives a valid USB_REQ_SETINTERFACE request + * + * - The USBSTRG_EVENT_ABORTBULKOUT event is signalled by the CMDFINISH + * logic when there is a residue after processing a host-to-device + * transfer. We need to discard all incoming request. + * + * All other events are just wakeup calls and are intended only + * drive the state machine. + */ + + if ((eventset & (USBSTRG_EVENT_DISCONNECT|USBSTRG_EVENT_RESET|USBSTRG_EVENT_CFGCHANGE| + USBSTRG_EVENT_IFCHANGE|USBSTRG_EVENT_ABORTBULKOUT)) != 0) + { + /* These events require that the current configuration be reset */ + + if ((eventset & USBSTRG_EVENT_IFCHANGE) != 0) + { + usbstrg_resetconfig(priv); + } + + /* These events require that a new configuration be established */ + + if ((eventset & (USBSTRG_EVENT_CFGCHANGE|USBSTRG_EVENT_IFCHANGE)) != 0) + { + usbstrg_setconfig(priv, priv->thvalue); + } + + /* These events required that we send a deferred EP0 setup response */ + + if ((eventset & (USBSTRG_EVENT_RESET|USBSTRG_EVENT_CFGCHANGE|USBSTRG_EVENT_IFCHANGE)) != 0) + { + usbstrg_deferredresponse(priv, false); + } + + /* For all of these events... terminate any transactions in progress */ + + priv->thstate = USBSTRG_STATE_IDLE; + } + irqrestore(flags); + + /* Loop processing each SCSI command state. Each state handling + * function will do the following: + * + * - If it must block for an event, it will return a negated errno value + * - If it completes the processing for that state, it will (1) set + * the next appropriate state value and (2) return OK. + * + * So the following will loop until we must block for an event in + * a particular state. When we are awakened by an event (above) we + * will resume processing in the same state. + */ + + do + { + switch (priv->thstate) + { + case USBSTRG_STATE_IDLE: /* Started and waiting for commands */ + ret = usbstrg_idlestate(priv); + break; + + case USBSTRG_STATE_CMDPARSE: /* Parsing the received a command */ + ret = usbstrg_cmdparsestate(priv); + break; + + case USBSTRG_STATE_CMDREAD: /* Continuing to process a SCSI read command */ + ret = usbstrg_cmdreadstate(priv); + break; + + case USBSTRG_STATE_CMDWRITE: /* Continuing to process a SCSI write command */ + ret = usbstrg_cmdwritestate(priv); + break; + + case USBSTRG_STATE_CMDFINISH: /* Finish command processing */ + ret = usbstrg_cmdfinishstate(priv); + break; + + case USBSTRG_STATE_CMDSTATUS: /* Processing the status phase of a command */ + ret = usbstrg_cmdstatusstate(priv); + break; + + case USBSTRG_STATE_NOTSTARTED: /* Thread has not yet been started */ + case USBSTRG_STATE_STARTED: /* Started, but is not yet initialized */ + case USBSTRG_STATE_TERMINATED: /* Thread has exitted */ + default: + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INVALIDSTATE), priv->thstate); + priv->thstate = USBSTRG_STATE_IDLE; + ret = OK; + break; + } + } + while (ret == OK); + } + + /* Transition to the TERMINATED state and exit */ + + priv->thstate = USBSTRG_STATE_TERMINATED; + return NULL; +} diff --git a/nuttx/drivers/usbdev/usbdev_serial.c b/nuttx/drivers/usbdev/usbdev_serial.c new file mode 100644 index 000000000..bef02ed73 --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_serial.c @@ -0,0 +1,2216 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_serial.c + * + * Copyright (C) 2008-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * This logic emulates the Prolific PL2303 serial/USB converter + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <semaphore.h> +#include <string.h> +#include <errno.h> +#include <queue.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/serial.h> +#include <nuttx/usb/usb.h> +#include <nuttx/usb/usbdev.h> +#include <nuttx/usb/usbdev_trace.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Number of requests in the write queue */ + +#ifndef CONFIG_USBSER_NWRREQS +# define CONFIG_USBSER_NWRREQS 4 +#endif + +/* Number of requests in the read queue */ + +#ifndef CONFIG_USBSER_NRDREQS +# define CONFIG_USBSER_NRDREQS 4 +#endif + +/* Logical endpoint numbers / max packet sizes */ + +#ifndef CONFIG_USBSER_EPINTIN +# warning "EPINTIN not defined in the configuration" +# define CONFIG_USBSER_EPINTIN 1 +#endif + +#ifndef CONFIG_USBSER_EPBULKOUT +# warning "EPBULKOUT not defined in the configuration" +# define CONFIG_USBSER_EPBULKOUT 2 +#endif + +#ifndef CONFIG_USBSER_EPBULKIN +# warning "EPBULKIN not defined in the configuration" +# define CONFIG_USBSER_EPBULKIN 3 +#endif + +/* Packet and request buffer sizes */ + +#ifndef CONFIG_USBSER_EP0MAXPACKET +# define CONFIG_USBSER_EP0MAXPACKET 64 +#endif + +#undef CONFIG_USBSER_BULKREQLEN + +/* Vendor and product IDs and strings */ + +#ifndef CONFIG_USBSER_VENDORID +# define CONFIG_USBSER_VENDORID 0x067b +#endif + +#ifndef CONFIG_USBSER_PRODUCTID +# define CONFIG_USBSER_PRODUCTID 0x2303 +#endif + +#ifndef CONFIG_USBSER_VENDORSTR +# warning "No Vendor string specified" +# define CONFIG_USBSER_VENDORSTR "NuttX" +#endif + +#ifndef CONFIG_USBSER_PRODUCTSTR +# warning "No Product string specified" +# define CONFIG_USBSER_PRODUCTSTR "USBdev Serial" +#endif + +#undef CONFIG_USBSER_SERIALSTR +#define CONFIG_USBSER_SERIALSTR "0" + +#undef CONFIG_USBSER_CONFIGSTR +#define CONFIG_USBSER_CONFIGSTR "Bulk" + +/* USB Controller */ + +#ifndef CONFIG_USBDEV_SELFPOWERED +# define SELFPOWERED USB_CONFIG_ATT_SELFPOWER +#else +# define SELFPOWERED (0) +#endif + +#ifndef CONFIG_USBDEV_REMOTEWAKEUP +# define REMOTEWAKEUP USB_CONFIG_ATTR_WAKEUP +#else +# define REMOTEWAKEUP (0) +#endif + +#ifndef CONFIG_USBDEV_MAXPOWER +# define CONFIG_USBDEV_MAXPOWER 100 +#endif + +/* Descriptors ****************************************************************/ + +/* These settings are not modifiable via the NuttX configuration */ + +#define USBSER_VERSIONNO (0x0202) /* Device version number */ +#define USBSER_CONFIGIDNONE (0) /* Config ID means to return to address mode */ +#define USBSER_CONFIGID (1) /* The only supported configuration ID */ +#define USBSER_NCONFIGS (1) /* Number of configurations supported */ +#define USBSER_INTERFACEID (0) +#define USBSER_ALTINTERFACEID (0) +#define USBSER_NINTERFACES (1) /* Number of interfaces in the configuration */ +#define USBSER_NENDPOINTS (3) /* Number of endpoints in the interface */ + +/* Endpoint configuration */ + +#define USBSER_EPINTIN_ADDR (USB_DIR_IN|CONFIG_USBSER_EPINTIN) +#define USBSER_EPINTIN_ATTR (USB_EP_ATTR_XFER_INT) +#define USBSER_EPINTIN_MXPACKET (10) + +#define USBSER_EPOUTBULK_ADDR (CONFIG_USBSER_EPBULKOUT) +#define USBSER_EPOUTBULK_ATTR (USB_EP_ATTR_XFER_BULK) + +#define USBSER_EPINBULK_ADDR (USB_DIR_IN|CONFIG_USBSER_EPBULKIN) +#define USBSER_EPINBULK_ATTR (USB_EP_ATTR_XFER_BULK) + +/* String language */ + +#define USBSER_STR_LANGUAGE (0x0409) /* en-us */ + +/* Descriptor strings */ + +#define USBSER_MANUFACTURERSTRID (1) +#define USBSER_PRODUCTSTRID (2) +#define USBSER_SERIALSTRID (3) +#define USBSER_CONFIGSTRID (4) + +/* Buffer big enough for any of our descriptors */ + +#define USBSER_MXDESCLEN (64) + +/* Vender specific control requests *******************************************/ + +#define PL2303_CONTROL_TYPE (0x20) +#define PL2303_SETLINEREQUEST (0x20) /* OUT, Recipient interface */ +#define PL2303_GETLINEREQUEST (0x21) /* IN, Recipient interface */ +#define PL2303_SETCONTROLREQUEST (0x22) /* OUT, Recipient interface */ +#define PL2303_BREAKREQUEST (0x23) /* OUT, Recipient interface */ + +/* Vendor read/write */ + +#define PL2303_RWREQUEST_TYPE (0x40) +#define PL2303_RWREQUEST (0x01) /* IN/OUT, Recipient device */ + +/* Misc Macros ****************************************************************/ + +/* min/max macros */ + +#ifndef min +# define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifndef max +# define max(a,b) ((a)>(b)?(a):(b)) +#endif + +/* Trace values *************************************************************/ + +#define USBSER_CLASSAPI_SETUP TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SETUP) +#define USBSER_CLASSAPI_SHUTDOWN TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SHUTDOWN) +#define USBSER_CLASSAPI_ATTACH TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_ATTACH) +#define USBSER_CLASSAPI_DETACH TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_DETACH) +#define USBSER_CLASSAPI_IOCTL TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_IOCTL) +#define USBSER_CLASSAPI_RECEIVE TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RECEIVE) +#define USBSER_CLASSAPI_RXINT TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXINT) +#define USBSER_CLASSAPI_RXAVAILABLE TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_RXAVAILABLE) +#define USBSER_CLASSAPI_SEND TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_SEND) +#define USBSER_CLASSAPI_TXINT TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXINT) +#define USBSER_CLASSAPI_TXREADY TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXREADY) +#define USBSER_CLASSAPI_TXEMPTY TRACE_EVENT(TRACE_CLASSAPI_ID, USBSER_TRACECLASSAPI_TXEMPTY) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Container to support a list of requests */ + +struct usbser_req_s +{ + FAR struct usbser_req_s *flink; /* Implements a singly linked list */ + FAR struct usbdev_req_s *req; /* The contained request */ +}; + +/* This structure describes the internal state of the driver */ + +struct usbser_dev_s +{ + FAR struct uart_dev_s serdev; /* Serial device structure */ + FAR struct usbdev_s *usbdev; /* usbdev driver pointer */ + + uint8_t config; /* Configuration number */ + uint8_t nwrq; /* Number of queue write requests (in reqlist)*/ + uint8_t nrdq; /* Number of queue read requests (in epbulkout) */ + bool rxenabled; /* true: UART RX "interrupts" enabled */ + uint8_t linest[7]; /* Fake line status */ + int16_t rxhead; /* Working head; used when rx int disabled */ + + FAR struct usbdev_ep_s *epintin; /* Interrupt IN endpoint structure */ + FAR struct usbdev_ep_s *epbulkin; /* Bulk IN endpoint structure */ + FAR struct usbdev_ep_s *epbulkout; /* Bulk OUT endpoint structure */ + FAR struct usbdev_req_s *ctrlreq; /* Control request */ + struct sq_queue_s reqlist; /* List of write request containers */ + + /* Pre-allocated write request containers. The write requests will + * be linked in a free list (reqlist), and used to send requests to + * EPBULKIN; Read requests will be queued in the EBULKOUT. + */ + + struct usbser_req_s wrreqs[CONFIG_USBSER_NWRREQS]; + struct usbser_req_s rdreqs[CONFIG_USBSER_NWRREQS]; + + /* Serial I/O buffers */ + + char rxbuffer[CONFIG_USBSER_RXBUFSIZE]; + char txbuffer[CONFIG_USBSER_TXBUFSIZE]; +}; + +/* The internal version of the class driver */ + +struct usbser_driver_s +{ + struct usbdevclass_driver_s drvr; + FAR struct usbser_dev_s *dev; +}; + +/* This is what is allocated */ + +struct usbser_alloc_s +{ + struct usbser_dev_s dev; + struct usbser_driver_s drvr; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Transfer helpers *********************************************************/ + +static uint16_t usbclass_fillrequest(FAR struct usbser_dev_s *priv, + uint8_t *reqbuf, uint16_t reqlen); +static int usbclass_sndpacket(FAR struct usbser_dev_s *priv); +static inline int usbclass_recvpacket(FAR struct usbser_dev_s *priv, + uint8_t *reqbuf, uint16_t reqlen); + +/* Request helpers *********************************************************/ + +static struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep, + uint16_t len); +static void usbclass_freereq(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); + +/* Configuration ***********************************************************/ + +static int usbclass_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc); +#ifdef CONFIG_USBDEV_DUALSPEED +static void usbclass_mkepbulkdesc(const struct usb_epdesc_s *indesc, + uint16_t mxpacket, struct usb_epdesc_s *outdesc); +static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type); +#else +static int16_t usbclass_mkcfgdesc(uint8_t *buf); +#endif +static void usbclass_resetconfig(FAR struct usbser_dev_s *priv); +static int usbclass_setconfig(FAR struct usbser_dev_s *priv, + uint8_t config); + +/* Completion event handlers ***********************************************/ + +static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); +static void usbclass_rdcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); +static void usbclass_wrcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); + +/* USB class device ********************************************************/ + +static int usbclass_bind(FAR struct usbdev_s *dev, + FAR struct usbdevclass_driver_s *driver); +static void usbclass_unbind(FAR struct usbdev_s *dev); +static int usbclass_setup(FAR struct usbdev_s *dev, + const struct usb_ctrlreq_s *ctrl); +static void usbclass_disconnect(FAR struct usbdev_s *dev); + +/* Serial port *************************************************************/ + +static int usbser_setup(FAR struct uart_dev_s *dev); +static void usbser_shutdown(FAR struct uart_dev_s *dev); +static int usbser_attach(FAR struct uart_dev_s *dev); +static void usbser_detach(FAR struct uart_dev_s *dev); +static void usbser_rxint(FAR struct uart_dev_s *dev, bool enable); +static void usbser_txint(FAR struct uart_dev_s *dev, bool enable); +static bool usbser_txempty(FAR struct uart_dev_s *dev); + +/**************************************************************************** + * Private Variables + ****************************************************************************/ + +/* USB class device ********************************************************/ + +static const struct usbdevclass_driverops_s g_driverops = +{ + usbclass_bind, /* bind */ + usbclass_unbind, /* unbind */ + usbclass_setup, /* setup */ + usbclass_disconnect, /* disconnect */ + NULL, /* suspend */ + NULL, /* resume */ +}; + +/* Serial port *************************************************************/ + +static const struct uart_ops_s g_uartops = +{ + usbser_setup, /* setup */ + usbser_shutdown, /* shutdown */ + usbser_attach, /* attach */ + usbser_detach, /* detach */ + NULL, /* ioctl */ + NULL, /* receive */ + usbser_rxint, /* rxinit */ + NULL, /* rxavailable */ + NULL, /* send */ + usbser_txint, /* txinit */ + NULL, /* txready */ + usbser_txempty /* txempty */ +}; + +/* USB descriptor templates these will be copied and modified **************/ + +static const struct usb_devdesc_s g_devdesc = +{ + USB_SIZEOF_DEVDESC, /* len */ + USB_DESC_TYPE_DEVICE, /* type */ + {LSBYTE(0x0200), MSBYTE(0x0200)}, /* usb */ + USB_CLASS_PER_INTERFACE, /* class */ + 0, /* subclass */ + 0, /* protocol */ + CONFIG_USBSER_EP0MAXPACKET, /* maxpacketsize */ + { LSBYTE(CONFIG_USBSER_VENDORID), /* vendor */ + MSBYTE(CONFIG_USBSER_VENDORID) }, + { LSBYTE(CONFIG_USBSER_PRODUCTID), /* product */ + MSBYTE(CONFIG_USBSER_PRODUCTID) }, + { LSBYTE(USBSER_VERSIONNO), /* device */ + MSBYTE(USBSER_VERSIONNO) }, + USBSER_MANUFACTURERSTRID, /* imfgr */ + USBSER_PRODUCTSTRID, /* iproduct */ + USBSER_SERIALSTRID, /* serno */ + USBSER_NCONFIGS /* nconfigs */ +}; + +static const struct usb_cfgdesc_s g_cfgdesc = +{ + USB_SIZEOF_CFGDESC, /* len */ + USB_DESC_TYPE_CONFIG, /* type */ + {0, 0}, /* totallen -- to be provided */ + USBSER_NINTERFACES, /* ninterfaces */ + USBSER_CONFIGID, /* cfgvalue */ + USBSER_CONFIGSTRID, /* icfg */ + USB_CONFIG_ATTR_ONE|SELFPOWERED|REMOTEWAKEUP, /* attr */ + (CONFIG_USBDEV_MAXPOWER + 1) / 2 /* mxpower */ +}; + +static const struct usb_ifdesc_s g_ifdesc = +{ + USB_SIZEOF_IFDESC, /* len */ + USB_DESC_TYPE_INTERFACE, /* type */ + 0, /* ifno */ + 0, /* alt */ + USBSER_NENDPOINTS, /* neps */ + USB_CLASS_VENDOR_SPEC, /* class */ + 0, /* subclass */ + 0, /* protocol */ + USBSER_CONFIGSTRID /* iif */ +}; + +static const struct usb_epdesc_s g_epintindesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + USBSER_EPINTIN_ADDR, /* addr */ + USBSER_EPINTIN_ATTR, /* attr */ + { LSBYTE(USBSER_EPINTIN_MXPACKET), /* maxpacket */ + MSBYTE(USBSER_EPINTIN_MXPACKET) }, + 1 /* interval */ +}; + +static const struct usb_epdesc_s g_epbulkoutdesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + USBSER_EPOUTBULK_ADDR, /* addr */ + USBSER_EPOUTBULK_ATTR, /* attr */ + { LSBYTE(64), MSBYTE(64) }, /* maxpacket -- might change to 512*/ + 0 /* interval */ +}; + +static const struct usb_epdesc_s g_epbulkindesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + USBSER_EPINBULK_ADDR, /* addr */ + USBSER_EPINBULK_ATTR, /* attr */ + { LSBYTE(64), MSBYTE(64) }, /* maxpacket -- might change to 512*/ + 0 /* interval */ +}; + +#ifdef CONFIG_USBDEV_DUALSPEED +static const struct usb_qualdesc_s g_qualdesc = +{ + USB_SIZEOF_QUALDESC, /* len */ + USB_DESC_TYPE_DEVICEQUALIFIER, /* type */ + {LSBYTE(0x0200), MSBYTE(0x0200) }, /* USB */ + USB_CLASS_VENDOR_SPEC, /* class */ + 0, /* subclass */ + 0, /* protocol */ + CONFIG_USBSER_EP0MAXPACKET, /* mxpacketsize */ + USBSER_NCONFIGS, /* nconfigs */ + 0, /* reserved */ +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/************************************************************************************ + * Name: usbclass_fillrequest + * + * Description: + * If there is data to send it is copied to the given buffer. Called either + * to initiate the first write operation, or from the completion interrupt handler + * service consecutive write operations. + * + * NOTE: The USB serial driver does not use the serial drivers uart_xmitchars() + * API. That logic is essentially duplicated here because unlike UART hardware, + * we need to be able to handle writes not byte-by-byte, but packet-by-packet. + * Unfortunately, that decision also exposes some internals of the serial driver + * in the following. + * + ************************************************************************************/ + +static uint16_t usbclass_fillrequest(FAR struct usbser_dev_s *priv, uint8_t *reqbuf, + uint16_t reqlen) +{ + FAR uart_dev_t *serdev = &priv->serdev; + FAR struct uart_buffer_s *xmit = &serdev->xmit; + irqstate_t flags; + uint16_t nbytes = 0; + + /* Disable interrupts */ + + flags = irqsave(); + + /* Transfer bytes while we have bytes available and there is room in the request */ + + while (xmit->head != xmit->tail && nbytes < reqlen) + { + *reqbuf++ = xmit->buffer[xmit->tail]; + nbytes++; + + /* Increment the tail pointer */ + + if (++(xmit->tail) >= xmit->size) + { + xmit->tail = 0; + } + } + + /* When all of the characters have been sent from the buffer + * disable the "TX interrupt". + */ + + if (xmit->head == xmit->tail) + { + uart_disabletxint(serdev); + } + + /* If any bytes were removed from the buffer, inform any waiters + * there there is space available. + */ + + if (nbytes) + { + uart_datasent(serdev); + } + + irqrestore(flags); + return nbytes; +} + +/************************************************************************************ + * Name: usbclass_sndpacket + * + * Description: + * This function obtains write requests, transfers the TX data into the request, + * and submits the requests to the USB controller. This continues untils either + * (1) there are no further packets available, or (2) thre is not further data + * to send. + * + ************************************************************************************/ + +static int usbclass_sndpacket(FAR struct usbser_dev_s *priv) +{ + FAR struct usbdev_ep_s *ep; + FAR struct usbdev_req_s *req; + FAR struct usbser_req_s *reqcontainer; + irqstate_t flags; + int len; + int ret = OK; + +#ifdef CONFIG_DEBUG + if (priv == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -ENODEV; + } +#endif + + flags = irqsave(); + + /* Use our IN endpoint for the transfer */ + + ep = priv->epbulkin; + + /* Loop until either (1) we run out or write requests, or (2) usbclass_fillrequest() + * is unable to fill the request with data (i.e., untilthere is no more data + * to be sent). + */ + + uvdbg("head=%d tail=%d nwrq=%d empty=%d\n", + priv->serdev.xmit.head, priv->serdev.xmit.tail, + priv->nwrq, sq_empty(&priv->reqlist)); + + while (!sq_empty(&priv->reqlist)) + { + /* Peek at the request in the container at the head of the list */ + + reqcontainer = (struct usbser_req_s *)sq_peek(&priv->reqlist); + req = reqcontainer->req; + + /* Fill the request with serial TX data */ + + len = usbclass_fillrequest(priv, req->buf, req->len); + if (len > 0) + { + /* Remove the empty container from the request list */ + + (void)sq_remfirst(&priv->reqlist); + priv->nwrq--; + + /* Then submit the request to the endpoint */ + + req->len = len; + req->priv = reqcontainer; + req->flags = USBDEV_REQFLAGS_NULLPKT; + ret = EP_SUBMIT(ep, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), (uint16_t)-ret); + break; + } + } + else + { + break; + } + } + + irqrestore(flags); + return ret; +} + +/************************************************************************************ + * Name: usbclass_recvpacket + * + * Description: + * A normal completion event was received by the read completion handler at the + * interrupt level (with interrupts disabled). This function handles the USB packet + * and provides the received data to the uart RX buffer. + * + * Assumptions: + * Called from the USB interrupt handler with interrupts disabled. + * + ************************************************************************************/ + +static inline int usbclass_recvpacket(FAR struct usbser_dev_s *priv, + uint8_t *reqbuf, uint16_t reqlen) +{ + FAR uart_dev_t *serdev = &priv->serdev; + FAR struct uart_buffer_s *recv = &serdev->recv; + uint16_t currhead; + uint16_t nexthead; + uint16_t nbytes = 0; + + /* Get the next head index. During the time that RX interrupts are disabled, the + * the serial driver will be extracting data from the circular buffer and modifying + * recv.tail. During this time, we should avoid modifying recv.head; Instead we will + * use a shadow copy of the index. When interrupts are restored, the real recv.head + * will be updated with this indes. + */ + + if (priv->rxenabled) + { + currhead = recv->head; + } + else + { + currhead = priv->rxhead; + } + + /* Pre-calculate the head index and check for wrap around. We need to do this + * so that we can determine if the circular buffer will overrun BEFORE we + * overrun the buffer! + */ + + nexthead = currhead + 1; + if (nexthead >= recv->size) + { + nexthead = 0; + } + + /* Then copy data into the RX buffer until either: (1) all of the data has been + * copied, or (2) the RX buffer is full. NOTE: If the RX buffer becomes full, + * then we have overrun the serial driver and data will be lost. + */ + + while (nexthead != recv->tail && nbytes < reqlen) + { + /* Copy one byte to the head of the circular RX buffer */ + + recv->buffer[currhead] = *reqbuf++; + + /* Update counts and indices */ + + currhead = nexthead; + nbytes++; + + /* Increment the head index and check for wrap around */ + + nexthead = currhead + 1; + if (nexthead >= recv->size) + { + nexthead = 0; + } + } + + /* Write back the head pointer using the shadow index if RX "interrupts" + * are disabled. + */ + + if (priv->rxenabled) + { + recv->head = currhead; + } + else + { + priv->rxhead = currhead; + } + + /* If data was added to the incoming serial buffer, then wake up any + * threads is waiting for incoming data. If we are running in an interrupt + * handler, then the serial driver will not run until the interrupt handler + * returns. + */ + + if (priv->rxenabled && nbytes > 0) + { + uart_datareceived(serdev); + } + + /* Return an error if the entire packet could not be transferred */ + + if (nbytes < reqlen) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RXOVERRUN), 0); + return -ENOSPC; + } + return OK; +} + +/**************************************************************************** + * Name: usbclass_allocreq + * + * Description: + * Allocate a request instance along with its buffer + * + ****************************************************************************/ + +static struct usbdev_req_s *usbclass_allocreq(FAR struct usbdev_ep_s *ep, + uint16_t len) +{ + FAR struct usbdev_req_s *req; + + req = EP_ALLOCREQ(ep); + if (req != NULL) + { + req->len = len; + req->buf = EP_ALLOCBUFFER(ep, len); + if (!req->buf) + { + EP_FREEREQ(ep, req); + req = NULL; + } + } + return req; +} + +/**************************************************************************** + * Name: usbclass_freereq + * + * Description: + * Free a request instance along with its buffer + * + ****************************************************************************/ + +static void usbclass_freereq(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + if (ep != NULL && req != NULL) + { + if (req->buf != NULL) + { + EP_FREEBUFFER(ep, req->buf); + } + EP_FREEREQ(ep, req); + } +} + +/**************************************************************************** + * Name: usbclass_mkstrdesc + * + * Description: + * Construct a string descriptor + * + ****************************************************************************/ + +static int usbclass_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc) +{ + const char *str; + int len; + int ndata; + int i; + + switch (id) + { + case 0: + { + /* Descriptor 0 is the language id */ + + strdesc->len = 4; + strdesc->type = USB_DESC_TYPE_STRING; + strdesc->data[0] = LSBYTE(USBSER_STR_LANGUAGE); + strdesc->data[1] = MSBYTE(USBSER_STR_LANGUAGE); + return 4; + } + + case USBSER_MANUFACTURERSTRID: + str = CONFIG_USBSER_VENDORSTR; + break; + + case USBSER_PRODUCTSTRID: + str = CONFIG_USBSER_PRODUCTSTR; + break; + + case USBSER_SERIALSTRID: + str = CONFIG_USBSER_SERIALSTR; + break; + + case USBSER_CONFIGSTRID: + str = CONFIG_USBSER_CONFIGSTR; + break; + + default: + return -EINVAL; + } + + /* The string is utf16-le. The poor man's utf-8 to utf16-le + * conversion below will only handle 7-bit en-us ascii + */ + + len = strlen(str); + for (i = 0, ndata = 0; i < len; i++, ndata += 2) + { + strdesc->data[ndata] = str[i]; + strdesc->data[ndata+1] = 0; + } + + strdesc->len = ndata+2; + strdesc->type = USB_DESC_TYPE_STRING; + return strdesc->len; +} + +/**************************************************************************** + * Name: usbclass_mkepbulkdesc + * + * Description: + * Construct the endpoint descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +static inline void usbclass_mkepbulkdesc(const FAR struct usb_epdesc_s *indesc, + uint16_t mxpacket, + FAR struct usb_epdesc_s *outdesc) +{ + /* Copy the canned descriptor */ + + memcpy(outdesc, indesc, USB_SIZEOF_EPDESC); + + /* Then add the correct max packet size */ + + outdesc->mxpacketsize[0] = LSBYTE(mxpacket); + outdesc->mxpacketsize[1] = MSBYTE(mxpacket); +} +#endif + +/**************************************************************************** + * Name: usbclass_mkcfgdesc + * + * Description: + * Construct the configuration descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +static int16_t usbclass_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type) +#else +static int16_t usbclass_mkcfgdesc(uint8_t *buf) +#endif +{ + FAR struct usb_cfgdesc_s *cfgdesc = (struct usb_cfgdesc_s*)buf; +#ifdef CONFIG_USBDEV_DUALSPEED + bool hispeed = (speed == USB_SPEED_HIGH); + uint16_t bulkmxpacket; +#endif + uint16_t totallen; + + /* This is the total length of the configuration (not necessarily the + * size that we will be sending now. + */ + + totallen = USB_SIZEOF_CFGDESC + USB_SIZEOF_IFDESC + USBSER_NENDPOINTS * USB_SIZEOF_EPDESC; + + /* Configuration descriptor -- Copy the canned descriptor and fill in the + * type (we'll also need to update the size below + */ + + memcpy(cfgdesc, &g_cfgdesc, USB_SIZEOF_CFGDESC); + buf += USB_SIZEOF_CFGDESC; + + /* Copy the canned interface descriptor */ + + memcpy(buf, &g_ifdesc, USB_SIZEOF_IFDESC); + buf += USB_SIZEOF_IFDESC; + + /* Make the three endpoint configurations. First, check for switches + * between high and full speed + */ + +#ifdef CONFIG_USBDEV_DUALSPEED + if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG) + { + hispeed = !hispeed; + } +#endif + + memcpy(buf, &g_epintindesc, USB_SIZEOF_EPDESC); + buf += USB_SIZEOF_EPDESC; + +#ifdef CONFIG_USBDEV_DUALSPEED + if (hispeed) + { + bulkmxpacket = 512; + } + else + { + bulkmxpacket = 64; + } + + usbclass_mkepbulkdesc(&g_epbulkoutdesc, bulkmxpacket, (struct usb_epdesc_s*)buf); + buf += USB_SIZEOF_EPDESC; + usbclass_mkepbulkdesc(&g_epbulkindesc, bulkmxpacket, (struct usb_epdesc_s*)buf); +#else + memcpy(buf, &g_epbulkoutdesc, USB_SIZEOF_EPDESC); + buf += USB_SIZEOF_EPDESC; + memcpy(buf, &g_epbulkindesc, USB_SIZEOF_EPDESC); +#endif + + /* Finally, fill in the total size of the configuration descriptor */ + + cfgdesc->totallen[0] = LSBYTE(totallen); + cfgdesc->totallen[1] = MSBYTE(totallen); + return totallen; +} + +/**************************************************************************** + * Name: usbclass_resetconfig + * + * Description: + * Mark the device as not configured and disable all endpoints. + * + ****************************************************************************/ + +static void usbclass_resetconfig(FAR struct usbser_dev_s *priv) +{ + /* Are we configured? */ + + if (priv->config != USBSER_CONFIGIDNONE) + { + /* Yes.. but not anymore */ + + priv->config = USBSER_CONFIGIDNONE; + + /* Disable endpoints. This should force completion of all pending + * transfers. + */ + + EP_DISABLE(priv->epintin); + EP_DISABLE(priv->epbulkin); + EP_DISABLE(priv->epbulkout); + } +} + +/**************************************************************************** + * Name: usbclass_setconfig + * + * Description: + * Set the device configuration by allocating and configuring endpoints and + * by allocating and queue read and write requests. + * + ****************************************************************************/ + +static int usbclass_setconfig(FAR struct usbser_dev_s *priv, uint8_t config) +{ + FAR struct usbdev_req_s *req; +#ifdef CONFIG_USBDEV_DUALSPEED + struct usb_epdesc_s epdesc; + uint16_t bulkmxpacket; +#endif + int i; + int ret = 0; + +#if CONFIG_DEBUG + if (priv == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -EIO; + } +#endif + + if (config == priv->config) + { + /* Already configured -- Do nothing */ + + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0); + return 0; + } + + /* Discard the previous configuration data */ + + usbclass_resetconfig(priv); + + /* Was this a request to simply discard the current configuration? */ + + if (config == USBSER_CONFIGIDNONE) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0); + return 0; + } + + /* We only accept one configuration */ + + if (config != USBSER_CONFIGID) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0); + return -EINVAL; + } + + /* Configure the IN interrupt endpoint */ + + ret = EP_CONFIGURE(priv->epintin, &g_epintindesc, false); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0); + goto errout; + } + priv->epintin->priv = priv; + + /* Configure the IN bulk endpoint */ + +#ifdef CONFIG_USBDEV_DUALSPEED + if (priv->usbdev->speed == USB_SPEED_HIGH) + { + bulkmxpacket = 512; + } + else + { + bulkmxpacket = 64; + } + + usbclass_mkepbulkdesc(&g_epbulkindesc, bulkmxpacket, &epdesc); + ret = EP_CONFIGURE(priv->epbulkin, &epdesc, false); +#else + ret = EP_CONFIGURE(priv->epbulkin, &g_epbulkindesc, false); +#endif + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0); + goto errout; + } + + priv->epbulkin->priv = priv; + + /* Configure the OUT bulk endpoint */ + +#ifdef CONFIG_USBDEV_DUALSPEED + usbclass_mkepbulkdesc(&g_epbulkoutdesc, bulkmxpacket, &epdesc); + ret = EP_CONFIGURE(priv->epbulkout, &epdesc, true); +#else + ret = EP_CONFIGURE(priv->epbulkout, &g_epbulkoutdesc, true); +#endif + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0); + goto errout; + } + + priv->epbulkout->priv = priv; + + /* Queue read requests in the bulk OUT endpoint */ + + DEBUGASSERT(priv->nrdq == 0); + for (i = 0; i < CONFIG_USBSER_NRDREQS; i++) + { + req = priv->rdreqs[i].req; + req->callback = usbclass_rdcomplete; + ret = EP_SUBMIT(priv->epbulkout, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-ret); + goto errout; + } + priv->nrdq++; + } + + priv->config = config; + return OK; + +errout: + usbclass_resetconfig(priv); + return ret; +} + +/**************************************************************************** + * Name: usbclass_ep0incomplete + * + * Description: + * Handle completion of EP0 control operations + * + ****************************************************************************/ + +static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + if (req->result || req->xfrd != req->len) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT), (uint16_t)-req->result); + } +} + +/**************************************************************************** + * Name: usbclass_rdcomplete + * + * Description: + * Handle completion of read request on the bulk OUT endpoint. This + * is handled like the receipt of serial data on the "UART" + * + ****************************************************************************/ + +static void usbclass_rdcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + FAR struct usbser_dev_s *priv; + irqstate_t flags; + int ret; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!ep || !ep->priv || !req) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract references to private data */ + + priv = (FAR struct usbser_dev_s*)ep->priv; + + /* Process the received data unless this is some unusual condition */ + + flags = irqsave(); + switch (req->result) + { + case 0: /* Normal completion */ + usbtrace(TRACE_CLASSRDCOMPLETE, priv->nrdq); + usbclass_recvpacket(priv, req->buf, req->xfrd); + break; + + case -ESHUTDOWN: /* Disconnection */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0); + priv->nrdq--; + irqrestore(flags); + return; + + default: /* Some other error occurred */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED), (uint16_t)-req->result); + break; + }; + + /* Requeue the read request */ + +#ifdef CONFIG_USBSER_BULKREQLEN + req->len = max(CONFIG_USBSER_BULKREQLEN, ep->maxpacket); +#else + req->len = ep->maxpacket; +#endif + + ret = EP_SUBMIT(ep, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-req->result); + } + irqrestore(flags); +} + +/**************************************************************************** + * Name: usbclass_wrcomplete + * + * Description: + * Handle completion of write request. This function probably executes + * in the context of an interrupt handler. + * + ****************************************************************************/ + +static void usbclass_wrcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + FAR struct usbser_dev_s *priv; + FAR struct usbser_req_s *reqcontainer; + irqstate_t flags; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!ep || !ep->priv || !req || !req->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract references to our private data */ + + priv = (FAR struct usbser_dev_s *)ep->priv; + reqcontainer = (FAR struct usbser_req_s *)req->priv; + + /* Return the write request to the free list */ + + flags = irqsave(); + sq_addlast((sq_entry_t*)reqcontainer, &priv->reqlist); + priv->nwrq++; + irqrestore(flags); + + /* Send the next packet unless this was some unusual termination + * condition + */ + + switch (req->result) + { + case OK: /* Normal completion */ + usbtrace(TRACE_CLASSWRCOMPLETE, priv->nwrq); + usbclass_sndpacket(priv); + break; + + case -ESHUTDOWN: /* Disconnection */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN), priv->nwrq); + break; + + default: /* Some other error occurred */ + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED), (uint16_t)-req->result); + break; + } +} + +/**************************************************************************** + * USB Class Driver Methods + ****************************************************************************/ + +/**************************************************************************** + * Name: usbclass_bind + * + * Description: + * Invoked when the driver is bound to a USB device driver + * + ****************************************************************************/ + +static int usbclass_bind(FAR struct usbdev_s *dev, FAR struct usbdevclass_driver_s *driver) +{ + FAR struct usbser_dev_s *priv = ((struct usbser_driver_s*)driver)->dev; + FAR struct usbser_req_s *reqcontainer; + irqstate_t flags; + uint16_t reqlen; + int ret; + int i; + + usbtrace(TRACE_CLASSBIND, 0); + + /* Bind the structures */ + + priv->usbdev = dev; + dev->ep0->priv = priv; + + /* Preallocate control request */ + + priv->ctrlreq = usbclass_allocreq(dev->ep0, USBSER_MXDESCLEN); + if (priv->ctrlreq == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0); + ret = -ENOMEM; + goto errout; + } + priv->ctrlreq->callback = usbclass_ep0incomplete; + + /* Pre-allocate all endpoints... the endpoints will not be functional + * until the SET CONFIGURATION request is processed in usbclass_setconfig. + * This is done here because there may be calls to kmalloc and the SET + * CONFIGURATION processing probably occurrs within interrupt handling + * logic where kmalloc calls will fail. + */ + + /* Pre-allocate the IN interrupt endpoint */ + + priv->epintin = DEV_ALLOCEP(dev, USBSER_EPINTIN_ADDR, true, USB_EP_ATTR_XFER_INT); + if (!priv->epintin) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epintin->priv = priv; + + /* Pre-allocate the IN bulk endpoint */ + + priv->epbulkin = DEV_ALLOCEP(dev, USBSER_EPINBULK_ADDR, true, USB_EP_ATTR_XFER_BULK); + if (!priv->epbulkin) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epbulkin->priv = priv; + + /* Pre-allocate the OUT bulk endpoint */ + + priv->epbulkout = DEV_ALLOCEP(dev, USBSER_EPOUTBULK_ADDR, false, USB_EP_ATTR_XFER_BULK); + if (!priv->epbulkout) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epbulkout->priv = priv; + + /* Pre-allocate read requests */ + +#ifdef CONFIG_USBSER_BULKREQLEN + reqlen = max(CONFIG_USBSER_BULKREQLEN, priv->epbulkout->maxpacket); +#else + reqlen = priv->epbulkout->maxpacket; +#endif + + for (i = 0; i < CONFIG_USBSER_NRDREQS; i++) + { + reqcontainer = &priv->rdreqs[i]; + reqcontainer->req = usbclass_allocreq(priv->epbulkout, reqlen); + if (reqcontainer->req == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM); + ret = -ENOMEM; + goto errout; + } + reqcontainer->req->priv = reqcontainer; + reqcontainer->req->callback = usbclass_rdcomplete; + } + + /* Pre-allocate write request containers and put in a free list */ + +#ifdef CONFIG_USBSER_BULKREQLEN + reqlen = max(CONFIG_USBSER_BULKREQLEN, priv->epbulkin->maxpacket); +#else + reqlen = priv->epbulkin->maxpacket; +#endif + + for (i = 0; i < CONFIG_USBSER_NWRREQS; i++) + { + reqcontainer = &priv->wrreqs[i]; + reqcontainer->req = usbclass_allocreq(priv->epbulkin, reqlen); + if (reqcontainer->req == NULL) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM); + ret = -ENOMEM; + goto errout; + } + reqcontainer->req->priv = reqcontainer; + reqcontainer->req->callback = usbclass_wrcomplete; + + flags = irqsave(); + sq_addlast((sq_entry_t*)reqcontainer, &priv->reqlist); + priv->nwrq++; /* Count of write requests available */ + irqrestore(flags); + } + + /* Report if we are selfpowered */ + +#ifdef CONFIG_USBDEV_SELFPOWERED + DEV_SETSELFPOWERED(dev); +#endif + + /* And pull-up the data line for the soft connect function */ + + DEV_CONNECT(dev); + return OK; + +errout: + usbclass_unbind(dev); + return ret; +} + +/**************************************************************************** + * Name: usbclass_unbind + * + * Description: + * Invoked when the driver is unbound from a USB device driver + * + ****************************************************************************/ + +static void usbclass_unbind(FAR struct usbdev_s *dev) +{ + FAR struct usbser_dev_s *priv; + FAR struct usbser_req_s *reqcontainer; + irqstate_t flags; + int i; + + usbtrace(TRACE_CLASSUNBIND, 0); + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct usbser_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); + return; + } +#endif + + /* Make sure that we are not already unbound */ + + if (priv != NULL) + { + /* Make sure that the endpoints have been unconfigured. If + * we were terminated gracefully, then the configuration should + * already have been reset. If not, then calling usbclass_resetconfig + * should cause the endpoints to immediately terminate all + * transfers and return the requests to us (with result == -ESHUTDOWN) + */ + + usbclass_resetconfig(priv); + up_mdelay(50); + + /* Free the interrupt IN endpoint */ + + if (priv->epintin) + { + DEV_FREEEP(dev, priv->epintin); + priv->epintin = NULL; + } + + /* Free the bulk IN endpoint */ + + if (priv->epbulkin) + { + DEV_FREEEP(dev, priv->epbulkin); + priv->epbulkin = NULL; + } + + /* Free the pre-allocated control request */ + + if (priv->ctrlreq != NULL) + { + usbclass_freereq(dev->ep0, priv->ctrlreq); + priv->ctrlreq = NULL; + } + + /* Free pre-allocated read requests (which should all have + * been returned to the free list at this time -- we don't check) + */ + + DEBUGASSERT(priv->nrdq == 0); + for (i = 0; i < CONFIG_USBSER_NRDREQS; i++) + { + reqcontainer = &priv->rdreqs[i]; + if (reqcontainer->req) + { + usbclass_freereq(priv->epbulkout, reqcontainer->req); + reqcontainer->req = NULL; + } + } + + /* Free the bulk OUT endpoint */ + + if (priv->epbulkout) + { + DEV_FREEEP(dev, priv->epbulkout); + priv->epbulkout = NULL; + } + + /* Free write requests that are not in use (which should be all + * of them + */ + + flags = irqsave(); + DEBUGASSERT(priv->nwrq == CONFIG_USBSER_NWRREQS); + while (!sq_empty(&priv->reqlist)) + { + reqcontainer = (struct usbser_req_s *)sq_remfirst(&priv->reqlist); + if (reqcontainer->req != NULL) + { + usbclass_freereq(priv->epbulkin, reqcontainer->req); + priv->nwrq--; /* Number of write requests queued */ + } + } + DEBUGASSERT(priv->nwrq == 0); + irqrestore(flags); + } + + /* Clear out all data in the circular buffer */ + + priv->serdev.xmit.head = 0; + priv->serdev.xmit.tail = 0; +} + +/**************************************************************************** + * Name: usbclass_setup + * + * Description: + * Invoked for ep0 control requests. This function probably executes + * in the context of an interrupt handler. + * + ****************************************************************************/ + +static int usbclass_setup(FAR struct usbdev_s *dev, const struct usb_ctrlreq_s *ctrl) +{ + FAR struct usbser_dev_s *priv; + FAR struct usbdev_req_s *ctrlreq; + uint16_t value; + uint16_t index; + uint16_t len; + int ret = -EOPNOTSUPP; + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0 || !ctrl) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -EIO; + } +#endif + + /* Extract reference to private data */ + + usbtrace(TRACE_CLASSSETUP, ctrl->req); + priv = (FAR struct usbser_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv || !priv->ctrlreq) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); + return -ENODEV; + } +#endif + ctrlreq = priv->ctrlreq; + + /* Extract the little-endian 16-bit values to host order */ + + value = GETUINT16(ctrl->value); + index = GETUINT16(ctrl->index); + len = GETUINT16(ctrl->len); + + uvdbg("type=%02x req=%02x value=%04x index=%04x len=%04x\n", + ctrl->type, ctrl->req, value, index, len); + + switch (ctrl->type & USB_REQ_TYPE_MASK) + { + /*********************************************************************** + * Standard Requests + ***********************************************************************/ + + case USB_REQ_TYPE_STANDARD: + { + switch (ctrl->req) + { + case USB_REQ_GETDESCRIPTOR: + { + /* The value field specifies the descriptor type in the MS byte and the + * descriptor index in the LS byte (order is little endian) + */ + + switch (ctrl->value[1]) + { + case USB_DESC_TYPE_DEVICE: + { + ret = USB_SIZEOF_DEVDESC; + memcpy(ctrlreq->buf, &g_devdesc, ret); + } + break; + +#ifdef CONFIG_USBDEV_DUALSPEED + case USB_DESC_TYPE_DEVICEQUALIFIER: + { + ret = USB_SIZEOF_QUALDESC; + memcpy(ctrlreq->buf, &g_qualdesc, ret); + } + break; + + case USB_DESC_TYPE_OTHERSPEEDCONFIG: +#endif /* CONFIG_USBDEV_DUALSPEED */ + + case USB_DESC_TYPE_CONFIG: + { +#ifdef CONFIG_USBDEV_DUALSPEED + ret = usbclass_mkcfgdesc(ctrlreq->buf, dev->speed, ctrl->req); +#else + ret = usbclass_mkcfgdesc(ctrlreq->buf); +#endif + } + break; + + case USB_DESC_TYPE_STRING: + { + /* index == language code. */ + + ret = usbclass_mkstrdesc(ctrl->value[0], (struct usb_strdesc_s *)ctrlreq->buf); + } + break; + + default: + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC), value); + } + break; + } + } + break; + + case USB_REQ_SETCONFIGURATION: + { + if (ctrl->type == 0) + { + ret = usbclass_setconfig(priv, value); + } + } + break; + + case USB_REQ_GETCONFIGURATION: + { + if (ctrl->type == USB_DIR_IN) + { + *(uint8_t*)ctrlreq->buf = priv->config; + ret = 1; + } + } + break; + + case USB_REQ_SETINTERFACE: + { + if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE) + { + if (priv->config == USBSER_CONFIGID && + index == USBSER_INTERFACEID && + value == USBSER_ALTINTERFACEID) + { + usbclass_resetconfig(priv); + usbclass_setconfig(priv, priv->config); + ret = 0; + } + } + } + break; + + case USB_REQ_GETINTERFACE: + { + if (ctrl->type == (USB_DIR_IN|USB_REQ_RECIPIENT_INTERFACE) && + priv->config == USBSER_CONFIGIDNONE) + { + if (index != USBSER_INTERFACEID) + { + ret = -EDOM; + } + else + { + *(uint8_t*) ctrlreq->buf = USBSER_ALTINTERFACEID; + ret = 1; + } + } + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ), ctrl->req); + break; + } + } + break; + + /*********************************************************************** + * PL2303 Vendor-Specific Requests + ***********************************************************************/ + + case PL2303_CONTROL_TYPE: + { + if ((ctrl->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE) + { + switch (ctrl->req) + { + case PL2303_SETLINEREQUEST: + { + memcpy(priv->linest, ctrlreq->buf, min(len, 7)); + ret = 0; + } + break; + + + case PL2303_GETLINEREQUEST: + { + memcpy(ctrlreq->buf, priv->linest, 7); + ret = 7; + } + break; + + case PL2303_SETCONTROLREQUEST: + case PL2303_BREAKREQUEST: + { + ret = 0; + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); + break; + } + } + } + break; + + case PL2303_RWREQUEST_TYPE: + { + if ((ctrl->type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE) + { + if (ctrl->req == PL2303_RWREQUEST) + { + if ((ctrl->type & USB_DIR_IN) != 0) + { + *(uint32_t*)ctrlreq->buf = 0xdeadbeef; + ret = 4; + } + else + { + ret = 0; + } + } + else + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), ctrl->type); + } + } + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE), ctrl->type); + break; + } + + /* Respond to the setup command if data was returned. On an error return + * value (ret < 0), the USB driver will stall. + */ + + if (ret >= 0) + { + ctrlreq->len = min(len, ret); + ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; + ret = EP_SUBMIT(dev->ep0, ctrlreq); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret); + ctrlreq->result = OK; + usbclass_ep0incomplete(dev->ep0, ctrlreq); + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbclass_disconnect + * + * Description: + * Invoked after all transfers have been stopped, when the host is + * disconnected. This function is probably called from the context of an + * interrupt handler. + * + ****************************************************************************/ + +static void usbclass_disconnect(FAR struct usbdev_s *dev) +{ + FAR struct usbser_dev_s *priv; + irqstate_t flags; + + usbtrace(TRACE_CLASSDISCONNECT, 0); + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct usbser_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); + return; + } +#endif + + /* Reset the configuration */ + + flags = irqsave(); + usbclass_resetconfig(priv); + + /* Clear out all data in the circular buffer */ + + priv->serdev.xmit.head = 0; + priv->serdev.xmit.tail = 0; + irqrestore(flags); + + /* Perform the soft connect function so that we will we can be + * re-enumerated. + */ + + DEV_CONNECT(dev); +} + +/**************************************************************************** + * Serial Device Methods + ****************************************************************************/ + +/**************************************************************************** + * Name: usbser_setup + * + * Description: + * This method is called the first time that the serial port is opened. + * + ****************************************************************************/ + +static int usbser_setup(FAR struct uart_dev_s *dev) +{ + FAR struct usbser_dev_s *priv; + + usbtrace(USBSER_CLASSAPI_SETUP, 0); + + /* Sanity check */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return -EIO; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct usbser_dev_s*)dev->priv; + + /* Check if we have been configured */ + + if (priv->config == USBSER_CONFIGIDNONE) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SETUPNOTCONNECTED), 0); + return -ENOTCONN; + } + + return OK; +} + +/**************************************************************************** + * Name: usbser_shutdown + * + * Description: + * This method is called when the serial port is closed. This operation + * is very simple for the USB serial backend because the serial driver + * has already assured that the TX data has full drained -- it calls + * usbser_txempty() until that function returns true before calling this + * function. + * + ****************************************************************************/ + +static void usbser_shutdown(FAR struct uart_dev_s *dev) +{ + usbtrace(USBSER_CLASSAPI_SHUTDOWN, 0); + + /* Sanity check */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + } +#endif +} + +/**************************************************************************** + * Name: usbser_attach + * + * Description: + * Does not apply to the USB serial class device + * + ****************************************************************************/ + +static int usbser_attach(FAR struct uart_dev_s *dev) +{ + usbtrace(USBSER_CLASSAPI_ATTACH, 0); + return OK; +} + +/**************************************************************************** + * Name: usbser_detach + * + * Description: +* Does not apply to the USB serial class device + * + ****************************************************************************/ + +static void usbser_detach(FAR struct uart_dev_s *dev) +{ + usbtrace(USBSER_CLASSAPI_DETACH, 0); +} + +/**************************************************************************** + * Name: usbser_rxint + * + * Description: + * Called by the serial driver to enable or disable RX interrupts. We, of + * course, have no RX interrupts but must behave consistently. This method + * is called under the conditions: + * + * 1. With enable==true when the port is opened (just after usbser_setup + * and usbser_attach are called called) + * 2. With enable==false while transferring data from the RX buffer + * 2. With enable==true while waiting for more incoming data + * 3. With enable==false when the port is closed (just before usbser_detach + * and usbser_shutdown are called). + * + ****************************************************************************/ + +static void usbser_rxint(FAR struct uart_dev_s *dev, bool enable) +{ + FAR struct usbser_dev_s *priv; + FAR uart_dev_t *serdev; + irqstate_t flags; + + usbtrace(USBSER_CLASSAPI_RXINT, (uint16_t)enable); + + /* Sanity check */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct usbser_dev_s*)dev->priv; + serdev = &priv->serdev; + + /* We need exclusive access to the RX buffer and private structure + * in the following. + */ + + flags = irqsave(); + if (enable) + { + /* RX "interrupts" are enabled. Is this a transition from disabled + * to enabled state? + */ + + if (!priv->rxenabled) + { + /* Yes. During the time that RX interrupts are disabled, the + * the serial driver will be extracting data from the circular + * buffer and modifying recv.tail. During this time, we + * should avoid modifying recv.head; When interrupts are restored, + * we can update the head pointer for all of the data that we + * put into cicular buffer while "interrupts" were disabled. + */ + + if (priv->rxhead != serdev->recv.head) + { + serdev->recv.head = priv->rxhead; + + /* Yes... signal the availability of new data */ + + uart_datareceived(serdev); + } + + /* RX "interrupts are no longer disabled */ + + priv->rxenabled = true; + } + } + + /* RX "interrupts" are disabled. Is this a transition from enabled + * to disabled state? + */ + + else if (priv->rxenabled) + { + /* Yes. During the time that RX interrupts are disabled, the + * the serial driver will be extracting data from the circular + * buffer and modifying recv.tail. During this time, we + * should avoid modifying recv.head; When interrupts are disabled, + * we use a shadow index and continue adding data to the circular + * buffer. + */ + + priv->rxhead = serdev->recv.head; + priv->rxenabled = false; + } + irqrestore(flags); +} + +/**************************************************************************** + * Name: usbser_txint + * + * Description: + * Called by the serial driver to enable or disable TX interrupts. We, of + * course, have no TX interrupts but must behave consistently. Initially, + * TX interrupts are disabled. This method is called under the conditions: + * + * 1. With enable==false while transferring data into the TX buffer + * 2. With enable==true when data may be taken from the buffer. + * 3. With enable==false when the TX buffer is empty + * + ****************************************************************************/ + +static void usbser_txint(FAR struct uart_dev_s *dev, bool enable) +{ + FAR struct usbser_dev_s *priv; + + usbtrace(USBSER_CLASSAPI_TXINT, (uint16_t)enable); + + /* Sanity checks */ + +#if CONFIG_DEBUG + if (!dev || !dev->priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return; + } +#endif + + /* Extract references to private data */ + + priv = (FAR struct usbser_dev_s*)dev->priv; + + /* If the new state is enabled and if there is data in the XMIT buffer, + * send the next packet now. + */ + + uvdbg("enable=%d head=%d tail=%d\n", + enable, priv->serdev.xmit.head, priv->serdev.xmit.tail); + + if (enable && priv->serdev.xmit.head != priv->serdev.xmit.tail) + { + usbclass_sndpacket(priv); + } +} + +/**************************************************************************** + * Name: usbser_txempty + * + * Description: + * Return true when all data has been sent. This is called from the + * serial driver when the driver is closed. It will call this API + * periodically until it reports true. NOTE that the serial driver takes all + * responsibility for flushing TX data through the hardware so we can be + * a bit sloppy about that. + * + ****************************************************************************/ + +static bool usbser_txempty(FAR struct uart_dev_s *dev) +{ + FAR struct usbser_dev_s *priv = (FAR struct usbser_dev_s*)dev->priv; + + usbtrace(USBSER_CLASSAPI_TXEMPTY, 0); + +#if CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); + return true; + } +#endif + + /* When all of the allocated write requests have been returned to the + * reqlist, then there is no longer any TX data in flight. + */ + + return priv->nwrq >= CONFIG_USBSER_NWRREQS; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbdev_serialinitialize + * + * Description: + * Register USB serial port (and USB serial console if so configured). + * + ****************************************************************************/ + +int usbdev_serialinitialize(int minor) +{ + FAR struct usbser_alloc_s *alloc; + FAR struct usbser_dev_s *priv; + FAR struct usbser_driver_s *drvr; + char devname[16]; + int ret; + + /* Allocate the structures needed */ + + alloc = (FAR struct usbser_alloc_s*)kmalloc(sizeof(struct usbser_alloc_s)); + if (!alloc) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0); + return -ENOMEM; + } + + /* Convenience pointers into the allocated blob */ + + priv = &alloc->dev; + drvr = &alloc->drvr; + + /* Initialize the USB serial driver structure */ + + memset(priv, 0, sizeof(struct usbser_dev_s)); + sq_init(&priv->reqlist); + + /* Fake line status */ + + priv->linest[0] = (115200) & 0xff; /* Baud=115200 */ + priv->linest[1] = (115200 >> 8) & 0xff; + priv->linest[2] = (115200 >> 16) & 0xff; + priv->linest[3] = (115200 >> 24) & 0xff; + priv->linest[4] = 0; /* One stop bit */ + priv->linest[5] = 0; /* No parity */ + priv->linest[6] = 8; /*8 data bits */ + + /* Initialize the serial driver sub-structure */ + + priv->serdev.recv.size = CONFIG_USBSER_RXBUFSIZE; + priv->serdev.recv.buffer = priv->rxbuffer; + priv->serdev.xmit.size = CONFIG_USBSER_TXBUFSIZE; + priv->serdev.xmit.buffer = priv->txbuffer; + priv->serdev.ops = &g_uartops; + priv->serdev.priv = priv; + + /* Initialize the USB class driver structure */ + +#ifdef CONFIG_USBDEV_DUALSPEED + drvr->drvr.speed = USB_SPEED_HIGH; +#else + drvr->drvr.speed = USB_SPEED_FULL; +#endif + drvr->drvr.ops = &g_driverops; + drvr->dev = priv; + + /* Register the USB serial class driver */ + + ret = usbdev_register(&drvr->drvr); + if (ret) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER), (uint16_t)-ret); + goto errout_with_alloc; + } + + /* Register the USB serial console */ + +#ifdef CONFIG_USBSER_CONSOLE + g_usbserialport.isconsole = true; + ret = uart_register("/dev/console", &pri->serdev); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONSOLEREGISTER), (uint16_t)-ret); + goto errout_with_class; + } +#endif + + /* Register the single port supported by this implementation */ + + sprintf(devname, "/dev/ttyUSB%d", minor); + ret = uart_register(devname, &priv->serdev); + if (ret) + { + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTREGISTER), (uint16_t)-ret); + goto errout_with_class; + } + return OK; + +errout_with_class: + usbdev_unregister(&drvr->drvr); +errout_with_alloc: + kfree(alloc); + return ret; +} diff --git a/nuttx/drivers/usbdev/usbdev_storage.c b/nuttx/drivers/usbdev/usbdev_storage.c new file mode 100644 index 000000000..360756b12 --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_storage.c @@ -0,0 +1,1651 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_storage.c + * + * Copyright (C) 2008-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Mass storage class device. Bulk-only with SCSI subclass. + * + * References: + * "Universal Serial Bus Mass Storage Class, Specification Overview," + * Revision 1.2, USB Implementer's Forum, June 23, 2003. + * + * "Universal Serial Bus Mass Storage Class, Bulk-Only Transport," + * Revision 1.0, USB Implementer's Forum, September 31, 1999. + * + * "SCSI Primary Commands - 3 (SPC-3)," American National Standard + * for Information Technology, May 4, 2005 + * + * "SCSI Primary Commands - 4 (SPC-4)," American National Standard + * for Information Technology, July 19, 2008 + * + * "SCSI Block Commands -2 (SBC-2)," American National Standard + * for Information Technology, November 13, 2004 + * + * "SCSI Multimedia Commands - 3 (MMC-3)," American National Standard + * for Information Technology, November 12, 2001 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <string.h> +#include <errno.h> +#include <queue.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/fs.h> +#include <nuttx/usb/usb.h> +#include <nuttx/usb/storage.h> +#include <nuttx/usb/usbdev.h> +#include <nuttx/usb/usbdev_trace.h> + +#include "usbdev_storage.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* The internal version of the class driver */ + +struct usbstrg_driver_s +{ + struct usbdevclass_driver_s drvr; + FAR struct usbstrg_dev_s *dev; +}; + +/* This is what is allocated */ + +struct usbstrg_alloc_s +{ + struct usbstrg_dev_s dev; + struct usbstrg_driver_s drvr; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Class Driver Support *****************************************************/ + +static void usbstrg_ep0incomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); +static struct usbdev_req_s *usbstrg_allocreq(FAR struct usbdev_ep_s *ep, + uint16_t len); +static void usbstrg_freereq(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); + +/* Class Driver Operations (most at interrupt level) ************************/ + +static int usbstrg_bind(FAR struct usbdev_s *dev, + FAR struct usbdevclass_driver_s *driver); +static void usbstrg_unbind(FAR struct usbdev_s *dev); +static int usbstrg_setup(FAR struct usbdev_s *dev, + FAR const struct usb_ctrlreq_s *ctrl); +static void usbstrg_disconnect(FAR struct usbdev_s *dev); + +/* Initialization/Uninitialization ******************************************/ + +static void usbstrg_lununinitialize(struct usbstrg_lun_s *lun); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Driver operations ********************************************************/ + +static struct usbdevclass_driverops_s g_driverops = +{ + usbstrg_bind, /* bind */ + usbstrg_unbind, /* unbind */ + usbstrg_setup, /* setup */ + usbstrg_disconnect, /* disconnect */ + NULL, /* suspend */ + NULL /* resume */ +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Class Driver Support + ****************************************************************************/ +/**************************************************************************** + * Name: usbstrg_ep0incomplete + * + * Description: + * Handle completion of EP0 control operations + * + ****************************************************************************/ + +static void usbstrg_ep0incomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req) +{ + if (req->result || req->xfrd != req->len) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_REQRESULT), + (uint16_t)-req->result); + } +} + +/**************************************************************************** + * Name: usbstrg_allocreq + * + * Description: + * Allocate a request instance along with its buffer + * + ****************************************************************************/ + +static struct usbdev_req_s *usbstrg_allocreq(FAR struct usbdev_ep_s *ep, + uint16_t len) +{ + FAR struct usbdev_req_s *req; + + req = EP_ALLOCREQ(ep); + if (req != NULL) + { + req->len = len; + req->buf = EP_ALLOCBUFFER(ep, len); + if (!req->buf) + { + EP_FREEREQ(ep, req); + req = NULL; + } + } + return req; +} + +/**************************************************************************** + * Name: usbstrg_freereq + * + * Description: + * Free a request instance along with its buffer + * + ****************************************************************************/ + +static void usbstrg_freereq(FAR struct usbdev_ep_s *ep, struct usbdev_req_s *req) +{ + if (ep != NULL && req != NULL) + { + if (req->buf != NULL) + { + EP_FREEBUFFER(ep, req->buf); + } + EP_FREEREQ(ep, req); + } +} + +/**************************************************************************** + * Class Driver Interfaces + ****************************************************************************/ +/**************************************************************************** + * Name: usbstrg_bind + * + * Description: + * Invoked when the driver is bound to a USB device driver + * + ****************************************************************************/ + +static int usbstrg_bind(FAR struct usbdev_s *dev, FAR struct usbdevclass_driver_s *driver) +{ + FAR struct usbstrg_dev_s *priv = ((struct usbstrg_driver_s*)driver)->dev; + FAR struct usbstrg_req_s *reqcontainer; + irqstate_t flags; + int ret = OK; + int i; + + usbtrace(TRACE_CLASSBIND, 0); + + /* Bind the structures */ + + priv->usbdev = dev; + dev->ep0->priv = priv; + + /* The configured EP0 size should match the reported EP0 size. We could + * easily adapt to the reported EP0 size, but then we could not use the + * const, canned descriptors. + */ + + DEBUGASSERT(CONFIG_USBSTRG_EP0MAXPACKET == dev->ep0->maxpacket); + + /* Preallocate control request */ + + priv->ctrlreq = usbstrg_allocreq(dev->ep0, USBSTRG_MXDESCLEN); + if (priv->ctrlreq == NULL) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_ALLOCCTRLREQ), 0); + ret = -ENOMEM; + goto errout; + } + priv->ctrlreq->callback = usbstrg_ep0incomplete; + + /* Pre-allocate all endpoints... the endpoints will not be functional + * until the SET CONFIGURATION request is processed in usbstrg_setconfig. + * This is done here because there may be calls to kmalloc and the SET + * CONFIGURATION processing probably occurrs within interrupt handling + * logic where kmalloc calls will fail. + */ + + /* Pre-allocate the IN bulk endpoint */ + + priv->epbulkin = DEV_ALLOCEP(dev, USBSTRG_EPINBULK_ADDR, true, USB_EP_ATTR_XFER_BULK); + if (!priv->epbulkin) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EPBULKINALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epbulkin->priv = priv; + + /* Pre-allocate the OUT bulk endpoint */ + + priv->epbulkout = DEV_ALLOCEP(dev, USBSTRG_EPOUTBULK_ADDR, false, USB_EP_ATTR_XFER_BULK); + if (!priv->epbulkout) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EPBULKOUTALLOCFAIL), 0); + ret = -ENODEV; + goto errout; + } + priv->epbulkout->priv = priv; + + /* Pre-allocate read requests */ + + for (i = 0; i < CONFIG_USBSTRG_NRDREQS; i++) + { + reqcontainer = &priv->rdreqs[i]; + reqcontainer->req = usbstrg_allocreq(priv->epbulkout, CONFIG_USBSTRG_BULKOUTREQLEN); + if (reqcontainer->req == NULL) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_RDALLOCREQ), + (uint16_t)-ret); + ret = -ENOMEM; + goto errout; + } + reqcontainer->req->priv = reqcontainer; + reqcontainer->req->callback = usbstrg_rdcomplete; + } + + /* Pre-allocate write request containers and put in a free list */ + + for (i = 0; i < CONFIG_USBSTRG_NWRREQS; i++) + { + reqcontainer = &priv->wrreqs[i]; + reqcontainer->req = usbstrg_allocreq(priv->epbulkin, CONFIG_USBSTRG_BULKINREQLEN); + if (reqcontainer->req == NULL) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRALLOCREQ), + (uint16_t)-ret); + ret = -ENOMEM; + goto errout; + } + reqcontainer->req->priv = reqcontainer; + reqcontainer->req->callback = usbstrg_wrcomplete; + + flags = irqsave(); + sq_addlast((sq_entry_t*)reqcontainer, &priv->wrreqlist); + irqrestore(flags); + } + + /* Report if we are selfpowered */ + +#ifdef CONFIG_USBDEV_SELFPOWERED + DEV_SETSELFPOWERED(dev); +#endif + + /* And pull-up the data line for the soft connect function */ + + DEV_CONNECT(dev); + return OK; + +errout: + usbstrg_unbind(dev); + return ret; +} + +/**************************************************************************** + * Name: usbstrg_unbind + * + * Description: + * Invoked when the driver is unbound from a USB device driver + * + ****************************************************************************/ + +static void usbstrg_unbind(FAR struct usbdev_s *dev) +{ + FAR struct usbstrg_dev_s *priv; + FAR struct usbstrg_req_s *reqcontainer; + irqstate_t flags; + int i; + + usbtrace(TRACE_CLASSUNBIND, 0); + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_UNBINDINVALIDARGS), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct usbstrg_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EP0NOTBOUND1), 0); + return; + } +#endif + + /* The worker thread should have already been stopped by the + * driver un-initialize logic. + */ + + DEBUGASSERT(priv->thstate == USBSTRG_STATE_TERMINATED); + + /* Make sure that we are not already unbound */ + + if (priv != NULL) + { + /* Make sure that the endpoints have been unconfigured. If + * we were terminated gracefully, then the configuration should + * already have been reset. If not, then calling usbstrg_resetconfig + * should cause the endpoints to immediately terminate all + * transfers and return the requests to us (with result == -ESHUTDOWN) + */ + + usbstrg_resetconfig(priv); + up_mdelay(50); + + /* Free the pre-allocated control request */ + + if (priv->ctrlreq != NULL) + { + usbstrg_freereq(dev->ep0, priv->ctrlreq); + priv->ctrlreq = NULL; + } + + /* Free pre-allocated read requests (which should all have + * been returned to the free list at this time -- we don't check) + */ + + for (i = 0; i < CONFIG_USBSTRG_NRDREQS; i++) + { + reqcontainer = &priv->rdreqs[i]; + if (reqcontainer->req) + { + usbstrg_freereq(priv->epbulkout, reqcontainer->req); + reqcontainer->req = NULL; + } + } + + /* Free the bulk OUT endpoint */ + + if (priv->epbulkout) + { + DEV_FREEEP(dev, priv->epbulkout); + priv->epbulkout = NULL; + } + + /* Free write requests that are not in use (which should be all + * of them + */ + + flags = irqsave(); + while (!sq_empty(&priv->wrreqlist)) + { + reqcontainer = (struct usbstrg_req_s *)sq_remfirst(&priv->wrreqlist); + if (reqcontainer->req != NULL) + { + usbstrg_freereq(priv->epbulkin, reqcontainer->req); + } + } + + /* Free the bulk IN endpoint */ + + if (priv->epbulkin) + { + DEV_FREEEP(dev, priv->epbulkin); + priv->epbulkin = NULL; + } + + irqrestore(flags); + } +} + +/**************************************************************************** + * Name: usbstrg_setup + * + * Description: + * Invoked for ep0 control requests. This function probably executes + * in the context of an interrupt handler. + * + ****************************************************************************/ + +static int usbstrg_setup(FAR struct usbdev_s *dev, + FAR const struct usb_ctrlreq_s *ctrl) +{ + FAR struct usbstrg_dev_s *priv; + FAR struct usbdev_req_s *ctrlreq; + uint16_t value; + uint16_t index; + uint16_t len; + int ret = -EOPNOTSUPP; + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0 || !ctrl) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SETUPINVALIDARGS), 0); + return -EIO; + } +#endif + + /* Extract reference to private data */ + + usbtrace(TRACE_CLASSSETUP, ctrl->req); + priv = (FAR struct usbstrg_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv || !priv->ctrlreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EP0NOTBOUND2), 0); + return -ENODEV; + } +#endif + ctrlreq = priv->ctrlreq; + + /* Extract the little-endian 16-bit values to host order */ + + value = GETUINT16(ctrl->value); + index = GETUINT16(ctrl->index); + len = GETUINT16(ctrl->len); + + uvdbg("type=%02x req=%02x value=%04x index=%04x len=%04x\n", + ctrl->type, ctrl->req, value, index, len); + + if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD) + { + /********************************************************************** + * Standard Requests + **********************************************************************/ + + switch (ctrl->req) + { + case USB_REQ_GETDESCRIPTOR: + { + /* The value field specifies the descriptor type in the MS byte and the + * descriptor index in the LS byte (order is little endian) + */ + + switch (ctrl->value[1]) + { + case USB_DESC_TYPE_DEVICE: + { + ret = USB_SIZEOF_DEVDESC; + memcpy(ctrlreq->buf, usbstrg_getdevdesc(), ret); + } + break; + +#ifdef CONFIG_USBDEV_DUALSPEED + case USB_DESC_TYPE_DEVICEQUALIFIER: + { + ret = USB_SIZEOF_QUALDESC; + memcpy(ctrlreq->buf, usbstrg_getqualdesc(), ret); + } + break; + + case USB_DESC_TYPE_OTHERSPEEDCONFIG: +#endif /* CONFIG_USBDEV_DUALSPEED */ + + case USB_DESC_TYPE_CONFIG: + { +#ifdef CONFIG_USBDEV_DUALSPEED + ret = usbstrg_mkcfgdesc(ctrlreq->buf, dev->speed, ctrl->value[1]); +#else + ret = usbstrg_mkcfgdesc(ctrlreq->buf); +#endif + } + break; + + case USB_DESC_TYPE_STRING: + { + /* index == language code. */ + + ret = usbstrg_mkstrdesc(ctrl->value[0], (struct usb_strdesc_s *)ctrlreq->buf); + } + break; + + default: + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_GETUNKNOWNDESC), value); + } + break; + } + } + break; + + case USB_REQ_SETCONFIGURATION: + { + if (ctrl->type == 0) + { + /* Signal the worker thread to instantiate the new configuration */ + + priv->theventset |= USBSTRG_EVENT_CFGCHANGE; + priv->thvalue = value; + pthread_cond_signal(&priv->cond); + + /* Return here... the response will be provided later by the + * worker thread. + */ + + return OK; + } + } + break; + + case USB_REQ_GETCONFIGURATION: + { + if (ctrl->type == USB_DIR_IN) + { + ctrlreq->buf[0] = priv->config; + ret = 1; + } + } + break; + + case USB_REQ_SETINTERFACE: + { + if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE) + { + if (priv->config == USBSTRG_CONFIGID && + index == USBSTRG_INTERFACEID && + value == USBSTRG_ALTINTERFACEID) + { + /* Signal to instantiate the interface change */ + + priv->theventset |= USBSTRG_EVENT_IFCHANGE; + pthread_cond_signal(&priv->cond); + + /* Return here... the response will be provided later by the + * worker thread. + */ + + return OK; + } + } + } + break; + + case USB_REQ_GETINTERFACE: + { + if (ctrl->type == (USB_DIR_IN|USB_REQ_RECIPIENT_INTERFACE) && + priv->config == USBSTRG_CONFIGIDNONE) + { + if (index != USBSTRG_INTERFACEID) + { + ret = -EDOM; + } + else + { + ctrlreq->buf[0] = USBSTRG_ALTINTERFACEID; + ret = 1; + } + } + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_UNSUPPORTEDSTDREQ), ctrl->req); + break; + } + } + else + { + /********************************************************************** + * Bulk-Only Mass Storage Class Requests + **********************************************************************/ + + /* Verify that we are configured */ + + if (!priv->config) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_NOTCONFIGURED), 0); + return ret; + } + + switch (ctrl->req) + { + case USBSTRG_REQ_MSRESET: /* Reset mass storage device and interface */ + { + if (ctrl->type == USBSTRG_TYPE_SETUPOUT && value == 0 && len == 0) + { + /* Only one interface is supported */ + + if (index != USBSTRG_CONFIGID) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_MSRESETNDX), index); + ret = -EDOM; + } + else + { + /* Signal to stop the current operation and reinitialize state */ + + priv->theventset |= USBSTRG_EVENT_RESET; + pthread_cond_signal(&priv->cond); + + /* Return here... the response will be provided later by the + * worker thread. + */ + + return OK; + } + } + } + break; + + case USBSTRG_REQ_GETMAXLUN: /* Return number LUNs supported */ + { + if (ctrl->type == USBSTRG_TYPE_SETUPIN && value == 0) + { + /* Only one interface is supported */ + + if (index != USBSTRG_INTERFACEID) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_GETMAXLUNNDX), index); + ret = -EDOM; + } + else + { + ctrlreq->buf[0] = priv->nluns - 1; + ret = 1; + } + } + } + break; + + default: + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_BADREQUEST), index); + break; + } + } + + /* Respond to the setup command if data was returned. On an error return + * value (ret < 0), the USB driver will stall EP0. + */ + + if (ret >= 0) + { + ctrlreq->len = min(len, ret); + ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; + ret = EP_SUBMIT(dev->ep0, ctrlreq); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EPRESPQ), (uint16_t)-ret); +#if 0 /* Not necessary */ + ctrlreq->result = OK; + usbstrg_ep0incomplete(dev->ep0, ctrlreq); +#endif + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbstrg_disconnect + * + * Description: + * Invoked after all transfers have been stopped, when the host is + * disconnected. This function is probably called from the context of an + * interrupt handler. + * + ****************************************************************************/ + +static void usbstrg_disconnect(FAR struct usbdev_s *dev) +{ + struct usbstrg_dev_s *priv; + irqstate_t flags; + + usbtrace(TRACE_CLASSDISCONNECT, 0); + +#ifdef CONFIG_DEBUG + if (!dev || !dev->ep0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_DISCONNECTINVALIDARGS), 0); + return; + } +#endif + + /* Extract reference to private data */ + + priv = (FAR struct usbstrg_dev_s *)dev->ep0->priv; + +#ifdef CONFIG_DEBUG + if (!priv) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EP0NOTBOUND3), 0); + return; + } +#endif + + /* Reset the configuration */ + + flags = irqsave(); + usbstrg_resetconfig(priv); + + /* Signal the worker thread */ + + priv->theventset |= USBSTRG_EVENT_DISCONNECT; + pthread_cond_signal(&priv->cond); + irqrestore(flags); + + /* Perform the soft connect function so that we will we can be + * re-enumerated. + */ + + DEV_CONNECT(dev); +} + +/**************************************************************************** + * Initialization/Un-Initialization + ****************************************************************************/ +/**************************************************************************** + * Name: usbstrg_lununinitialize + ****************************************************************************/ + +static void usbstrg_lununinitialize(struct usbstrg_lun_s *lun) +{ + /* Has a block driver has been bound to the LUN? */ + + if (lun->inode) + { + /* Close the block driver */ + + (void)close_blockdriver(lun->inode); + } + + memset(lun, 0, sizeof(struct usbstrg_lun_s *)); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +/**************************************************************************** + * Internal Interfaces + ****************************************************************************/ + +/**************************************************************************** + * Name: usbstrg_setconfig + * + * Description: + * Set the device configuration by allocating and configuring endpoints and + * by allocating and queuing read and write requests. + * + ****************************************************************************/ + +int usbstrg_setconfig(FAR struct usbstrg_dev_s *priv, uint8_t config) +{ + FAR struct usbstrg_req_s *privreq; + FAR struct usbdev_req_s *req; +#ifdef CONFIG_USBDEV_DUALSPEED + FAR const struct usb_epdesc_s *epdesc; + bool hispeed = (priv->usbdev->speed == USB_SPEED_HIGH); + uint16_t bulkmxpacket; +#endif + int i; + int ret = 0; + +#if CONFIG_DEBUG + if (priv == NULL) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_SETCONFIGINVALIDARGS), 0); + return -EIO; + } +#endif + + if (config == priv->config) + { + /* Already configured -- Do nothing */ + + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_ALREADYCONFIGURED), 0); + return OK; + } + + /* Discard the previous configuration data */ + + usbstrg_resetconfig(priv); + + /* Was this a request to simply discard the current configuration? */ + + if (config == USBSTRG_CONFIGIDNONE) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CONFIGNONE), 0); + return OK; + } + + /* We only accept one configuration */ + + if (config != USBSTRG_CONFIGID) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_CONFIGIDBAD), 0); + return -EINVAL; + } + + /* Configure the IN bulk endpoint */ + +#ifdef CONFIG_USBDEV_DUALSPEED + bulkmxpacket = USBSTRG_BULKMAXPACKET(hispeed); + epdesc = USBSTRG_EPBULKINDESC(hispeed); + ret = EP_CONFIGURE(priv->epbulkin, epdesc, false); +#else + ret = EP_CONFIGURE(priv->epbulkin, + usbstrg_getepdesc(USBSTRG_EPFSBULKIN), false); +#endif + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EPBULKINCONFIGFAIL), 0); + goto errout; + } + + priv->epbulkin->priv = priv; + + /* Configure the OUT bulk endpoint */ + +#ifdef CONFIG_USBDEV_DUALSPEED + epdesc = USBSTRG_EPBULKOUTDESC(hispeed); + ret = EP_CONFIGURE(priv->epbulkout, epdesc, true); +#else + ret = EP_CONFIGURE(priv->epbulkout, + usbstrg_getepdesc(USBSTRG_EPFSBULKOUT), true); +#endif + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EPBULKOUTCONFIGFAIL), 0); + goto errout; + } + + priv->epbulkout->priv = priv; + + /* Queue read requests in the bulk OUT endpoint */ + + for (i = 0; i < CONFIG_USBSTRG_NRDREQS; i++) + { + privreq = &priv->rdreqs[i]; + req = privreq->req; + req->len = CONFIG_USBSTRG_BULKOUTREQLEN; + req->priv = privreq; + req->callback = usbstrg_rdcomplete; + ret = EP_SUBMIT(priv->epbulkout, req); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_RDSUBMIT), (uint16_t)-ret); + goto errout; + } + } + + priv->config = config; + return OK; + +errout: + usbstrg_resetconfig(priv); + return ret; +} + +/**************************************************************************** + * Name: usbstrg_resetconfig + * + * Description: + * Mark the device as not configured and disable all endpoints. + * + ****************************************************************************/ + +void usbstrg_resetconfig(FAR struct usbstrg_dev_s *priv) +{ + /* Are we configured? */ + + if (priv->config != USBSTRG_CONFIGIDNONE) + { + /* Yes.. but not anymore */ + + priv->config = USBSTRG_CONFIGIDNONE; + + /* Disable endpoints. This should force completion of all pending + * transfers. + */ + + EP_DISABLE(priv->epbulkin); + EP_DISABLE(priv->epbulkout); + } +} + +/**************************************************************************** + * Name: usbstrg_wrcomplete + * + * Description: + * Handle completion of write request. This function probably executes + * in the context of an interrupt handler. + * + ****************************************************************************/ + +void usbstrg_wrcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) +{ + FAR struct usbstrg_dev_s *priv; + FAR struct usbstrg_req_s *privreq; + irqstate_t flags; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!ep || !ep->priv || !req || !req->priv) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRCOMPLETEINVALIDARGS), 0); + return; + } +#endif + + /* Extract references to private data */ + + priv = (FAR struct usbstrg_dev_s*)ep->priv; + privreq = (FAR struct usbstrg_req_s *)req->priv; + + /* Return the write request to the free list */ + + flags = irqsave(); + sq_addlast((sq_entry_t*)privreq, &priv->wrreqlist); + irqrestore(flags); + + /* Process the received data unless this is some unusual condition */ + + switch (req->result) + { + case OK: /* Normal completion */ + usbtrace(TRACE_CLASSWRCOMPLETE, req->xfrd); + break; + + case -ESHUTDOWN: /* Disconnection */ + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRSHUTDOWN), 0); + break; + + default: /* Some other error occurred */ + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_WRUNEXPECTED), + (uint16_t)-req->result); + break; + }; + + /* Inform the worker thread that a write request has been returned */ + + priv->theventset |= USBSTRG_EVENT_WRCOMPLETE; + pthread_cond_signal(&priv->cond); +} + +/**************************************************************************** + * Name: usbstrg_rdcomplete + * + * Description: + * Handle completion of read request on the bulk OUT endpoint. This + * is handled like the receipt of serial data on the "UART" + * + ****************************************************************************/ + +void usbstrg_rdcomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) +{ + FAR struct usbstrg_dev_s *priv; + FAR struct usbstrg_req_s *privreq; + irqstate_t flags; + int ret; + + /* Sanity check */ + +#ifdef CONFIG_DEBUG + if (!ep || !ep->priv || !req || !req->priv) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_RDCOMPLETEINVALIDARGS), 0); + return; + } +#endif + + /* Extract references to private data */ + + priv = (FAR struct usbstrg_dev_s*)ep->priv; + privreq = (FAR struct usbstrg_req_s *)req->priv; + + /* Process the received data unless this is some unusual condition */ + + switch (req->result) + { + case 0: /* Normal completion */ + { + usbtrace(TRACE_CLASSRDCOMPLETE, req->xfrd); + + /* Add the filled read request from the rdreqlist */ + + flags = irqsave(); + sq_addlast((sq_entry_t*)privreq, &priv->rdreqlist); + irqrestore(flags); + + /* Signal the worker thread that there is received data to be processed */ + + priv->theventset |= USBSTRG_EVENT_RDCOMPLETE; + pthread_cond_signal(&priv->cond); + } + break; + + case -ESHUTDOWN: /* Disconnection */ + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_RDSHUTDOWN), 0); + + /* Drop the read request... it will be cleaned up later */ + } + break; + + default: /* Some other error occurred */ + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_RDUNEXPECTED), + (uint16_t)-req->result); + + /* Return the read request to the bulk out endpoint for re-filling */ + + req = privreq->req; + req->priv = privreq; + req->callback = usbstrg_rdcomplete; + + ret = EP_SUBMIT(priv->epbulkout, req); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_RDCOMPLETERDSUBMIT), + (uint16_t)-ret); + } + } + break; + } +} + +/**************************************************************************** + * Name: usbstrg_deferredresponse + * + * Description: + * Some EP0 setup request cannot be responded to immediately becuase they + * require some asynchronous action from the SCSI worker thread. This + * function is provided for the SCSI thread to make that deferred response. + * The specific requests that require this deferred response are: + * + * 1. USB_REQ_SETCONFIGURATION, + * 2. USB_REQ_SETINTERFACE, or + * 3. USBSTRG_REQ_MSRESET + * + * In all cases, the success reponse is a zero-length packet; the failure + * response is an EP0 stall. + * + * Input parameters: + * priv - Private state structure for this USB storage instance + * stall - true is the action failed and a stall is required + * + ****************************************************************************/ + +void usbstrg_deferredresponse(FAR struct usbstrg_dev_s *priv, bool failed) +{ + FAR struct usbdev_s *dev; + FAR struct usbdev_req_s *ctrlreq; + int ret; + +#ifdef CONFIG_DEBUG + if (!priv || !priv->usbdev || !priv->ctrlreq) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_DEFERREDRESPINVALIDARGS), 0); + return; + } +#endif + + dev = priv->usbdev; + ctrlreq = priv->ctrlreq; + + /* If no error occurs, respond to the deferred setup command with a null + * packet. + */ + + if (!failed) + { + ctrlreq->len = 0; + ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; + ret = EP_SUBMIT(dev->ep0, ctrlreq); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_DEFERREDRESPSUBMIT), + (uint16_t)-ret); +#if 0 /* Not necessary */ + ctrlreq->result = OK; + usbstrg_ep0incomplete(dev->ep0, ctrlreq); +#endif + } + } + else + { + /* On a failure, the USB driver will stall. */ + + usbtrace(TRACE_DEVERROR(USBSTRG_TRACEERR_DEFERREDRESPSTALLED), 0); + EP_STALL(dev->ep0); + } +} + +/**************************************************************************** + * User Interfaces + ****************************************************************************/ +/**************************************************************************** + * Name: usbstrg_configure + * + * Description: + * One-time initialization of the USB storage driver. The initialization + * sequence is as follows: + * + * 1. Call usbstrg_configure to perform one-time initialization specifying + * the number of luns. + * 2. Call usbstrg_bindlun to configure each supported LUN + * 3. Call usbstrg_exportluns when all LUNs are configured + * + * Input Parameters: + * nluns - the number of LUNs that will be registered + * handle - Location to return a handle that is used in other API calls. + * + * Returned Value: + * 0 on success; a negated errno on failure + * + ****************************************************************************/ + +int usbstrg_configure(unsigned int nluns, void **handle) +{ + FAR struct usbstrg_alloc_s *alloc; + FAR struct usbstrg_dev_s *priv; + FAR struct usbstrg_driver_s *drvr; + int ret; + +#ifdef CONFIG_DEBUG + if (nluns > 15) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_TOOMANYLUNS), 0); + return -EDOM; + } +#endif + + /* Allocate the structures needed */ + + alloc = (FAR struct usbstrg_alloc_s*)kmalloc(sizeof(struct usbstrg_alloc_s)); + if (!alloc) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_ALLOCDEVSTRUCT), 0); + return -ENOMEM; + } + + /* Initialize the USB storage driver structure */ + + priv = &alloc->dev; + memset(priv, 0, sizeof(struct usbstrg_dev_s)); + + pthread_mutex_init(&priv->mutex, NULL); + pthread_cond_init(&priv->cond, NULL); + sq_init(&priv->wrreqlist); + + priv->nluns = nluns; + + /* Allocate the LUN table */ + + priv->luntab = (struct usbstrg_lun_s*)kmalloc(priv->nluns*sizeof(struct usbstrg_lun_s)); + if (!priv->luntab) + { + ret = -ENOMEM; + goto errout; + } + memset(priv->luntab, 0, priv->nluns * sizeof(struct usbstrg_lun_s)); + + /* Initialize the USB class driver structure */ + + drvr = &alloc->drvr; +#ifdef CONFIG_USBDEV_DUALSPEED + drvr->drvr.speed = USB_SPEED_HIGH; +#else + drvr->drvr.speed = USB_SPEED_FULL; +#endif + drvr->drvr.ops = &g_driverops; + drvr->dev = priv; + + /* Return the handle and success */ + + *handle = (FAR void*)alloc; + return OK; + +errout: + usbstrg_uninitialize(alloc); + return ret; +} + +/**************************************************************************** + * Name: usbstrg_bindlun + * + * Description: + * Bind the block driver specified by drvrpath to a USB storage LUN. + * + * Input Parameters: + * handle - The handle returned by a previous call to usbstrg_configure(). + * drvrpath - the full path to the block driver + * startsector - A sector offset into the block driver to the start of the + * partition on drvrpath (0 if no partitions) + * nsectors - The number of sectors in the partition (if 0, all sectors + * to the end of the media will be exported). + * lunno - the LUN to bind to + * + * Returned Value: + * 0 on success; a negated errno on failure. + * + ****************************************************************************/ + +int usbstrg_bindlun(FAR void *handle, FAR const char *drvrpath, + unsigned int lunno, off_t startsector, size_t nsectors, + bool readonly) +{ + FAR struct usbstrg_alloc_s *alloc = (FAR struct usbstrg_alloc_s *)handle; + FAR struct usbstrg_dev_s *priv; + FAR struct usbstrg_lun_s *lun; + FAR struct inode *inode; + struct geometry geo; + int ret; + +#ifdef CONFIG_DEBUG + if (!alloc || !drvrpath || startsector < 0 || nsectors < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_BINLUNINVALIDARGS1), 0); + return -EINVAL; + } +#endif + + priv = &alloc->dev; + +#ifdef CONFIG_DEBUG + if (!priv->luntab) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INTERNALCONFUSION1), 0); + return -EIO; + } + + if (lunno > priv->nluns) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_BINDLUNINVALIDARGS2), 0); + return -EINVAL; + } +#endif + + lun = &priv->luntab[lunno]; + +#ifdef CONFIG_DEBUG + if (lun->inode != NULL) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_LUNALREADYBOUND), 0); + return -EBUSY; + } +#endif + + /* Open the block driver */ + + ret = open_blockdriver(drvrpath, 0, &inode); + if (ret < 0) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_BLKDRVEOPEN), 0); + return ret; + } + + /* Get the drive geometry */ + + if (!inode || !inode->u.i_bops || !inode->u.i_bops->geometry || + inode->u.i_bops->geometry(inode, &geo) != OK || !geo.geo_available) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_NOGEOMETRY), 0); + return -ENODEV; + } + + /* Verify that the partition parameters are valid */ + + if (startsector >= geo.geo_nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_BINDLUNINVALIDARGS3), 0); + return -EDOM; + } + else if (nsectors == 0) + { + nsectors = geo.geo_nsectors - startsector; + } + else if (startsector + nsectors >= geo.geo_nsectors) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_BINDLUNINVALIDARGS4), 0); + return -EDOM; + } + + /* Initialize the LUN structure */ + + memset(lun, 0, sizeof(struct usbstrg_lun_s *)); + + /* Allocate an I/O buffer big enough to hold one hardware sector. SCSI commands + * are processed one at a time so all LUNs may share a single I/O buffer. The + * I/O buffer will be allocated so that is it as large as the largest block + * device sector size + */ + + if (!priv->iobuffer) + { + priv->iobuffer = (uint8_t*)kmalloc(geo.geo_sectorsize); + if (!priv->iobuffer) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_ALLOCIOBUFFER), geo.geo_sectorsize); + return -ENOMEM; + } + priv->iosize = geo.geo_sectorsize; + } + else if (priv->iosize < geo.geo_sectorsize) + { + void *tmp; + tmp = (uint8_t*)realloc(priv->iobuffer, geo.geo_sectorsize); + if (!tmp) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_REALLOCIOBUFFER), geo.geo_sectorsize); + return -ENOMEM; + } + + priv->iobuffer = (uint8_t*)tmp; + priv->iosize = geo.geo_sectorsize; + } + + lun->inode = inode; + lun->startsector = startsector; + lun->nsectors = nsectors; + lun->sectorsize = geo.geo_sectorsize; + + /* If the driver does not support the write method, then this is read-only */ + + if (!inode->u.i_bops->write) + { + lun->readonly = true; + } + return OK; +} + +/**************************************************************************** + * Name: usbstrg_unbindlun + * + * Description: + * Un-bind the block driver for the specified LUN + * + * Input Parameters: + * handle - The handle returned by a previous call to usbstrg_configure(). + * lun - the LUN to unbind from + * + * Returned Value: + * 0 on success; a negated errno on failure. + * + ****************************************************************************/ + +int usbstrg_unbindlun(FAR void *handle, unsigned int lunno) +{ + FAR struct usbstrg_alloc_s *alloc = (FAR struct usbstrg_alloc_s *)handle; + FAR struct usbstrg_dev_s *priv; + FAR struct usbstrg_lun_s *lun; + int ret; + +#ifdef CONFIG_DEBUG + if (!alloc) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_UNBINDLUNINVALIDARGS1), 0); + return -EINVAL; + } +#endif + + priv = &alloc->dev; + +#ifdef CONFIG_DEBUG + if (!priv->luntab) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_INTERNALCONFUSION2), 0); + return -EIO; + } + + if (lunno > priv->nluns) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_UNBINDLUNINVALIDARGS2), 0); + return -EINVAL; + } +#endif + + lun = &priv->luntab[lunno]; + pthread_mutex_lock(&priv->mutex); + +#ifdef CONFIG_DEBUG + if (lun->inode == NULL) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_LUNNOTBOUND), 0); + ret = -EBUSY; + } + else +#endif + { + /* Close the block driver */ + + usbstrg_lununinitialize(lun); + ret = OK; + } + + pthread_mutex_unlock(&priv->mutex); + return ret; +} + +/**************************************************************************** + * Name: usbstrg_exportluns + * + * Description: + * After all of the LUNs have been bound, this function may be called + * in order to export those LUNs in the USB storage device. + * + * Input Parameters: + * handle - The handle returned by a previous call to usbstrg_configure(). + * + * Returned Value: + * 0 on success; a negated errno on failure + * + ****************************************************************************/ + +int usbstrg_exportluns(FAR void *handle) +{ + FAR struct usbstrg_alloc_s *alloc = (FAR struct usbstrg_alloc_s *)handle; + FAR struct usbstrg_dev_s *priv; + FAR struct usbstrg_driver_s *drvr; + irqstate_t flags; +#ifdef SDCC + pthread_attr_t attr; +#endif + int ret; + +#ifdef CONFIG_DEBUG + if (!alloc) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_EXPORTLUNSINVALIDARGS), 0); + return -ENXIO; + } +#endif + + priv = &alloc->dev; + drvr = &alloc->drvr; + + /* Start the worker thread */ + + pthread_mutex_lock(&priv->mutex); + priv->thstate = USBSTRG_STATE_NOTSTARTED; + priv->theventset = USBSTRG_EVENT_NOEVENTS; + +#ifdef SDCC + (void)pthread_attr_init(&attr); + ret = pthread_create(&priv->thread, &attr, usbstrg_workerthread, (pthread_addr_t)priv); +#else + ret = pthread_create(&priv->thread, NULL, usbstrg_workerthread, (pthread_addr_t)priv); +#endif + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_THREADCREATE), (uint16_t)-ret); + goto errout_with_mutex; + } + + /* Register the USB storage class driver */ + + ret = usbdev_register(&drvr->drvr); + if (ret != OK) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_DEVREGISTER), (uint16_t)-ret); + goto errout_with_mutex; + } + + /* Signal to start the thread */ + + flags = irqsave(); + priv->theventset |= USBSTRG_EVENT_READY; + pthread_cond_signal(&priv->cond); + irqrestore(flags); + +errout_with_mutex: + pthread_mutex_unlock(&priv->mutex); + return ret; +} + +/**************************************************************************** + * Name: usbstrg_uninitialize + * + * Description: + * Un-initialize the USB storage class driver + * + * Input Parameters: + * handle - The handle returned by a previous call to usbstrg_configure(). + * + * Returned Value: + * None + * + ****************************************************************************/ + +void usbstrg_uninitialize(FAR void *handle) +{ + FAR struct usbstrg_alloc_s *alloc = (FAR struct usbstrg_alloc_s *)handle; + FAR struct usbstrg_dev_s *priv; + irqstate_t flags; +#ifdef SDCC + pthread_addr_t result1, result2; + pthread_attr_t attr; +#endif + void *value; + int ret; + int i; + +#ifdef CONFIG_DEBUG + if (!handle) + { + usbtrace(TRACE_CLSERROR(USBSTRG_TRACEERR_UNINITIALIZEINVALIDARGS), 0); + return; + } +#endif + priv = &alloc->dev; + + /* If the thread hasn't already exitted, tell it to exit now */ + + if (priv->thstate != USBSTRG_STATE_NOTSTARTED) + { + /* The thread was started.. Is it still running? */ + + pthread_mutex_lock(&priv->mutex); + if (priv->thstate != USBSTRG_STATE_TERMINATED) + { + /* Yes.. Ask the thread to stop */ + + flags = irqsave(); + priv->theventset |= USBSTRG_EVENT_TERMINATEREQUEST; + pthread_cond_signal(&priv->cond); + irqrestore(flags); + } + pthread_mutex_unlock(&priv->mutex); + + /* Wait for the thread to exit. This is necessary even if the + * thread has already exitted in order to collect the join + * garbage + */ + + ret = pthread_join(priv->thread, &value); + } + priv->thread = 0; + + /* Unregister the driver */ + + usbdev_unregister(&alloc->drvr.drvr); + + /* Uninitialize and release the LUNs */ + + for (i = 0; i < priv->nluns; ++i) + { + usbstrg_lununinitialize(&priv->luntab[i]); + } + kfree(priv->luntab); + + /* Release the I/O buffer */ + + if (priv->iobuffer) + { + kfree(priv->iobuffer); + } + + /* Uninitialize and release the driver structure */ + + pthread_mutex_destroy(&priv->mutex); + pthread_cond_destroy(&priv->cond); + + kfree(priv); +} diff --git a/nuttx/drivers/usbdev/usbdev_storage.h b/nuttx/drivers/usbdev/usbdev_storage.h new file mode 100644 index 000000000..77cc0e53c --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_storage.h @@ -0,0 +1,608 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_storage.h + * + * Copyright (C) 2008-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Mass storage class device. Bulk-only with SCSI subclass. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_USBDEV_USBDEV_STORAGE_H +#define __DRIVERS_USBDEV_USBDEV_STORAGE_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <stdbool.h> +#include <pthread.h> +#include <queue.h> + +#include <nuttx/fs.h> +#include <nuttx/usb/storage.h> +#include <nuttx/usb/usbdev.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ +/* Number of requests in the write queue */ + +#ifndef CONFIG_USBSTRG_NWRREQS +# define CONFIG_USBSTRG_NWRREQS 4 +#endif + +/* Number of requests in the read queue */ + +#ifndef CONFIG_USBSTRG_NRDREQS +# define CONFIG_USBSTRG_NRDREQS 4 +#endif + +/* Logical endpoint numbers / max packet sizes */ + +#ifndef CONFIG_USBSTRG_EPBULKOUT +# warning "EPBULKOUT not defined in the configuration" +# define CONFIG_USBSTRG_EPBULKOUT 2 +#endif + +#ifndef CONFIG_USBSTRG_EPBULKIN +# warning "EPBULKIN not defined in the configuration" +# define CONFIG_USBSTRG_EPBULKIN 3 +#endif + +/* Packet and request buffer sizes */ + +#ifndef CONFIG_USBSTRG_EP0MAXPACKET +# define CONFIG_USBSTRG_EP0MAXPACKET 64 +#endif + +#ifndef CONFIG_USBSTRG_BULKINREQLEN +# ifdef CONFIG_USBDEV_DUALSPEED +# define CONFIG_USBSTRG_BULKINREQLEN 512 +# else +# define CONFIG_USBSTRG_BULKINREQLEN 64 +# endif +#else +# ifdef CONFIG_USBDEV_DUALSPEED +# if CONFIG_USBSTRG_BULKINREQLEN < 512 +# warning "Bulk in buffer size smaller than max packet" +# undef CONFIG_USBSTRG_BULKINREQLEN +# define CONFIG_USBSTRG_BULKINREQLEN 512 +# endif +# else +# if CONFIG_USBSTRG_BULKINREQLEN < 64 +# warning "Bulk in buffer size smaller than max packet" +# undef CONFIG_USBSTRG_BULKINREQLEN +# define CONFIG_USBSTRG_BULKINREQLEN 64 +# endif +# endif +#endif + +#ifndef CONFIG_USBSTRG_BULKOUTREQLEN +# ifdef CONFIG_USBDEV_DUALSPEED +# define CONFIG_USBSTRG_BULKOUTREQLEN 512 +# else +# define CONFIG_USBSTRG_BULKOUTREQLEN 64 +# endif +#else +# ifdef CONFIG_USBDEV_DUALSPEED +# if CONFIG_USBSTRG_BULKOUTREQLEN < 512 +# warning "Bulk in buffer size smaller than max packet" +# undef CONFIG_USBSTRG_BULKOUTREQLEN +# define CONFIG_USBSTRG_BULKOUTREQLEN 512 +# endif +# else +# if CONFIG_USBSTRG_BULKOUTREQLEN < 64 +# warning "Bulk in buffer size smaller than max packet" +# undef CONFIG_USBSTRG_BULKOUTREQLEN +# define CONFIG_USBSTRG_BULKOUTREQLEN 64 +# endif +# endif +#endif + +/* Vendor and product IDs and strings */ + +#ifndef CONFIG_USBSTRG_VENDORID +# warning "CONFIG_USBSTRG_VENDORID not defined" +# define CONFIG_USBSTRG_VENDORID 0x584e +#endif + +#ifndef CONFIG_USBSTRG_PRODUCTID +# warning "CONFIG_USBSTRG_PRODUCTID not defined" +# define CONFIG_USBSTRG_PRODUCTID 0x5342 +#endif + +#ifndef CONFIG_USBSTRG_VERSIONNO +# define CONFIG_USBSTRG_VERSIONNO (0x0399) +#endif + +#ifndef CONFIG_USBSTRG_VENDORSTR +# warning "No Vendor string specified" +# define CONFIG_USBSTRG_VENDORSTR "NuttX" +#endif + +#ifndef CONFIG_USBSTRG_PRODUCTSTR +# warning "No Product string specified" +# define CONFIG_USBSTRG_PRODUCTSTR "USBdev Storage" +#endif + +#undef CONFIG_USBSTRG_SERIALSTR +#define CONFIG_USBSTRG_SERIALSTR "0101" + +#undef CONFIG_USBSTRG_CONFIGSTR +#define CONFIG_USBSTRG_CONFIGSTR "Bulk" + +/* Debug -- must be consistent with include/debug.h */ + +#ifdef CONFIG_CPP_HAVE_VARARGS +# ifdef CONFIG_DEBUG +# ifdef CONFIG_ARCH_LOWPUTC +# define dbgprintf(format, arg...) lib_lowprintf(format, ##arg) +# else +# define dbgprintf(format, arg...) lib_rawprintf(format, ##arg) +# endif +# else +# define dbgprintf(x...) +# endif +#else +# ifdef CONFIG_DEBUG +# ifdef CONFIG_ARCH_LOWPUTC +# define dbgprintf lib_lowprintf +# else +# define dbgprintf lib_rawprintf +# endif +# else +# define dbgprintf (void) +# endif +#endif + +/* Packet and request buffer sizes */ + +#ifndef CONFIG_USBSTRG_EP0MAXPACKET +# define CONFIG_USBSTRG_EP0MAXPACKET 64 +#endif + +/* USB Controller */ + +#ifndef CONFIG_USBDEV_SELFPOWERED +# define SELFPOWERED USB_CONFIG_ATT_SELFPOWER +#else +# define SELFPOWERED (0) +#endif + +#ifndef CONFIG_USBDEV_REMOTEWAKEUP +# define REMOTEWAKEUP USB_CONFIG_ATTR_WAKEUP +#else +# define REMOTEWAKEUP (0) +#endif + +#ifndef CONFIG_USBDEV_MAXPOWER +# define CONFIG_USBDEV_MAXPOWER 100 +#endif + +/* Current state of the worker thread */ + +#define USBSTRG_STATE_NOTSTARTED (0) /* Thread has not yet been started */ +#define USBSTRG_STATE_STARTED (1) /* Started, but is not yet initialized */ +#define USBSTRG_STATE_IDLE (2) /* Started and waiting for commands */ +#define USBSTRG_STATE_CMDPARSE (3) /* Processing a received command */ +#define USBSTRG_STATE_CMDREAD (4) /* Processing a SCSI read command */ +#define USBSTRG_STATE_CMDWRITE (5) /* Processing a SCSI write command */ +#define USBSTRG_STATE_CMDFINISH (6) /* Finish command processing */ +#define USBSTRG_STATE_CMDSTATUS (7) /* Processing the final status of the command */ +#define USBSTRG_STATE_TERMINATED (8) /* Thread has exitted */ + +/* Event communicated to worker thread */ + +#define USBSTRG_EVENT_NOEVENTS (0) /* There are no outstanding events */ +#define USBSTRG_EVENT_READY (1 << 0) /* Initialization is complete */ +#define USBSTRG_EVENT_RDCOMPLETE (1 << 1) /* A read has completed there is data to be processed */ +#define USBSTRG_EVENT_WRCOMPLETE (1 << 2) /* A write has completed and a request is available */ +#define USBSTRG_EVENT_TERMINATEREQUEST (1 << 3) /* Shutdown requested */ +#define USBSTRG_EVENT_DISCONNECT (1 << 4) /* USB disconnect received */ +#define USBSTRG_EVENT_RESET (1 << 5) /* USB storage setup reset received */ +#define USBSTRG_EVENT_CFGCHANGE (1 << 6) /* USB setup configuration change received */ +#define USBSTRG_EVENT_IFCHANGE (1 << 7) /* USB setup interface change received */ +#define USBSTRG_EVENT_ABORTBULKOUT (1 << 8) /* SCSI receive failure */ + +/* SCSI command flags (passed to usbstrg_setupcmd()) */ + +#define USBSTRG_FLAGS_DIRMASK (0x03) /* Bits 0-1: Data direction */ +#define USBSTRG_FLAGS_DIRNONE (0x00) /* No data to send */ +#define USBSTRG_FLAGS_DIRHOST2DEVICE (0x01) /* Host-to-device */ +#define USBSTRG_FLAGS_DIRDEVICE2HOST (0x02) /* Device-to-host */ +#define USBSTRG_FLAGS_BLOCKXFR (0x04) /* Bit 2: Command is a block transfer request */ +#define USBSTRG_FLAGS_LUNNOTNEEDED (0x08) /* Bit 3: Command does not require a valid LUN */ +#define USBSTRG_FLAGS_UACOKAY (0x10) /* Bit 4: Command OK if unit attention condition */ +#define USBSTRG_FLAGS_RETAINSENSEDATA (0x20) /* Bit 5: Do not clear sense data */ + +/* Descriptors **************************************************************/ + +/* Big enough to hold our biggest descriptor */ + +#define USBSTRG_MXDESCLEN (64) + +/* String language */ + +#define USBSTRG_STR_LANGUAGE (0x0409) /* en-us */ + +/* Descriptor strings */ + +#define USBSTRG_MANUFACTURERSTRID (1) +#define USBSTRG_PRODUCTSTRID (2) +#define USBSTRG_SERIALSTRID (3) +#define USBSTRG_CONFIGSTRID (4) +#define USBSTRG_NCONFIGS (1) /* Number of configurations supported */ + +/* Configuration Descriptor */ + +#define USBSTRG_NINTERFACES (1) /* Number of interfaces in the configuration */ +#define USBSTRG_INTERFACEID (0) +#define USBSTRG_ALTINTERFACEID (0) +#define USBSTRG_CONFIGIDNONE (0) /* Config ID means to return to address mode */ +#define USBSTRG_CONFIGID (1) /* The only supported configuration ID */ + +#define STRING_MANUFACTURER (1) +#define STRING_PRODUCT (2) +#define STRING_SERIAL (3) + +#define USBSTRG_CONFIGID (1) /* There is only one configuration. */ + +/* Interface description */ + +#define USBSTRG_NENDPOINTS (2) /* Number of endpoints in the interface */ + +/* Endpoint configuration */ + +#define USBSTRG_EPOUTBULK_ADDR (CONFIG_USBSTRG_EPBULKOUT) +#define USBSTRG_EPOUTBULK_ATTR (USB_EP_ATTR_XFER_BULK) + +#define USBSTRG_EPINBULK_ADDR (USB_DIR_IN|CONFIG_USBSTRG_EPBULKIN) +#define USBSTRG_EPINBULK_ATTR (USB_EP_ATTR_XFER_BULK) + +#define USBSTRG_HSBULKMAXPACKET (512) +#define USBSTRG_HSBULKMXPKTSHIFT (9) +#define USBSTRG_HSBULKMXPKTMASK (0x000001ff) +#define USBSTRG_FSBULKMAXPACKET (64) +#define USBSTRG_FSBULKMXPKTSHIFT (6) +#define USBSTRG_FSBULKMXPKTMASK (0x0000003f) + +/* Macros for dual speed vs. full speed only operation */ + +#ifdef CONFIG_USBDEV_DUALSPEED +# define USBSTRG_EPBULKINDESC(hs) \ + usbstrg_getepdesc((hs) ? USBSTRG_EPHSBULKIN : USBSTRG_EPFSBULKIN) +# define USBSTRG_EPBULKOUTDESC(hs) \ + usbstrg_getepdesc((hs) ? USBSTRG_EPHSBULKOUT : USBSTRG_EPFSBULKOUT) +# define USBSTRG_BULKMAXPACKET(hs) \ + ((hs) ? USBSTRG_HSBULKMAXPACKET : USBSTRG_FSBULKMAXPACKET) +# define USBSTRG_BULKMXPKTSHIFT(d) \ + (((d)->speed==USB_SPEED_HIGH) ? USBSTRG_HSBULKMXPKTSHIFT : USBSTRG_FSBULKMXPKTSHIFT) +# define USBSTRG_BULKMXPKTMASK(d) \ + (((d)->speed==USB_SPEED_HIGH) ? USBSTRG_HSBULKMXPKTMASK : USBSTRG_FSBULKMXPKTMASK) +#else +# define USBSTRG_EPBULKINDESC(d) usbstrg_getepdesc(USBSTRG_EPFSBULKIN) +# define USBSTRG_EPBULKOUTDESC(d) usbstrg_getepdesc(USBSTRG_EPFSBULKOUT) +# define USBSTRG_BULKMAXPACKET(hs) USBSTRG_FSBULKMAXPACKET +# define USBSTRG_BULKMXPKTSHIFT(d) USBSTRG_FSBULKMXPKTSHIFT +# define USBSTRG_BULKMXPKTMASK(d) USBSTRG_FSBULKMXPKTMASK +#endif + +/* Block driver helpers *****************************************************/ + +#define USBSTRG_DRVR_READ(l,b,s,n) ((l)->inode->u.i_bops->read((l)->inode,b,s,n)) +#define USBSTRG_DRVR_WRITE(l,b,s,n) ((l)->inode->u.i_bops->write((l)->inode,b,s,n)) +#define USBSTRG_DRVR_GEOMETRY(l,g) ((l)->inode->u.i_bops->geometry((l)->inode,g)) + +/* Everpresent min/max macros ***********************************************/ + +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef max +# define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ +/* Endpoint descriptors */ + +enum usbstrg_epdesc_e +{ + USBSTRG_EPFSBULKOUT = 0, /* Full speed bulk OUT endpoint descriptor */ + USBSTRG_EPFSBULKIN /* Full speed bulk IN endpoint descriptor */ +#ifdef CONFIG_USBDEV_DUALSPEED + , + USBSTRG_EPHSBULKOUT, /* High speed bulk OUT endpoint descriptor */ + USBSTRG_EPHSBULKIN /* High speed bulk IN endpoint descriptor */ +#endif +}; + +/* Container to support a list of requests */ + +struct usbstrg_req_s +{ + FAR struct usbstrg_req_s *flink; /* Implements a singly linked list */ + FAR struct usbdev_req_s *req; /* The contained request */ +}; + +/* This structure describes one LUN: */ + +struct usbstrg_lun_s +{ + struct inode *inode; /* Inode structure of open'ed block driver */ + uint8_t readonly:1; /* Media is read-only */ + uint8_t locked:1; /* Media removal is prevented */ + uint16_t sectorsize; /* The size of one sector */ + uint32_t sd; /* Sense data */ + uint32_t sdinfo; /* Sense data information */ + uint32_t uad; /* Unit needs attention data */ + off_t startsector; /* Sector offset to start of partition */ + size_t nsectors; /* Number of sectors in the partition */ +}; + +/* Describes the overall state of the driver */ + +struct usbstrg_dev_s +{ + FAR struct usbdev_s *usbdev; /* usbdev driver pointer (Non-null if registered) */ + + /* Worker thread interface */ + + pthread_t thread; /* The worker thread */ + pthread_mutex_t mutex; /* Mutually exclusive access to resources*/ + pthread_cond_t cond; /* Used to signal worker thread */ + volatile uint8_t thstate; /* State of the worker thread */ + volatile uint16_t theventset; /* Set of pending events signaled to worker thread */ + volatile uint8_t thvalue; /* Value passed with the event (must persist) */ + + /* Storage class configuration and state */ + + uint8_t nluns:4; /* Number of LUNs */ + uint8_t config; /* Configuration number */ + + /* Endpoints */ + + FAR struct usbdev_ep_s *epbulkin; /* Bulk IN endpoint structure */ + FAR struct usbdev_ep_s *epbulkout; /* Bulk OUT endpoint structure */ + FAR struct usbdev_req_s *ctrlreq; /* Control request (for ep0 setup responses) */ + + /* SCSI command processing */ + + struct usbstrg_lun_s *lun; /* Currently selected LUN */ + struct usbstrg_lun_s *luntab; /* Allocated table of all LUNs */ + uint8_t cdb[USBSTRG_MAXCDBLEN]; /* Command data (cdb[]) from CBW */ + uint8_t phaseerror:1; /* Need to send phase sensing status */ + uint8_t shortpacket:1; /* Host transmission stopped unexpectedly */ + uint8_t cbwdir:2; /* Direction from CBW. See USBSTRG_FLAGS_DIR* definitions */ + uint8_t cdblen; /* Length of cdb[] from CBW */ + uint8_t cbwlun; /* LUN from the CBW */ + uint16_t nsectbytes; /* Bytes buffered in iobuffer[] */ + uint16_t nreqbytes; /* Bytes buffered in head write requests */ + uint16_t iosize; /* Size of iobuffer[] */ + uint32_t cbwlen; /* Length of data from CBW */ + uint32_t cbwtag; /* Tag from the CBW */ + union + { + uint32_t xfrlen; /* Read/Write: Sectors remaining to be transferred */ + uint32_t alloclen; /* Other device-to-host: Host allocation length */ + } u; + uint32_t sector; /* Current sector (relative to lun->startsector) */ + uint32_t residue; /* Untransferred amount reported in the CSW */ + uint8_t *iobuffer; /* Buffer for data transfers */ + + /* Write request list */ + + struct sq_queue_s wrreqlist; /* List of empty write request containers */ + struct sq_queue_s rdreqlist; /* List of filled read request containers */ + + /* Pre-allocated write request containers. The write requests will + * be linked in a free list (wrreqlist), and used to send requests to + * EPBULKIN; Read requests will be queued in the EBULKOUT. + */ + + struct usbstrg_req_s wrreqs[CONFIG_USBSTRG_NWRREQS]; + struct usbstrg_req_s rdreqs[CONFIG_USBSTRG_NRDREQS]; +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +# define EXTERN extern "C" +extern "C" +{ +#else +# define EXTERN extern +#endif + +/* String *******************************************************************/ + +EXTERN const char g_vendorstr[]; +EXTERN const char g_productstr[]; +EXTERN const char g_serialstr[]; + +/************************************************************************************ + * Public Function Prototypes + ************************************************************************************/ + +/************************************************************************************ + * Name: usbstrg_mkstrdesc + * + * Description: + * Construct a string descriptor + * + ************************************************************************************/ + +struct usb_strdesc_s; +int usbstrg_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc); + +/************************************************************************************ + * Name: usbstrg_getepdesc + * + * Description: + * Return a pointer to the raw device descriptor + * + ************************************************************************************/ + +FAR const struct usb_devdesc_s *usbstrg_getdevdesc(void); + +/************************************************************************************ + * Name: usbstrg_getepdesc + * + * Description: + * Return a pointer to the raw endpoint descriptor (used for configuring endpoints) + * + ************************************************************************************/ + +struct usb_epdesc_s; +FAR const struct usb_epdesc_s *usbstrg_getepdesc(enum usbstrg_epdesc_e epid); + +/************************************************************************************ + * Name: usbstrg_mkcfgdesc + * + * Description: + * Construct the configuration descriptor + * + ************************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +int16_t usbstrg_mkcfgdesc(FAR uint8_t *buf, uint8_t speed, uint8_t type); +#else +int16_t usbstrg_mkcfgdesc(FAR uint8_t *buf); +#endif + +/************************************************************************************ + * Name: usbstrg_getqualdesc + * + * Description: + * Return a pointer to the raw qual descriptor + * + ************************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +FAR const struct usb_qualdesc_s *usbstrg_getqualdesc(void); +#endif + +/**************************************************************************** + * Name: usbstrg_workerthread + * + * Description: + * This is the main function of the USB storage worker thread. It loops + * until USB-related events occur, then processes those events accordingly + * + ****************************************************************************/ + +EXTERN void *usbstrg_workerthread(void *arg); + +/**************************************************************************** + * Name: usbstrg_setconfig + * + * Description: + * Set the device configuration by allocating and configuring endpoints and + * by allocating and queue read and write requests. + * + ****************************************************************************/ + +EXTERN int usbstrg_setconfig(FAR struct usbstrg_dev_s *priv, uint8_t config); + +/**************************************************************************** + * Name: usbstrg_resetconfig + * + * Description: + * Mark the device as not configured and disable all endpoints. + * + ****************************************************************************/ + +EXTERN void usbstrg_resetconfig(FAR struct usbstrg_dev_s *priv); + +/**************************************************************************** + * Name: usbstrg_wrcomplete + * + * Description: + * Handle completion of write request. This function probably executes + * in the context of an interrupt handler. + * + ****************************************************************************/ + +EXTERN void usbstrg_wrcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); + +/**************************************************************************** + * Name: usbstrg_rdcomplete + * + * Description: + * Handle completion of read request on the bulk OUT endpoint. This + * is handled like the receipt of serial data on the "UART" + * + ****************************************************************************/ + +EXTERN void usbstrg_rdcomplete(FAR struct usbdev_ep_s *ep, + FAR struct usbdev_req_s *req); + +/**************************************************************************** + * Name: usbstrg_deferredresponse + * + * Description: + * Some EP0 setup request cannot be responded to immediately becuase they + * require some asynchronous action from the SCSI worker thread. This + * function is provided for the SCSI thread to make that deferred response. + * The specific requests that require this deferred response are: + * + * 1. USB_REQ_SETCONFIGURATION, + * 2. USB_REQ_SETINTERFACE, or + * 3. USBSTRG_REQ_MSRESET + * + * In all cases, the success reponse is a zero-length packet; the failure + * response is an EP0 stall. + * + * Input parameters: + * priv - Private state structure for this USB storage instance + * stall - true is the action failed and a stall is required + * + ****************************************************************************/ + +EXTERN void usbstrg_deferredresponse(FAR struct usbstrg_dev_s *priv, bool failed); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* #define __DRIVERS_USBDEV_USBDEV_STORAGE_H */ diff --git a/nuttx/drivers/usbdev/usbdev_stordesc.c b/nuttx/drivers/usbdev/usbdev_stordesc.c new file mode 100644 index 000000000..68dffa96c --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_stordesc.c @@ -0,0 +1,408 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_stordesc.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/usb/usb.h> +#include <nuttx/usb/usbdev_trace.h> + +#include "usbdev_storage.h" + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ +/* Descriptors **************************************************************/ +/* Device descriptor */ + +static const struct usb_devdesc_s g_devdesc = +{ + USB_SIZEOF_DEVDESC, /* len */ + USB_DESC_TYPE_DEVICE, /* type */ + {LSBYTE(0x0200), MSBYTE(0x0200)}, /* usb */ + USB_CLASS_PER_INTERFACE, /* class */ + 0, /* subclass */ + 0, /* protocol */ + CONFIG_USBSTRG_EP0MAXPACKET, /* maxpacketsize */ + { /* vendor */ + LSBYTE(CONFIG_USBSTRG_VENDORID), + MSBYTE(CONFIG_USBSTRG_VENDORID) + }, + { /* product */ + LSBYTE(CONFIG_USBSTRG_PRODUCTID), + MSBYTE(CONFIG_USBSTRG_PRODUCTID) }, + { /* device */ + LSBYTE(CONFIG_USBSTRG_VERSIONNO), + MSBYTE(CONFIG_USBSTRG_VERSIONNO) + }, + USBSTRG_MANUFACTURERSTRID, /* imfgr */ + USBSTRG_PRODUCTSTRID, /* iproduct */ + USBSTRG_SERIALSTRID, /* serno */ + USBSTRG_NCONFIGS /* nconfigs */ +}; + +/* Configuration descriptor */ + +static const struct usb_cfgdesc_s g_cfgdesc = +{ + USB_SIZEOF_CFGDESC, /* len */ + USB_DESC_TYPE_CONFIG, /* type */ + {0, 0}, /* totallen -- to be provided */ + USBSTRG_NINTERFACES, /* ninterfaces */ + USBSTRG_CONFIGID, /* cfgvalue */ + USBSTRG_CONFIGSTRID, /* icfg */ + USB_CONFIG_ATTR_ONE|SELFPOWERED|REMOTEWAKEUP, /* attr */ + (CONFIG_USBDEV_MAXPOWER + 1) / 2 /* mxpower */ +}; + +/* Single interface descriptor */ + +static const struct usb_ifdesc_s g_ifdesc = +{ + USB_SIZEOF_IFDESC, /* len */ + USB_DESC_TYPE_INTERFACE, /* type */ + USBSTRG_INTERFACEID, /* ifno */ + USBSTRG_ALTINTERFACEID, /* alt */ + USBSTRG_NENDPOINTS, /* neps */ + USB_CLASS_MASS_STORAGE, /* class */ + USBSTRG_SUBCLASS_SCSI, /* subclass */ + USBSTRG_PROTO_BULKONLY, /* protocol */ + USBSTRG_CONFIGSTRID /* iif */ +}; + +/* Endpoint descriptors */ + +static const struct usb_epdesc_s g_fsepbulkoutdesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + USBSTRG_EPOUTBULK_ADDR, /* addr */ + USBSTRG_EPOUTBULK_ATTR, /* attr */ + { /* maxpacket */ + LSBYTE(USBSTRG_FSBULKMAXPACKET), + MSBYTE(USBSTRG_FSBULKMAXPACKET) + }, + 0 /* interval */ +}; + +static const struct usb_epdesc_s g_fsepbulkindesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + USBSTRG_EPINBULK_ADDR, /* addr */ + USBSTRG_EPINBULK_ATTR, /* attr */ + { /* maxpacket */ + LSBYTE(USBSTRG_FSBULKMAXPACKET), + MSBYTE(USBSTRG_FSBULKMAXPACKET) + }, + 0 /* interval */ +}; + +#ifdef CONFIG_USBDEV_DUALSPEED +static const struct usb_qualdesc_s g_qualdesc = +{ + USB_SIZEOF_QUALDESC, /* len */ + USB_DESC_TYPE_DEVICEQUALIFIER, /* type */ + { /* usb */ + LSBYTE(0x0200), + MSBYTE(0x0200) + }, + USB_CLASS_PER_INTERFACE, /* class */ + 0, /* subclass */ + 0, /* protocol */ + CONFIG_USBSTRG_EP0MAXPACKET, /* mxpacketsize */ + USBSTRG_NCONFIGS, /* nconfigs */ + 0, /* reserved */ +}; + +static const struct usb_epdesc_s g_hsepbulkoutdesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + USBSTRG_EPOUTBULK_ADDR, /* addr */ + USBSTRG_EPOUTBULK_ATTR, /* attr */ + { /* maxpacket */ + LSBYTE(USBSTRG_HSBULKMAXPACKET), + MSBYTE(USBSTRG_HSBULKMAXPACKET) + }, + 0 /* interval */ +}; + +static const struct usb_epdesc_s g_hsepbulkindesc = +{ + USB_SIZEOF_EPDESC, /* len */ + USB_DESC_TYPE_ENDPOINT, /* type */ + USBSTRG_EPINBULK_ADDR, /* addr */ + USBSTRG_EPINBULK_ATTR, /* attr */ + { /* maxpacket */ + LSBYTE(USBSTRG_HSBULKMAXPACKET), + MSBYTE(USBSTRG_HSBULKMAXPACKET) + }, + 0 /* interval */ +}; +#endif + +/**************************************************************************** + * Public Data + ****************************************************************************/ +/* Strings ******************************************************************/ + +const char g_vendorstr[] = CONFIG_USBSTRG_VENDORSTR; +const char g_productstr[] = CONFIG_USBSTRG_PRODUCTSTR; +const char g_serialstr[] = CONFIG_USBSTRG_SERIALSTR; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbstrg_mkstrdesc + * + * Description: + * Construct a string descriptor + * + ****************************************************************************/ + +int usbstrg_mkstrdesc(uint8_t id, struct usb_strdesc_s *strdesc) +{ + const char *str; + int len; + int ndata; + int i; + + switch (id) + { + case 0: + { + /* Descriptor 0 is the language id */ + + strdesc->len = 4; + strdesc->type = USB_DESC_TYPE_STRING; + strdesc->data[0] = LSBYTE(USBSTRG_STR_LANGUAGE); + strdesc->data[1] = MSBYTE(USBSTRG_STR_LANGUAGE); + return 4; + } + + case USBSTRG_MANUFACTURERSTRID: + str = g_vendorstr; + break; + + case USBSTRG_PRODUCTSTRID: + str = g_productstr; + break; + + case USBSTRG_SERIALSTRID: + str = g_serialstr; + break; + + case USBSTRG_CONFIGSTRID: + str = CONFIG_USBSTRG_CONFIGSTR; + break; + + default: + return -EINVAL; + } + + /* The string is utf16-le. The poor man's utf-8 to utf16-le + * conversion below will only handle 7-bit en-us ascii + */ + + len = strlen(str); + for (i = 0, ndata = 0; i < len; i++, ndata += 2) + { + strdesc->data[ndata] = str[i]; + strdesc->data[ndata+1] = 0; + } + + strdesc->len = ndata+2; + strdesc->type = USB_DESC_TYPE_STRING; + return strdesc->len; +} + +/**************************************************************************** + * Name: usbstrg_getepdesc + * + * Description: + * Return a pointer to the raw device descriptor + * + ****************************************************************************/ + +FAR const struct usb_devdesc_s *usbstrg_getdevdesc(void) +{ + return &g_devdesc; +} + +/**************************************************************************** + * Name: usbstrg_getepdesc + * + * Description: + * Return a pointer to the raw endpoint descriptor (used for configuring + * endpoints) + * + ****************************************************************************/ + +FAR const struct usb_epdesc_s *usbstrg_getepdesc(enum usbstrg_epdesc_e epid) +{ + switch (epid) + { + case USBSTRG_EPFSBULKOUT: /* Full speed bulk OUT endpoint descriptor */ + return &g_fsepbulkoutdesc; + + case USBSTRG_EPFSBULKIN: /* Full speed bulk IN endpoint descriptor */ + return &g_fsepbulkindesc; + +#ifdef CONFIG_USBDEV_DUALSPEED + case USBSTRG_EPHSBULKOUT: /* High speed bulk OUT endpoint descriptor */ + return &g_hsepbulkoutdesc; + + case USBSTRG_EPHSBULKIN: /* High speed bulk IN endpoint descriptor */ + return &g_hsepbulkindesc; +#endif + default: + return NULL; + } +}; + +/**************************************************************************** + * Name: usbstrg_mkcfgdesc + * + * Description: + * Construct the configuration descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +int16_t usbstrg_mkcfgdesc(uint8_t *buf, uint8_t speed, uint8_t type) +#else +int16_t usbstrg_mkcfgdesc(uint8_t *buf) +#endif +{ + FAR struct usb_cfgdesc_s *cfgdesc = (struct usb_cfgdesc_s*)buf; +#ifdef CONFIG_USBDEV_DUALSPEED + FAR const struct usb_epdesc_s *epdesc; + bool hispeed = (speed == USB_SPEED_HIGH); + uint16_t bulkmxpacket; +#endif + uint16_t totallen; + + /* This is the total length of the configuration (not necessarily the + * size that we will be sending now. + */ + + totallen = USB_SIZEOF_CFGDESC + USB_SIZEOF_IFDESC + USBSTRG_NENDPOINTS * USB_SIZEOF_EPDESC; + + /* Configuration descriptor -- Copy the canned descriptor and fill in the + * type (we'll also need to update the size below + */ + + memcpy(cfgdesc, &g_cfgdesc, USB_SIZEOF_CFGDESC); + buf += USB_SIZEOF_CFGDESC; + + /* Copy the canned interface descriptor */ + + memcpy(buf, &g_ifdesc, USB_SIZEOF_IFDESC); + buf += USB_SIZEOF_IFDESC; + + /* Make the two endpoint configurations */ + +#ifdef CONFIG_USBDEV_DUALSPEED + /* Check for switches between high and full speed */ + + hispeed = (speed == USB_SPEED_HIGH); + if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG) + { + hispeed = !hispeed; + } + + bulkmxpacket = USBSTRG_BULKMAXPACKET(hispeed); + epdesc = USBSTRG_EPBULKINDESC(hispeed); + memcpy(buf, epdesc, USB_SIZEOF_EPDESC); + buf += USB_SIZEOF_EPDESC; + + epdesc = USBSTRG_EPBULKOUTDESC(hispeed); + memcpy(buf, epdesc, USB_SIZEOF_EPDESC); +#else + memcpy(buf, &g_fsepbulkoutdesc, USB_SIZEOF_EPDESC); + buf += USB_SIZEOF_EPDESC; + memcpy(buf, &g_fsepbulkindesc, USB_SIZEOF_EPDESC); +#endif + + /* Finally, fill in the total size of the configuration descriptor */ + + cfgdesc->totallen[0] = LSBYTE(totallen); + cfgdesc->totallen[1] = MSBYTE(totallen); + return totallen; +} + +/**************************************************************************** + * Name: usbstrg_getqualdesc + * + * Description: + * Return a pointer to the raw qual descriptor + * + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_DUALSPEED +FAR const struct usb_qualdesc_s *usbstrg_getqualdesc(void) +{ + return &g_qualdesc; +} +#endif + diff --git a/nuttx/drivers/usbdev/usbdev_trace.c b/nuttx/drivers/usbdev/usbdev_trace.c new file mode 100644 index 000000000..7b258b1cb --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_trace.c @@ -0,0 +1,233 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_trace.c + * + * Copyright (C) 2008-2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <errno.h> +#include <debug.h> + +#include <arch/irq.h> +#include <nuttx/usb/usbdev_trace.h> +#undef usbtrace + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_USBDEV_TRACE_NRECORDS +# define CONFIG_USBDEV_TRACE_NRECORDS 128 +#endif + +#ifndef CONFIG_USBDEV_TRACE_INITIALIDSET +# define CONFIG_USBDEV_TRACE_INITIALIDSET 0 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_USBDEV_TRACE +static struct usbtrace_s g_trace[CONFIG_USBDEV_TRACE_NRECORDS]; +static uint16_t g_head = 0; +static uint16_t g_tail = 0; +#endif + +#if defined(CONFIG_USBDEV_TRACE) || (defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_USB)) +static usbtrace_idset_t g_maskedidset = CONFIG_USBDEV_TRACE_INITIALIDSET; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/******************************************************************************* + * Name: usbtrace_enable + * + * Description: + * Enable/disable tracing per trace ID. The initial state is all IDs enabled. + * + * Input Parameters: + * idset - The bitset of IDs to be masked. TRACE_ALLIDS enables all IDS; zero + * masks all IDs. + * + * Returned Value: + * The previous idset value. + * + * Assumptions: + * - May be called from an interrupt handler + * + *******************************************************************************/ + +#if defined(CONFIG_USBDEV_TRACE) || (defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_USB)) +usbtrace_idset_t usbtrace_enable(usbtrace_idset_t idset) +{ + irqstate_t flags; + usbtrace_idset_t ret; + + /* The following read and write must be atomic */ + + flags = irqsave(); + ret = g_maskedidset; + g_maskedidset = idset; + irqrestore(flags); + return ret; +} +#endif /* CONFIG_USBDEV_TRACE || CONFIG_DEBUG && CONFIG_DEBUG_USB */ + +/******************************************************************************* + * Name: usbtrace + * + * Description: + * Record a USB event (tracing must be enabled) + * + * Assumptions: + * May be called from an interrupt handler + * + *******************************************************************************/ + +#if defined(CONFIG_USBDEV_TRACE) || (defined(CONFIG_DEBUG) && defined(CONFIG_DEBUG_USB)) +void usbtrace(uint16_t event, uint16_t value) +{ + irqstate_t flags; + + /* Check if tracing is enabled for this ID */ + + flags = irqsave(); + if ((g_maskedidset & TRACE_ID2BIT(event)) != 0) + { +#ifdef CONFIG_USBDEV_TRACE + /* Yes... save the new trace data at the head */ + + g_trace[g_head].event = event; + g_trace[g_head].value = value; + + /* Increment the head and (probably) the tail index */ + + if (++g_head >= CONFIG_USBDEV_TRACE_NRECORDS) + { + g_head = 0; + } + + if (g_head == g_tail) + { + if (++g_tail >= CONFIG_USBDEV_TRACE_NRECORDS) + { + g_tail = 0; + } + } +#else + /* Just print the data using lib_lowprintf */ + + usbtrace_trprintf((trprintf_t)lib_lowprintf, event, value); +#endif + } + irqrestore(flags); +} +#endif /* CONFIG_USBDEV_TRACE || CONFIG_DEBUG && CONFIG_DEBUG_USB */ + +/******************************************************************************* + * Name: usbtrace_enumerate + * + * Description: + * Enumerate all buffer trace data (will temporarily disable tracing) + * + * Assumptions: + * NEVER called from an interrupt handler + * + *******************************************************************************/ + +#ifdef CONFIG_USBDEV_TRACE +int usbtrace_enumerate(trace_callback_t callback, void *arg) +{ + uint16_t ndx; + uint32_t idset; + int ret = OK; + + /* Temporarily disable tracing */ + + idset = usbtrace_enable(0); + + /* Visit every entry, starting with the tail */ + + for (ndx = g_tail; ndx != g_head; ) + { + /* Call the user provided callback */ + + ret = callback(&g_trace[ndx], arg); + if (ret != OK) + { + /* Abort the enumeration */ + + break; + } + + /* Increment the index */ + + if (++ndx >= CONFIG_USBDEV_TRACE_NRECORDS) + { + ndx = 0; + } + } + + /* Discard the trace data after it has been reported */ + + g_tail = g_head; + + /* Restore tracing state */ + + (void)usbtrace_enable(idset); + return ret; +} +#endif /* CONFIG_USBDEV_TRACE */ diff --git a/nuttx/drivers/usbdev/usbdev_trprintf.c b/nuttx/drivers/usbdev/usbdev_trprintf.c new file mode 100644 index 000000000..55c517b2e --- /dev/null +++ b/nuttx/drivers/usbdev/usbdev_trprintf.c @@ -0,0 +1,253 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_trprintf.c + * + * Copyright (C) 2008-2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <debug.h> + +#include <nuttx/usb/usbdev_trace.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/******************************************************************************* + * Name: usbtrace_trprintf + * + * Description: + * Print the trace record using the supplied printing function + * + *******************************************************************************/ + +void usbtrace_trprintf(trprintf_t trprintf, uint16_t event, uint16_t value) +{ + switch (event) + { + case TRACE_DEVINIT: + trprintf("USB controller initialization: %04x\n", value); + break; + + case TRACE_DEVUNINIT: + trprintf("USB controller un-initialization: %04x\n", value); + break; + + case TRACE_DEVREGISTER: + trprintf("usbdev_register(): %04x\n", value); + break; + + case TRACE_DEVUNREGISTER: + trprintf("usbdev_unregister(): %04x\n", value); + break; + + case TRACE_EPCONFIGURE: + trprintf("Endpoint configure(): %04x\n", value); + break; + + case TRACE_EPDISABLE: + trprintf("Endpoint disable(): %04x\n", value); + break; + + case TRACE_EPALLOCREQ: + trprintf("Endpoint allocreq(): %04x\n", value); + break; + + case TRACE_EPFREEREQ: + trprintf("Endpoint freereq(): %04x\n", value); + break; + + case TRACE_EPALLOCBUFFER: + trprintf("Endpoint allocbuffer(): %04x\n", value); + break; + + case TRACE_EPFREEBUFFER: + trprintf("Endpoint freebuffer(): %04x\n", value); + break; + + case TRACE_EPSUBMIT: + trprintf("Endpoint submit(): %04x\n", value); + break; + + case TRACE_EPCANCEL: + trprintf("Endpoint cancel(): %04x\n", value); + break; + + case TRACE_EPSTALL: + trprintf("Endpoint stall(true): %04x\n", value); + break; + + case TRACE_EPRESUME: + trprintf("Endpoint stall(false): %04x\n", value); + break; + + case TRACE_DEVALLOCEP: + trprintf("Device allocep(): %04x\n", value); + break; + + case TRACE_DEVFREEEP: + trprintf("Device freeep(): %04x\n", value); + break; + + case TRACE_DEVGETFRAME: + trprintf("Device getframe(): %04x\n", value); + break; + + case TRACE_DEVWAKEUP: + trprintf("Device wakeup(): %04x\n", value); + break; + + case TRACE_DEVSELFPOWERED: + trprintf("Device selfpowered(): %04x\n", value); + break; + + case TRACE_DEVPULLUP: + trprintf("Device pullup(): %04x\n", value); + break; + + case TRACE_CLASSBIND: + trprintf("Class bind(): %04x\n", value); + break; + + case TRACE_CLASSUNBIND: + trprintf("Class unbind(): %04x\n", value); + break; + + case TRACE_CLASSDISCONNECT: + trprintf("Class disconnect(): %04x\n", value); + break; + + case TRACE_CLASSSETUP: + trprintf("Class setup(): %04x\n", value); + break; + + case TRACE_CLASSSUSPEND: + trprintf("Class suspend(): %04x\n", value); + break; + + case TRACE_CLASSRESUME: + trprintf("Class resume(): %04x\n", value); + break; + + case TRACE_CLASSRDCOMPLETE: + trprintf("Class RD request complete: %04x\n", value); + break; + + case TRACE_CLASSWRCOMPLETE: + trprintf("Class WR request complete: %04x\n", value); + break; + + default: + switch (TRACE_ID(event)) + { + case TRACE_CLASSAPI_ID: /* Other class driver system API calls */ + trprintf("Class API call %d: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_CLASSSTATE_ID: /* Track class driver state changes */ + trprintf("Class state %d: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_INTENTRY_ID: /* Interrupt handler entry */ + trprintf("Interrupt %d entry: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_INTDECODE_ID: /* Decoded interrupt event */ + trprintf("Interrupt decode %d: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_INTEXIT_ID: /* Interrupt handler exit */ + trprintf("Interrupt %d exit: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_OUTREQQUEUED_ID: /* Request queued for OUT endpoint */ + trprintf("EP%d OUT request queued: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_INREQQUEUED_ID: /* Request queued for IN endpoint */ + trprintf("EP%d IN request queued: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_READ_ID: /* Read (OUT) action */ + trprintf("EP%d OUT read: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_WRITE_ID: /* Write (IN) action */ + trprintf("EP%d IN write: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_COMPLETE_ID: /* Request completed */ + trprintf("EP%d request complete: %04x\n", TRACE_DATA(event), value); + break; + + case TRACE_DEVERROR_ID: /* USB controller driver error event */ + trprintf("Controller error: %02x:%04x\n", TRACE_DATA(event), value); + break; + + case TRACE_CLSERROR_ID: /* USB class driver error event */ + trprintf("Class error: %02x:%04x\n", TRACE_DATA(event), value); + break; + + default: + trprintf("Unrecognized event: %02x:%02x:%04x\n", + TRACE_ID(event) >> 8, TRACE_DATA(event), value); + break; + } + } +} diff --git a/nuttx/drivers/usbhost/Make.defs b/nuttx/drivers/usbhost/Make.defs new file mode 100644 index 000000000..cc28e874d --- /dev/null +++ b/nuttx/drivers/usbhost/Make.defs @@ -0,0 +1,57 @@ +############################################################################ +# drivers/usbhost/Make.defs +# +# Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +CSRCS += hid_parser.c + +ifeq ($(CONFIG_USBHOST),y) + +# Include built-in USB host driver logic + +CSRCS += usbhost_registry.c usbhost_registerclass.c usbhost_findclass.c +CSRCS += usbhost_enumerate.c usbhost_storage.c usbhost_hidkbd.c + +# Include add-on USB host driver logic (see misc/drivers) + +ifeq ($(CONFIG_NET),y) + RTL8187_CSRC := ${shell if [ -f usbhost/rtl8187x.c ]; then echo "rtl8187x.c"; fi} + CSRCS += $(RTL8187_CSRC) +endif +endif + +# Include USB host driver build logic + +DEPPATH += --dep-path usbhost +VPATH += :usbhost +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/usbhost} diff --git a/nuttx/drivers/usbhost/hid_parser.c b/nuttx/drivers/usbhost/hid_parser.c new file mode 100644 index 000000000..a0ca6066b --- /dev/null +++ b/nuttx/drivers/usbhost/hid_parser.c @@ -0,0 +1,529 @@ +/**************************************************************************** + * drivers/usbhost/hid_parser.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * + * Adapted from the LUFA Library: + * + * Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com) + * dean [at] fourwalledcubicle [dot] com, www.lufa-lib.org + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/usb/hid.h> +#include <nuttx/usb/hid_parser.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct hid_state_s +{ + struct hid_rptitem_attributes_s attrib; + uint8_t rptcount; + uint8_t id; +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: hid_parsereport + * + * Description: + * Function to process a given HID report returned from an attached device, + * and store it into a given struct hid_rptinfo_s structure. + * + * Input Parameters: + * report Buffer containing the device's HID report table. + * rptlen Size in bytes of the HID report table. + * filter Callback function to decide if an item should be retained + * rptinfo Pointer to a struct hid_rptinfo_s instance for the parser output. + * + * Returned Value: + * Zero on success, otherwise a negated errno value. + ****************************************************************************/ + +int hid_parsereport(FAR const uint8_t *report, int rptlen, + hid_rptfilter_t filter, FAR struct hid_rptinfo_s *rptinfo) +{ + struct hid_state_s state[CONFIG_HID_STATEDEPTH]; + struct hid_state_s *currstate = &state[0]; + struct hid_collectionpath_s *collectionpath = NULL; + struct hid_rptsizeinfo_s *rptidinfo = &rptinfo->rptsize[0]; + uint16_t usage[CONFIG_HID_USAGEDEPTH]; + uint8_t nusage = 0; + struct hid_range_s usage_range = { 0, 0 }; + int i; + + DEBUGASSERT(report && filter && rptinfo); + + memset(rptinfo, 0x00, sizeof(struct hid_rptinfo_s)); + memset(currstate, 0x00, sizeof(struct hid_state_s)); + memset(rptidinfo, 0x00, sizeof(struct hid_rptsizeinfo_s)); + + rptinfo->nreports = 1; + + while (rptlen > 0) + { + uint8_t item = *report; + uint32_t data = 0; + + report++; + rptlen--; + + switch (item & USBHID_RPTITEM_SIZE_MASK) + { + case USBHID_RPTITEM_SIZE_4: /* 4 bytes of little endian data follow */ + data = (uint32_t)(*report++); + data |= (uint32_t)(*report++) << 8; + data |= (uint32_t)(*report++) << 16; + data |= (uint32_t)(*report++) << 24; + rptlen -= 4; + break; + + case USBHID_RPTITEM_SIZE_2: /* 2 bytes of little endian data follow */ + data = (uint32_t)(*report++); + data |= (uint32_t)(*report++) << 8; + rptlen -= 2; + break; + + case USBHID_RPTITEM_SIZE_1: /* 1 byte of data follows */ + data = (uint32_t)(*report++); + rptlen -= 1; + break; + + case USBHID_RPTITEM_SIZE_0: /* No data follows */ + default: + break; + } + + switch (item & ~USBHID_RPTITEM_SIZE_MASK) + { + case USBHID_GLOBAL_PUSH_PREFIX: + if (currstate == &state[CONFIG_HID_STATEDEPTH - 1]) + { + return -E2BIG; + } + + memcpy((currstate + 1), + currstate, sizeof(struct hid_rptitem_s)); + + currstate++; + break; + + case USBHID_GLOBAL_POP_PREFIX: + if (currstate == &state[0]) + { + return -EINVAL; /* Pop without push? */ + } + + currstate--; + break; + + case USBHID_GLOBAL_USAGEPAGE_PREFIX: + if ((item & USBHID_RPTITEM_SIZE_MASK) == USBHID_RPTITEM_SIZE_4) + { + currstate->attrib.usage.page = (data >> 16); + } + + currstate->attrib.usage.page = data; + break; + + case USBHID_GLOBAL_LOGICALMIN_PREFIX: + currstate->attrib.logical.min = data; + break; + + case USBHID_GLOBAL_LOGICALMAX_PREFIX: + currstate->attrib.logical.max = data; + break; + + case USBHID_GLOBAL_PHYSICALMIN_PREFIX: + currstate->attrib.physical.min = data; + break; + + case USBHID_GLOBAL_PHYSMICALAX_PREFIX: + currstate->attrib.physical.max = data; + break; + + case USBHID_GLOBAL_UNITEXP_PREFIX: + currstate->attrib.unit.exponent = data; + break; + + case USBHID_GLOBAL_UNIT_PREFIX: + currstate->attrib.unit.type = data; + break; + + case USBHID_GLOBAL_REPORTSIZE_PREFIX: + currstate->attrib.bitsize = data; + break; + + case USBHID_GLOBAL_REPORTCOUNT_PREFIX: + currstate->rptcount = data; + break; + + case USBHID_GLOBAL_REPORTID_PREFIX: + currstate->id = data; + + if (rptinfo->haverptid) + { + rptidinfo = NULL; + + for (i = 0; i < rptinfo->nreports; i++) + { + if (rptinfo->rptsize[i].id == currstate->id) + { + rptidinfo = &rptinfo->rptsize[i]; + break; + } + } + + if (rptidinfo == NULL) + { + if (rptinfo->nreports == CONFIG_HID_MAXIDS) + { + return -EINVAL; + } + + rptidinfo = &rptinfo->rptsize[rptinfo->nreports++]; + memset(rptidinfo, 0x00, sizeof(struct hid_rptsizeinfo_s)); + } + } + + rptinfo->haverptid = true; + + rptidinfo->id = currstate->id; + break; + + case USBHID_LOCAL_USAGE_PREFIX: + if (nusage == CONFIG_HID_USAGEDEPTH) + { + return -E2BIG; + } + + usage[nusage++] = data; + break; + + case USBHID_LOCAL_USAGEMIN_PREFIX: + usage_range.min = data; + break; + + case USBHID_LOCAL_USAGEMAX_PREFIX: + usage_range.max = data; + break; + + case USBHID_MAIN_COLLECTION_PREFIX: + if (collectionpath == NULL) + { + collectionpath = &rptinfo->collectionpaths[0]; + } + else + { + struct hid_collectionpath_s *ParentCollectionPath = collectionpath; + + collectionpath = &rptinfo->collectionpaths[1]; + + while (collectionpath->parent != NULL) + { + if (collectionpath == &rptinfo->collectionpaths[CONFIG_HID_MAXCOLLECTIONS - 1]) + { + return -EINVAL; + } + + collectionpath++; + } + + collectionpath->parent = ParentCollectionPath; + } + + collectionpath->type = data; + collectionpath->usage.page = currstate->attrib.usage.page; + + if (nusage) + { + collectionpath->usage.usage = usage[0]; + + for (i = 0; i < nusage; i++) + usage[i] = usage[i + 1]; + + nusage--; + } + else if (usage_range.min <= usage_range.max) + { + collectionpath->usage.usage = usage_range.min++; + } + + break; + + case USBHID_MAIN_ENDCOLLECTION_PREFIX: + if (collectionpath == NULL) + { + return -EINVAL; + } + + collectionpath = collectionpath->parent; + break; + + case USBHID_MAIN_INPUT_PREFIX: + case USBHID_MAIN_OUTPUT_PREFIX: + case USBHID_MAIN_FEATURE_PREFIX: + { + int itemno; + for (itemno = 0; itemno < currstate->rptcount; itemno++) + { + struct hid_rptitem_s newitem; + uint8_t tag; + + memcpy(&newitem.attrib, &currstate->attrib, + sizeof(struct hid_rptitem_attributes_s)); + + newitem.flags = data; + newitem.collectionpath = collectionpath; + newitem.id = currstate->id; + + if (nusage) + { + newitem.attrib.usage.usage = usage[0]; + + for (i = 0; i < nusage; i++) + { + usage[i] = usage[i + 1]; + } + nusage--; + } + else if (usage_range.min <= usage_range.max) + { + newitem.attrib.usage.usage = usage_range.min++; + } + + tag = (item & ~USBHID_RPTITEM_SIZE_MASK); + if (tag == USBHID_MAIN_INPUT_PREFIX) + { + newitem.type = HID_REPORT_ITEM_IN; + } + else if (tag == USBHID_MAIN_OUTPUT_PREFIX) + { + newitem.type = HID_REPORT_ITEM_OUT; + } + else + { + newitem.type = HID_REPORT_ITEM_FEATURE; + } + + newitem.bitoffset = rptidinfo->size[newitem.type]; + rptidinfo->size[newitem.type] += currstate->attrib.bitsize; + + /* Accumulate the maximum report size */ + + if (rptinfo->maxrptsize < newitem.bitoffset) + { + rptinfo->maxrptsize = newitem.bitoffset; + } + + if ((data & USBHID_MAIN_CONSTANT) == 0 && filter(&newitem)) + { + if (rptinfo->nitems == CONFIG_HID_MAXITEMS) + { + return -EINVAL; + } + + memcpy(&rptinfo->items[rptinfo->nitems], + &newitem, sizeof(struct hid_rptitem_s)); + + rptinfo->nitems++; + } + } + } + break; + } + + if ((item & USBHID_RPTITEM_TYPE_MASK) == USBHID_RPTITEM_TYPE_MAIN) + { + usage_range.min = 0; + usage_range.max = 0; + nusage = 0; + } + } + + if (!(rptinfo->nitems)) + { + return -ENOENT; + } + + return OK; +} + +/**************************************************************************** + * Name: hid_getitem + * + * Description: + * Extracts the given report item's value out of the given HID report and + * places it into the value member of the report item's struct hid_rptitem_s + * structure. + * + * When called on a report with an item that exists in that report, this + * copies the report item's Value to it's previous element for easy + * checking to see if an item's value has changed before processing a + * report. If the given item does not exist in the report, the function + * does not modify the report item's data. + * + * Input Parameters + * report Buffer containing an IN or FEATURE report from an attached + * device. + * item Pointer to the report item of interest in a struct hid_rptinfo_s + * item array. + * + * Returned Value: + * Zero on success, otherwise a negated errno value. + * + ****************************************************************************/ + +int hid_getitem(FAR const uint8_t *report, FAR struct hid_rptitem_s *item) +{ + uint16_t remaining = item->attrib.bitsize; + uint16_t offset = item->bitoffset; + uint32_t mask = (1 << 0); + + if (item->id) + { + if (item->id != report[0]) + { + return -ENOENT; + } + + report++; + } + + item->previous = item->value; + item->value = 0; + + while (remaining--) + { + if (report[offset >> 3] & (1 << (offset & 7))) + { + item->value |= mask; + } + + offset++; + mask <<= 1; + } + + return OK; +} + +/**************************************************************************** + * Name: hid_putitem + * + * Desription: + * Retrieves the given report item's value out of the Value member of the + * report item's struct hid_rptitem_s structure and places it into the correct + * position in the HID report buffer. The report buffer is assumed to have + * the appropriate bits cleared before calling this function (i.e., the + * buffer should be explicitly cleared before report values are added). + * + * When called, this copies the report item's Value element to it's + * previous element for easy checking to see if an item's value has + * changed before sending a report. + * + * If the device has multiple HID reports, the first byte in the report is + * set to the report ID of the given item. + * + * Input Parameters: + * report Buffer holding the current OUT or FEATURE report data. + * item Pointer to the report item of interest in a struct hid_rptinfo_s + * item array. + * + ****************************************************************************/ + +#if 0 /* Not needed by host */ +void hid_putitem(FAR uint8_t *report, struct hid_rptitem_s *item) +{ + uint16_t remaining = item->attrib.bitsize; + uint16_t offset = item->bitoffset; + uint32_t mask = (1 << 0); + + if (item->id) + { + report[0] = item->id; + report++; + } + + item->previous = item->value; + + while (remaining--) + { + if (item->value & (1 << (offset & 7))) + { + report[offset >> 3] |= mask; + } + + offset++; + mask <<= 1; + } +} +#endif + +/**************************************************************************** + * Name: hid_reportsize + * + * Description: + * Retrieves the size of a given HID report in bytes from it's Report ID. + * + * InputParameters: + * rptinfo Pointer to a struct hid_rptinfo_s instance containing the parser output. + * id Report ID of the report whose size is to be retrieved. + * rpttype Type of the report whose size is to be determined, a valued from the + * HID_ReportItemTypes_t enum. + * + * Size of the report in bytes, or 0 if the report does not exist. + * + ****************************************************************************/ + +size_t hid_reportsize(FAR struct hid_rptinfo_s *rptinfo, uint8_t id, uint8_t rpttype) +{ + int i; + for (i = 0; i < CONFIG_HID_MAXIDS; i++) + { + size_t size = rptinfo->rptsize[i].size[rpttype]; + + if (rptinfo->rptsize[i].id == id) + { + return ((size >> 3) + ((size & 0x07) ? 1 : 0)); + } + } + + return 0; +} diff --git a/nuttx/drivers/usbhost/usbhost_enumerate.c b/nuttx/drivers/usbhost/usbhost_enumerate.c new file mode 100644 index 000000000..8e1cd80e7 --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_enumerate.c @@ -0,0 +1,517 @@ +/******************************************************************************* + * drivers/usbhost/usbhost_enumerate.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Authors: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *******************************************************************************/ + +/******************************************************************************* + * Included Files + *******************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/arch.h> +#include <nuttx/usb/usb.h> +#include <nuttx/usb/usbhost.h> + +/******************************************************************************* + * Definitions + *******************************************************************************/ + +/******************************************************************************* + * Private Types + *******************************************************************************/ + +/******************************************************************************* + * Private Function Prototypes + *******************************************************************************/ + +static inline uint16_t usbhost_getle16(const uint8_t *val); +static void usbhost_putle16(uint8_t *dest, uint16_t val); + +static inline int usbhost_devdesc(const struct usb_devdesc_s *devdesc, + struct usbhost_id_s *id); +static inline int usbhost_configdesc(const uint8_t *configdesc, int desclen, + struct usbhost_id_s *id); +static inline int usbhost_classbind(FAR struct usbhost_driver_s *drvr, + const uint8_t *configdesc, int desclen, + struct usbhost_id_s *id, uint8_t funcaddr, + FAR struct usbhost_class_s **class); + +/******************************************************************************* + * Private Data + *******************************************************************************/ + +/******************************************************************************* + * Public Data + *******************************************************************************/ + +/******************************************************************************* + * Private Functions + *******************************************************************************/ + +/**************************************************************************** + * Name: usbhost_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + *******************************************************************************/ + +static inline uint16_t usbhost_getle16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +} + +/**************************************************************************** + * Name: usbhost_putle16 + * + * Description: + * Put a (possibly unaligned) 16-bit little endian value. + * + *******************************************************************************/ + +static void usbhost_putle16(uint8_t *dest, uint16_t val) +{ + dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ + dest[1] = val >> 8; +} + +/******************************************************************************* + * Name: usbhost_devdesc + * + * Description: + * A configuration descriptor has been obtained from the device. Find the + * ID information for the class that supports this device. + * + *******************************************************************************/ + +static inline int usbhost_devdesc(const struct usb_devdesc_s *devdesc, + struct usbhost_id_s *id) +{ + /* Clear the ID info */ + + memset(id, 0, sizeof(struct usbhost_id_s)); + + /* Pick off the class ID info */ + + id->base = devdesc->class; + id->subclass = devdesc->subclass; + id->proto = devdesc->protocol; + + /* Pick off the VID and PID as well (for vendor specfic devices) */ + + id->vid = usbhost_getle16(devdesc->vendor); + id->pid = usbhost_getle16(devdesc->product); + + uvdbg("class:%d subclass:%04x protocol:%04x vid:%d pid:%d\n", + id->base, id->subclass, id->proto, id->vid, id->pid); + return OK; +} + +/******************************************************************************* + * Name: usbhost_configdesc + * + * Description: + * A configuration descriptor has been obtained from the device. Find the + * ID information for the class that supports this device. + * + *******************************************************************************/ + +static inline int usbhost_configdesc(const uint8_t *configdesc, int cfglen, + struct usbhost_id_s *id) +{ + struct usb_cfgdesc_s *cfgdesc; + struct usb_ifdesc_s *ifdesc; + int remaining; + + DEBUGASSERT(configdesc != NULL && cfglen >= USB_SIZEOF_CFGDESC); + + /* Verify that we were passed a configuration descriptor */ + + cfgdesc = (struct usb_cfgdesc_s *)configdesc; + uvdbg("cfg len:%d total len:%d\n", cfgdesc->len, cfglen); + + if (cfgdesc->type != USB_DESC_TYPE_CONFIG) + { + return -EINVAL; + } + + /* Skip to the next entry descriptor */ + + configdesc += cfgdesc->len; + remaining = cfglen - cfgdesc->len; + + /* Loop where there are more dscriptors to examine */ + + memset(id, 0, sizeof(FAR struct usb_desc_s)); + while (remaining >= sizeof(struct usb_desc_s)) + { + /* What is the next descriptor? Is it an interface descriptor? */ + + ifdesc = (struct usb_ifdesc_s *)configdesc; + if (ifdesc->type == USB_DESC_TYPE_INTERFACE) + { + /* Yes, extract the class information from the interface descriptor. + * Typically these values are zero meaning that the "real" ID + * information resides in the device descriptor. + */ + + DEBUGASSERT(remaining >= sizeof(struct usb_ifdesc_s)); + id->base = ifdesc->class; + id->subclass = ifdesc->subclass; + id->proto = ifdesc->protocol; + uvdbg("class:%d subclass:%d protocol:%d\n", + id->base, id->subclass, id->proto); + return OK; + } + + /* Increment the address of the next descriptor */ + + configdesc += ifdesc->len; + remaining -= ifdesc->len; + } + + return -ENOENT; +} + +/******************************************************************************* + * Name: usbhost_classbind + * + * Description: + * A configuration descriptor has been obtained from the device. Try to + * bind this configuration descriptor with a supported class. + * + *******************************************************************************/ + +static inline int usbhost_classbind(FAR struct usbhost_driver_s *drvr, + const uint8_t *configdesc, int desclen, + struct usbhost_id_s *id, uint8_t funcaddr, + FAR struct usbhost_class_s **class) +{ + FAR struct usbhost_class_s *devclass; + const struct usbhost_registry_s *reg; + int ret = -EINVAL; + + /* Is there is a class implementation registered to support this device. */ + + reg = usbhost_findclass(id); + uvdbg("usbhost_findclass: %p\n", reg); + if (reg) + { + /* Yes.. there is a class for this device. Get an instance of + * its interface. + */ + + ret = -ENOMEM; + devclass = CLASS_CREATE(reg, drvr, id); + uvdbg("CLASS_CREATE: %p\n", devclass); + if (devclass) + { + /* Then bind the newly instantiated class instance */ + + ret = CLASS_CONNECT(devclass, configdesc, desclen, funcaddr); + if (ret != OK) + { + /* On failures, call the class disconnect method which + * should then free the allocated devclass instance. + */ + + udbg("CLASS_CONNECT failed: %d\n", ret); + CLASS_DISCONNECTED(devclass); + } + else + { + *class = devclass; + } + } + } + + uvdbg("Returning: %d\n", ret); + return ret; +} + +/******************************************************************************* + * Public Functions + *******************************************************************************/ + +/******************************************************************************* + * Name: usbhost_enumerate + * + * Description: + * Enumerate the connected device. As part of this enumeration process, + * the driver will (1) get the device's configuration descriptor, (2) + * extract the class ID info from the configuration descriptor, (3) call + * usbhost_findclass() to find the class that supports this device, (4) + * call the create() method on the struct usbhost_registry_s interface + * to get a class instance, and finally (5) call the configdesc() method + * of the struct usbhost_class_s interface. After that, the class is in + * charge of the sequence of operations. + * + * Input Parameters: + * drvr - The USB host driver instance obtained as a parameter from the call to + * the class create() method. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * class - If the class driver for the device is successful located + * and bound to the driver, the allocated class instance is returned into + * this caller-provided memory location. + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * - Only a single class bound to a single device is supported. + * - Called from a single thread so no mutual exclusion is required. + * - Never called from an interrupt handler. + * + *******************************************************************************/ + +int usbhost_enumerate(FAR struct usbhost_driver_s *drvr, uint8_t funcaddr, + FAR struct usbhost_class_s **class) +{ + struct usb_ctrlreq_s *ctrlreq; + struct usbhost_id_s id; + size_t maxlen; + unsigned int cfglen; + uint8_t maxpacketsize; + uint8_t *buffer; + int ret; + + DEBUGASSERT(drvr && class); + + /* Allocate TD buffers for use in this function. We will need two: + * One for the request and one for the data buffer. + */ + + ret = DRVR_ALLOC(drvr, (FAR uint8_t **)&ctrlreq, &maxlen); + if (ret != OK) + { + udbg("DRVR_ALLOC failed: %d\n", ret); + return ret; + } + + ret = DRVR_ALLOC(drvr, &buffer, &maxlen); + if (ret != OK) + { + udbg("DRVR_ALLOC failed: %d\n", ret); + goto errout; + } + + /* Set max pkt size = 8 */ + + DRVR_EP0CONFIGURE(drvr, 0, 8); + + /* Read first 8 bytes of the device descriptor */ + + ctrlreq->type = USB_REQ_DIR_IN|USB_REQ_RECIPIENT_DEVICE; + ctrlreq->req = USB_REQ_GETDESCRIPTOR; + usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_DEVICE << 8)); + usbhost_putle16(ctrlreq->index, 0); + usbhost_putle16(ctrlreq->len, 8); + + ret = DRVR_CTRLIN(drvr, ctrlreq, buffer); + if (ret != OK) + { + udbg("ERROR: GETDESCRIPTOR/DEVICE, DRVR_CTRLIN returned %d\n", ret); + goto errout; + } + + /* Extract the correct max packetsize from the device descriptor */ + + maxpacketsize = ((struct usb_devdesc_s *)buffer)->mxpacketsize; + uvdbg("maxpacksetsize: %d\n", maxpacketsize); + + /* And reconfigure EP0 */ + + DRVR_EP0CONFIGURE(drvr, 0, maxpacketsize); + + /* Now read the full device descriptor */ + + ctrlreq->type = USB_REQ_DIR_IN|USB_REQ_RECIPIENT_DEVICE; + ctrlreq->req = USB_REQ_GETDESCRIPTOR; + usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_DEVICE << 8)); + usbhost_putle16(ctrlreq->index, 0); + usbhost_putle16(ctrlreq->len, USB_SIZEOF_DEVDESC); + + ret = DRVR_CTRLIN(drvr, ctrlreq, buffer); + if (ret != OK) + { + udbg("ERROR: GETDESCRIPTOR/DEVICE, DRVR_CTRLIN returned %d\n", ret); + goto errout; + } + + /* Get class identification information from the device descriptor. Most + * devices set this to USB_CLASS_PER_INTERFACE (zero) and provide the + * identification informatino in the interface descriptor(s). That allows + * a device to support multiple, different classes. + */ + + (void)usbhost_devdesc((struct usb_devdesc_s *)buffer, &id); + + /* Set the USB device address to the value in the 'funcaddr' input */ + + ctrlreq->type = USB_REQ_DIR_OUT|USB_REQ_RECIPIENT_DEVICE; + ctrlreq->req = USB_REQ_SETADDRESS; + usbhost_putle16(ctrlreq->value, (uint16_t)funcaddr); + usbhost_putle16(ctrlreq->index, 0); + usbhost_putle16(ctrlreq->len, 0); + + ret = DRVR_CTRLOUT(drvr, ctrlreq, NULL); + if (ret != OK) + { + udbg("ERROR: SETADDRESS DRVR_CTRLOUT returned %d\n", ret); + goto errout; + } + up_mdelay(2); + + /* Modify control pipe with the provided USB device address */ + + DRVR_EP0CONFIGURE(drvr, funcaddr, maxpacketsize); + + /* Get the configuration descriptor (only), index == 0. Should not be + * hard-coded! More logic is needed in order to handle devices with + * multiple configurations. + */ + + ctrlreq->type = USB_REQ_DIR_IN|USB_REQ_RECIPIENT_DEVICE; + ctrlreq->req = USB_REQ_GETDESCRIPTOR; + usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_CONFIG << 8)); + usbhost_putle16(ctrlreq->index, 0); + usbhost_putle16(ctrlreq->len, USB_SIZEOF_CFGDESC); + + ret = DRVR_CTRLIN(drvr, ctrlreq, buffer); + if (ret != OK) + { + udbg("ERROR: GETDESCRIPTOR/CONFIG, DRVR_CTRLIN returned %d\n", ret); + goto errout; + } + + /* Extract the full size of the configuration data */ + + cfglen = (unsigned int)usbhost_getle16(((struct usb_cfgdesc_s *)buffer)->totallen); + uvdbg("sizeof config data: %d\n", cfglen); + + /* Get all of the configuration descriptor data, index == 0 (Should not be + * hard-coded!) + */ + + ctrlreq->type = USB_REQ_DIR_IN|USB_REQ_RECIPIENT_DEVICE; + ctrlreq->req = USB_REQ_GETDESCRIPTOR; + usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_CONFIG << 8)); + usbhost_putle16(ctrlreq->index, 0); + usbhost_putle16(ctrlreq->len, cfglen); + + ret = DRVR_CTRLIN(drvr, ctrlreq, buffer); + if (ret != OK) + { + udbg("ERROR: GETDESCRIPTOR/CONFIG, DRVR_CTRLIN returned %d\n", ret); + goto errout; + } + + /* Select device configuration 1 (Should not be hard-coded!) */ + + ctrlreq->type = USB_REQ_DIR_OUT|USB_REQ_RECIPIENT_DEVICE; + ctrlreq->req = USB_REQ_SETCONFIGURATION; + usbhost_putle16(ctrlreq->value, 1); + usbhost_putle16(ctrlreq->index, 0); + usbhost_putle16(ctrlreq->len, 0); + + ret = DRVR_CTRLOUT(drvr, ctrlreq, NULL); + if (ret != OK) + { + udbg("ERROR: SETCONFIGURATION, DRVR_CTRLOUT returned %d\n", ret); + goto errout; + } + + /* Free the TD that we were using for the request buffer. It is not needed + * further here but it may be needed by the class driver during its connection + * operations. + */ + + DRVR_FREE(drvr, (uint8_t*)ctrlreq); + ctrlreq = NULL; + + /* Was the class identification information provided in the device descriptor? + * Or do we need to find it in the interface descriptor(s)? + */ + + if (id.base == USB_CLASS_PER_INTERFACE) + { + /* Get the class identification information for this device from the + * interface descriptor(s). Hmmm.. More logic is need to handle the + * case of multiple interface descriptors. + */ + + ret = usbhost_configdesc(buffer, cfglen, &id); + if (ret != OK) + { + udbg("ERROR: usbhost_configdesc returned %d\n", ret); + goto errout; + } + } + + /* Some devices may require this delay before initialization */ + + up_mdelay(100); + + /* Parse the configuration descriptor and bind to the class instance for the + * device. This needs to be the last thing done because the class driver + * will begin configuring the device. + */ + + ret = usbhost_classbind(drvr, buffer, cfglen, &id, funcaddr, class); + if (ret != OK) + { + udbg("ERROR: usbhost_classbind returned %d\n", ret); + } + +errout: + if (buffer) + { + DRVR_FREE(drvr, buffer); + } + + if (ctrlreq) + { + DRVR_FREE(drvr, (uint8_t*)ctrlreq); + } + return ret; +} diff --git a/nuttx/drivers/usbhost/usbhost_findclass.c b/nuttx/drivers/usbhost/usbhost_findclass.c new file mode 100644 index 000000000..f08aff580 --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_findclass.c @@ -0,0 +1,199 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_findclass.c + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdbool.h> +#include <debug.h> + +#include <nuttx/usb/usb.h> +#include <nuttx/usb/usbhost.h> +#include <arch/irq.h> + +#include "usbhost_registry.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_idmatch + * + * Description: + * Check if the class ID matches what the host controller found. + * + * Input Parameters: + * classid - ID info for the class under consideration. + * devid - ID info reported by the device. + * + * Returned Values: + * TRUE - the class will support this device. + * + ****************************************************************************/ + +static bool usbhost_idmatch(const struct usbhost_id_s *classid, + const struct usbhost_id_s *devid) +{ + uvdbg("Compare to class:%d subclass:%d protocol:%d vid:%04x pid:%04x\n", + classid->base, classid->subclass, classid->proto, + classid->vid, classid->pid); + + /* The base class ID, subclass and protocol have to match up in any event */ + + if (devid->base == classid->base && + devid->subclass == classid->subclass && + devid->proto == classid->proto) + { + /* If this is a vendor-specific class ID, then the VID and PID have to + * match as well. + */ + + if (devid->base == USB_CLASS_VENDOR_SPEC) + { + /* Vendor specific... do the VID and PID also match? */ + + if (devid->vid == classid->vid && devid->pid == classid->pid) + { + /* Yes.. then we have a match */ + + return true; + } + } + else + { + /* Not vendor specific? Then we have a match */ + + return true; + } + } + + /* No match.. not supported */ + + return false; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_findclass + * + * Description: + * Find a USB host class implementation previously registered by + * usbhost_registerclass(). On success, an instance of struct + * usbhost_registry_s will be returned. That instance will contain all of + * the information that will be needed to obtain and bind a struct + * usbhost_class_s instance for the device. + * + * Input Parameters: + * id - Identifies the USB device class that has connect to the USB host. + * + * Returned Values: + * On success this function will return a non-NULL instance of struct + * usbhost_registry_s. NULL will be returned on failure. This function + * can only fail if (1) id is NULL, or (2) no USB host class is registered + * that matches the device class ID. + * + ****************************************************************************/ + +const struct usbhost_registry_s *usbhost_findclass(const struct usbhost_id_s *id) +{ + struct usbhost_registry_s *class; + irqstate_t flags; + int ndx; + + DEBUGASSERT(id); + uvdbg("Looking for class:%d subclass:%d protocol:%d vid:%04x pid:%04x\n", + id->base, id->subclass, id->proto, id->vid, id->pid); + + /* g_classregistry is a singly-linkedlist of class ID information added by + * calls to usbhost_registerclass(). Since this list is accessed from USB + * host controller interrupt handling logic, accesses to this list must be + * protected by disabling interrupts. + */ + + flags = irqsave(); + + /* Examine each register class in the linked list */ + + for (class = g_classregistry; class; class = class->flink) + { + /* If the registered class supports more than one ID, subclass, or + * protocol, then try each. + */ + + uvdbg("Checking class:%p nids:%d\n", class, class->nids); + for (ndx = 0; ndx < class->nids; ndx++) + { + /* Did we find a matching ID? */ + + if (usbhost_idmatch(&class->id[ndx], id)) + { + /* Yes.. restore interrupts and return the class info */ + + irqrestore(flags); + return class; + } + } + } + + /* Not found... restore interrupts and return NULL */ + + irqrestore(flags); + return NULL; +} + diff --git a/nuttx/drivers/usbhost/usbhost_hidkbd.c b/nuttx/drivers/usbhost/usbhost_hidkbd.c new file mode 100644 index 000000000..baf18703d --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_hidkbd.c @@ -0,0 +1,2048 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_hidkbd.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <poll.h> +#include <semaphore.h> +#include <time.h> +#include <fcntl.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> +#include <nuttx/arch.h> +#include <nuttx/wqueue.h> + +#include <nuttx/usb/usb.h> +#include <nuttx/usb/usbhost.h> +#include <nuttx/usb/hid.h> + +/* Don't compile if prerequisites are not met */ + +#if defined(CONFIG_USBHOST) && !defined(CONFIG_USBHOST_INT_DISABLE) && CONFIG_NFILE_DESCRIPTORS > 0 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ +/* This determines how often the USB keyboard will be polled in units of + * of microseconds. The default is 100MS. + */ + +#ifndef CONFIG_HIDKBD_POLLUSEC +# define CONFIG_HIDKBD_POLLUSEC (100*1000) +#endif + +/* Worker thread is needed, unfortunately, to handle some cornercase failure + * conditions. This is kind of wasteful and begs for a re-design. + */ + +#ifndef CONFIG_SCHED_WORKQUEUE +# warning "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + +/* Signals must not be disabled as they are needed by usleep. Need to have + * CONFIG_DISABLE_SIGNALS=n + */ + +#ifdef CONFIG_DISABLE_SIGNALS +# warning "Signal support is required (CONFIG_DISABLE_SIGNALS)" +#endif + +/* Provide some default values for other configuration settings */ + +#ifndef CONFIG_HIDKBD_DEFPRIO +# define CONFIG_HIDKBD_DEFPRIO 50 +#endif + +#ifndef CONFIG_HIDKBD_STACKSIZE +# define CONFIG_HIDKBD_STACKSIZE 1024 +#endif + +#ifndef CONFIG_HIDKBD_BUFSIZE +# define CONFIG_HIDKBD_BUFSIZE 64 +#endif + +#ifndef CONFIG_HIDKBD_NPOLLWAITERS +# define CONFIG_HIDKBD_NPOLLWAITERS 2 +#endif + +/* The default is to support scancode mapping for the standard 104 key + * keyboard. Setting CONFIG_HIDKBD_RAWSCANCODES will disable all scancode + * mapping; Setting CONFIG_HIDKBD_ALLSCANCODES will enable mapping of all + * scancodes; + */ + +#ifndef CONFIG_HIDKBD_RAWSCANCODES +# ifdef CONFIG_HIDKBD_ALLSCANCODES +# define USBHID_NUMSCANCODES (USBHID_KBDUSE_MAX+1) +# else +# define USBHID_NUMSCANCODES 104 +# endif +#endif + +/* Driver support ***********************************************************/ +/* This format is used to construct the /dev/kbd[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/kbd%c" +#define DEV_NAMELEN 11 + +/* Used in usbhost_cfgdesc() */ + +#define USBHOST_IFFOUND 0x01 /* Required I/F descriptor found */ +#define USBHOST_EPINFOUND 0x02 /* Required interrupt IN EP descriptor found */ +#define USBHOST_EPOUTFOUND 0x04 /* Optional interrupt OUT EP descriptor found */ +#define USBHOST_RQDFOUND (USBHOST_IFFOUND|USBHOST_EPINFOUND) +#define USBHOST_ALLFOUND (USBHOST_RQDFOUND|USBHOST_EPOUTFOUND) + +#define USBHOST_MAX_CREFS 0x7fff + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure contains the internal, private state of the USB host + * keyboard storage class. + */ + +struct usbhost_state_s +{ + /* This is the externally visible portion of the state */ + + struct usbhost_class_s class; + + /* This is an instance of the USB host driver bound to this class instance */ + + struct usbhost_driver_s *drvr; + + /* The remainder of the fields are provide o the keyboard class driver */ + + char devchar; /* Character identifying the /dev/kbd[n] device */ + volatile bool disconnected; /* TRUE: Device has been disconnected */ + volatile bool polling; /* TRUE: Poll thread is running */ + volatile bool open; /* TRUE: The keyboard device is open */ + volatile bool waiting; /* TRUE: waiting for keyboard data */ + uint8_t ifno; /* Interface number */ + int16_t crefs; /* Reference count on the driver instance */ + sem_t exclsem; /* Used to maintain mutual exclusive access */ + sem_t waitsem; /* Used to wait for keyboard data */ + FAR uint8_t *tbuffer; /* The allocated transfer buffer */ + size_t tbuflen; /* Size of the allocated transfer buffer */ + pid_t pollpid; /* PID of the poll task */ + struct work_s work; /* For cornercase error handling by the worker thread */ + + /* Endpoints: + * EP0 (Control): + * - Receiving and responding to requests for USB control and class data. + * - IN data when polled by the HID class driver (Get_Report) + * - OUT data from the host. + * EP Interrupt IN: + * - Receiving asynchronous (unrequested) IN data from the device. + * EP Interrrupt OUT (optional): + * - Transmitting low latency OUT data to the device. + * - If not present, EP0 used. + */ + + usbhost_ep_t epin; /* Interrupt IN endpoint */ + usbhost_ep_t epout; /* Optional interrupt OUT endpoint */ + + /* The following is a list if poll structures of threads waiting for + * driver events. The 'struct pollfd' reference for each open is also + * retained in the f_priv field of the 'struct file'. + */ + +#ifndef CONFIG_DISABLE_POLL + struct pollfd *fds[CONFIG_HIDKBD_NPOLLWAITERS]; +#endif + + /* Buffer used to collect and buffer incoming keyboard characters */ + + volatile uint16_t headndx; /* Buffer head index */ + volatile uint16_t tailndx; /* Buffer tail index */ + uint8_t kbdbuffer[CONFIG_HIDKBD_BUFSIZE]; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Semaphores */ + +static void usbhost_takesem(sem_t *sem); +#define usbhost_givesem(s) sem_post(s); + +/* Polling support */ + +#ifndef CONFIG_DISABLE_POLL +static void usbhost_pollnotify(FAR struct usbhost_state_s *dev); +#else +# define usbhost_pollnotify(dev) +#endif + +/* Memory allocation services */ + +static inline FAR struct usbhost_state_s *usbhost_allocclass(void); +static inline void usbhost_freeclass(FAR struct usbhost_state_s *class); + +/* Device name management */ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv); +static void usbhost_freedevno(FAR struct usbhost_state_s *priv); +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname); + +/* Keyboard polling thread */ + +static void usbhost_destroy(FAR void *arg); +static inline uint8_t usbhost_mapscancode(uint8_t scancode, uint8_t modifier); +static int usbhost_kbdpoll(int argc, char *argv[]); + +/* Helpers for usbhost_connect() */ + +static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr); +static inline int usbhost_devinit(FAR struct usbhost_state_s *priv); + +/* (Little Endian) Data helpers */ + +static inline uint16_t usbhost_getle16(const uint8_t *val); +static inline void usbhost_putle16(uint8_t *dest, uint16_t val); +static inline uint32_t usbhost_getle32(const uint8_t *val); +static void usbhost_putle32(uint8_t *dest, uint32_t val); + +/* Transfer descriptor memory management */ + +static inline int usbhost_tdalloc(FAR struct usbhost_state_s *priv); +static inline int usbhost_tdfree(FAR struct usbhost_state_s *priv); + +/* struct usbhost_registry_s methods */ + +static struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_id_s *id); + +/* struct usbhost_class_s methods */ + +static int usbhost_connect(FAR struct usbhost_class_s *class, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr); +static int usbhost_disconnected(FAR struct usbhost_class_s *class); + +/* Driver methods. We export the keyboard as a standard character driver */ + +static int usbhost_open(FAR struct file *filep); +static int usbhost_close(FAR struct file *filep); +static ssize_t usbhost_read(FAR struct file *filep, + FAR char *buffer, size_t len); +static ssize_t usbhost_write(FAR struct file *filep, + FAR const char *buffer, size_t len); +#ifndef CONFIG_DISABLE_POLL +static int usbhost_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This structure provides the registry entry ID informatino that will be + * used to associate the USB host keyboard class driver to a connected USB + * device. + */ + +static const const struct usbhost_id_s g_id = +{ + USB_CLASS_HID, /* base */ + USBHID_SUBCLASS_BOOTIF, /* subclass */ + USBHID_PROTOCOL_KEYBOARD, /* proto */ + 0, /* vid */ + 0 /* pid */ +}; + +/* This is the USB host storage class's registry entry */ + +static struct usbhost_registry_s g_skeleton = +{ + NULL, /* flink */ + usbhost_create, /* create */ + 1, /* nids */ + &g_id /* id[] */ +}; + +static const struct file_operations usbhost_fops = +{ + usbhost_open, /* open */ + usbhost_close, /* close */ + usbhost_read, /* read */ + usbhost_write, /* write */ + 0, /* seek */ + 0 /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , usbhost_poll /* poll */ +#endif +}; + +/* This is a bitmap that is used to allocate device names /dev/kbda-z. */ + +static uint32_t g_devinuse; + +/* The following are used to managed the class creation operation */ + +static sem_t g_exclsem; /* For mutually exclusive thread creation */ +static sem_t g_syncsem; /* Thread data passing interlock */ +static struct usbhost_state_s *g_priv; /* Data passed to thread */ + +/* The following tables map keyboard scan codes to printable ASIC + * characters. There is no support here for function keys or cursor + * controls. + */ + +#ifndef CONFIG_HIDKBD_RAWSCANCODES +static const uint8_t ucmap[USBHID_NUMSCANCODES] = +{ + 0, 0, 0, 0, 'A', 'B', 'C', 'D', /* 0x00-0x07: Reserved, errors, A-D */ + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', /* 0x08-0x0f: E-L */ + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 0x10-0x17: M-T */ + 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@', /* 0x18-0x1f: U-Z,!,@ */ + '#', '$', '%', '^', '&', '*', '(', ')', /* 0x20-0x27: #,$,%,^,&,*,(,) */ + '\n', '\033', '\177', 0, ' ', '_', '+', '{', /* 0x28-0x2f: Enter,escape,del,back-tab,space,_,+,{ */ + '}', '|', 0, ':', '"', 0, '<', '>', /* 0x30-0x37: },|,Non-US tilde,:,",grave tidle,<,> */ + '?', 0, 0, 0, 0, 0, 0, 0, /* 0x38-0x3f: /,CapsLock,F1,F2,F3,F4,F5,F6 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x47: F7,F8,F9,F10,F11,F12,PrtScn,sScrollLock */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48-0x4f: Pause,Insert,Home,PageUp,DeleteForward,End,PageDown,RightArrow */ + 0, 0, 0, 0, '/', '*', '-', '+', /* 0x50-0x57: LeftArrow,DownArrow,UpArrow,Num Lock,/,*,-,+ */ + '\n', '1', '2', '3', '4', '4', '6', '7', /* 0x58-0x5f: Enter,1-7 */ + '8', '9', '0', '.', 0, 0, 0, '=', /* 0x60-0x67: 8-9,0,.,Non-US \,Application,Power,= */ +#ifdef CONFIG_HIDKBD_ALLSCANCODES + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x68-0x6f: F13,F14,F15,F16,F17,F18,F19,F20 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70-0x77: F21,F22,F23,F24,Execute,Help,Menu,Select */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x78-0x7f: Stop,Again,Undo,Cut,Copy,Paste,Find,Mute */ + 0, 0, 0, 0, 0, ',', 0, 0, /* 0x80-0x87: VolUp,VolDown,LCapsLock,lNumLock,LScrollLock,,,=,International1 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x88-0x8f: International 2-9 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90-0x97: LAN 1-8 */ + 0, 0, 0, 0, 0, 0, '\n', 0, /* 0x98-0x9f: LAN 9,Ease,SysReq,Cancel,Clear,Prior,Return,Separator */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0-0xa7: Out,Oper,Clear,CrSel,Excel,(reserved) */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa8-0xaf: (reserved) */ + 0, 0, 0, 0, 0, 0, '(', ')', /* 0xb0-0xb7: 00,000,ThouSeparator,DecSeparator,CurrencyUnit,SubUnit,(,) */ + '{', '}', '\t', \177, 'A', 'B', 'C', 'D', /* 0xb8-0xbf: {,},tab,backspace,A-D */ + 'F', 'F', 0, '^', '%', '<', '>', '&', /* 0xc0-0xc7: E-F,XOR,^,%,<,>,& */ + 0, '|', 0, ':', '%', ' ', '@', '!', /* 0xc8-0xcf: &&,|,||,:,#, ,@,! */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0-0xd7: Memory Store,Recall,Clear,Add,Subtract,Muliply,Divide,+/- */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd8-0xdf: Clear,ClearEntry,Binary,Octal,Decimal,Hexadecimal */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0-0xe7: Left Ctrl,Shift,Alt,GUI, Right Ctrl,Shift,Alt,GUI */ +#endif +}; + +static const uint8_t lcmap[USBHID_NUMSCANCODES] = +{ + 0, 0, 0, 0, 'a', 'b', 'c', 'd', /* 0x00-0x07: Reserved, errors, a-d */ + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', /* 0x08-0x0f: e-l */ + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 0x10-0x17: m-t */ + 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', /* 0x18-0x1f: u-z,1-2 */ + '3', '4', '5', '6', '7', '8', '9', '0', /* 0x20-0x27: 3-9,0 */ + '\n', '\033', '\177', '\t', ' ', '-', '=', '[', /* 0x28-0x2f: Enter,escape,del,tab,space,-,=,[ */ + ']', '\\', '\234', ';', '\'', 0, ',', '.', /* 0x30-0x37: ],\,Non-US pound,;,',grave accent,,,. */ + '/', 0, 0, 0, 0, 0, 0, 0, /* 0x38-0x3f: /,CapsLock,F1,F2,F3,F4,F5,F6 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x47: F7,F8,F9,F10,F11,F12,PrtScn,ScrollLock */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48-0x4f: Pause,Insert,Home,PageUp,DeleteForward,End,PageDown,RightArrow */ + 0, 0, 0, 0, '/', '*', '-', '+', /* 0x50-0x57: LeftArrow,DownArrow,UpArrow,Num Lock,/,*,-,+ */ + '\n', '1', '2', '3', '4', '4', '6', '7', /* 0x58-0x5f: Enter,1-7 */ + '8', '9', '0', '.', 0, 0, 0, '=', /* 0x60-0x67: 8-9,0,.,Non-US \,Application,Power,= */ +#ifdef CONFIG_HIDKBD_ALLSCANCODES + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x68-0x6f: F13,F14,F15,F16,F17,F18,F19,F20 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70-0x77: F21,F22,F23,F24,Execute,Help,Menu,Select */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x78-0x7f: Stop,Again,Undo,Cut,Copy,Paste,Find,Mute */ + 0, 0, 0, 0, 0, ',', 0, 0, /* 0x80-0x87: VolUp,VolDown,LCapsLock,lNumLock,LScrollLock,,,=,International1 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x88-0x8f: International 2-9 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90-0x97: LAN 1-8 */ + 0, 0, 0, 0, 0, 0, '\n', 0, /* 0x98-0x9f: LAN 9,Ease,SysReq,Cancel,Clear,Prior,Return,Separator */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0-0xa7: Out,Oper,Clear,CrSel,Excel,(reserved) */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa8-0xaf: (reserved) */ + 0, 0, 0, 0, 0, 0, '(', ')', /* 0xb0-0xb7: 00,000,ThouSeparator,DecSeparator,CurrencyUnit,SubUnit,(,) */ + '{', '}', '\t', '\177', 'A', 'B', 'C', 'D', /* 0xb8-0xbf: {,},tab,backspace,A-D */ + 'F', 'F', 0, '^', '%', '<', '>', '&', /* 0xc0-0xc7: E-F,XOR,^,%,<,>,& */ + 0, '|', 0, ':', '%', ' ', '@', '!', /* 0xc8-0xcf: &&,|,||,:,#, ,@,! */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0-0xd7: Memory Store,Recall,Clear,Add,Subtract,Muliply,Divide,+/- */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd8-0xdf: Clear,ClearEntry,Binary,Octal,Decimal,Hexadecimal */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0-0xe7: Left Ctrl,Shift,Alt,GUI, Right Ctrl,Shift,Alt,GUI */ +#endif +}; +#endif /* CONFIG_HIDKBD_RAWSCANCODES */ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_takesem + * + * Description: + * This is just a wrapper to handle the annoying behavior of semaphore + * waits that return due to the receipt of a signal. + * + ****************************************************************************/ + +static void usbhost_takesem(sem_t *sem) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(sem) != 0) + { + /* The only case that an error should occr here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: usbhost_pollnotify + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static void usbhost_pollnotify(FAR struct usbhost_state_s *priv) +{ + int i; + + for (i = 0; i < CONFIG_HIDKBD_NPOLLWAITERS; i++) + { + struct pollfd *fds = priv->fds[i]; + if (fds) + { + fds->revents |= (fds->events & POLLIN); + if (fds->revents != 0) + { + uvdbg("Report events: %02x\n", fds->revents); + sem_post(fds->sem); + } + } + } +} +#endif + +/**************************************************************************** + * Name: usbhost_allocclass + * + * Description: + * This is really part of the logic that implements the create() method + * of struct usbhost_registry_s. This function allocates memory for one + * new class instance. + * + * Input Parameters: + * None + * + * Returned Values: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s. NULL is returned on failure; this function will + * will fail only if there are insufficient resources to create another + * USB host class instance. + * + ****************************************************************************/ + +static inline FAR struct usbhost_state_s *usbhost_allocclass(void) +{ + FAR struct usbhost_state_s *priv; + + DEBUGASSERT(!up_interrupt_context()); + priv = (FAR struct usbhost_state_s *)kmalloc(sizeof(struct usbhost_state_s)); + uvdbg("Allocated: %p\n", priv);; + return priv; +} + +/**************************************************************************** + * Name: usbhost_freeclass + * + * Description: + * Free a class instance previously allocated by usbhost_allocclass(). + * + * Input Parameters: + * class - A reference to the class instance to be freed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline void usbhost_freeclass(FAR struct usbhost_state_s *class) +{ + DEBUGASSERT(class != NULL); + + /* Free the class instance. */ + + uvdbg("Freeing: %p\n", class);; + kfree(class); +} + +/**************************************************************************** + * Name: Device name management + * + * Description: + * Some tiny functions to coordinate management of device names. + * + ****************************************************************************/ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv) +{ + irqstate_t flags; + int devno; + + flags = irqsave(); + for (devno = 0; devno < 26; devno++) + { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) + { + g_devinuse |= bitno; + priv->devchar = 'a' + devno; + irqrestore(flags); + return OK; + } + } + + irqrestore(flags); + return -EMFILE; +} + +static void usbhost_freedevno(FAR struct usbhost_state_s *priv) +{ + int devno = 'a' - priv->devchar; + + if (devno >= 0 && devno < 26) + { + irqstate_t flags = irqsave(); + g_devinuse &= ~(1 << devno); + irqrestore(flags); + } +} + +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname) +{ + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->devchar); +} + +/**************************************************************************** + * Name: usbhost_destroy + * + * Description: + * The USB device has been disconnected and the refernce count on the USB + * host class instance has gone to 1.. Time to destroy the USB host class + * instance. + * + * Input Parameters: + * arg - A reference to the class instance to be destroyed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_destroy(FAR void *arg) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)arg; + char devname[DEV_NAMELEN]; + + DEBUGASSERT(priv != NULL); + uvdbg("crefs: %d\n", priv->crefs); + + /* Unregister the driver */ + + uvdbg("Unregister driver\n"); + usbhost_mkdevname(priv, devname); + (void)unregister_driver(devname); + + /* Release the device name used by this connection */ + + usbhost_freedevno(priv); + + /* Free the interrupt endpoints */ + + if (priv->epin) + { + DRVR_EPFREE(priv->drvr, priv->epin); + } + + if (priv->epout) + { + DRVR_EPFREE(priv->drvr, priv->epout); + } + + /* Free any transfer buffers */ + + usbhost_tdfree(priv); + + /* Destroy the semaphores */ + + sem_destroy(&priv->exclsem); + sem_destroy(&priv->waitsem); + + /* Disconnect the USB host device */ + + DRVR_DISCONNECT(priv->drvr); + + /* And free the class instance. Hmmm.. this may execute on the worker + * thread and the work structure is part of what is getting freed. That + * should be okay because once the work contained is removed from the + * queue, it should not longer be accessed by the worker thread. + */ + + usbhost_freeclass(priv); +} + +/**************************************************************************** + * Name: usbhost_mapscancode + * + * Description: + * Map a keyboard scancode to a printable ASCII character. There is no + * support here for function keys or cursor controls in this version of + * the driver. + * + * Input Parameters: + * scancode - Scan code to be mapped. + * modifier - Ctrl,Alt,Shift,GUI modifier bits + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline uint8_t usbhost_mapscancode(uint8_t scancode, uint8_t modifier) +{ +#ifndef CONFIG_HIDKBD_RAWSCANCODES + /* Range check */ + + if (scancode >= USBHID_NUMSCANCODES) + { + return 0; + } + + /* Is either shift key pressed? */ + + if ((modifier & (USBHID_MODIFER_LSHIFT|USBHID_MODIFER_RSHIFT)) != 0) + { + return ucmap[scancode]; + } + else + { + return lcmap[scancode]; + } +#else + return scancode; +#endif +} + +/**************************************************************************** + * Name: usbhost_kbdpoll + * + * Description: + * Periodically check for new keyboard data. + * + * Input Parameters: + * arg - A reference to the class instance to be destroyed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static int usbhost_kbdpoll(int argc, char *argv[]) +{ + FAR struct usbhost_state_s *priv; + FAR struct usb_ctrlreq_s *ctrlreq; +#ifndef CONFIG_HIDKBD_NODEBOUNCE + uint8_t lastkey[6] = {0, 0, 0, 0, 0, 0}; +#endif +#if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_VERBOSE) + unsigned int npolls = 0; +#endif + unsigned int nerrors; + int ret; + + uvdbg("Started\n"); + + /* Synchronize with the start-up logic. Get the private instance, re-start + * the start-up logic, and wait a bit to make sure that all of the class + * creation logic has a chance to run to completion. + * + * NOTE: that the reference count is incremented here. Therefore, we know + * that the driver data structure will remain stable while this thread is + * running. + */ + + priv = g_priv; + DEBUGASSERT(priv != NULL); + + priv->polling = true; + priv->crefs++; + usbhost_givesem(&g_syncsem); + sleep(1); + + /* Loop here until the device is disconnected */ + + uvdbg("Entering poll loop\n"); + while (!priv->disconnected) + { + /* Make sure that we have exclusive access to the private data + * structure. There may now be other tasks with the character driver + * open and actively trying to interact with the class driver. + */ + + usbhost_takesem(&priv->exclsem); + + /* Format the HID report request: + * + * bmRequestType 10100001 + * bRequest GET_REPORT (0x01) + * wValue Report Type and Report Index + * wIndex Interface Number + * wLength Descriptor Length + * Data Descriptor Data + */ + + ctrlreq = (struct usb_ctrlreq_s *)priv->tbuffer; + ctrlreq->type = USB_REQ_DIR_IN|USB_REQ_TYPE_CLASS|USB_REQ_RECIPIENT_INTERFACE; + ctrlreq->req = USBHID_REQUEST_GETREPORT; + + usbhost_putle16(ctrlreq->value, (USBHID_REPORTTYPE_INPUT << 8)); + usbhost_putle16(ctrlreq->index, priv->ifno); + usbhost_putle16(ctrlreq->len, sizeof(struct usbhid_kbdreport_s)); + + /* Send HID report request */ + + ret = DRVR_CTRLIN(priv->drvr, ctrlreq, priv->tbuffer); + usbhost_givesem(&priv->exclsem); + + /* Check for errors -- Bail if an excessive number of errors + * are encountered. + */ + + if (ret != OK) + { + nerrors++; + udbg("ERROR: GETREPORT/INPUT, DRVR_CTRLIN returned: %d/%d\n", + ret, nerrors); + + if (nerrors > 200) + { + udbg("Too many errors... aborting: %d\n", nerrors); + break; + } + } + + /* The report was received correctly. But ignore the keystrokes if no + * task has opened the driver. + */ + + else if (priv->open) + { + struct usbhid_kbdreport_s *rpt = (struct usbhid_kbdreport_s *)priv->tbuffer; + unsigned int head; + unsigned int tail; + uint8_t ascii; + int i; + + /* Add the newly received keystrokes to our internal buffer */ + + usbhost_takesem(&priv->exclsem); + head = priv->headndx; + tail = priv->tailndx; + + for (i = 0; i < 6; i++) + { + /* Is this key pressed? But not pressed last time? + * HID spec: "The order of keycodes in array fields has no + * significance. Order determination is done by the host + * software comparing the contents of the previous report to + * the current report. If two or more keys are reported in + * one report, their order is indeterminate. Keyboards may + * buffer events that would have otherwise resulted in + * multiple event in a single report. + * + * "'Repeat Rate' and 'Delay Before First Repeat' are + * implemented by the host and not in the keyboard (this + * means the BIOS in legacy mode). The host may use the + * device report rate and the number of reports to determine + * how long a key is being held down. Alternatively, the host + * may use its own clock or the idle request for the timing + * of these features." + */ + + if (rpt->key[i] != USBHID_KBDUSE_NONE +#ifndef CONFIG_HIDKBD_NODEBOUNCE + && rpt->key[i] != lastkey[i] +#endif + ) + { + /* Yes.. Add it to the buffer. */ + + /* Map the keyboard scancode to a printable ASCII + * character. There is no support here for function keys + * or cursor controls in this version of the driver. + */ + + ascii = usbhost_mapscancode(rpt->key[i], rpt->modifier); + uvdbg("Key %d: %02x ASCII:%c modifier: %02x\n", + i, rpt->key[i], ascii ? ascii : ' ', rpt->modifier); + + /* Zero at this point means that the key does not map to a + * printable character. + */ + + if (ascii != 0) + { + /* Handle control characters. Zero after this means + * a valid, NUL character. + */ + + if ((rpt->modifier & (USBHID_MODIFER_LCTRL|USBHID_MODIFER_RCTRL)) != 0) + { + ascii &= 0x1f; + } + + /* Copy the next keyboard character into the user + * buffer. + */ + + priv->kbdbuffer[head] = ascii; + + /* Increment the head index */ + + if (++head >= CONFIG_HIDKBD_BUFSIZE) + { + head = 0; + } + + /* If the buffer is full, then increment the tail + * index to make space. Is it better to lose old + * keystrokes or new? + */ + + if (tail == head) + { + if (++tail >= CONFIG_HIDKBD_BUFSIZE) + { + tail = 0; + } + } + } + } + /* Save the scancode (or lack thereof) for key debouncing on + * next keyboard report. + */ + +#ifndef CONFIG_HIDKBD_NODEBOUNCE + lastkey[i] = rpt->key[i]; +#endif + } + + /* Did we just transition from no data available to data available? */ + + if (head != tail && priv->headndx == priv->tailndx) + { + /* Yes.. Is there a thread waiting for keyboard data now? */ + + if (priv->waiting) + { + /* Yes.. wake it up */ + + usbhost_givesem(&priv->waitsem); + priv->waiting = false; + } + + /* And wake up any threads waiting for the POLLIN event */ + + usbhost_pollnotify(priv); + } + + /* Update the head/tail indices */ + + priv->headndx = head; + priv->tailndx = tail; + usbhost_givesem(&priv->exclsem); + } + + /* If USB debug is on, then provide some periodic indication that + * polling is still happening. + */ + +#if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_VERBOSE) + npolls++; + if ((npolls & 31) == 0) + { + udbg("Still polling: %d\n", npolls); + } +#endif + /* Wait for the required amount (or until a signal is received). We + * will wake up when either the delay elapses or we are signalled that + * the device has been disconnected. + */ + + usleep(CONFIG_HIDKBD_POLLUSEC); + } + + /* We get here when the driver is removed.. or when too many errors have + * been encountered. + * + * Make sure that we have exclusive access to the private data structure. + * There may now be other tasks with the character driver open and actively + * trying to interact with the class driver. + */ + + usbhost_takesem(&priv->exclsem); + + /* Indicate that we are no longer running and decrement the reference + * count help by this thread. If there are no other users of the class, + * we can destroy it now. Otherwise, we have to wait until the all + * of the file descriptors are closed. + */ + + udbg("Keyboard removed, polling halted\n"); + priv->polling = false; + if (--priv->crefs < 2) + { + /* Destroy the instance (while we hold the semaphore!) */ + + usbhost_destroy(priv); + } + else + { + /* No, we will destroy the driver instance when it is finally closed */ + + usbhost_givesem(&priv->exclsem); + } + return 0; +} + +/**************************************************************************** + * Name: usbhost_cfgdesc + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * priv - The USB host class instance. + * configdesc - A pointer to a uint8_t buffer container the configuration descripor. + * desclen - The length in bytes of the configuration descriptor. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr) +{ + FAR struct usb_cfgdesc_s *cfgdesc; + FAR struct usb_desc_s *desc; + FAR struct usbhost_epdesc_s epindesc; + FAR struct usbhost_epdesc_s epoutdesc; + int remaining; + uint8_t found = 0; + bool done = false; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + /* Verify that we were passed a configuration descriptor */ + + cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc; + if (cfgdesc->type != USB_DESC_TYPE_CONFIG) + { + return -EINVAL; + } + + /* Get the total length of the configuration descriptor (little endian). + * It might be a good check to get the number of interfaces here too. + */ + + remaining = (int)usbhost_getle16(cfgdesc->totallen); + + /* Skip to the next entry descriptor */ + + configdesc += cfgdesc->len; + remaining -= cfgdesc->len; + + /* Loop where there are more dscriptors to examine */ + + while (remaining >= sizeof(struct usb_desc_s) && !done) + { + /* What is the next descriptor? */ + + desc = (FAR struct usb_desc_s *)configdesc; + switch (desc->type) + { + /* Interface descriptor. We really should get the number of endpoints + * from this descriptor too. + */ + + case USB_DESC_TYPE_INTERFACE: + { + FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)configdesc; + + uvdbg("Interface descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC); + + /* Did we already find what we needed from a preceding interface? */ + + if ((found & USBHOST_RQDFOUND) == USBHOST_RQDFOUND) + { + /* Yes.. then break out of the loop and use the preceding + * interface. + */ + + done = true; + } + else + { + /* Otherwise, save the interface number and discard any + * endpoints previously found + */ + + priv->ifno = ifdesc->ifno; + found = USBHOST_IFFOUND; + } + } + break; + + /* HID descriptor */ + + case USBHID_DESCTYPE_HID: + uvdbg("HID descriptor\n"); + break; + + /* Endpoint descriptor. We expect one or two interrupt endpoints, + * a required IN endpoint and an optional OUT endpoint. + */ + + case USB_DESC_TYPE_ENDPOINT: + { + FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)configdesc; + + uvdbg("Endpoint descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC); + + /* Check for an interrupt endpoint. */ + + if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_INT) + { + /* Yes.. it is a interrupt endpoint. IN or OUT? */ + + if (USB_ISEPOUT(epdesc->addr)) + { + /* It is an interrupt OUT endpoint. There not be more than one + * interrupt OUT endpoint. + */ + + if ((found & USBHOST_EPOUTFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know what to do with this. */ + + return -EINVAL; + } + found |= USBHOST_EPOUTFOUND; + + /* Save the interrupt OUT endpoint information */ + + epoutdesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + epoutdesc.in = false; + epoutdesc.funcaddr = funcaddr; + epoutdesc.xfrtype = USB_EP_ATTR_XFER_INT; + epoutdesc.interval = epdesc->interval; + epoutdesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + uvdbg("Interrupt OUT EP addr:%d mxpacketsize:%d\n", + epoutdesc.addr, epoutdesc.mxpacketsize); + } + else + { + /* It is an interrupt IN endpoint. There should be only + * one interrupt IN endpoint. + */ + + if ((found & USBHOST_EPINFOUND) != 0) + { + /* Oops.. more than one endpint. We don't know what + * to do with this. + */ + + return -EINVAL; + } + found |= USBHOST_EPINFOUND; + + /* Save the interrupt IN endpoint information */ + + epindesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + epindesc.in = 1; + epindesc.funcaddr = funcaddr; + epindesc.xfrtype = USB_EP_ATTR_XFER_INT; + epindesc.interval = epdesc->interval; + epindesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + uvdbg("Interrupt IN EP addr:%d mxpacketsize:%d\n", + epindesc.addr, epindesc.mxpacketsize); + } + } + } + break; + + /* Other descriptors are just ignored for now */ + + default: + uvdbg("Other descriptor: %d\n", desc->type); + break; + } + + /* What we found everything that we are going to find? */ + + if (found == USBHOST_ALLFOUND) + { + /* Yes.. then break out of the loop and use the preceding interface */ + + done = true; + } + + /* Increment the address of the next descriptor */ + + configdesc += desc->len; + remaining -= desc->len; + } + + /* Sanity checking... did we find all of things that we need? */ + + if ((found & USBHOST_RQDFOUND) != USBHOST_RQDFOUND) + { + ulldbg("ERROR: Found IF:%s EPIN:%s\n", + (found & USBHOST_IFFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_EPINFOUND) != 0 ? "YES" : "NO"); + return -EINVAL; + } + + /* We are good... Allocate the endpoints. First, the required interrupt + * IN endpoint. + */ + + ret = DRVR_EPALLOC(priv->drvr, &epindesc, &priv->epin); + if (ret != OK) + { + udbg("ERROR: Failed to allocate interrupt IN endpoint\n"); + return ret; + } + + /* Then the optional interrupt OUT endpoint */ + + ullvdbg("Found EPOOUT:%s\n", + (found & USBHOST_EPOUTFOUND) != 0 ? "YES" : "NO"); + + if ((found & USBHOST_EPOUTFOUND) != 0) + { + ret = DRVR_EPALLOC(priv->drvr, &epoutdesc, &priv->epout); + if (ret != OK) + { + udbg("ERROR: Failed to allocate interrupt OUT endpoint\n"); + (void)DRVR_EPFREE(priv->drvr, priv->epin); + return ret; + } + } + + ullvdbg("Endpoints allocated\n"); + return OK; +} + +/**************************************************************************** + * Name: usbhost_devinit + * + * Description: + * The USB device has been successfully connected. This completes the + * initialization operations. It is first called after the + * configuration descriptor has been received. + * + * This function is called from the connect() method. This function always + * executes on the thread of the caller of connect(). + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline int usbhost_devinit(FAR struct usbhost_state_s *priv) +{ + char devname[DEV_NAMELEN]; + int ret; + + /* Set aside a transfer buffer for exclusive use by the keyboard class driver */ + + ret = usbhost_tdalloc(priv); + if (ret != OK) + { + udbg("ERROR: Failed to allocate transfer buffer\n"); + return ret; + } + + /* Increment the reference count. This will prevent usbhost_destroy() from + * being called asynchronously if the device is removed. + */ + + priv->crefs++; + DEBUGASSERT(priv->crefs == 2); + + /* Start a worker task to poll the USB device. It would be nice to used the + * the NuttX worker thread to do this, but this task needs to wait for events + * and activities on the worker thread should not involve significant waiting. + * Having a dedicated thread is more efficient in this sense, but requires more + * memory resources, primarily for the dedicated stack (CONFIG_HIDKBD_STACKSIZE). + */ + + uvdbg("user_start: Start poll task\n"); + + /* The inputs to a task started by task_create() are very awkard for this + * purpose. They are really designed for command line tasks (argc/argv). So + * the following is kludge pass binary data when the keyboard poll task + * is started. + * + * First, make sure we have exclusive access to g_priv (what is the likelihood + * of this being used? About zero, but we protect it anyway). + */ + + usbhost_takesem(&g_exclsem); + g_priv = priv; + +#ifndef CONFIG_CUSTOM_STACK + priv->pollpid = task_create("usbhost", CONFIG_HIDKBD_DEFPRIO, + CONFIG_HIDKBD_STACKSIZE, + (main_t)usbhost_kbdpoll, (const char **)NULL); +#else + priv->pollpid = task_create("usbhost", CONFIG_HIDKBD_DEFPRIO, + (main_t)usbhost_kbdpoll, (const char **)NULL); +#endif + if (priv->pollpid == ERROR) + { + /* Failed to started the poll thread... probably due to memory resources */ + + usbhost_givesem(&g_exclsem); + ret = -ENOMEM; + goto errout; + } + + /* Now wait for the poll task to get properly initialized */ + + usbhost_takesem(&g_syncsem); + usbhost_givesem(&g_exclsem); + + /* Register the driver */ + + uvdbg("Register driver\n"); + usbhost_mkdevname(priv, devname); + ret = register_driver(devname, &usbhost_fops, 0666, priv); + + /* We now have to be concerned about asynchronous modification of crefs + * because the driver has been registerd. + */ + +errout: + usbhost_takesem(&priv->exclsem); + priv->crefs--; + usbhost_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: usbhost_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * val - A pointer to the first byte of the little endian value. + * + * Returned Values: + * A uint16_t representing the whole 16-bit integer value + * + ****************************************************************************/ + +static inline uint16_t usbhost_getle16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +} + +/**************************************************************************** + * Name: usbhost_putle16 + * + * Description: + * Put a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 16-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putle16(uint8_t *dest, uint16_t val) +{ + dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ + dest[1] = val >> 8; +} + +/**************************************************************************** + * Name: usbhost_getle32 + * + * Description: + * Get a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the big endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline uint32_t usbhost_getle32(const uint8_t *val) +{ + /* Little endian means LS halfword first in byte stream */ + + return (uint32_t)usbhost_getle16(&val[2]) << 16 | (uint32_t)usbhost_getle16(val); +} + +/**************************************************************************** + * Name: usbhost_putle32 + * + * Description: + * Put a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putle32(uint8_t *dest, uint32_t val) +{ + /* Little endian means LS halfword first in byte stream */ + + usbhost_putle16(dest, (uint16_t)(val & 0xffff)); + usbhost_putle16(dest+2, (uint16_t)(val >> 16)); +} + +/**************************************************************************** + * Name: usbhost_tdalloc + * + * Description: + * Allocate transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * On sucess, zero (OK) is returned. On failure, an negated errno value + * is returned to indicate the nature of the failure. + * + ****************************************************************************/ + +static inline int usbhost_tdalloc(FAR struct usbhost_state_s *priv) +{ + DEBUGASSERT(priv && priv->tbuffer == NULL); + return DRVR_ALLOC(priv->drvr, &priv->tbuffer, &priv->tbuflen); +} + +/**************************************************************************** + * Name: usbhost_tdfree + * + * Description: + * Free transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * On sucess, zero (OK) is returned. On failure, an negated errno value + * is returned to indicate the nature of the failure. + * + ****************************************************************************/ + +static inline int usbhost_tdfree(FAR struct usbhost_state_s *priv) +{ + int result = OK; + DEBUGASSERT(priv); + + if (priv->tbuffer) + { + DEBUGASSERT(priv->drvr); + result = DRVR_FREE(priv->drvr, priv->tbuffer); + priv->tbuffer = NULL; + priv->tbuflen = 0; + } + return result; +} + +/**************************************************************************** + * struct usbhost_registry_s methods + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_create + * + * Description: + * This function implements the create() method of struct usbhost_registry_s. + * The create() method is a callback into the class implementation. It is + * used to (1) create a new instance of the USB host class state and to (2) + * bind a USB host driver "session" to the class instance. Use of this + * create() method will support environments where there may be multiple + * USB ports and multiple USB devices simultaneously connected. + * + * Input Parameters: + * drvr - An instance of struct usbhost_driver_s that the class + * implementation will "bind" to its state structure and will + * subsequently use to communicate with the USB host driver. + * id - In the case where the device supports multiple base classes, + * subclasses, or protocols, this specifies which to configure for. + * + * Returned Values: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s that can be used by the USB host driver to communicate + * with the USB host class. NULL is returned on failure; this function + * will fail only if the drvr input parameter is NULL or if there are + * insufficient resources to create another USB host class instance. + * + ****************************************************************************/ + +static FAR struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_id_s *id) +{ + FAR struct usbhost_state_s *priv; + + /* Allocate a USB host class instance */ + + priv = usbhost_allocclass(); + if (priv) + { + /* Initialize the allocated storage class instance */ + + memset(priv, 0, sizeof(struct usbhost_state_s)); + + /* Assign a device number to this class instance */ + + if (usbhost_allocdevno(priv) == OK) + { + /* Initialize class method function pointers */ + + priv->class.connect = usbhost_connect; + priv->class.disconnected = usbhost_disconnected; + + /* The initial reference count is 1... One reference is held by the driver */ + + priv->crefs = 1; + + /* Initialize semaphores */ + + sem_init(&priv->exclsem, 0, 1); + sem_init(&priv->waitsem, 0, 0); + + /* Bind the driver to the storage class instance */ + + priv->drvr = drvr; + + /* Return the instance of the USB keyboard class driver */ + + return &priv->class; + } + } + + /* An error occurred. Free the allocation and return NULL on all failures */ + + if (priv) + { + usbhost_freeclass(priv); + } + return NULL; +} + +/**************************************************************************** + * struct usbhost_class_s methods + ****************************************************************************/ +/**************************************************************************** + * Name: usbhost_connect + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * class - The USB host class entry previously obtained from a call to create(). + * configdesc - A pointer to a uint8_t buffer container the configuration descripor. + * desclen - The length in bytes of the configuration descriptor. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * NOTE that the class instance remains valid upon return with a failure. It is + * the responsibility of the higher level enumeration logic to call + * CLASS_DISCONNECTED to free up the class driver resources. + * + * Assumptions: + * - This function will *not* be called from an interrupt handler. + * - If this function returns an error, the USB host controller driver + * must call to DISCONNECTED method to recover from the error + * + ****************************************************************************/ + +static int usbhost_connect(FAR struct usbhost_class_s *class, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)class; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + /* Parse the configuration descriptor to get the endpoints */ + + ret = usbhost_cfgdesc(priv, configdesc, desclen, funcaddr); + if (ret != OK) + { + udbg("usbhost_cfgdesc() failed: %d\n", ret); + } + else + { + /* Now configure the device and register the NuttX driver */ + + ret = usbhost_devinit(priv); + if (ret != OK) + { + udbg("usbhost_devinit() failed: %d\n", ret); + } + } + + /* ERROR handling: Do nothing. If we return and error during connection, + * the driver is required to call the DISCONNECT method. Possibilities: + * + * - Failure occurred before the kbdpoll task was started successfully. + * In this case, the disconnection will have to be handled on the worker + * task. + * - Failure occured after the kbdpoll task was started succesffuly. In + * this case, the disconnetion can be performed on the kbdpoll thread. + */ + + return ret; +} + +/**************************************************************************** + * Name: usbhost_disconnected + * + * Description: + * This function implements the disconnected() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to inform the class that the USB device has + * been disconnected. + * + * Input Parameters: + * class - The USB host class entry previously obtained from a call to + * create(). + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function may be called from an interrupt handler. + * + ****************************************************************************/ + +static int usbhost_disconnected(struct usbhost_class_s *class) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)class; + + DEBUGASSERT(priv != NULL); + + /* Set an indication to any users of the keyboard device that the device + * is no longer available. + */ + + priv->disconnected = true; + ullvdbg("Disconnected\n"); + + /* Is there a thread waiting for keyboard data that will never come? */ + + if (priv->waiting) + { + /* Yes.. wake it up */ + + usbhost_givesem(&priv->waitsem); + priv->waiting = false; + } + + /* Possibilities: + * + * - Failure occurred before the kbdpoll task was started successfully. + * In this case, the disconnection will have to be handled on the worker + * task. + * - Failure occured after the kbdpoll task was started succesffuly. In + * this case, the disconnetion can be performed on the kbdpoll thread. + */ + + if (priv->polling) + { + /* The polling task is still alive. Signal the keyboard polling task. + * When that task wakes up, it will decrement the reference count and, + * perhaps, destroy the class instance. Then it will exit. + */ + + (void)kill(priv->pollpid, SIGALRM); + } + else + { + /* In the case where the failure occurs before the polling task was + * started. Now what? We are probably executing from an interrupt + * handler here. We will use the worker thread. This is kind of + * wasteful and begs for a re-design. + */ + + DEBUGASSERT(priv->work.worker == NULL); + (void)work_queue(&priv->work, usbhost_destroy, priv, 0); + } + + return OK; +} + +/**************************************************************************** + * Character driver methods + ****************************************************************************/ +/**************************************************************************** + * Name: usbhost_open + * + * Description: + * Standard character driver open method. + * + ****************************************************************************/ + +static int usbhost_open(FAR struct file *filep) +{ + FAR struct inode *inode; + FAR struct usbhost_state_s *priv; + irqstate_t flags; + int ret; + + uvdbg("Entry\n"); + DEBUGASSERT(filep && filep->f_inode); + inode = filep->f_inode; + priv = inode->i_private; + + /* Make sure that we have exclusive access to the private data structure */ + + DEBUGASSERT(priv && priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS); + usbhost_takesem(&priv->exclsem); + + /* Check if the keyboard device is still connected. We need to disable + * interrupts momentarily to assure that there are no asynchronous disconnect + * events. + */ + + flags = irqsave(); + if (priv->disconnected) + { + /* No... the driver is no longer bound to the class. That means that + * the USB storage device is no longer connected. Refuse any further + * attempts to open the driver. + */ + + ret = -ENODEV; + } + else + { + /* Otherwise, just increment the reference count on the driver */ + + priv->crefs++; + priv->open = true; + ret = OK; + } + irqrestore(flags); + + usbhost_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: usbhost_close + * + * Description: + * Standard character driver close method. + * + ****************************************************************************/ + +static int usbhost_close(FAR struct file *filep) +{ + FAR struct inode *inode; + FAR struct usbhost_state_s *priv; + + uvdbg("Entry\n"); + DEBUGASSERT(filep && filep->f_inode); + inode = filep->f_inode; + priv = inode->i_private; + + /* Decrement the reference count on the driver */ + + DEBUGASSERT(priv->crefs > 1); + usbhost_takesem(&priv->exclsem); + priv->crefs--; + + /* Is this the last reference (other than the one held by the USB host + * controller driver) + */ + + if (priv->crefs <= 1) + { + irqstate_t flags; + + /* Yes.. then the driver is no longer open */ + + priv->open = false; + priv->headndx = 0; + priv->tailndx = 0; + + /* We need to disable interrupts momentarily to assure that there are + * no asynchronous disconnect events. + */ + + flags = irqsave(); + + /* Check if the USB keyboard device is still connected. If the device is + * no longer connected, then unregister the driver and free the driver + * class instance. + */ + + if (priv->disconnected) + { + /* Destroy the class instance (we can't use priv after this; we can't + * 'give' the semapore) + */ + + usbhost_destroy(priv); + irqrestore(flags); + return OK; + } + irqrestore(flags); + } + + usbhost_givesem(&priv->exclsem); + return OK; +} + +/**************************************************************************** + * Name: usbhost_read + * + * Description: + * Standard character driver read method. + * + ****************************************************************************/ + +static ssize_t usbhost_read(FAR struct file *filep, FAR char *buffer, size_t len) +{ + FAR struct inode *inode; + FAR struct usbhost_state_s *priv; + size_t nbytes; + unsigned int tail; + int ret; + + uvdbg("Entry\n"); + DEBUGASSERT(filep && filep->f_inode && buffer); + inode = filep->f_inode; + priv = inode->i_private; + + /* Make sure that we have exclusive access to the private data structure */ + + DEBUGASSERT(priv && priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS); + usbhost_takesem(&priv->exclsem); + + /* Check if the keyboard is still connected. We need to disable interrupts + * momentarily to assure that there are no asynchronous disconnect events. + */ + + if (priv->disconnected) + { + /* No... the driver is no longer bound to the class. That means that + * the USB keybaord is no longer connected. Refuse any further attempts + * to access the driver. + */ + + ret = -ENODEV; + } + else + { + /* Is there keyboard data now? */ + + while (priv->tailndx == priv->headndx) + { + /* No.. were we open non-blocking? */ + + if (filep->f_oflags & O_NONBLOCK) + { + /* Yes.. then return a failure */ + + ret = -EAGAIN; + goto errout; + } + + /* Wait for data to be available */ + + uvdbg("Waiting...\n"); + priv->waiting = true; + usbhost_givesem(&priv->exclsem); + usbhost_takesem(&priv->waitsem); + usbhost_takesem(&priv->exclsem); + + /* Did the keyboard become disconnected while we were waiting */ + + if (priv->disconnected) + { + ret = -ENODEV; + goto errout; + } + } + + /* Read data from our internal buffer of received characters */ + + for (tail = priv->tailndx, nbytes = 0; + tail != priv->headndx && nbytes < len; + nbytes++) + { + /* Copy the next keyboard character into the user buffer */ + + *buffer++ = priv->kbdbuffer[tail]; + + /* Handle wrap-around of the tail index */ + + if (++tail >= CONFIG_HIDKBD_BUFSIZE) + { + tail = 0; + } + } + ret = nbytes; + + /* Update the tail index (pehaps marking the buffer empty) */ + + priv->tailndx = tail; + } + +errout: + usbhost_givesem(&priv->exclsem); + return (ssize_t)ret; +} + +/**************************************************************************** + * Name: usbhost_write + * + * Description: + * Standard character driver write method. + * + ****************************************************************************/ + +static ssize_t usbhost_write(FAR struct file *filep, FAR const char *buffer, size_t len) +{ + /* We won't try to write to the keyboard */ + + return -ENOSYS; +} + +/**************************************************************************** + * Name: usbhost_poll + * + * Description: + * Standard character driver poll method. + * + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int usbhost_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode; + FAR struct usbhost_state_s *priv; + int ret = OK; + int i; + + uvdbg("Entry\n"); + DEBUGASSERT(filep && filep->f_inode && fds); + inode = filep->f_inode; + priv = inode->i_private; + + /* Make sure that we have exclusive access to the private data structure */ + + DEBUGASSERT(priv); + usbhost_takesem(&priv->exclsem); + + /* Check if the keyboard is still connected. We need to disable interrupts + * momentarily to assure that there are no asynchronous disconnect events. + */ + + if (priv->disconnected) + { + /* No... the driver is no longer bound to the class. That means that + * the USB keybaord is no longer connected. Refuse any further attempts + * to access the driver. + */ + + ret = -ENODEV; + } + else if (setup) + { + /* This is a request to set up the poll. Find an availableslot for + * the poll structure reference + */ + + for (i = 0; i < CONFIG_HIDKBD_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!priv->fds[i]) + { + /* Bind the poll structure and this slot */ + + priv->fds[i] = fds; + fds->priv = &priv->fds[i]; + break; + } + } + + if (i >= CONFIG_HIDKBD_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto errout; + } + + /* Should we immediately notify on any of the requested events? Notify + * the POLLIN event if there is buffered keyboard data. + */ + + if (priv->headndx != priv->tailndx) + { + usbhost_pollnotify(priv); + } + } + else + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + DEBUGASSERT(slot); + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +errout: + sem_post(&priv->exclsem); + return ret; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_kbdinit + * + * Description: + * Initialize the USB storage HID keyboard class driver. This function + * should be called be platform-specific code in order to initialize and + * register support for the USB host HID keyboard class device. + * + * Input Parameters: + * None + * + * Returned Values: + * On success this function will return zero (OK); A negated errno value + * will be returned on failure. + * + ****************************************************************************/ + +int usbhost_kbdinit(void) +{ + /* Perform any one-time initialization of the class implementation */ + + sem_init(&g_exclsem, 0, 1); + sem_init(&g_syncsem, 0, 0); + + /* Advertise our availability to support (certain) devices */ + + return usbhost_registerclass(&g_skeleton); +} + +#endif /* CONFIG_USBHOST)&& !CONFIG_USBHOST_INT_DISABLE && CONFIG_NFILE_DESCRIPTORS */ + + diff --git a/nuttx/drivers/usbhost/usbhost_registerclass.c b/nuttx/drivers/usbhost/usbhost_registerclass.c new file mode 100644 index 000000000..76ef511af --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_registerclass.c @@ -0,0 +1,117 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_registerclass.c + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> +#include <errno.h> +#include <debug.h> + +#include <arch/irq.h> +#include <nuttx/usb/usbhost.h> + +#include "usbhost_registry.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_registerclass + * + * Description: + * Register a USB host class implementation. The caller provides an + * instance of struct usbhost_registry_s that contains all of the + * information that will be needed later to (1) associate the USB host + * class implementation with a connected USB device, and (2) to obtain and + * bind a struct usbhost_class_s instance for the device. + * + * Input Parameters: + * class - An write-able instance of struct usbhost_registry_s that will be + * maintained in a registry. + * + * Returned Values: + * On success, this function will return zero (OK). Otherwise, a negated + * errno value is returned. + * + ****************************************************************************/ + +int usbhost_registerclass(struct usbhost_registry_s *class) +{ + irqstate_t flags; + + uvdbg("Registering class:%p nids:%d\n", class, class->nids); + + /* g_classregistry is a singly-linkedlist of class ID information added by + * calls to usbhost_registerclass(). Since this list is accessed from USB + * host controller interrupt handling logic, accesses to this list must be + * protected by disabling interrupts. + */ + + flags = irqsave(); + + /* Add the new class ID info to the head of the list */ + + class->flink = g_classregistry; + g_classregistry = class; + + irqrestore(flags); + return OK; +} + diff --git a/nuttx/drivers/usbhost/usbhost_registry.c b/nuttx/drivers/usbhost/usbhost_registry.c new file mode 100644 index 000000000..56c03e2dc --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_registry.c @@ -0,0 +1,80 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_registry.c + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <nuttx/usb/usbhost.h> + +#include "usbhost_registry.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* g_classregistry is a singly-linkedlist of class ID information added by + * calls to usbhost_registerclass(). Since this list is accessed from USB + * host controller interrupt handling logic, accesses to this list must be + * protected by disabling interrupts. + */ + +struct usbhost_registry_s *g_classregistry; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ diff --git a/nuttx/drivers/usbhost/usbhost_registry.h b/nuttx/drivers/usbhost/usbhost_registry.h new file mode 100644 index 000000000..63436af5d --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_registry.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * drivers/usbhost/usbdev_registry.h + * + * Copyright (C) 2010 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __DRIVERS_USBHOST_USBHOST_REGISTRY_H +#define __DRIVERS_USBHOST_USBHOST_REGISTRY_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <nuttx/usb/usbhost.h> + +/**************************************************************************** + * Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +# define EXTERN extern "C" +extern "C" +{ +#else +# define EXTERN extern +#endif + +/* g_classregistry is a singly-linkedlist of class ID information added by + * calls to usbhost_registerclass(). Since this list is accessed from USB + * host controller interrupt handling logic, accesses to this list must be + * protected by disabling interrupts. + */ + +EXTERN struct usbhost_registry_s *g_classregistry; + +/************************************************************************************ + * Public Function Prototypes + ************************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* #define __DRIVERS_USBHOST_USBHOST_REGISTRY_H */ diff --git a/nuttx/drivers/usbhost/usbhost_skeleton.c b/nuttx/drivers/usbhost/usbhost_skeleton.c new file mode 100644 index 000000000..206b347c5 --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_skeleton.c @@ -0,0 +1,1060 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_skeleton.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <semaphore.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> +#include <nuttx/arch.h> +#include <nuttx/wqueue.h> + +#include <nuttx/usb/usb.h> +#include <nuttx/usb/usbhost.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_SCHED_WORKQUEUE +# warning "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + +/* Driver support ***********************************************************/ +/* This format is used to construct the /dev/skel[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/skel%c" +#define DEV_NAMELEN 12 + +/* Used in usbhost_cfgdesc() */ + +#define USBHOST_IFFOUND 0x01 +#define USBHOST_BINFOUND 0x02 +#define USBHOST_BOUTFOUND 0x04 +#define USBHOST_ALLFOUND 0x07 + +#define USBHOST_MAX_CREFS 0x7fff + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure contains the internal, private state of the USB host class + * driver. + */ + +struct usbhost_state_s +{ + /* This is the externally visible portion of the state */ + + struct usbhost_class_s class; + + /* This is an instance of the USB host driver bound to this class instance */ + + struct usbhost_driver_s *drvr; + + /* The remainder of the fields are provide to the class driver */ + + char devchar; /* Character identifying the /dev/skel[n] device */ + volatile bool disconnected; /* TRUE: Device has been disconnected */ + uint8_t ifno; /* Interface number */ + int16_t crefs; /* Reference count on the driver instance */ + sem_t exclsem; /* Used to maintain mutual exclusive access */ + struct work_s work; /* For interacting with the worker thread */ + FAR uint8_t *tbuffer; /* The allocated transfer buffer */ + size_t tbuflen; /* Size of the allocated transfer buffer */ + usbhost_ep_t epin; /* IN endpoint */ + usbhost_ep_t epout; /* OUT endpoint */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Semaphores */ + +static void usbhost_takesem(sem_t *sem); +#define usbhost_givesem(s) sem_post(s); + +/* Memory allocation services */ + +static inline FAR struct usbhost_state_s *usbhost_allocclass(void); +static inline void usbhost_freeclass(FAR struct usbhost_state_s *class); + +/* Device name management */ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv); +static void usbhost_freedevno(FAR struct usbhost_state_s *priv); +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname); + +/* Worker thread actions */ + +static void usbhost_destroy(FAR void *arg); + +/* Helpers for usbhost_connect() */ + +static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr); +static inline int usbhost_devinit(FAR struct usbhost_state_s *priv); + +/* (Little Endian) Data helpers */ + +static inline uint16_t usbhost_getle16(const uint8_t *val); +static inline void usbhost_putle16(uint8_t *dest, uint16_t val); +static inline uint32_t usbhost_getle32(const uint8_t *val); +static void usbhost_putle32(uint8_t *dest, uint32_t val); + +/* Transfer descriptor memory management */ + +static inline int usbhost_talloc(FAR struct usbhost_state_s *priv); +static inline int usbhost_tfree(FAR struct usbhost_state_s *priv); + +/* struct usbhost_registry_s methods */ + +static struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_id_s *id); + +/* struct usbhost_class_s methods */ + +static int usbhost_connect(FAR struct usbhost_class_s *class, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr); +static int usbhost_disconnected(FAR struct usbhost_class_s *class); + +/* Driver methods -- depend upon the type of NuttX driver interface exported */ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This structure provides the registry entry ID informatino that will be + * used to associate the USB class driver to a connected USB device. + */ + +static const const struct usbhost_id_s g_id = +{ + 0, /* base -- Must be one of the USB_CLASS_* definitions in usb.h */ + 0, /* subclass -- depends on the device */ + 0, /* proto -- depends on the device */ + 0, /* vid */ + 0 /* pid */ +}; + +/* This is the USB host storage class's registry entry */ + +static struct usbhost_registry_s g_skeleton = +{ + NULL, /* flink */ + usbhost_create, /* create */ + 1, /* nids */ + &g_id /* id[] */ +}; + +/* This is a bitmap that is used to allocate device names /dev/skela-z. */ + +static uint32_t g_devinuse; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_takesem + * + * Description: + * This is just a wrapper to handle the annoying behavior of semaphore + * waits that return due to the receipt of a signal. + * + ****************************************************************************/ + +static void usbhost_takesem(sem_t *sem) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(sem) != 0) + { + /* The only case that an error should occr here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: usbhost_allocclass + * + * Description: + * This is really part of the logic that implements the create() method + * of struct usbhost_registry_s. This function allocates memory for one + * new class instance. + * + * Input Parameters: + * None + * + * Returned Values: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s. NULL is returned on failure; this function will + * will fail only if there are insufficient resources to create another + * USB host class instance. + * + ****************************************************************************/ + +static inline FAR struct usbhost_state_s *usbhost_allocclass(void) +{ + FAR struct usbhost_state_s *priv; + + DEBUGASSERT(!up_interrupt_context()); + priv = (FAR struct usbhost_state_s *)kmalloc(sizeof(struct usbhost_state_s)); + uvdbg("Allocated: %p\n", priv);; + return priv; +} + +/**************************************************************************** + * Name: usbhost_freeclass + * + * Description: + * Free a class instance previously allocated by usbhost_allocclass(). + * + * Input Parameters: + * class - A reference to the class instance to be freed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline void usbhost_freeclass(FAR struct usbhost_state_s *class) +{ + DEBUGASSERT(class != NULL); + + /* Free the class instance (perhaps calling sched_free() in case we are + * executing from an interrupt handler. + */ + + uvdbg("Freeing: %p\n", class);; + kfree(class); +} + +/**************************************************************************** + * Name: Device name management + * + * Description: + * Some tiny functions to coordinate management of device names. + * + ****************************************************************************/ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv) +{ + irqstate_t flags; + int devno; + + flags = irqsave(); + for (devno = 0; devno < 26; devno++) + { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) + { + g_devinuse |= bitno; + priv->devchar = 'a' + devno; + irqrestore(flags); + return OK; + } + } + + irqrestore(flags); + return -EMFILE; +} + +static void usbhost_freedevno(FAR struct usbhost_state_s *priv) +{ + int devno = 'a' - priv->devchar; + + if (devno >= 0 && devno < 26) + { + irqstate_t flags = irqsave(); + g_devinuse &= ~(1 << devno); + irqrestore(flags); + } +} + +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname) +{ + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->devchar); +} + +/**************************************************************************** + * Name: usbhost_destroy + * + * Description: + * The USB device has been disconnected and the refernce count on the USB + * host class instance has gone to 1.. Time to destroy the USB host class + * instance. + * + * Input Parameters: + * arg - A reference to the class instance to be destroyed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_destroy(FAR void *arg) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)arg; + + DEBUGASSERT(priv != NULL); + uvdbg("crefs: %d\n", priv->crefs); + + /* Unregister the driver */ + + /* Release the device name used by this connection */ + + usbhost_freedevno(priv); + + /* Free the endpoints */ + + /* Free any transfer buffers */ + + /* Destroy the semaphores */ + + /* Disconnect the USB host device */ + + DRVR_DISCONNECT(priv->drvr); + + /* And free the class instance. Hmmm.. this may execute on the worker + * thread and the work structure is part of what is getting freed. That + * should be okay because once the work contained is removed from the + * queue, it should not longer be accessed by the worker thread. + */ + + usbhost_freeclass(priv); +} + +/**************************************************************************** + * Name: usbhost_cfgdesc + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * priv - The USB host class instance. + * configdesc - A pointer to a uint8_t buffer container the configuration descripor. + * desclen - The length in bytes of the configuration descriptor. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr) +{ + FAR struct usb_cfgdesc_s *cfgdesc; + FAR struct usb_desc_s *desc; + FAR struct usbhost_epdesc_s bindesc; + FAR struct usbhost_epdesc_s boutdesc; + int remaining; + uint8_t found = 0; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + /* Verify that we were passed a configuration descriptor */ + + cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc; + if (cfgdesc->type != USB_DESC_TYPE_CONFIG) + { + return -EINVAL; + } + + /* Get the total length of the configuration descriptor (little endian). + * It might be a good check to get the number of interfaces here too. + */ + + remaining = (int)usbhost_getle16(cfgdesc->totallen); + + /* Skip to the next entry descriptor */ + + configdesc += cfgdesc->len; + remaining -= cfgdesc->len; + + /* Loop where there are more dscriptors to examine */ + + while (remaining >= sizeof(struct usb_desc_s)) + { + /* What is the next descriptor? */ + + desc = (FAR struct usb_desc_s *)configdesc; + switch (desc->type) + { + /* Interface descriptor. We really should get the number of endpoints + * from this descriptor too. + */ + + case USB_DESC_TYPE_INTERFACE: + { + FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)configdesc; + + uvdbg("Interface descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC); + + /* Save the interface number and mark ONLY the interface found */ + + priv->ifno = ifdesc->ifno; + found = USBHOST_IFFOUND; + } + break; + + /* Endpoint descriptor. Here, we expect two bulk endpoints, an IN + * and an OUT. + */ + + case USB_DESC_TYPE_ENDPOINT: + { + FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)configdesc; + + uvdbg("Endpoint descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC); + + /* Check for a bulk endpoint. */ + + if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_BULK) + { + /* Yes.. it is a bulk endpoint. IN or OUT? */ + + if (USB_ISEPOUT(epdesc->addr)) + { + /* It is an OUT bulk endpoint. There should be only one + * bulk OUT endpoint. + */ + + if ((found & USBHOST_BOUTFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + found |= USBHOST_BOUTFOUND; + + /* Save the bulk OUT endpoint information */ + + boutdesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + boutdesc.in = false; + boutdesc.funcaddr = funcaddr; + boutdesc.xfrtype = USB_EP_ATTR_XFER_BULK; + boutdesc.interval = epdesc->interval; + boutdesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + uvdbg("Bulk OUT EP addr:%d mxpacketsize:%d\n", + boutdesc.addr, boutdesc.mxpacketsize); + } + else + { + /* It is an IN bulk endpoint. There should be only one + * bulk IN endpoint. + */ + + if ((found & USBHOST_BINFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + found |= USBHOST_BINFOUND; + + /* Save the bulk IN endpoint information */ + + bindesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + bindesc.in = 1; + bindesc.funcaddr = funcaddr; + bindesc.xfrtype = USB_EP_ATTR_XFER_BULK; + bindesc.interval = epdesc->interval; + bindesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + uvdbg("Bulk IN EP addr:%d mxpacketsize:%d\n", + bindesc.addr, bindesc.mxpacketsize); + } + } + } + break; + + /* Other descriptors are just ignored for now */ + + default: + break; + } + + /* If we found everything we need with this interface, then break out + * of the loop early. + */ + + if (found == USBHOST_ALLFOUND) + { + break; + } + + /* Increment the address of the next descriptor */ + + configdesc += desc->len; + remaining -= desc->len; + } + + /* Sanity checking... did we find all of things that we need? */ + + if (found != USBHOST_ALLFOUND) + { + ulldbg("ERROR: Found IF:%s BIN:%s BOUT:%s\n", + (found & USBHOST_IFFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BINFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BOUTFOUND) != 0 ? "YES" : "NO"); + return -EINVAL; + } + + /* We are good... Allocate the endpoints */ + + ret = DRVR_EPALLOC(priv->drvr, &boutdesc, &priv->epout); + if (ret != OK) + { + udbg("ERROR: Failed to allocate Bulk OUT endpoint\n"); + return ret; + } + + ret = DRVR_EPALLOC(priv->drvr, &bindesc, &priv->epin); + if (ret != OK) + { + udbg("ERROR: Failed to allocate Bulk IN endpoint\n"); + (void)DRVR_EPFREE(priv->drvr, priv->epout); + return ret; + } + + ullvdbg("Endpoints allocated\n"); + return OK; +} + +/**************************************************************************** + * Name: usbhost_devinit + * + * Description: + * The USB device has been successfully connected. This completes the + * initialization operations. It is first called after the + * configuration descriptor has been received. + * + * This function is called from the connect() method. This function always + * executes on the thread of the caller of connect(). + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline int usbhost_devinit(FAR struct usbhost_state_s *priv) +{ + int ret = OK; + + /* Set aside a transfer buffer for exclusive use by the class driver */ + + /* Increment the reference count. This will prevent usbhost_destroy() from + * being called asynchronously if the device is removed. + */ + + priv->crefs++; + DEBUGASSERT(priv->crefs == 2); + + /* Configure the device */ + + /* Register the driver */ + + if (ret == OK) + { + char devname[DEV_NAMELEN]; + + uvdbg("Register block driver\n"); + usbhost_mkdevname(priv, devname); + // ret = register_blockdriver(devname, &g_bops, 0, priv); + } + + /* Check if we successfully initialized. We now have to be concerned + * about asynchronous modification of crefs because the block + * driver has been registerd. + */ + + if (ret == OK) + { + usbhost_takesem(&priv->exclsem); + DEBUGASSERT(priv->crefs >= 2); + + /* Handle a corner case where (1) open() has been called so the + * reference count is > 2, but the device has been disconnected. + * In this case, the class instance needs to persist until close() + * is called. + */ + + if (priv->crefs <= 2 && priv->disconnected) + { + /* We don't have to give the semaphore because it will be + * destroyed when usb_destroy is called. + */ + + ret = -ENODEV; + } + else + { + /* Ready for normal operation as a block device driver */ + + uvdbg("Successfully initialized\n"); + priv->crefs--; + usbhost_givesem(&priv->exclsem); + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * val - A pointer to the first byte of the little endian value. + * + * Returned Values: + * A uint16_t representing the whole 16-bit integer value + * + ****************************************************************************/ + +static inline uint16_t usbhost_getle16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +} + +/**************************************************************************** + * Name: usbhost_putle16 + * + * Description: + * Put a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 16-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putle16(uint8_t *dest, uint16_t val) +{ + dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ + dest[1] = val >> 8; +} + +/**************************************************************************** + * Name: usbhost_getle32 + * + * Description: + * Get a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the big endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline uint32_t usbhost_getle32(const uint8_t *val) +{ + /* Little endian means LS halfword first in byte stream */ + + return (uint32_t)usbhost_getle16(&val[2]) << 16 | (uint32_t)usbhost_getle16(val); +} + +/**************************************************************************** + * Name: usbhost_putle32 + * + * Description: + * Put a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putle32(uint8_t *dest, uint32_t val) +{ + /* Little endian means LS halfword first in byte stream */ + + usbhost_putle16(dest, (uint16_t)(val & 0xffff)); + usbhost_putle16(dest+2, (uint16_t)(val >> 16)); +} + +/**************************************************************************** + * Name: usbhost_talloc + * + * Description: + * Allocate transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * On sucess, zero (OK) is returned. On failure, an negated errno value + * is returned to indicate the nature of the failure. + * + ****************************************************************************/ + +static inline int usbhost_talloc(FAR struct usbhost_state_s *priv) +{ + DEBUGASSERT(priv && priv->tbuffer == NULL); + return DRVR_ALLOC(priv->drvr, &priv->tbuffer, &priv->tbuflen); +} + +/**************************************************************************** + * Name: usbhost_tfree + * + * Description: + * Free transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * On sucess, zero (OK) is returned. On failure, an negated errno value + * is returned to indicate the nature of the failure. + * + ****************************************************************************/ + +static inline int usbhost_tfree(FAR struct usbhost_state_s *priv) +{ + int result = OK; + DEBUGASSERT(priv); + + if (priv->tbuffer) + { + DEBUGASSERT(priv->drvr); + result = DRVR_FREE(priv->drvr, priv->tbuffer); + priv->tbuffer = NULL; + priv->tbuflen = 0; + } + return result; +} + +/**************************************************************************** + * struct usbhost_registry_s methods + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_create + * + * Description: + * This function implements the create() method of struct usbhost_registry_s. + * The create() method is a callback into the class implementation. It is + * used to (1) create a new instance of the USB host class state and to (2) + * bind a USB host driver "session" to the class instance. Use of this + * create() method will support environments where there may be multiple + * USB ports and multiple USB devices simultaneously connected. + * + * Input Parameters: + * drvr - An instance of struct usbhost_driver_s that the class + * implementation will "bind" to its state structure and will + * subsequently use to communicate with the USB host driver. + * id - In the case where the device supports multiple base classes, + * subclasses, or protocols, this specifies which to configure for. + * + * Returned Values: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s that can be used by the USB host driver to communicate + * with the USB host class. NULL is returned on failure; this function + * will fail only if the drvr input parameter is NULL or if there are + * insufficient resources to create another USB host class instance. + * + ****************************************************************************/ + +static FAR struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_id_s *id) +{ + FAR struct usbhost_state_s *priv; + + /* Allocate a USB host class instance */ + + priv = usbhost_allocclass(); + if (priv) + { + /* Initialize the allocated storage class instance */ + + memset(priv, 0, sizeof(struct usbhost_state_s)); + + /* Assign a device number to this class instance */ + + if (usbhost_allocdevno(priv) == OK) + { + /* Initialize class method function pointers */ + + priv->class.connect = usbhost_connect; + priv->class.disconnected = usbhost_disconnected; + + /* The initial reference count is 1... One reference is held by the driver */ + + priv->crefs = 1; + + /* Initialize semphores (this works okay in the interrupt context) */ + + sem_init(&priv->exclsem, 0, 1); + + /* Bind the driver to the storage class instance */ + + priv->drvr = drvr; + + /* Return the instance of the USB class driver */ + + return &priv->class; + } + } + + /* An error occurred. Free the allocation and return NULL on all failures */ + + if (priv) + { + usbhost_freeclass(priv); + } + return NULL; +} + +/**************************************************************************** + * struct usbhost_class_s methods + ****************************************************************************/ +/**************************************************************************** + * Name: usbhost_connect + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * class - The USB host class entry previously obtained from a call to create(). + * configdesc - A pointer to a uint8_t buffer container the configuration descripor. + * desclen - The length in bytes of the configuration descriptor. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * NOTE that the class instance remains valid upon return with a failure. It is + * the responsibility of the higher level enumeration logic to call + * CLASS_DISCONNECTED to free up the class driver resources. + * + * Assumptions: + * - This function will *not* be called from an interrupt handler. + * - If this function returns an error, the USB host controller driver + * must call to DISCONNECTED method to recover from the error + * + ****************************************************************************/ + +static int usbhost_connect(FAR struct usbhost_class_s *class, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)class; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + /* Parse the configuration descriptor to get the endpoints */ + + ret = usbhost_cfgdesc(priv, configdesc, desclen, funcaddr); + if (ret != OK) + { + udbg("usbhost_cfgdesc() failed: %d\n", ret); + } + else + { + /* Now configure the device and register the NuttX driver */ + + ret = usbhost_devinit(priv); + if (ret != OK) + { + udbg("usbhost_devinit() failed: %d\n", ret); + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_disconnected + * + * Description: + * This function implements the disconnected() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to inform the class that the USB device has + * been disconnected. + * + * Input Parameters: + * class - The USB host class entry previously obtained from a call to + * create(). + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function may be called from an interrupt handler. + * + ****************************************************************************/ + +static int usbhost_disconnected(struct usbhost_class_s *class) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)class; + irqstate_t flags; + + DEBUGASSERT(priv != NULL); + + /* Set an indication to any users of the device that the device is no + * longer available. + */ + + flags = irqsave(); + priv->disconnected = true; + + /* Now check the number of references on the class instance. If it is one, + * then we can free the class instance now. Otherwise, we will have to + * wait until the holders of the references free them by closing the + * block driver. + */ + + ullvdbg("crefs: %d\n", priv->crefs); + if (priv->crefs == 1) + { + /* Destroy the class instance. If we are executing from an interrupt + * handler, then defer the destruction to the worker thread. + * Otherwise, destroy the instance now. + */ + + if (up_interrupt_context()) + { + /* Destroy the instance on the worker thread. */ + + uvdbg("Queuing destruction: worker %p->%p\n", priv->work.worker, usbhost_destroy); + DEBUGASSERT(priv->work.worker == NULL); + (void)work_queue(&priv->work, usbhost_destroy, priv, 0); + } + else + { + /* Do the work now */ + + usbhost_destroy(priv); + } + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_skelinit + * + * Description: + * Initialize the USB class driver. This function should be called + * be platform-specific code in order to initialize and register support + * for the USB host class device. + * + * Input Parameters: + * None + * + * Returned Values: + * On success this function will return zero (OK); A negated errno value + * will be returned on failure. + * + ****************************************************************************/ + +int usbhost_skelinit(void) +{ + /* Perform any one-time initialization of the class implementation */ + + /* Advertise our availability to support (certain) devices */ + + return usbhost_registerclass(&g_skeleton); +} diff --git a/nuttx/drivers/usbhost/usbhost_storage.c b/nuttx/drivers/usbhost/usbhost_storage.c new file mode 100644 index 000000000..8f5864b6d --- /dev/null +++ b/nuttx/drivers/usbhost/usbhost_storage.c @@ -0,0 +1,2233 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_storage.c + * + * Copyright (C) 2010-2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <semaphore.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/fs.h> +#include <nuttx/arch.h> +#include <nuttx/wqueue.h> +#include <nuttx/scsi.h> + +#include <nuttx/usb/usb.h> +#include <nuttx/usb/usbhost.h> +#include <nuttx/usb/storage.h> + +/* Don't compile if prerequisites are not met */ + +#if defined(CONFIG_USBHOST) && !defined(CONFIG_USBHOST_BULK_DISABLE) && \ + !defined(CONFIG_DISABLE_MOUNTPOINT) && CONFIG_NFILE_DESCRIPTORS > 0 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_SCHED_WORKQUEUE +# warning "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + +/* If the create() method is called by the USB host device driver from an + * interrupt handler, then it will be unable to call kmalloc() in order to + * allocate a new class instance. If the create() method is called from the + * interrupt level, then class instances must be pre-allocated. + */ + +#ifndef CONFIG_USBHOST_NPREALLOC +# define CONFIG_USBHOST_NPREALLOC 0 +#endif + +#if CONFIG_USBHOST_NPREALLOC > 26 +# error "Currently limited to 26 devices /dev/sda-z" +#endif + +/* Driver support ***********************************************************/ +/* This format is used to construct the /dev/sd[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/sd%c" +#define DEV_NAMELEN 10 + +/* Used in usbhost_connect() */ + +#define USBHOST_IFFOUND 0x01 +#define USBHOST_BINFOUND 0x02 +#define USBHOST_BOUTFOUND 0x04 +#define USBHOST_ALLFOUND 0x07 + +#define USBHOST_MAX_RETRIES 100 +#define USBHOST_MAX_CREFS 0x7fff + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure contains the internal, private state of the USB host mass + * storage class. + */ + +struct usbhost_state_s +{ + /* This is the externally visible portion of the state */ + + struct usbhost_class_s class; + + /* This is an instance of the USB host driver bound to this class instance */ + + struct usbhost_driver_s *drvr; + + /* The remainder of the fields are provide to the mass storage class */ + + char sdchar; /* Character identifying the /dev/sd[n] device */ + volatile bool disconnected; /* TRUE: Device has been disconnected */ + uint8_t ifno; /* Interface number */ + int16_t crefs; /* Reference count on the driver instance */ + uint16_t blocksize; /* Block size of USB mass storage device */ + uint32_t nblocks; /* Number of blocks on the USB mass storage device */ + sem_t exclsem; /* Used to maintain mutual exclusive access */ + struct work_s work; /* For interacting with the worker thread */ + FAR uint8_t *tbuffer; /* The allocated transfer buffer */ + size_t tbuflen; /* Size of the allocated transfer buffer */ + usbhost_ep_t bulkin; /* Bulk IN endpoint */ + usbhost_ep_t bulkout; /* Bulk OUT endpoint */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Semaphores */ + +static void usbhost_takesem(sem_t *sem); +#define usbhost_givesem(s) sem_post(s); + +/* Memory allocation services */ + +static inline FAR struct usbhost_state_s *usbhost_allocclass(void); +static inline void usbhost_freeclass(FAR struct usbhost_state_s *class); + +/* Device name management */ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv); +static void usbhost_freedevno(FAR struct usbhost_state_s *priv); +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname); + +/* CBW/CSW debug helpers */ + +#if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_VERBOSE) +static void usbhost_dumpcbw(FAR struct usbstrg_cbw_s *cbw); +static void usbhost_dumpcsw(FAR struct usbstrg_csw_s *csw); +#else +# define usbhost_dumpcbw(cbw); +# define usbhost_dumpcsw(csw); +#endif + +/* CBW helpers */ + +static inline void usbhost_requestsensecbw(FAR struct usbstrg_cbw_s *cbw); +static inline void usbhost_testunitreadycbw(FAR struct usbstrg_cbw_s *cbw); +static inline void usbhost_readcapacitycbw(FAR struct usbstrg_cbw_s *cbw); +static inline void usbhost_inquirycbw (FAR struct usbstrg_cbw_s *cbw); +static inline void usbhost_readcbw (size_t startsector, uint16_t blocksize, + unsigned int nsectors, + FAR struct usbstrg_cbw_s *cbw); +static inline void usbhost_writecbw(size_t startsector, uint16_t blocksize, + unsigned int nsectors, + FAR struct usbstrg_cbw_s *cbw); +/* Command helpers */ + +static inline int usbhost_maxlunreq(FAR struct usbhost_state_s *priv); +static inline int usbhost_testunitready(FAR struct usbhost_state_s *priv); +static inline int usbhost_requestsense(FAR struct usbhost_state_s *priv); +static inline int usbhost_readcapacity(FAR struct usbhost_state_s *priv); +static inline int usbhost_inquiry(FAR struct usbhost_state_s *priv); + +/* Worker thread actions */ + +static void usbhost_destroy(FAR void *arg); + +/* Helpers for usbhost_connect() */ + +static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr); +static inline int usbhost_initvolume(FAR struct usbhost_state_s *priv); + +/* (Little Endian) Data helpers */ + +static inline uint16_t usbhost_getle16(const uint8_t *val); +static inline uint16_t usbhost_getbe16(const uint8_t *val); +static inline void usbhost_putle16(uint8_t *dest, uint16_t val); +static inline void usbhost_putbe16(uint8_t *dest, uint16_t val); +static inline uint32_t usbhost_getle32(const uint8_t *val); +static inline uint32_t usbhost_getbe32(const uint8_t *val); +static void usbhost_putle32(uint8_t *dest, uint32_t val); +static void usbhost_putbe32(uint8_t *dest, uint32_t val); + +/* Transfer descriptor memory management */ + +static inline int usbhost_talloc(FAR struct usbhost_state_s *priv); +static inline int usbhost_tfree(FAR struct usbhost_state_s *priv); +static FAR struct usbstrg_cbw_s *usbhost_cbwalloc(FAR struct usbhost_state_s *priv); + +/* struct usbhost_registry_s methods */ + +static struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_id_s *id); + +/* struct usbhost_class_s methods */ + +static int usbhost_connect(FAR struct usbhost_class_s *class, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr); +static int usbhost_disconnected(FAR struct usbhost_class_s *class); + +/* struct block_operations methods */ + +static int usbhost_open(FAR struct inode *inode); +static int usbhost_close(FAR struct inode *inode); +static ssize_t usbhost_read(FAR struct inode *inode, FAR unsigned char *buffer, + size_t startsector, unsigned int nsectors); +#ifdef CONFIG_FS_WRITABLE +static ssize_t usbhost_write(FAR struct inode *inode, + FAR const unsigned char *buffer, size_t startsector, + unsigned int nsectors); +#endif +static int usbhost_geometry(FAR struct inode *inode, + FAR struct geometry *geometry); +static int usbhost_ioctl(FAR struct inode *inode, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This structure provides the registry entry ID informatino that will be + * used to associate the USB host mass storage class to a connected USB + * device. + */ + +static const const struct usbhost_id_s g_id = +{ + USB_CLASS_MASS_STORAGE, /* base */ + USBSTRG_SUBCLASS_SCSI, /* subclass */ + USBSTRG_PROTO_BULKONLY, /* proto */ + 0, /* vid */ + 0 /* pid */ +}; + +/* This is the USB host storage class's registry entry */ + +static struct usbhost_registry_s g_storage = +{ + NULL, /* flink */ + usbhost_create, /* create */ + 1, /* nids */ + &g_id /* id[] */ +}; + +/* Block driver operations. This is the interface exposed to NuttX by the + * class that permits it to behave like a block driver. + */ + +static const struct block_operations g_bops = +{ + usbhost_open, /* open */ + usbhost_close, /* close */ + usbhost_read, /* read */ +#ifdef CONFIG_FS_WRITABLE + usbhost_write, /* write */ +#else + NULL, /* write */ +#endif + usbhost_geometry, /* geometry */ + usbhost_ioctl /* ioctl */ +}; + +/* This is an array of pre-allocated USB host storage class instances */ + +#if CONFIG_USBHOST_NPREALLOC > 0 +static struct usbhost_state_s g_prealloc[CONFIG_USBHOST_NPREALLOC]; +#endif + +/* This is a list of free, pre-allocated USB host storage class instances */ + +#if CONFIG_USBHOST_NPREALLOC > 0 +static struct usbhost_state_s *g_freelist; +#endif + +/* This is a bitmap that is used to allocate device names /dev/sda-z. */ + +static uint32_t g_devinuse; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_takesem + * + * Description: + * This is just a wrapper to handle the annoying behavior of semaphore + * waits that return due to the receipt of a signal. + * + ****************************************************************************/ + +static void usbhost_takesem(sem_t *sem) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(sem) != 0) + { + /* The only case that an error should occr here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: usbhost_allocclass + * + * Description: + * This is really part of the logic that implements the create() method + * of struct usbhost_registry_s. This function allocates memory for one + * new class instance. + * + * Input Parameters: + * None + * + * Returned Values: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s. NULL is returned on failure; this function will + * will fail only if there are insufficient resources to create another + * USB host class instance. + * + ****************************************************************************/ + +#if CONFIG_USBHOST_NPREALLOC > 0 +static inline FAR struct usbhost_state_s *usbhost_allocclass(void) +{ + struct usbhost_state_s *priv; + irqstate_t flags; + + /* We may be executing from an interrupt handler so we need to take one of + * our pre-allocated class instances from the free list. + */ + + flags = irqsave(); + priv = g_freelist; + if (priv) + { + g_freelist = priv->class.flink; + priv->class.flink = NULL; + } + irqrestore(flags); + ullvdbg("Allocated: %p\n", priv);; + return priv; +} +#else +static inline FAR struct usbhost_state_s *usbhost_allocclass(void) +{ + FAR struct usbhost_state_s *priv; + + /* We are not executing from an interrupt handler so we can just call + * kmalloc() to get memory for the class instance. + */ + + DEBUGASSERT(!up_interrupt_context()); + priv = (FAR struct usbhost_state_s *)kmalloc(sizeof(struct usbhost_state_s)); + uvdbg("Allocated: %p\n", priv);; + return priv; +} +#endif + +/**************************************************************************** + * Name: usbhost_freeclass + * + * Description: + * Free a class instance previously allocated by usbhost_allocclass(). + * + * Input Parameters: + * class - A reference to the class instance to be freed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +#if CONFIG_USBHOST_NPREALLOC > 0 +static inline void usbhost_freeclass(FAR struct usbhost_state_s *class) +{ + irqstate_t flags; + DEBUGASSERT(class != NULL); + + ullvdbg("Freeing: %p\n", class);; + + /* Just put the pre-allocated class structure back on the freelist */ + + flags = irqsave(); + class->class.flink = g_freelist; + g_freelist = class; + irqrestore(flags); +} +#else +static inline void usbhost_freeclass(FAR struct usbhost_state_s *class) +{ + DEBUGASSERT(class != NULL); + + /* Free the class instance (calling sched_free() in case we are executing + * from an interrupt handler. + */ + + uvdbg("Freeing: %p\n", class);; + kfree(class); +} +#endif + +/**************************************************************************** + * Name: Device name management + * + * Description: + * Some tiny functions to coordinate management of mass storage device names. + * + ****************************************************************************/ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv) +{ + irqstate_t flags; + int devno; + + flags = irqsave(); + for (devno = 0; devno < 26; devno++) + { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) + { + g_devinuse |= bitno; + priv->sdchar = 'a' + devno; + irqrestore(flags); + return OK; + } + } + + irqrestore(flags); + return -EMFILE; +} + +static void usbhost_freedevno(FAR struct usbhost_state_s *priv) +{ + int devno = 'a' - priv->sdchar; + + if (devno >= 0 && devno < 26) + { + irqstate_t flags = irqsave(); + g_devinuse &= ~(1 << devno); + irqrestore(flags); + } +} + +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname) +{ + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->sdchar); +} + +/**************************************************************************** + * Name: CBW/CSW debug helpers + * + * Description: + * The following functions are helper functions used to dump CBWs and CSWs. + * + * Input Parameters: + * cbw/csw - A reference to the CBW/CSW to dump. + * + * Returned Values: + * None + * + ****************************************************************************/ + +#if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_VERBOSE) +static void usbhost_dumpcbw(FAR struct usbstrg_cbw_s *cbw) +{ + int i; + + uvdbg("CBW:\n"); + uvdbg(" signature: %08x\n", usbhost_getle32(cbw->signature)); + uvdbg(" tag: %08x\n", usbhost_getle32(cbw->tag)); + uvdbg(" datlen: %08x\n", usbhost_getle32(cbw->datlen)); + uvdbg(" flags: %02x\n", cbw->flags); + uvdbg(" lun: %02x\n", cbw->lun); + uvdbg(" cdblen: %02x\n", cbw->cdblen); + + uvdbg("CDB:\n"); + for (i = 0; i < cbw->cdblen; i += 8) + { + uvdbg(" %02x %02x %02x %02x %02x %02x %02x %02x\n", + cbw->cdb[i], cbw->cdb[i+1], cbw->cdb[i+2], cbw->cdb[i+3], + cbw->cdb[i+4], cbw->cdb[i+5], cbw->cdb[i+6], cbw->cdb[i+7]); + } +} + +static void usbhost_dumpcsw(FAR struct usbstrg_csw_s *csw) +{ + uvdbg("CSW:\n"); + uvdbg(" signature: %08x\n", usbhost_getle32(csw->signature)); + uvdbg(" tag: %08x\n", usbhost_getle32(csw->tag)); + uvdbg(" residue: %08x\n", usbhost_getle32(csw->residue)); + uvdbg(" status: %02x\n", csw->status); +} +#endif + +/**************************************************************************** + * Name: CBW helpers + * + * Description: + * The following functions are helper functions used to format CBWs. + * + * Input Parameters: + * cbw - A reference to allocated and initialized CBW to be built. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline void usbhost_requestsensecbw(FAR struct usbstrg_cbw_s *cbw) +{ + FAR struct scsicmd_requestsense_s *reqsense; + + /* Format the CBW */ + + usbhost_putle32(cbw->datlen, SCSIRESP_FIXEDSENSEDATA_SIZEOF); + cbw->flags = USBSTRG_CBWFLAG_IN; + cbw->cdblen = SCSICMD_REQUESTSENSE_SIZEOF; + + /* Format the CDB */ + + reqsense = (FAR struct scsicmd_requestsense_s *)cbw->cdb; + reqsense->opcode = SCSI_CMD_REQUESTSENSE; + reqsense->alloclen = SCSIRESP_FIXEDSENSEDATA_SIZEOF; + + usbhost_dumpcbw(cbw); +} + +static inline void usbhost_testunitreadycbw(FAR struct usbstrg_cbw_s *cbw) +{ + /* Format the CBW */ + + cbw->cdblen = SCSICMD_TESTUNITREADY_SIZEOF; + + /* Format the CDB */ + + cbw->cdb[0] = SCSI_CMD_TESTUNITREADY; + + usbhost_dumpcbw(cbw); +} + +static inline void usbhost_readcapacitycbw(FAR struct usbstrg_cbw_s *cbw) +{ + FAR struct scsicmd_readcapacity10_s *rcap10; + + /* Format the CBW */ + + usbhost_putle32(cbw->datlen, SCSIRESP_READCAPACITY10_SIZEOF); + cbw->flags = USBSTRG_CBWFLAG_IN; + cbw->cdblen = SCSICMD_READCAPACITY10_SIZEOF; + + /* Format the CDB */ + + rcap10 = (FAR struct scsicmd_readcapacity10_s *)cbw->cdb; + rcap10->opcode = SCSI_CMD_READCAPACITY10; + + usbhost_dumpcbw(cbw); +} + +static inline void usbhost_inquirycbw (FAR struct usbstrg_cbw_s *cbw) +{ + FAR struct scscicmd_inquiry_s *inq; + + /* Format the CBW */ + + usbhost_putle32(cbw->datlen, SCSIRESP_INQUIRY_SIZEOF); + cbw->flags = USBSTRG_CBWFLAG_IN; + cbw->cdblen = SCSICMD_INQUIRY_SIZEOF; + + /* Format the CDB */ + + inq = (FAR struct scscicmd_inquiry_s *)cbw->cdb; + inq->opcode = SCSI_CMD_INQUIRY; + usbhost_putbe16(inq->alloclen, SCSIRESP_INQUIRY_SIZEOF); + + usbhost_dumpcbw(cbw); +} + +static inline void +usbhost_readcbw (size_t startsector, uint16_t blocksize, + unsigned int nsectors, FAR struct usbstrg_cbw_s *cbw) +{ + FAR struct scsicmd_read10_s *rd10; + + /* Format the CBW */ + + usbhost_putle32(cbw->datlen, blocksize * nsectors); + cbw->flags = USBSTRG_CBWFLAG_IN; + cbw->cdblen = SCSICMD_READ10_SIZEOF; + + /* Format the CDB */ + + rd10 = (FAR struct scsicmd_read10_s *)cbw->cdb; + rd10->opcode = SCSI_CMD_READ10; + usbhost_putbe32(rd10->lba, startsector); + usbhost_putbe16(rd10->xfrlen, nsectors); + + usbhost_dumpcbw(cbw); +} + +static inline void +usbhost_writecbw(size_t startsector, uint16_t blocksize, + unsigned int nsectors, FAR struct usbstrg_cbw_s *cbw) +{ + FAR struct scsicmd_write10_s *wr10; + + /* Format the CBW */ + + usbhost_putle32(cbw->datlen, blocksize * nsectors); + cbw->cdblen = SCSICMD_WRITE10_SIZEOF; + + /* Format the CDB */ + + wr10 = (FAR struct scsicmd_write10_s *)cbw->cdb; + wr10->opcode = SCSI_CMD_WRITE10; + usbhost_putbe32(wr10->lba, startsector); + usbhost_putbe16(wr10->xfrlen, nsectors); + + usbhost_dumpcbw(cbw); +} + +/**************************************************************************** + * Name: Command helpers + * + * Description: + * The following functions are helper functions used to send commands. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline int usbhost_maxlunreq(FAR struct usbhost_state_s *priv) +{ + FAR struct usb_ctrlreq_s *req = (FAR struct usb_ctrlreq_s *)priv->tbuffer; + DEBUGASSERT(priv && priv->tbuffer); + int ret; + + /* Request maximum logical unit number. NOTE: On an IN transaction, The + * req and buffer pointers passed to DRVR_CTRLIN may refer to the same + * allocated memory. + */ + + uvdbg("Request maximum logical unit number\n"); + memset(req, 0, sizeof(struct usb_ctrlreq_s)); + req->type = USB_DIR_IN|USB_REQ_TYPE_CLASS|USB_REQ_RECIPIENT_INTERFACE; + req->req = USBSTRG_REQ_GETMAXLUN; + usbhost_putle16(req->len, 1); + + ret = DRVR_CTRLIN(priv->drvr, req, priv->tbuffer); + if (ret != OK) + { + /* Devices that do not support multiple LUNs may stall this command. + * On a failure, a single LUN is assumed. + */ + + *(priv->tbuffer) = 0; + } + return OK; +} + +static inline int usbhost_testunitready(FAR struct usbhost_state_s *priv) +{ + FAR struct usbstrg_cbw_s *cbw; + int result; + + /* Initialize a CBW (re-using the allocated transfer buffer) */ + + cbw = usbhost_cbwalloc(priv); + if (!cbw) + { + udbg("ERROR: Failed to create CBW\n"); + return -ENOMEM; + } + + /* Construct and send the CBW */ + + usbhost_testunitreadycbw(cbw); + result = DRVR_TRANSFER(priv->drvr, priv->bulkout, + (uint8_t*)cbw, USBSTRG_CBW_SIZEOF); + if (result == OK) + { + /* Receive the CSW */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, USBSTRG_CSW_SIZEOF); + if (result == OK) + { + usbhost_dumpcsw((FAR struct usbstrg_csw_s *)priv->tbuffer); + } + } + return result; +} + +static inline int usbhost_requestsense(FAR struct usbhost_state_s *priv) +{ + FAR struct usbstrg_cbw_s *cbw; + int result; + + /* Initialize a CBW (re-using the allocated transfer buffer) */ + + cbw = usbhost_cbwalloc(priv); + if (!cbw) + { + udbg("ERROR: Failed to create CBW\n"); + return -ENOMEM; + } + + /* Construct and send the CBW */ + + usbhost_requestsensecbw(cbw); + result = DRVR_TRANSFER(priv->drvr, priv->bulkout, + (uint8_t*)cbw, USBSTRG_CBW_SIZEOF); + if (result == OK) + { + /* Receive the sense data response */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, SCSIRESP_FIXEDSENSEDATA_SIZEOF); + if (result == OK) + { + /* Receive the CSW */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, USBSTRG_CSW_SIZEOF); + if (result == OK) + { + usbhost_dumpcsw((FAR struct usbstrg_csw_s *)priv->tbuffer); + } + } + } + + return result; +} + +static inline int usbhost_readcapacity(FAR struct usbhost_state_s *priv) +{ + FAR struct usbstrg_cbw_s *cbw; + FAR struct scsiresp_readcapacity10_s *resp; + int result; + + /* Initialize a CBW (re-using the allocated transfer buffer) */ + + cbw = usbhost_cbwalloc(priv); + if (!cbw) + { + udbg("ERROR: Failed to create CBW\n"); + return -ENOMEM; + } + + /* Construct and send the CBW */ + + usbhost_readcapacitycbw(cbw); + result = DRVR_TRANSFER(priv->drvr, priv->bulkout, + (uint8_t*)cbw, USBSTRG_CBW_SIZEOF); + if (result == OK) + { + /* Receive the read capacity CBW IN response */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, SCSIRESP_READCAPACITY10_SIZEOF); + if (result == OK) + { + /* Save the capacity information */ + + resp = (FAR struct scsiresp_readcapacity10_s *)priv->tbuffer; + priv->nblocks = usbhost_getbe32(resp->lba) + 1; + priv->blocksize = usbhost_getbe32(resp->blklen); + + /* Receive the CSW */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, USBSTRG_CSW_SIZEOF); + if (result == OK) + { + usbhost_dumpcsw((FAR struct usbstrg_csw_s *)priv->tbuffer); + } + } + } + + return result; +} + +static inline int usbhost_inquiry(FAR struct usbhost_state_s *priv) +{ + FAR struct usbstrg_cbw_s *cbw; + FAR struct scsiresp_inquiry_s *resp; + int result; + + /* Initialize a CBW (re-using the allocated transfer buffer) */ + + cbw = usbhost_cbwalloc(priv); + if (!cbw) + { + udbg("ERROR: Failed to create CBW\n"); + return -ENOMEM; + } + + /* Construct and send the CBW */ + + usbhost_inquirycbw(cbw); + result = DRVR_TRANSFER(priv->drvr, priv->bulkout, + (uint8_t*)cbw, USBSTRG_CBW_SIZEOF); + if (result == OK) + { + /* Receive the CBW IN response */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, SCSIRESP_INQUIRY_SIZEOF); + if (result == OK) + { + /* TODO: If USB debug is enabled, dump the response data here */ + + resp = (FAR struct scsiresp_inquiry_s *)priv->tbuffer; + + /* Receive the CSW */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, USBSTRG_CSW_SIZEOF); + if (result == OK) + { + usbhost_dumpcsw((FAR struct usbstrg_csw_s *)priv->tbuffer); + } + } + } + + return result; +} + +/**************************************************************************** + * Name: usbhost_destroy + * + * Description: + * The USB mass storage device has been disconnected and the refernce count + * on the USB host class instance has gone to 1.. Time to destroy the USB + * host class instance. + * + * Input Parameters: + * arg - A reference to the class instance to be destroyed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_destroy(FAR void *arg) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)arg; + char devname[DEV_NAMELEN]; + + DEBUGASSERT(priv != NULL); + uvdbg("crefs: %d\n", priv->crefs); + + /* Unregister the block driver */ + + usbhost_mkdevname(priv, devname); + (void)unregister_blockdriver(devname); + + /* Release the device name used by this connection */ + + usbhost_freedevno(priv); + + /* Free the bulk endpoints */ + + if (priv->bulkout) + { + DRVR_EPFREE(priv->drvr, priv->bulkout); + } + + if (priv->bulkin) + { + DRVR_EPFREE(priv->drvr, priv->bulkin); + } + + /* Free any transfer buffers */ + + usbhost_tfree(priv); + + /* Destroy the semaphores */ + + sem_destroy(&priv->exclsem); + + /* Disconnect the USB host device */ + + DRVR_DISCONNECT(priv->drvr); + + /* And free the class instance. Hmmm.. this may execute on the worker + * thread and the work structure is part of what is getting freed. That + * should be okay because once the work contained is removed from the + * queue, it should not longer be accessed by the worker thread. + */ + + usbhost_freeclass(priv); +} + +/**************************************************************************** + * Name: usbhost_cfgdesc + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * priv - The USB host class instance. + * configdesc - A pointer to a uint8_t buffer container the configuration descripor. + * desclen - The length in bytes of the configuration descriptor. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr) +{ + FAR struct usb_cfgdesc_s *cfgdesc; + FAR struct usb_desc_s *desc; + FAR struct usbhost_epdesc_s bindesc; + FAR struct usbhost_epdesc_s boutdesc; + int remaining; + uint8_t found = 0; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + /* Verify that we were passed a configuration descriptor */ + + cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc; + if (cfgdesc->type != USB_DESC_TYPE_CONFIG) + { + return -EINVAL; + } + + /* Get the total length of the configuration descriptor (little endian). + * It might be a good check to get the number of interfaces here too. + */ + + remaining = (int)usbhost_getle16(cfgdesc->totallen); + + /* Skip to the next entry descriptor */ + + configdesc += cfgdesc->len; + remaining -= cfgdesc->len; + + /* Loop where there are more dscriptors to examine */ + + while (remaining >= sizeof(struct usb_desc_s)) + { + /* What is the next descriptor? */ + + desc = (FAR struct usb_desc_s *)configdesc; + switch (desc->type) + { + /* Interface descriptor. We really should get the number of endpoints + * from this descriptor too. + */ + + case USB_DESC_TYPE_INTERFACE: + { + FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)configdesc; + + uvdbg("Interface descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC); + + /* Save the interface number and mark ONLY the interface found */ + + priv->ifno = ifdesc->ifno; + found = USBHOST_IFFOUND; + } + break; + + /* Endpoint descriptor. We expect two bulk endpoints, an IN and an + * OUT. + */ + + case USB_DESC_TYPE_ENDPOINT: + { + FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)configdesc; + + uvdbg("Endpoint descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC); + + /* Check for a bulk endpoint. We only support the bulk-only + * protocol so I suppose anything else should really be an error. + */ + + if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_BULK) + { + /* Yes.. it is a bulk endpoint. IN or OUT? */ + + if (USB_ISEPOUT(epdesc->addr)) + { + /* It is an OUT bulk endpoint. There should be only one + * bulk OUT endpoint. + */ + + if ((found & USBHOST_BOUTFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + found |= USBHOST_BOUTFOUND; + + /* Save the bulk OUT endpoint information */ + + boutdesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + boutdesc.in = false; + boutdesc.funcaddr = funcaddr; + boutdesc.xfrtype = USB_EP_ATTR_XFER_BULK; + boutdesc.interval = epdesc->interval; + boutdesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + uvdbg("Bulk OUT EP addr:%d mxpacketsize:%d\n", + boutdesc.addr, boutdesc.mxpacketsize); + } + else + { + /* It is an IN bulk endpoint. There should be only one + * bulk IN endpoint. + */ + + if ((found & USBHOST_BINFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + found |= USBHOST_BINFOUND; + + /* Save the bulk IN endpoint information */ + + bindesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + bindesc.in = 1; + bindesc.funcaddr = funcaddr; + bindesc.xfrtype = USB_EP_ATTR_XFER_BULK; + bindesc.interval = epdesc->interval; + bindesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + uvdbg("Bulk IN EP addr:%d mxpacketsize:%d\n", + bindesc.addr, bindesc.mxpacketsize); + } + } + } + break; + + /* Other descriptors are just ignored for now */ + + default: + break; + } + + /* If we found everything we need with this interface, then break out + * of the loop early. + */ + + if (found == USBHOST_ALLFOUND) + { + break; + } + + /* Increment the address of the next descriptor */ + + configdesc += desc->len; + remaining -= desc->len; + } + + /* Sanity checking... did we find all of things that we need? Hmmm.. I wonder.. + * can we work read-only or write-only if only one bulk endpoint found? + */ + + if (found != USBHOST_ALLFOUND) + { + ulldbg("ERROR: Found IF:%s BIN:%s BOUT:%s\n", + (found & USBHOST_IFFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BINFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BOUTFOUND) != 0 ? "YES" : "NO"); + return -EINVAL; + } + + /* We are good... Allocate the endpoints */ + + ret = DRVR_EPALLOC(priv->drvr, &boutdesc, &priv->bulkout); + if (ret != OK) + { + udbg("ERROR: Failed to allocate Bulk OUT endpoint\n"); + return ret; + } + + ret = DRVR_EPALLOC(priv->drvr, &bindesc, &priv->bulkin); + if (ret != OK) + { + udbg("ERROR: Failed to allocate Bulk IN endpoint\n"); + (void)DRVR_EPFREE(priv->drvr, priv->bulkout); + return ret; + } + + ullvdbg("Endpoints allocated\n"); + return OK; +} + +/**************************************************************************** + * Name: usbhost_initvolume + * + * Description: + * The USB mass storage device has been successfully connected. This + * completes the initialization operations. It is first called after the + * configuration descriptor has been received. + * + * This function is called from the connect() method. This function always + * executes on the thread of the caller of connect(). + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline int usbhost_initvolume(FAR struct usbhost_state_s *priv) +{ + FAR struct usbstrg_csw_s *csw; + unsigned int retries; + int ret = OK; + + DEBUGASSERT(priv != NULL); + + /* Set aside a transfer buffer for exclusive use by the mass storage driver */ + + ret = usbhost_talloc(priv); + if (ret != OK) + { + udbg("ERROR: Failed to allocate transfer buffer\n"); + return ret; + } + + /* Increment the reference count. This will prevent usbhost_destroy() from + * being called asynchronously if the device is removed. + */ + + priv->crefs++; + DEBUGASSERT(priv->crefs == 2); + + /* Request the maximum logical unit number */ + + uvdbg("Get max LUN\n"); + ret = usbhost_maxlunreq(priv); + + /* Wait for the unit to be ready */ + + for (retries = 0; retries < USBHOST_MAX_RETRIES && ret == OK; retries++) + { + uvdbg("Test unit ready, retries=%d\n", retries); + + /* Send TESTUNITREADY to see the unit is ready */ + + ret = usbhost_testunitready(priv); + if (ret == OK) + { + /* Is the unit is ready */ + + csw = (FAR struct usbstrg_csw_s *)priv->tbuffer; + if (csw->status == 0) + { + /* Yes... break out of the loop */ + + break; + } + + /* No.. Request mode sense information. The REQUEST SENSE command + * is sent only "to clear interlocked unit attention conditions." + * The returned status is ignored here. + */ + + uvdbg("Request sense\n"); + ret = usbhost_requestsense(priv); + } + } + + /* Did the unit become ready? Did an error occur? Or did we time out? */ + + if (retries >= USBHOST_MAX_RETRIES) + { + udbg("ERROR: Timeout!\n"); + ret = -ETIMEDOUT; + } + + if (ret == OK) + { + /* Get the capacity of the volume */ + + uvdbg("Read capacity\n"); + ret = usbhost_readcapacity(priv); + if (ret == OK) + { + /* Check the CSW for errors */ + + csw = (FAR struct usbstrg_csw_s *)priv->tbuffer; + if (csw->status != 0) + { + udbg("ERROR: CSW status error: %d\n", csw->status); + ret = -ENODEV; + } + } + } + + /* Get information about the volume */ + + if (ret == OK) + { + /* Inquiry */ + + uvdbg("Inquiry\n"); + ret = usbhost_inquiry(priv); + if (ret == OK) + { + /* Check the CSW for errors */ + + csw = (FAR struct usbstrg_csw_s *)priv->tbuffer; + if (csw->status != 0) + { + udbg("ERROR: CSW status error: %d\n", csw->status); + ret = -ENODEV; + } + } + } + + /* Register the block driver */ + + if (ret == OK) + { + char devname[DEV_NAMELEN]; + + uvdbg("Register block driver\n"); + usbhost_mkdevname(priv, devname); + ret = register_blockdriver(devname, &g_bops, 0, priv); + } + + /* Check if we successfully initialized. We now have to be concerned + * about asynchronous modification of crefs because the block + * driver has been registerd. + */ + + if (ret == OK) + { + usbhost_takesem(&priv->exclsem); + DEBUGASSERT(priv->crefs >= 2); + + /* Decrement the reference count */ + + priv->crefs--; + + /* Handle a corner case where (1) open() has been called so the + * reference count was > 2, but the device has been disconnected. + * In this case, the class instance needs to persist until close() + * is called. + */ + + if (priv->crefs <= 1 && priv->disconnected) + { + /* The will cause the enumeration logic to disconnect + * the class driver. + */ + + ret = -ENODEV; + } + + /* Release the semaphore... there is a race condition here. + * Decrementing the reference count and releasing the semaphore + * allows usbhost_destroy() to execute (on the worker thread); + * the class driver instance could get destoryed before we are + * ready to handle it! + */ + + usbhost_givesem(&priv->exclsem); + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * val - A pointer to the first byte of the little endian value. + * + * Returned Values: + * A uint16_t representing the whole 16-bit integer value + * + ****************************************************************************/ + +static inline uint16_t usbhost_getle16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +} + +/**************************************************************************** + * Name: usbhost_getbe16 + * + * Description: + * Get a (possibly unaligned) 16-bit big endian value. + * + * Input Parameters: + * val - A pointer to the first byte of the big endian value. + * + * Returned Values: + * A uint16_t representing the whole 16-bit integer value + * + ****************************************************************************/ + +static inline uint16_t usbhost_getbe16(const uint8_t *val) +{ + return (uint16_t)val[0] << 8 | (uint16_t)val[1]; +} + +/**************************************************************************** + * Name: usbhost_putle16 + * + * Description: + * Put a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 16-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putle16(uint8_t *dest, uint16_t val) +{ + dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ + dest[1] = val >> 8; +} + +/**************************************************************************** + * Name: usbhost_putbe16 + * + * Description: + * Put a (possibly unaligned) 16-bit big endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the big endian value. + * val - The 16-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putbe16(uint8_t *dest, uint16_t val) +{ + dest[0] = val >> 8; /* Big endian means MS byte first in byte stream */ + dest[1] = val & 0xff; +} + +/**************************************************************************** + * Name: usbhost_getle32 + * + * Description: + * Get a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the big endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline uint32_t usbhost_getle32(const uint8_t *val) +{ + /* Little endian means LS halfword first in byte stream */ + + return (uint32_t)usbhost_getle16(&val[2]) << 16 | (uint32_t)usbhost_getle16(val); +} + +/**************************************************************************** + * Name: usbhost_getbe32 + * + * Description: + * Get a (possibly unaligned) 32-bit big endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the big endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static inline uint32_t usbhost_getbe32(const uint8_t *val) +{ + /* Big endian means MS halfword first in byte stream */ + + return (uint32_t)usbhost_getbe16(val) << 16 | (uint32_t)usbhost_getbe16(&val[2]); +} + +/**************************************************************************** + * Name: usbhost_putle32 + * + * Description: + * Put a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putle32(uint8_t *dest, uint32_t val) +{ + /* Little endian means LS halfword first in byte stream */ + + usbhost_putle16(dest, (uint16_t)(val & 0xffff)); + usbhost_putle16(dest+2, (uint16_t)(val >> 16)); +} + +/**************************************************************************** + * Name: usbhost_putbe32 + * + * Description: + * Put a (possibly unaligned) 32-bit big endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the big endian value. + * val - The 32-bit value to be saved. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_putbe32(uint8_t *dest, uint32_t val) +{ + /* Big endian means MS halfword first in byte stream */ + + usbhost_putbe16(dest, (uint16_t)(val >> 16)); + usbhost_putbe16(dest+2, (uint16_t)(val & 0xffff)); +} + +/**************************************************************************** + * Name: usbhost_talloc + * + * Description: + * Allocate transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * On sucess, zero (OK) is returned. On failure, an negated errno value + * is returned to indicate the nature of the failure. + * + ****************************************************************************/ + +static inline int usbhost_talloc(FAR struct usbhost_state_s *priv) +{ + DEBUGASSERT(priv && priv->tbuffer == NULL); + return DRVR_ALLOC(priv->drvr, &priv->tbuffer, &priv->tbuflen); +} + +/**************************************************************************** + * Name: usbhost_tfree + * + * Description: + * Free transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * On sucess, zero (OK) is returned. On failure, an negated errno value + * is returned to indicate the nature of the failure. + * + ****************************************************************************/ + +static inline int usbhost_tfree(FAR struct usbhost_state_s *priv) +{ + int result = OK; + DEBUGASSERT(priv); + + if (priv->tbuffer) + { + DEBUGASSERT(priv->drvr); + result = DRVR_FREE(priv->drvr, priv->tbuffer); + priv->tbuffer = NULL; + priv->tbuflen = 0; + } + return result; +} + +/**************************************************************************** + * Name: usbhost_cbwalloc + * + * Description: + * Initialize a CBW (re-using the allocated transfer buffer). Upon + * successful return, the CBW is cleared and has the CBW signature in place. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static FAR struct usbstrg_cbw_s *usbhost_cbwalloc(FAR struct usbhost_state_s *priv) +{ + FAR struct usbstrg_cbw_s *cbw = NULL; + + DEBUGASSERT(priv->tbuffer && priv->tbuflen >= sizeof(struct usbstrg_cbw_s)) + + /* Intialize the CBW sructure */ + + cbw = (FAR struct usbstrg_cbw_s *)priv->tbuffer; + memset(cbw, 0, sizeof(struct usbstrg_cbw_s)); + usbhost_putle32(cbw->signature, USBSTRG_CBW_SIGNATURE); + return cbw; +} + +/**************************************************************************** + * struct usbhost_registry_s methods + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_create + * + * Description: + * This function implements the create() method of struct usbhost_registry_s. + * The create() method is a callback into the class implementation. It is + * used to (1) create a new instance of the USB host class state and to (2) + * bind a USB host driver "session" to the class instance. Use of this + * create() method will support environments where there may be multiple + * USB ports and multiple USB devices simultaneously connected. + * + * Input Parameters: + * drvr - An instance of struct usbhost_driver_s that the class + * implementation will "bind" to its state structure and will + * subsequently use to communicate with the USB host driver. + * id - In the case where the device supports multiple base classes, + * subclasses, or protocols, this specifies which to configure for. + * + * Returned Values: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s that can be used by the USB host driver to communicate + * with the USB host class. NULL is returned on failure; this function + * will fail only if the drvr input parameter is NULL or if there are + * insufficient resources to create another USB host class instance. + * + ****************************************************************************/ + +static FAR struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *drvr, + FAR const struct usbhost_id_s *id) +{ + FAR struct usbhost_state_s *priv; + + /* Allocate a USB host mass storage class instance */ + + priv = usbhost_allocclass(); + if (priv) + { + /* Initialize the allocated storage class instance */ + + memset(priv, 0, sizeof(struct usbhost_state_s)); + + /* Assign a device number to this class instance */ + + if (usbhost_allocdevno(priv) == OK) + { + /* Initialize class method function pointers */ + + priv->class.connect = usbhost_connect; + priv->class.disconnected = usbhost_disconnected; + + /* The initial reference count is 1... One reference is held by the driver */ + + priv->crefs = 1; + + /* Initialize semphores (this works okay in the interrupt context) */ + + sem_init(&priv->exclsem, 0, 1); + + /* Bind the driver to the storage class instance */ + + priv->drvr = drvr; + + /* NOTE: We do not yet know the geometry of the USB mass storage device */ + + /* Return the instance of the USB mass storage class */ + + return &priv->class; + } + } + + /* An error occurred. Free the allocation and return NULL on all failures */ + + if (priv) + { + usbhost_freeclass(priv); + } + return NULL; +} + +/**************************************************************************** + * struct usbhost_class_s methods + ****************************************************************************/ +/**************************************************************************** + * Name: usbhost_connect + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * class - The USB host class entry previously obtained from a call to create(). + * configdesc - A pointer to a uint8_t buffer container the configuration descripor. + * desclen - The length in bytes of the configuration descriptor. + * funcaddr - The USB address of the function containing the endpoint that EP0 + * controls + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * NOTE that the class instance remains valid upon return with a failure. It is + * the responsibility of the higher level enumeration logic to call + * CLASS_DISCONNECTED to free up the class driver resources. + * + * Assumptions: + * - This function will *not* be called from an interrupt handler. + * - If this function returns an error, the USB host controller driver + * must call to DISCONNECTED method to recover from the error + * + ****************************************************************************/ + +static int usbhost_connect(FAR struct usbhost_class_s *class, + FAR const uint8_t *configdesc, int desclen, + uint8_t funcaddr) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)class; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + /* Parse the configuration descriptor to get the bulk I/O endpoints */ + + ret = usbhost_cfgdesc(priv, configdesc, desclen, funcaddr); + if (ret != OK) + { + udbg("usbhost_cfgdesc() failed: %d\n", ret); + } + else + { + /* Now configure the LUNs and register the block driver(s) */ + + ret = usbhost_initvolume(priv); + if (ret != OK) + { + udbg("usbhost_initvolume() failed: %d\n", ret); + } + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_disconnected + * + * Description: + * This function implements the disconnected() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to inform the class that the USB device has + * been disconnected. + * + * Input Parameters: + * class - The USB host class entry previously obtained from a call to + * create(). + * + * Returned Values: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function may be called from an interrupt handler. + * + ****************************************************************************/ + +static int usbhost_disconnected(struct usbhost_class_s *class) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)class; + irqstate_t flags; + + DEBUGASSERT(priv != NULL); + + /* Set an indication to any users of the mass storage device that the device + * is no longer available. + */ + + flags = irqsave(); + priv->disconnected = true; + + /* Now check the number of references on the class instance. If it is one, + * then we can free the class instance now. Otherwise, we will have to + * wait until the holders of the references free them by closing the + * block driver. + */ + + ullvdbg("crefs: %d\n", priv->crefs); + if (priv->crefs == 1) + { + /* Destroy the class instance. If we are executing from an interrupt + * handler, then defer the destruction to the worker thread. + * Otherwise, destroy the instance now. + */ + + if (up_interrupt_context()) + { + /* Destroy the instance on the worker thread. */ + + uvdbg("Queuing destruction: worker %p->%p\n", priv->work.worker, usbhost_destroy); + DEBUGASSERT(priv->work.worker == NULL); + (void)work_queue(&priv->work, usbhost_destroy, priv, 0); + } + else + { + /* Do the work now */ + + usbhost_destroy(priv); + } + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * struct block_operations methods + ****************************************************************************/ +/**************************************************************************** + * Name: usbhost_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int usbhost_open(FAR struct inode *inode) +{ + FAR struct usbhost_state_s *priv; + irqstate_t flags; + int ret; + + uvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct usbhost_state_s *)inode->i_private; + + /* Make sure that we have exclusive access to the private data structure */ + + DEBUGASSERT(priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS); + usbhost_takesem(&priv->exclsem); + + /* Check if the mass storage device is still connected. We need to disable + * interrupts momentarily to assure that there are no asynchronous disconnect + * events. + */ + + flags = irqsave(); + if (priv->disconnected) + { + /* No... the block driver is no longer bound to the class. That means that + * the USB storage device is no longer connected. Refuse any further + * attempts to open the driver. + */ + + ret = -ENODEV; + } + else + { + /* Otherwise, just increment the reference count on the driver */ + + priv->crefs++; + ret = OK; + } + irqrestore(flags); + + usbhost_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: usbhost_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int usbhost_close(FAR struct inode *inode) +{ + FAR struct usbhost_state_s *priv; + irqstate_t flags; + + uvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct usbhost_state_s *)inode->i_private; + + /* Decrement the reference count on the block driver */ + + DEBUGASSERT(priv->crefs > 1); + usbhost_takesem(&priv->exclsem); + priv->crefs--; + + /* Release the semaphore. The following operations when crefs == 1 are + * safe because we know that there is no outstanding open references to + * the block driver. + */ + + usbhost_givesem(&priv->exclsem); + + /* We need to disable interrupts momentarily to assure that there are + * no asynchronous disconnect events. + */ + + flags = irqsave(); + + /* Check if the USB mass storage device is still connected. If the + * storage device is not connected and the reference count just + * decremented to one, then unregister the block driver and free + * the class instance. + */ + + if (priv->crefs <= 1 && priv->disconnected) + { + /* Destroy the class instance */ + + DEBUGASSERT(priv->crefs == 1); + usbhost_destroy(priv); + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Name: usbhost_read + * + * Description: + * Read the specified numer of sectors from the read-ahead buffer or from + * the physical device. + * + ****************************************************************************/ + +static ssize_t usbhost_read(FAR struct inode *inode, unsigned char *buffer, + size_t startsector, unsigned int nsectors) +{ + FAR struct usbhost_state_s *priv; + ssize_t ret = 0; + int result; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct usbhost_state_s *)inode->i_private; + uvdbg("startsector: %d nsectors: %d sectorsize: %d\n", + startsector, nsectors, priv->blocksize); + + /* Check if the mass storage device is still connected */ + + if (priv->disconnected) + { + /* No... the block driver is no longer bound to the class. That means that + * the USB storage device is no longer connected. Refuse any attempt to read + * from the device. + */ + + ret = -ENODEV; + } + else if (nsectors > 0) + { + FAR struct usbstrg_cbw_s *cbw; + + usbhost_takesem(&priv->exclsem); + + /* Assume allocation failure */ + + ret = -ENOMEM; + + /* Initialize a CBW (re-using the allocated transfer buffer) */ + + cbw = usbhost_cbwalloc(priv); + if (cbw) + { + /* Assume some device failure */ + + ret = -ENODEV; + + /* Construct and send the CBW */ + + usbhost_readcbw(startsector, priv->blocksize, nsectors, cbw); + result = DRVR_TRANSFER(priv->drvr, priv->bulkout, + (uint8_t*)cbw, USBSTRG_CBW_SIZEOF); + if (result == OK) + { + /* Receive the user data */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + buffer, priv->blocksize * nsectors); + if (result == OK) + { + /* Receive the CSW */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, USBSTRG_CSW_SIZEOF); + if (result == OK) + { + FAR struct usbstrg_csw_s *csw; + + /* Check the CSW status */ + + csw = (FAR struct usbstrg_csw_s *)priv->tbuffer; + if (csw->status == 0) + { + ret = nsectors; + } + } + } + } + } + + usbhost_givesem(&priv->exclsem); + } + + /* On success, return the number of blocks read */ + + return ret; +} + +/**************************************************************************** + * Name: usbhost_write + * + * Description: + * Write the specified number of sectors to the write buffer or to the + * physical device. + * + ****************************************************************************/ + +#ifdef CONFIG_FS_WRITABLE +static ssize_t usbhost_write(FAR struct inode *inode, const unsigned char *buffer, + size_t startsector, unsigned int nsectors) +{ + FAR struct usbhost_state_s *priv; + ssize_t ret; + int result; + + uvdbg("sector: %d nsectors: %d sectorsize: %d\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct usbhost_state_s *)inode->i_private; + + /* Check if the mass storage device is still connected */ + + if (priv->disconnected) + { + /* No... the block driver is no longer bound to the class. That means that + * the USB storage device is no longer connected. Refuse any attempt to + * write to the device. + */ + + ret = -ENODEV; + } + else + { + FAR struct usbstrg_cbw_s *cbw; + + usbhost_takesem(&priv->exclsem); + + /* Assume allocation failure */ + + ret = -ENOMEM; + + /* Initialize a CBW (re-using the allocated transfer buffer) */ + + cbw = usbhost_cbwalloc(priv); + if (cbw) + { + /* Assume some device failure */ + + ret = -ENODEV; + + /* Construct and send the CBW */ + + usbhost_writecbw(startsector, priv->blocksize, nsectors, cbw); + result = DRVR_TRANSFER(priv->drvr, priv->bulkout, + (uint8_t*)cbw, USBSTRG_CBW_SIZEOF); + if (result == OK) + { + /* Send the user data */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkout, + (uint8_t*)buffer, priv->blocksize * nsectors); + if (result == OK) + { + /* Receive the CSW */ + + result = DRVR_TRANSFER(priv->drvr, priv->bulkin, + priv->tbuffer, USBSTRG_CSW_SIZEOF); + if (result == OK) + { + FAR struct usbstrg_csw_s *csw; + + /* Check the CSW status */ + + csw = (FAR struct usbstrg_csw_s *)priv->tbuffer; + if (csw->status == 0) + { + ret = nsectors; + } + } + } + } + } + + usbhost_givesem(&priv->exclsem); + } + + /* On success, return the number of blocks written */ + + return ret; +} +#endif + +/**************************************************************************** + * Name: usbhost_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int usbhost_geometry(FAR struct inode *inode, struct geometry *geometry) +{ + FAR struct usbhost_state_s *priv; + int ret = -EINVAL; + + uvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + /* Check if the mass storage device is still connected */ + + priv = (FAR struct usbhost_state_s *)inode->i_private; + if (priv->disconnected) + { + /* No... the block driver is no longer bound to the class. That means that + * the USB storage device is no longer connected. Refuse to return any + * geometry info. + */ + + ret = -ENODEV; + } + else if (geometry) + { + /* Return the geometry of the USB mass storage device */ + + usbhost_takesem(&priv->exclsem); + + geometry->geo_available = true; + geometry->geo_mediachanged = false; +#ifdef CONFIG_FS_WRITABLE + geometry->geo_writeenabled = true; +#else + geometry->geo_writeenabled = false; +#endif + geometry->geo_nsectors = priv->nblocks; + geometry->geo_sectorsize = priv->blocksize; + usbhost_givesem(&priv->exclsem); + + uvdbg("nsectors: %ld sectorsize: %d\n", + (long)geometry->geo_nsectors, geometry->geo_sectorsize); + + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int usbhost_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) +{ + FAR struct usbhost_state_s *priv; + int ret; + + uvdbg("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct usbhost_state_s *)inode->i_private; + + /* Check if the mass storage device is still connected */ + + if (priv->disconnected) + { + /* No... the block driver is no longer bound to the class. That means that + * the USB storage device is no longer connected. Refuse to process any + * ioctl commands. + */ + + ret = -ENODEV; + } + else + { + /* Process the IOCTL by command */ + + usbhost_takesem(&priv->exclsem); + switch (cmd) + { + /* Add support for ioctl commands here */ + + default: + ret = -ENOTTY; + break; + } + usbhost_givesem(&priv->exclsem); + } + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_storageinit + * + * Description: + * Initialize the USB host storage class. This function should be called + * be platform-specific code in order to initialize and register support + * for the USB host storage class. + * + * Input Parameters: + * None + * + * Returned Values: + * On success this function will return zero (OK); A negated errno value + * will be returned on failure. + * + ****************************************************************************/ + +int usbhost_storageinit(void) +{ + /* If we have been configured to use pre-allocated storage class instances, + * then place all of the pre-allocated USB host storage class instances + * into a free list. + */ + +#if CONFIG_USBHOST_NPREALLOC > 0 + int i; + + g_freelist = NULL; + for (i = 0; i < CONFIG_USBHOST_NPREALLOC; i++) + { + struct usbhost_state_s *class = &g_prealloc[i]; + class->class.flink = g_freelist; + g_freelist = class; + } +#endif + + /* Advertise our availability to support (certain) mass storage devices */ + + return usbhost_registerclass(&g_storage); +} + +#endif /* CONFIG_USBHOST && !CONFIG_USBHOST_BULK_DISABLE && !CONFIG_DISABLE_MOUNTPOINT && CONFIG_NFILE_DESCRIPTORS > 0 */ diff --git a/nuttx/drivers/wireless/Make.defs b/nuttx/drivers/wireless/Make.defs new file mode 100644 index 000000000..ac73c8d85 --- /dev/null +++ b/nuttx/drivers/wireless/Make.defs @@ -0,0 +1,47 @@ +############################################################################ +# drivers/wireless/Make.defs +# +# Copyright (C) 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +ifeq ($(CONFIG_WIRELESS),y) + +# Include wireless drivers + +CSRCS += cc1101.c ISM1_868MHzGFSK100kbps.c ISM2_905MHzGFSK250kbps.c + +# Include wireless build support + +DEPPATH += --dep-path wireless/cc1101 +VPATH += :wireless/cc1101 +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/wireless/cc1101} +endif diff --git a/nuttx/drivers/wireless/cc1101/ISM1_868MHzGFSK100kbps.c b/nuttx/drivers/wireless/cc1101/ISM1_868MHzGFSK100kbps.c new file mode 100644 index 000000000..5c4c58ab2 --- /dev/null +++ b/nuttx/drivers/wireless/cc1101/ISM1_868MHzGFSK100kbps.c @@ -0,0 +1,113 @@ +/**************************************************************************** + * drivers/wireless/cc1101/ISM1_868MHzGFSK100kbps.c + * + * Copyright (C) 2011 Uros Platise. All rights reserved. + * Copyright (C) 2011 Ales Verbic. All rights reserved. + * + * Authors: Uros Platise <uros.platise@isotel.eu> + * Ales Verbic <ales.verbic@isotel.eu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#include <nuttx/wireless/cc1101.h> + +/** Settings for 868 MHz, GFSK at 100kbps + * + * ISM Region 1 (Europe) only, Band 868–870 MHz + * + * Frequency ERP Duty Cycle Bandwidth Remarks + * 868 – 868.6 MHz +14 dBm < 1% No limits + * 868.7 – 869.2 MHz +14 dBm < 0.1% No limits + * 869.3 – 869.4 MHz +10 dBm No limits < 25 kHz Appropriate access protocol required + * 869.4 – 869.65 MHz +27 dBm < 10% < 25 kHz Channels may be combined to one high speed channel + * 869.7 -870 MHz +7 dBm No limits No limits + * + * Deviation = 46.142578 + * Base frequency = 867.999985 + * Carrier frequency = 867.999985 + * Channel number = 0 + * Carrier frequency = 867.999985 + * Modulated = true + * Modulation format = GFSK + * Manchester enable = false + * Sync word qualifier mode = 30/32 sync word bits detected + * Preamble count = 4 + * Channel spacing = 199.813843 + * Carrier frequency = 867.999985 + * Data rate = 99.9069 + * RX filter BW = 210.937500 + * Data format = Normal mode + * Length config = Fixed packet length mode. Length configured in PKTLEN register + * CRC enable = true + * Packet length = 62 + * Device address = 00 + * Address config = NO Address check, no broadcast + * CRC autoflush = true + * PA ramping = false + * TX power = 0 + */ +const struct c1101_rfsettings_s cc1101_rfsettings_ISM1_868MHzGFSK100kbps = { + .FSCTRL1 = 0x08, // FSCTRL1 Frequency Synthesizer Control + .FSCTRL0 = 0x00, // FSCTRL0 Frequency Synthesizer Control + + .FREQ2 = 0x20, // FREQ2 Frequency Control Word, High Byte + .FREQ1 = 0x25, // FREQ1 Frequency Control Word, Middle Byte + .FREQ0 = 0xED, // FREQ0 Frequency Control Word, Low Byte + + .MDMCFG4 = 0x8B, // MDMCFG4 Modem Configuration + .MDMCFG3 = 0xE5, // MDMCFG3 Modem Configuration + .MDMCFG2 = 0x13, // MDMCFG2 Modem Configuration + .MDMCFG1 = 0x22, // MDMCFG1 Modem Configuration + .MDMCFG0 = 0xE5, // MDMCFG0 Modem Configuration + + .DEVIATN = 0x46, // DEVIATN Modem Deviation Setting + + .FOCCFG = 0x1D, // FOCCFG Frequency Offset Compensation Configuration + + .BSCFG = 0x1C, // BSCFG Bit Synchronization Configuration + + .AGCCTRL2= 0xC7, // AGCCTRL2 AGC Control + .AGCCTRL1= 0x00, // AGCCTRL1 AGC Control + .AGCCTRL0= 0xB2, // AGCCTRL0 AGC Control + + .FREND1 = 0xB6, // FREND1 Front End RX Configuration + .FREND0 = 0x10, // FREND0 Front End TX Configuration + + .FSCAL3 = 0xEA, // FSCAL3 Frequency Synthesizer Calibration + .FSCAL2 = 0x2A, // FSCAL2 Frequency Synthesizer Calibration + .FSCAL1 = 0x00, // FSCAL1 Frequency Synthesizer Calibration + .FSCAL0 = 0x1F, // FSCAL0 Frequency Synthesizer Calibration + + .CHMIN = 0, // Fix at 9th channel: 869.80 MHz +- 100 kHz RF Bandwidth + .CHMAX = 9, // single channel + + .PAMAX = 8, // 0 means power OFF, 8 represents PA[7] + .PA = {0x03, 0x0F, 0x1E, 0x27, 0x67, 0x50, 0x81, 0xC2} +}; diff --git a/nuttx/drivers/wireless/cc1101/ISM2_905MHzGFSK250kbps.c b/nuttx/drivers/wireless/cc1101/ISM2_905MHzGFSK250kbps.c new file mode 100644 index 000000000..e5655bed6 --- /dev/null +++ b/nuttx/drivers/wireless/cc1101/ISM2_905MHzGFSK250kbps.c @@ -0,0 +1,111 @@ +/**************************************************************************** + * drivers/wireless/cc1101/ISM2_905MHzGFSK250kbps.c + * + * Copyright (C) 2011 Uros Platise. All rights reserved. + * Copyright (C) 2011 Ales Verbic. All rights reserved. + * + * Authors: Uros Platise <uros.platise@isotel.eu> + * Ales Verbic <ales.verbic@isotel.eu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#include <nuttx/wireless/cc1101.h> + +/** Settings for 905 MHz, GFSK at 250kbps + * + * ISM Region 2 (America) only, Band 902–928 MHz + * + * Cordless phones 1 W + * Microwave ovens 750 W + * Industrial heaters 100 kW + * Military radar 1000 kW + * + * Deviation = 126.953125 + * Base frequency = 901.999969 + * Carrier frequency = 905.998993 + * Channel number = 20 + * Carrier frequency = 905.998993 + * Modulated = true + * Modulation format = GFSK + * Manchester enable = false + * Sync word qualifier mode = 30/32 sync word bits detected + * Preamble count = 4 + * Channel spacing = 199.951172 + * Carrier frequency = 905.998993 + * Data rate = 249.939 + * RX filter BW = 541.666667 + * Data format = Normal mode + * Length config = Variable packet length mode. Packet length configured by the first byte after sync word + * CRC enable = true + * Packet length = 61 + * Device address = 0 + * Address config = No address check + * CRC autoflush = false + * PA ramping = false + * TX power = 0 + */ +const struct c1101_rfsettings_s cc1101_rfsettings_ISM2_905MHzGFSK250kbps = { + .FSCTRL1 = 0x0C, // FSCTRL1 Frequency Synthesizer Control + .FSCTRL0 = 0x00, // FSCTRL0 Frequency Synthesizer Control + + .FREQ2 = 0x22, // FREQ2 Frequency Control Word, High Byte + .FREQ1 = 0xB1, // FREQ1 Frequency Control Word, Middle Byte + .FREQ0 = 0x3B, // FREQ0 Frequency Control Word, Low Byte + + .MDMCFG4 = 0x2D, // MDMCFG4 Modem Configuration + .MDMCFG3 = 0x3B, // MDMCFG3 Modem Configuration + .MDMCFG2 = 0x13, // MDMCFG2 Modem Configuration + .MDMCFG1 = 0x22, // MDMCFG1 Modem Configuration + .MDMCFG0 = 0xF8, // MDMCFG0 Modem Configuration + + .DEVIATN = 0x62, // DEVIATN Modem Deviation Setting + + .FOCCFG = 0x1D, // FOCCFG Frequency Offset Compensation Configuration + + .BSCFG = 0x1C, // BSCFG Bit Synchronization Configuration + + .AGCCTRL2= 0xC7, // AGCCTRL2 AGC Control + .AGCCTRL1= 0x00, // AGCCTRL1 AGC Control + .AGCCTRL0= 0xB0, // AGCCTRL0 AGC Control + + .FREND1 = 0xB6, // FREND1 Front End RX Configuration + .FREND0 = 0x10, // FREND0 Front End TX Configuration + + .FSCAL3 = 0xEA, // FSCAL3 Frequency Synthesizer Calibration + .FSCAL2 = 0x2A, // FSCAL2 Frequency Synthesizer Calibration + .FSCAL1 = 0x00, // FSCAL1 Frequency Synthesizer Calibration + .FSCAL0 = 0x1F, // FSCAL0 Frequency Synthesizer Calibration + + .CHMIN = 0, // VERIFY REGULATIONS! + .CHMAX = 0xFF, + + .PAMAX = 8, // 0 means power OFF, 8 represents PA[7] + .PA = {0x03, 0x0E, 0x1E, 0x27, 0x39, 0x8E, 0xCD, 0xC0} +}; diff --git a/nuttx/drivers/wireless/cc1101/cc1101.c b/nuttx/drivers/wireless/cc1101/cc1101.c new file mode 100755 index 000000000..a35575690 --- /dev/null +++ b/nuttx/drivers/wireless/cc1101/cc1101.c @@ -0,0 +1,813 @@ +/**************************************************************************** + * drivers/wireless/cc1101/cc1101.c + * + * Copyright (C) 2011 Uros Platise. All rights reserved. + * + * Authors: Uros Platise <uros.platise@isotel.eu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** \file + * \author Uros Platise + * \brief Chipcon CC1101 Device Driver + * + * Features: + * - Maximum data length: 61 bytes CC1101_PACKET_MAXDATALEN + * - Packet length includes two additional bytes: CC1101_PACKET_MAXTOTALLEN + * - Requires one GDO to trigger end-of-packets in RX and TX modes. + * - Variable packet length with data payload between 1..61 bytes + * (three bytes are reserved for packet length, and RSSI and LQI + * appended at the end of RXFIFO after each reception) + * - Support for General Digital Outputs with overload protection + * (single XOSC pin is allowed, otherwise error is returned) + * - Loadable RF settings, one for ISM Region 1 (Europe) and one for + * ISM Region 2 (Complete America) + * + * Todo: + * - Extend max packet length up to 255 bytes or rather infinite < 4096 bytes + * - Power up/down modes + * - Sequencing between states or add protection for correct termination of + * various different state (so that CC1101 does not block in case of improper use) + * + * \par RSSI and LQI value interpretation + * + * The LQI can be read from the LQI status register or it can be appended + * to the received packet in the RX FIFO. LQI is a metric of the current + * quality of the received signal. The LQI gives an estimate of how easily + * a received signal can be demodulated by accumulating the magnitude of + * the error between ideal constellations and the received signal over + * the 64 symbols immediately following the sync word. LQI is best used + * as a relative measurement of the link quality (a high value indicates + * a better link than what a low value does), since the value is dependent + * on the modulation format. + * + * To simplify: If the received modulation is FSK or GFSK, the receiver + * will measure the frequency of each "bit" and compare it with the + * expected frequency based on the channel frequency and the deviation + * and the measured frequency offset. If other modulations are used, the + * error of the modulated parameter (frequency for FSK/GFSK, phase for + * MSK, amplitude for ASK etc) will be measured against the expected + * ideal value + * + * RSSI (Received Signal Strength Indicator) is a signal strength + * indication. It does not care about the "quality" or "correctness" of + * the signal. LQI does not care about the actual signal strength, but + * the signal quality often is linked to signal strength. This is because + * a strong signal is likely to be less affected by noise and thus will + * be seen as "cleaner" or more "correct" by the receiver. + * + * There are four to five "extreme cases" that can be used to illustrate + * how RSSI and LQI work: + * 1. A weak signal in the presence of noise may give low RSSI and low LQI. + * 2. A weak signal in "total" absence of noise may give low RSSI and high LQI. + * 3. Strong noise (usually coming from an interferer) may give high RSSI and low LQI. + * 4. A strong signal without much noise may give high RSSI and high LQI. + * 5. A very strong signal that causes the receiver to saturate may give + * high RSSI and low LQI. + * + * Note that both RSSI and LQI are best used as relative measurements since + * the values are dependent on the modulation format. + **/ + +#include <nuttx/config.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/wireless/cc1101.h> + + +/**************************************************************************** + * Declarations + ****************************************************************************/ + +#define CC1101_SPIFREQ_BURST 6500000 /* Hz, no delay */ +#define CC1101_SPIFREQ_SINGLE 9000000 /* Hz, single access only - no delay */ + +#define CC1101_MCSM0_VALUE 0x1C + +/**************************************************************************** + * Chipcon CC1101 Internal Registers + ****************************************************************************/ + +/* Configuration Registers */ + +#define CC1101_IOCFG2 0x00 /* GDO2 output pin configuration */ +#define CC1101_IOCFG1 0x01 /* GDO1 output pin configuration */ +#define CC1101_IOCFG0 0x02 /* GDO0 output pin configuration */ +#define CC1101_FIFOTHR 0x03 /* RX FIFO and TX FIFO thresholds */ +#define CC1101_SYNC1 0x04 /* Sync word, high byte */ +#define CC1101_SYNC0 0x05 /* Sync word, low byte */ +#define CC1101_PKTLEN 0x06 /* Packet length */ +#define CC1101_PKTCTRL1 0x07 /* Packet automation control */ +#define CC1101_PKTCTRL0 0x08 /* Packet automation control */ +#define CC1101_ADDR 0x09 /* Device address */ +#define CC1101_CHANNR 0x0A /* Channel number */ +#define CC1101_FSCTRL1 0x0B /* Frequency synthesizer control */ +#define CC1101_FSCTRL0 0x0C /* Frequency synthesizer control */ +#define CC1101_FREQ2 0x0D /* Frequency control word, high byte */ +#define CC1101_FREQ1 0x0E /* Frequency control word, middle byte */ +#define CC1101_FREQ0 0x0F /* Frequency control word, low byte */ +#define CC1101_MDMCFG4 0x10 /* Modem configuration */ +#define CC1101_MDMCFG3 0x11 /* Modem configuration */ +#define CC1101_MDMCFG2 0x12 /* Modem configuration */ +#define CC1101_MDMCFG1 0x13 /* Modem configuration */ +#define CC1101_MDMCFG0 0x14 /* Modem configuration */ +#define CC1101_DEVIATN 0x15 /* Modem deviation setting */ +#define CC1101_MCSM2 0x16 /* Main Radio Cntrl State Machine config */ +#define CC1101_MCSM1 0x17 /* Main Radio Cntrl State Machine config */ +#define CC1101_MCSM0 0x18 /* Main Radio Cntrl State Machine config */ +#define CC1101_FOCCFG 0x19 /* Frequency Offset Compensation config */ +#define CC1101_BSCFG 0x1A /* Bit Synchronization configuration */ +#define CC1101_AGCCTRL2 0x1B /* AGC control */ +#define CC1101_AGCCTRL1 0x1C /* AGC control */ +#define CC1101_AGCCTRL0 0x1D /* AGC control */ +#define CC1101_WOREVT1 0x1E /* High byte Event 0 timeout */ +#define CC1101_WOREVT0 0x1F /* Low byte Event 0 timeout */ +#define CC1101_WORCTRL 0x20 /* Wake On Radio control */ +#define CC1101_FREND1 0x21 /* Front end RX configuration */ +#define CC1101_FREND0 0x22 /* Front end TX configuration */ +#define CC1101_FSCAL3 0x23 /* Frequency synthesizer calibration */ +#define CC1101_FSCAL2 0x24 /* Frequency synthesizer calibration */ +#define CC1101_FSCAL1 0x25 /* Frequency synthesizer calibration */ +#define CC1101_FSCAL0 0x26 /* Frequency synthesizer calibration */ +#define CC1101_RCCTRL1 0x27 /* RC oscillator configuration */ +#define CC1101_RCCTRL0 0x28 /* RC oscillator configuration */ +#define CC1101_FSTEST 0x29 /* Frequency synthesizer cal control */ +#define CC1101_PTEST 0x2A /* Production test */ +#define CC1101_AGCTEST 0x2B /* AGC test */ +#define CC1101_TEST2 0x2C /* Various test settings */ +#define CC1101_TEST1 0x2D /* Various test settings */ +#define CC1101_TEST0 0x2E /* Various test settings */ + +/* Status registers */ + +#define CC1101_PARTNUM (0x30 | 0xc0) /* Part number */ +#define CC1101_VERSION (0x31 | 0xc0) /* Current version number */ +#define CC1101_FREQEST (0x32 | 0xc0) /* Frequency offset estimate */ +#define CC1101_LQI (0x33 | 0xc0) /* Demodulator estimate for link quality */ +#define CC1101_RSSI (0x34 | 0xc0) /* Received signal strength indication */ +#define CC1101_MARCSTATE (0x35 | 0xc0) /* Control state machine state */ +#define CC1101_WORTIME1 (0x36 | 0xc0) /* High byte of WOR timer */ +#define CC1101_WORTIME0 (0x37 | 0xc0) /* Low byte of WOR timer */ +#define CC1101_PKTSTATUS (0x38 | 0xc0) /* Current GDOx status and packet status */ +#define CC1101_VCO_VC_DAC (0x39 | 0xc0) /* Current setting from PLL cal module */ +#define CC1101_TXBYTES (0x3A | 0xc0) /* Underflow and # of bytes in TXFIFO */ +#define CC1101_RXBYTES (0x3B | 0xc0) /* Overflow and # of bytes in RXFIFO */ +#define CC1101_RCCTRL1_STATUS (0x3C | 0xc0) /* Last RC oscilator calibration results */ +#define CC1101_RCCTRL0_STATUS (0x3D | 0xc0) /* Last RC oscilator calibration results */ + +/* Multi byte memory locations */ + +#define CC1101_PATABLE 0x3E +#define CC1101_TXFIFO 0x3F +#define CC1101_RXFIFO 0x3F + +/* Definitions for burst/single access to registers */ + +#define CC1101_WRITE_BURST 0x40 +#define CC1101_READ_SINGLE 0x80 +#define CC1101_READ_BURST 0xC0 + +/* Strobe commands */ + +#define CC1101_SRES 0x30 /* Reset chip. */ +#define CC1101_SFSTXON 0x31 /* Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1). */ +#define CC1101_SXOFF 0x32 /* Turn off crystal oscillator. */ +#define CC1101_SCAL 0x33 /* Calibrate frequency synthesizer and turn it off */ +#define CC1101_SRX 0x34 /* Enable RX. Perform calibration first if switching from IDLE and MCSM0.FS_AUTOCAL=1. */ +#define CC1101_STX 0x35 /* Enable TX. Perform calibration first if IDLE and MCSM0.FS_AUTOCAL=1. */ + /* If switching from RX state and CCA is enabled then go directly to TX if channel is clear. */ +#define CC1101_SIDLE 0x36 /* Exit RX / TX, turn off frequency synthesizer and exit Wake-On-Radio mode if applicable. */ +#define CC1101_SAFC 0x37 /* Perform AFC adjustment of the frequency synthesizer */ +#define CC1101_SWOR 0x38 /* Start automatic RX polling sequence (Wake-on-Radio) */ +#define CC1101_SPWD 0x39 /* Enter power down mode when CSn goes high. */ +#define CC1101_SFRX 0x3A /* Flush the RX FIFO buffer. */ +#define CC1101_SFTX 0x3B /* Flush the TX FIFO buffer. */ +#define CC1101_SWORRST 0x3C /* Reset real time clock. */ +#define CC1101_SNOP 0x3D /* No operation. */ + +/* Modem Control */ + +#define CC1101_MCSM0_XOSC_FORCE_ON 0x01 + + +/* + * Chip Status Byte + */ + +/* Bit fields in the chip status byte */ + +#define CC1101_STATUS_CHIP_RDYn_BM 0x80 +#define CC1101_STATUS_STATE_BM 0x70 +#define CC1101_STATUS_FIFO_BYTES_AVAILABLE_BM 0x0F + +/* Chip states */ + +#define CC1101_STATE_MASK 0x70 +#define CC1101_STATE_IDLE 0x00 +#define CC1101_STATE_RX 0x10 +#define CC1101_STATE_TX 0x20 +#define CC1101_STATE_FSTXON 0x30 +#define CC1101_STATE_CALIBRATE 0x40 +#define CC1101_STATE_SETTLING 0x50 +#define CC1101_STATE_RX_OVERFLOW 0x60 +#define CC1101_STATE_TX_UNDERFLOW 0x70 + +/* Values of the MACRSTATE register */ + +#define CC1101_MARCSTATE_SLEEP 0x00 +#define CC1101_MARCSTATE_IDLE 0x01 +#define CC1101_MARCSTATE_XOFF 0x02 +#define CC1101_MARCSTATE_VCOON_MC 0x03 +#define CC1101_MARCSTATE_REGON_MC 0x04 +#define CC1101_MARCSTATE_MANCAL 0x05 +#define CC1101_MARCSTATE_VCOON 0x06 +#define CC1101_MARCSTATE_REGON 0x07 +#define CC1101_MARCSTATE_STARTCAL 0x08 +#define CC1101_MARCSTATE_BWBOOST 0x09 +#define CC1101_MARCSTATE_FS_LOCK 0x0A +#define CC1101_MARCSTATE_IFADCON 0x0B +#define CC1101_MARCSTATE_ENDCAL 0x0C +#define CC1101_MARCSTATE_RX 0x0D +#define CC1101_MARCSTATE_RX_END 0x0E +#define CC1101_MARCSTATE_RX_RST 0x0F +#define CC1101_MARCSTATE_TXRX_SWITCH 0x10 +#define CC1101_MARCSTATE_RXFIFO_OVERFLOW 0x11 +#define CC1101_MARCSTATE_FSTXON 0x12 +#define CC1101_MARCSTATE_TX 0x13 +#define CC1101_MARCSTATE_TX_END 0x14 +#define CC1101_MARCSTATE_RXTX_SWITCH 0x15 +#define CC1101_MARCSTATE_TXFIFO_UNDERFLOW 0x16 + +/* Part number and version */ + +#define CC1101_PARTNUM_VALUE 0x00 +#define CC1101_VERSION_VALUE 0x04 + +/* + * Others ... + */ + +#define CC1101_LQI_CRC_OK_BM 0x80 +#define CC1101_LQI_EST_BM 0x7F + + +/**************************************************************************** + * Private Data Types + ****************************************************************************/ + +#define FLAGS_RXONLY 1 /* Indicates receive operation only */ +#define FLAGS_XOSCENABLED 2 /* Indicates that one pin is configured as XOSC/n */ + +struct cc1101_dev_s { + const struct c1101_rfsettings_s *rfsettings; + + struct spi_dev_s * spi; + uint8_t isrpin; /* CC1101 pin used to trigger interrupts */ + uint32_t pinset; /* GPIO of the MCU */ + uint8_t flags; + uint8_t channel; + uint8_t power; +}; + + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +void cc1101_access_begin(struct cc1101_dev_s * dev) +{ + SPI_LOCK(dev->spi, true); + SPI_SELECT(dev->spi, SPIDEV_WIRELESS, true); + SPI_SETMODE(dev->spi, SPIDEV_MODE0); /* CPOL=0, CPHA=0 */ + SPI_SETBITS(dev->spi, 8); +} + + +void cc1101_access_end(struct cc1101_dev_s * dev) +{ + SPI_SELECT(dev->spi, SPIDEV_WIRELESS, false); + SPI_LOCK(dev->spi, false); +} + + + +/** CC1101 Access with Range Check + * + * \param dev CC1101 Private Structure + * \param addr CC1101 Address + * \param buf Pointer to buffer, either for read or write access + * \param length when >0 it denotes read access, when <0 it denotes write + * access of -length. abs(length) greater of 1 implies burst mode, + * however + * \return OK on success or errno is set. + */ +int cc1101_access(struct cc1101_dev_s * dev, uint8_t addr, uint8_t *buf, int length) +{ + int stabyte; + + /* Address cannot explicitly define READ command while length WRITE. + * Also access to these cells is only permitted as one byte, eventhough + * transfer is marked as BURST! + */ + + if ( (addr & CC1101_READ_SINGLE) && length != 1 ) + return ERROR; + + /* Prepare SPI */ + + cc1101_access_begin(dev); + + if (length>1 || length < -1) + SPI_SETFREQUENCY(dev->spi, CC1101_SPIFREQ_BURST); + else SPI_SETFREQUENCY(dev->spi, CC1101_SPIFREQ_SINGLE); + + /* Transfer */ + + if (length <= 0) { /* 0 length are command strobes */ + if (length < -1) + addr |= CC1101_WRITE_BURST; + + stabyte = SPI_SEND(dev->spi, addr); + if (length) { + SPI_SNDBLOCK(dev->spi, buf, -length); + } + } + else { + addr |= CC1101_READ_SINGLE; + if (length > 1) + addr |= CC1101_READ_BURST; + + stabyte = SPI_SEND(dev->spi, addr); + SPI_RECVBLOCK(dev->spi, buf, length); + } + + cc1101_access_end(dev); + + return stabyte; +} + + +/** Strobes command and returns chip status byte + * + * By default commands are send as Write. To a command, + * CC1101_READ_SINGLE may be OR'ed to obtain the number of RX bytes + * pending in RX FIFO. + */ +inline uint8_t cc1101_strobe(struct cc1101_dev_s * dev, uint8_t command) +{ + uint8_t status; + + cc1101_access_begin(dev); + SPI_SETFREQUENCY(dev->spi, CC1101_SPIFREQ_SINGLE); + + status = SPI_SEND(dev->spi, command); + + cc1101_access_end(dev); + + return status; +} + + +int cc1101_reset(struct cc1101_dev_s * dev) +{ + cc1101_strobe(dev, CC1101_SRES); + return OK; +} + + +int cc1101_checkpart(struct cc1101_dev_s * dev) +{ + uint8_t partnum, version; + + if (cc1101_access(dev, CC1101_PARTNUM, &partnum, 1) < 0 || + cc1101_access(dev, CC1101_VERSION, &version, 1) < 0) + return ERROR; + + if (partnum == CC1101_PARTNUM_VALUE && version == CC1101_VERSION_VALUE) + return OK; + + return ERROR; +} + + +void cc1101_dumpregs(struct cc1101_dev_s * dev, uint8_t addr, uint8_t length) +{ + uint8_t buf[0x30], i; + + cc1101_access(dev, addr, buf, length); + + printf("CC1101[%2x]: ", addr); + for (i=0; i<length; i++) printf(" %2x,", buf[i]); + printf("\n"); +} + + +void cc1101_setpacketctrl(struct cc1101_dev_s * dev) +{ + uint8_t values[3]; + + values[0] = 0; /* Rx FIFO threshold = 32, Tx FIFO threshold = 33 */ + cc1101_access(dev, CC1101_FIFOTHR, values, -1); + + /* Packet length + * Limit it to 61 bytes in total: pktlen, data[61], rssi, lqi + */ + + values[0] = CC1101_PACKET_MAXDATALEN; + cc1101_access(dev, CC1101_PKTLEN, values, -1); + + /* Packet Control */ + + values[0] = 0x04; /* Append status: RSSI and LQI at the end of received packet */ + /* TODO: CRC Auto Flash bit 0x08 ??? */ + values[1] = 0x05; /* CRC in Rx and Tx Enabled: Variable Packet mode, defined by first byte */ + /* TODO: Enable data whitening ... */ + cc1101_access(dev, CC1101_PKTCTRL1, values, -2); + + /* Main Radio Control State Machine */ + + values[0] = 0x07; /* No time-out */ + values[1] = 0x00; /* Clear channel if RSSI < thr && !receiving; + * TX -> RX, RX -> RX: 0x3F */ + values[2] = CC1101_MCSM0_VALUE; /* Calibrate on IDLE -> RX/TX, OSC Timeout = ~500 us + TODO: has XOSC_FORCE_ON */ + cc1101_access(dev, CC1101_MCSM2, values, -3); + + /* Wake-On Radio Control */ + + // Not used yet. + + // WOREVT1:WOREVT0 - 16-bit timeout register +} + + +/**************************************************************************** + * Callbacks + ****************************************************************************/ + +volatile int cc1101_interrupt = 0; + +/** External line triggers this callback + * + * The concept todo is: + * - GPIO provides EXTI Interrupt + * - It should handle EXTI Interrupts in ISR, to which chipcon can + * register a callback (and others). The ISR then foreach() calls a + * its callback, and it is up to peripheral to find, whether the cause + * of EXTI ISR was itself. + **/ + +int cc1101_eventcb(int irq, FAR void *context) +{ + cc1101_interrupt++; + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct cc1101_dev_s * cc1101_init(struct spi_dev_s * spi, uint8_t isrpin, + uint32_t pinset, const struct c1101_rfsettings_s * rfsettings) +{ + struct cc1101_dev_s * dev; + + ASSERT(spi); + + if ( (dev = kmalloc( sizeof(struct cc1101_dev_s) )) == NULL) { + errno = ENOMEM; + return NULL; + } + + dev->rfsettings = rfsettings; + dev->spi = spi; + dev->isrpin = isrpin; + dev->pinset = pinset; + dev->flags = 0; + dev->channel = rfsettings->CHMIN; + dev->power = rfsettings->PAMAX; + + /* Reset chip, check status bytes */ + + if ( cc1101_reset(dev) < 0 ) { + kfree(dev); + errno = EFAULT; + return NULL; + } + + /* Check part compatibility */ + + if ( cc1101_checkpart(dev) < 0 ) { + kfree(dev); + errno = ENODEV; + return NULL; + } + + /* Configure CC1101: + * - disable GDOx for best performance + * - load RF + * - and packet control + */ + + cc1101_setgdo(dev, CC1101_PIN_GDO0, CC1101_GDO_HIZ); + cc1101_setgdo(dev, CC1101_PIN_GDO1, CC1101_GDO_HIZ); + cc1101_setgdo(dev, CC1101_PIN_GDO2, CC1101_GDO_HIZ); + cc1101_setrf(dev, rfsettings); + cc1101_setpacketctrl(dev); + + /* Set the ISR to be triggerred on falling edge of the: + * + * 6 (0x06) Asserts when sync word has been sent / received, and + * de-asserts at the end of the packet. In RX, the pin will de-assert + * when the optional address check fails or the RX FIFO overflows. + * In TX the pin will de-assert if the TX FIFO underflows. + */ + + cc1101_setgdo(dev, dev->isrpin, CC1101_GDO_SYNC); + + /* Bind to external interrupt line */ + + // depends on STM32: TODO: Make that config within pinset and + // provide general gpio interface + //stm32_gpiosetevent(pinset, false, true, true, cc1101_eventcb); + + return dev; +} + + +int cc1101_deinit(struct cc1101_dev_s * dev) +{ + ASSERT(dev); + + /* Release interrupt */ + //stm32_gpiosetevent(pinset, false, false, false, NULL); + + /* Power down chip */ + cc1101_powerdown(dev); + + /* Release external interrupt line */ + kfree(dev); + + return 0; +} + + +int cc1101_powerup(struct cc1101_dev_s * dev) +{ + ASSERT(dev); + return 0; +} + + +int cc1101_powerdown(struct cc1101_dev_s * dev) +{ + ASSERT(dev); + return 0; +} + + +int cc1101_setgdo(struct cc1101_dev_s * dev, uint8_t pin, uint8_t function) +{ + ASSERT(dev); + ASSERT(pin <= CC1101_IOCFG0); + + if (function >= CC1101_GDO_CLK_XOSC1) { + + /* Only one pin can be enabled at a time as XOSC/n */ + + if (dev->flags & FLAGS_XOSCENABLED) return -EPERM; + + /* Force XOSC to stay active even in sleep mode */ + + int value = CC1101_MCSM0_VALUE | CC1101_MCSM0_XOSC_FORCE_ON; + cc1101_access(dev, CC1101_MCSM0, &value, -1); + + dev->flags |= FLAGS_XOSCENABLED; + } + else if (dev->flags & FLAGS_XOSCENABLED) { + + /* Disable XOSC in sleep mode */ + + int value = CC1101_MCSM0_VALUE; + cc1101_access(dev, CC1101_MCSM0, &value, -1); + + dev->flags &= ~FLAGS_XOSCENABLED; + } + + return cc1101_access(dev, pin, &function, -1); +} + + +int cc1101_setrf(struct cc1101_dev_s * dev, const struct c1101_rfsettings_s *settings) +{ + ASSERT(dev); + ASSERT(settings); + + if (cc1101_access(dev, CC1101_FSCTRL1, &settings->FSCTRL1, -11) < 0) return ERROR; + if (cc1101_access(dev, CC1101_FOCCFG, &settings->FOCCFG, -5) < 0) return ERROR; + if (cc1101_access(dev, CC1101_FREND1, &settings->FREND1, -6) < 0) return ERROR; + + /* Load Power Table */ + + if (cc1101_access(dev, CC1101_PATABLE, settings->PA, -8) < 0) return ERROR; + + /* If channel is out of valid range, mark that. Limit power. + * We are not allowed to send any data, but are allowed to listen + * and receive. + */ + + cc1101_setchannel(dev, dev->channel); + cc1101_setpower(dev, dev->power); + + return OK; +} + + +int cc1101_setchannel(struct cc1101_dev_s * dev, uint8_t channel) +{ + ASSERT(dev); + + /* Store localy in further checks */ + + dev->channel = channel; + + /* If channel is out of valid, we are allowed to listen and receive only */ + + if (channel < dev->rfsettings->CHMIN || channel > dev->rfsettings->CHMAX) + dev->flags |= FLAGS_RXONLY; + else dev->flags &= ~FLAGS_RXONLY; + + cc1101_access(dev, CC1101_CHANNR, &dev->channel, -1); + + return dev->flags & FLAGS_RXONLY; +} + + +uint8_t cc1101_setpower(struct cc1101_dev_s * dev, uint8_t power) +{ + ASSERT(dev); + + if (power > dev->rfsettings->PAMAX) + power = dev->rfsettings->PAMAX; + + dev->power = power; + + if (power == 0) { + dev->flags |= FLAGS_RXONLY; + return 0; + } + else dev->flags &= ~FLAGS_RXONLY; + + /* Add remaining part from RF table (to get rid of readback) */ + + power--; + power |= dev->rfsettings->FREND0; + + /* On error, report that as zero power */ + + if (cc1101_access(dev, CC1101_FREND0, &power, -1) < 0) + dev->power = 0; + + return dev->power; +} + + +int cc1101_calcRSSIdBm(int rssi) +{ + if (rssi >= 128) rssi -= 256; + return (rssi >> 1) - 74; +} + + +int cc1101_receive(struct cc1101_dev_s * dev) +{ + ASSERT(dev); + + /* \todo Wait for IDLE before going into another state? */ + + cc1101_interrupt = 0; + + cc1101_strobe(dev, CC1101_SRX | CC1101_READ_SINGLE); + + return 0; +} + + +int cc1101_read(struct cc1101_dev_s * dev, uint8_t * buf, size_t size) +{ + ASSERT(dev); + + if (buf==NULL) { + if (size==0) return 64; + // else received packet size + return 0; + } + + if (cc1101_interrupt == 0) return 0; + + int status = cc1101_strobe(dev, CC1101_SNOP | CC1101_READ_SINGLE); + + if (status & CC1101_STATUS_FIFO_BYTES_AVAILABLE_BM && + (status & CC1101_STATE_MASK) == CC1101_STATE_IDLE) { + + uint8_t nbytes; + + cc1101_access(dev, CC1101_RXFIFO, &nbytes, 1); + + nbytes += 2; /* RSSI and LQI */ + + cc1101_access(dev, CC1101_RXFIFO, buf, (nbytes > size) ? size : nbytes); + + /* Flush remaining bytes, if there is no room to receive + * or if there is a BAD CRC + */ + + if (nbytes > size || (nbytes <= size && !(buf[nbytes-1]&0x80)) ) { + printf("Flushing RX FIFO\n"); + cc1101_strobe(dev, CC1101_SFRX); + } + + return nbytes; + } + + return 0; +} + + +int cc1101_write(struct cc1101_dev_s * dev, const uint8_t * buf, size_t size) +{ + uint8_t packetlen; + + ASSERT(dev); + ASSERT(buf); + + if (dev->flags & FLAGS_RXONLY) return -EPERM; + + /* Present limit */ + if (size > CC1101_PACKET_MAXDATALEN) + packetlen = CC1101_PACKET_MAXDATALEN; + else packetlen = size; + + cc1101_access(dev, CC1101_TXFIFO, &packetlen, -1); + cc1101_access(dev, CC1101_TXFIFO, buf, -size); + + return 0; +} + + +int cc1101_send(struct cc1101_dev_s * dev) +{ + ASSERT(dev); + + if (dev->flags & FLAGS_RXONLY) return -EPERM; + + cc1101_interrupt = 0; + + cc1101_strobe(dev, CC1101_STX); + + /* wait until send, going to IDLE */ + + while( cc1101_interrupt == 0 ); + + return 0; +} + + +int cc1101_idle(struct cc1101_dev_s * dev) +{ + ASSERT(dev); + cc1101_strobe(dev, CC1101_SIDLE); + return 0; +} |