diff --git a/drivers/usbmisc/Kconfig b/drivers/usbmisc/Kconfig index 66690612833..cf2fca823c2 100644 --- a/drivers/usbmisc/Kconfig +++ b/drivers/usbmisc/Kconfig @@ -6,11 +6,11 @@ comment "USB Miscellaneous drivers" config FUSB301 - bool "Fairchild FUSB301 USB type-C controller support" + bool "On Semiconductor FUSB301 USB Type-C controller support" default n select I2C ---help--- - Enable device driver for Fairchild USB type-C controller + Enable device driver for Fairchild/On Semiconductor USB Type-C controller if FUSB301 @@ -32,3 +32,31 @@ config FUSB301_NPOLLWAITERS Maximum number of threads that can be waiting on poll() endif + +config FUSB303 + bool "On Semiconductor FUSB303 USB Type-C controller support" + default n + select I2C + ---help--- + Enable device driver for Fairchild/On Semiconductor USB Type-C controller + +if FUSB303 + +config FUSB303_I2C_FREQUENCY + int "FUSB303 I2C frequency" + default 400000 + range 1 400000 + +config DEBUG_FUSB303 + bool "Enable debug support for the FUSB303" + default n + ---help--- + Enables debug support for the FUSB303 + +config FUSB303_NPOLLWAITERS + int "Number of waiters to poll" + default 2 + ---help--- + Maximum number of threads that can be waiting on poll() + +endif diff --git a/drivers/usbmisc/Make.defs b/drivers/usbmisc/Make.defs index fd9dd6af37e..a3d6a57b532 100644 --- a/drivers/usbmisc/Make.defs +++ b/drivers/usbmisc/Make.defs @@ -41,6 +41,10 @@ ifeq ($(CONFIG_FUSB301),y) CSRCS += fusb301.c endif +ifeq ($(CONFIG_FUSB303),y) + CSRCS += fusb303.c +endif + # Include USB miscellaneous build support DEPPATH += --dep-path usbmisc diff --git a/drivers/usbmisc/fusb301.c b/drivers/usbmisc/fusb301.c index 98da77b5964..fdedd9cf0b7 100644 --- a/drivers/usbmisc/fusb301.c +++ b/drivers/usbmisc/fusb301.c @@ -120,7 +120,7 @@ static const struct file_operations g_fusb301ops = #endif #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS , NULL /* unlink */ - #endif +#endif }; /**************************************************************************** @@ -410,7 +410,7 @@ static int fusb301_set_state(FAR struct fusb301_dev_s *priv, * Name: fusb301_read_status * * Description: - * Clear read status register + * Read status register * ****************************************************************************/ @@ -848,7 +848,11 @@ int fusb301_register(FAR const char *devpath, FAR struct i2c_master_s *i2c, /* Prepare interrupt line and handler. */ - priv->config->irq_clear(config); + if (priv->config->irq_clear) + { + priv->config->irq_clear(config); + } + priv->config->irq_attach(config, fusb301_int_handler, priv); priv->config->irq_enable(config, false); diff --git a/drivers/usbmisc/fusb303.c b/drivers/usbmisc/fusb303.c new file mode 100644 index 00000000000..9f34521ab03 --- /dev/null +++ b/drivers/usbmisc/fusb303.c @@ -0,0 +1,1056 @@ +/**************************************************************************** + * drivers/usbmisc/fusb303.c + * + * FUSB303 USB-C controller driver + * + * Copyright (C) 2019 Haltian Ltd. All rights reserved. + * Authors: Harri Luhtala + * Juha Niskanen + * + * 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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_FUSB303 +# define fusb303_err(x, ...) _err(x, ##__VA_ARGS__) +# define fusb303_info(x, ...) _info(x, ##__VA_ARGS__) +#else +# define fusb303_err(x, ...) uerr(x, ##__VA_ARGS__) +# define fusb303_info(x, ...) uinfo(x, ##__VA_ARGS__) +#endif + +#ifndef CONFIG_FUSB303_I2C_FREQUENCY +# define CONFIG_FUSB303_I2C_FREQUENCY 400000 +#endif + +/* Other macros */ + +#define FUSB303_I2C_RETRIES 10 + +#define FUSB303_ALL_INTR (INTERRUPT_ATTACH | INTERRUPT_DETACH | \ + INTERRUPT_BC_LVL | INTERRUPT_AUTOSNK | \ + INTERRUPT_VBUS_CHG | INTERRUPT_FAULT | \ + INTERRUPT_ORIENT) + +#define FUSB303_ALL_INTR1 (INTERRUPT1_REMEDY | INTERRUPT1_FRC_SUCC | \ + INTERRUPT1_FRC_FAIL | INTERRUPT1_REM_FAIL | \ + INTERRUPT1_REM_VBON | INTERRUPT1_REM_VBOFF) + +/* Debug */ + +#ifdef CONFIG_DEBUG_FUSB303 +# define DUMPREG(priv, x) \ + do \ + { \ + int ret = fusb303_getreg((priv), (x)); \ + if (ret < 0) \ + { \ + fusb303_err("ERROR: Failed to read %s(0x%02X)\n", #x, (x)); \ + } \ + else \ + { \ + fusb303_info("%s(0x%02X): 0x%02X\n", #x, (x), ret); \ + } \ + } \ + while(0) +#endif + +/**************************************************************************** + * Private Data Types + ****************************************************************************/ + +struct fusb303_dev_s +{ + FAR struct i2c_master_s *i2c; /* I2C interface */ + uint8_t addr; /* I2C address */ + volatile bool int_pending; /* Interrupt received but handled */ + sem_t devsem; /* Manages exclusive access */ + FAR struct fusb303_config_s *config; /* Platform specific configuration */ +#ifndef CONFIG_DISABLE_POLL + FAR struct pollfd *fds[CONFIG_FUSB303_NPOLLWAITERS]; +#endif +}; + +/**************************************************************************** + * Private Function prototypes + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_FUSB303 +static int fusb303_dumpregs(FAR struct fusb303_dev_s *priv); +#endif +static int fusb303_open(FAR struct file *filep); +static int fusb303_close(FAR struct file *filep); +static ssize_t fusb303_read(FAR struct file *, FAR char *, size_t); +static ssize_t fusb303_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static int fusb303_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +#ifndef CONFIG_DISABLE_POLL +static int fusb303_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); +static void fusb303_notify(FAR struct fusb303_dev_s *priv); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_fusb303ops = +{ + fusb303_open, /* open */ + fusb303_close, /* close */ + fusb303_read, /* read */ + fusb303_write, /* write */ + NULL, /* seek */ + fusb303_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , fusb303_poll /* poll */ +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , NULL /* unlink */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fusb303_getreg + * + * Description: + * Read from an 8-bit FUSB303 register + * + * Input Parameters: + * priv - pointer to FUSB303 Private Structure + * reg - register to read + * + * Returned Value: + * Returns positive register value in case of success, otherwise ERROR + * + ****************************************************************************/ + +static int fusb303_getreg(FAR struct fusb303_dev_s *priv, uint8_t reg) +{ + int ret = -EIO; + int retries; + uint8_t regval; + struct i2c_msg_s msg[2]; + + DEBUGASSERT(priv); + + msg[0].frequency = CONFIG_FUSB303_I2C_FREQUENCY; + msg[0].addr = priv->addr; + msg[0].flags = 0; + msg[0].buffer = ® + msg[0].length = 1; + + msg[1].frequency = CONFIG_FUSB303_I2C_FREQUENCY; + msg[1].addr = priv->addr; + msg[1].flags = I2C_M_READ; + msg[1].buffer = ®val; + msg[1].length = 1; + + /* Perform the transfer */ + + for (retries = 0; retries < FUSB303_I2C_RETRIES; retries++) + { + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret >= 0) + { + fusb303_info("reg:%02X, value:%02X\n", reg, regval); + return regval; + } + else + { + /* Some error. Try to reset I2C bus and keep trying. */ + +#ifdef CONFIG_I2C_RESET + if (retries == FUSB303_I2C_RETRIES - 1) + { + break; + } + + ret = I2C_RESET(priv->i2c); + if (ret < 0) + { + fusb303_err("ERROR: I2C_RESET failed: %d\n", ret); + return ret; + } +#endif + } + } + + fusb303_info("reg:%02X, error:%d\n", reg, ret); + return ret; +} + +/**************************************************************************** + * Name: fusb303_putreg + * + * Description: + * Write a value to an 8-bit FUSB303 register + * + * Input Parameters: + * priv - pointer to FUSB303 Private Structure + * regaddr - register to read + * regval - value to be written + * + * Returned Value: + * None + * + ****************************************************************************/ + +static int fusb303_putreg(FAR struct fusb303_dev_s *priv, uint8_t regaddr, + uint8_t regval) +{ + int ret = -EIO; + int retries; + struct i2c_msg_s msg; + uint8_t txbuffer[2]; + + /* Setup to the data to be transferred (register address and data). */ + + txbuffer[0] = regaddr; + txbuffer[1] = regval; + + /* Setup 8-bit FUSB303 address write message */ + + msg.frequency = CONFIG_FUSB303_I2C_FREQUENCY; + msg.addr = priv->addr; + msg.flags = 0; + msg.buffer = txbuffer; + msg.length = 2; + + /* Perform the transfer */ + + for (retries = 0; retries < FUSB303_I2C_RETRIES; retries++) + { + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret == OK) + { + fusb303_info("reg:%02X, value:%02X\n", regaddr, regval); + + return OK; + } + else + { + /* Some error. Try to reset I2C bus and keep trying. */ + +#ifdef CONFIG_I2C_RESET + if (retries == FUSB303_I2C_RETRIES - 1) + { + break; + } + + ret = I2C_RESET(priv->i2c); + if (ret < 0) + { + fusb303_err("ERROR: I2C_RESET failed: %d\n", ret); + return ret; + } +#endif + } + } + + fusb303_err("ERROR: failed reg:%02X, value:%02X, error:%d\n", + regaddr, regval, ret); + return ret; +} + +/**************************************************************************** + * Name: fusb303_dumpregs + * + * Description: + * Dump FUSB303 registers + * + * Input Parameters: + * priv - pointer to FUSB303 Private Structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_FUSB303 +static int noinline_function fusb303_dumpregs(FAR struct fusb303_dev_s *priv) +{ + DUMPREG(priv, FUSB303_DEV_ID_REG); + DUMPREG(priv, FUSB303_DEV_TYPE_REG); + DUMPREG(priv, FUSB303_PORTROLE_REG); + DUMPREG(priv, FUSB303_CONTROL_REG); + DUMPREG(priv, FUSB303_CONTROL1_REG); + DUMPREG(priv, FUSB303_MANUAL_REG); + DUMPREG(priv, FUSB303_RESET_REG); + DUMPREG(priv, FUSB303_MASK_REG); + DUMPREG(priv, FUSB303_MASK1_REG); + DUMPREG(priv, FUSB303_STATUS_REG); + DUMPREG(priv, FUSB303_STATUS1_REG); + DUMPREG(priv, FUSB303_TYPE_REG); + DUMPREG(priv, FUSB303_INTERRUPT_REG); + DUMPREG(priv, FUSB303_INTERRUPT1_REG); + return OK; +} +#endif /* CONFIG_DEBUG_FUSB303 */ + +/**************************************************************************** + * Name: fusb303_read_device_id + * + * Description: + * Read device version, revision ID and type. + * + ****************************************************************************/ + +static int fusb303_read_device_id(FAR struct fusb303_dev_s *priv, + FAR uint8_t *dev_id, FAR uint8_t *dev_type) +{ + int ret; + + ret = fusb303_getreg(priv, FUSB303_DEV_ID_REG); + if (ret < 0) + { + fusb303_err("ERROR: Failed to read device ID\n"); + return -EIO; + } + + if (dev_id != NULL) + { + *dev_id = ret; + } + + ret = fusb303_getreg(priv, FUSB303_DEV_TYPE_REG); + if (ret < 0) + { + fusb303_err("ERROR: Failed to read device type\n"); + return -EIO; + } + + if (dev_type != NULL) + { + *dev_type = ret; + } + + return ret; +} + +/**************************************************************************** + * Name: fusb303_clear_interrupts + * + * Description: + * Clear interrupts from FUSB303 chip + * + ****************************************************************************/ + +static int fusb303_clear_interrupts(FAR struct fusb303_dev_s *priv) +{ + int ret; + + ret = fusb303_putreg(priv, FUSB303_INTERRUPT_REG, FUSB303_ALL_INTR); + if (ret < 0) + { + fusb303_err("ERROR: Failed to clear interrupts\n"); + return -EIO; + } + + ret = fusb303_putreg(priv, FUSB303_INTERRUPT1_REG, FUSB303_ALL_INTR1); + if (ret < 0) + { + fusb303_err("ERROR: Failed to clear interrupts\n"); + return -EIO; + } + + return ret; +} + +/**************************************************************************** + * Name: fusb303_setup + * + * Description: + * Setup FUSB303 chip + * + ****************************************************************************/ + +static int fusb303_setup(FAR struct fusb303_dev_s *priv, + struct fusb303_setup_s *setup) +{ + int ret = OK; + uint8_t regval; + + fusb303_info("drp_tgl:%02X, host_curr:%02X\n" + "dcable_en: %d, remedy_en: %d, auto_snk_en: %d\n" + "global_int: %d, mask: %02X, mask1: %02X\n", + setup->drp_toggle_timing, setup->host_current, + (int)setup->dcable_en, (int)setup->remedy_en, + (int)setup->auto_snk_en, (int)setup->global_int_mask, + setup->int_mask, setup->int_mask1); + + /* Enable chip in I2C mode. */ + + ret = fusb303_getreg(priv, FUSB303_CONTROL1_REG); + if (ret < 0) + { + fusb303_err("ERROR: Failed to enable chip\n"); + goto err_out; + } + + /* TODO: no way to change AUTO_SNK_TH or TCCDEB at the moment. */ + + regval = (uint8_t)ret | CONTROL1_ENABLE; + if (setup->auto_snk_en) + { + regval |= CONTROL1_AUTO_SNK_EN; + } + else + { + regval &= ~CONTROL1_AUTO_SNK_EN; + } + + if (setup->remedy_en) + { + regval |= CONTROL1_REMEDY_EN; + } + else + { + regval &= ~CONTROL1_REMEDY_EN; + } + + ret = fusb303_putreg(priv, FUSB303_CONTROL1_REG, regval); + if (ret < 0) + { + fusb303_err("ERROR: Failed to enable chip\n"); + goto err_out; + } + + /* Setup the interrupt masks and remaining settings. */ + + ret = fusb303_putreg(priv, FUSB303_MASK_REG, setup->int_mask); + if (ret < 0) + { + fusb303_err("ERROR: Failed to write mask register\n"); + goto err_out; + } + + ret = fusb303_putreg(priv, FUSB303_MASK1_REG, setup->int_mask1); + if (ret < 0) + { + fusb303_err("ERROR: Failed to write mask register\n"); + goto err_out; + } + + /* Interrupts can happen only after unmasking global_int_mask */ + + regval = setup->drp_toggle_timing | setup->host_current | + setup->global_int_mask; + if (setup->dcable_en) + { + regval |= CONTROL_DCABLE_EN; + } + + ret = fusb303_putreg(priv, FUSB303_CONTROL_REG, regval); + if (ret < 0) + { + fusb303_err("ERROR: Failed to write control register\n"); + goto err_out; + } + +err_out: +#ifdef CONFIG_DEBUG_FUSB303 + fusb303_dumpregs(priv); +#endif + return ret; +} + +/**************************************************************************** + * Name: fusb303_set_mode + * + * Description: + * Configure supported device modes (sink, source, DRP, accessory) + * + ****************************************************************************/ + +static int fusb303_set_mode(FAR struct fusb303_dev_s *priv, + enum fusb303_mode_e mode) +{ + int ret; + + if (mode > MODE_ORIENTDEB) + { + return -EINVAL; + } + + ret = fusb303_putreg(priv, FUSB303_PORTROLE_REG, mode); + if (ret < 0) + { + fusb303_err("ERROR: Failed to set portrole\n"); + ret = -EIO; + } + + return ret; +} + +/**************************************************************************** + * Name: fusb303_set_state + * + * Description: + * Force device in specified state + * + ****************************************************************************/ + +static int fusb303_set_state(FAR struct fusb303_dev_s *priv, + enum fusb303_manual_e state) +{ + int ret; + + if (state > MANUAL_FORCE_SRC) + { + return -EINVAL; + } + + ret = fusb303_putreg(priv, FUSB303_MANUAL_REG, state); + if (ret < 0) + { + fusb303_err("ERROR: Failed to set state\n"); + ret = -EIO; + } + + return ret; +} + +/**************************************************************************** + * Name: fusb303_read_status + * + * Description: + * Read status register + * + ****************************************************************************/ + +static int fusb303_read_status(FAR struct fusb303_dev_s *priv, + FAR uint8_t *arg) +{ + int ret; + + ret = fusb303_getreg(priv, FUSB303_STATUS_REG); + if (ret < 0) + { + fusb303_err("ERROR: Failed to read status\n"); + return -EIO; + } + + *arg = ret; + return OK; +} + +/**************************************************************************** + * Name: fusb303_read_devtype + * + * Description: + * Read type of attached device + * + ****************************************************************************/ + +static int fusb303_read_devtype(FAR struct fusb303_dev_s *priv, + FAR uint8_t *arg) +{ + int ret; + + ret = fusb303_getreg(priv, FUSB303_TYPE_REG); + if (ret < 0) + { + fusb303_err("ERROR: Failed to read type\n"); + return -EIO; + } + + *arg = ret; + return OK; +} + +/**************************************************************************** + * Name: fusb303_reset + * + * Description: + * Reset FUSB303 HW and clear I2C registers + * + ****************************************************************************/ + +static int fusb303_reset(FAR struct fusb303_dev_s *priv) +{ + int ret; + + ret = fusb303_putreg(priv, FUSB303_RESET_REG, RESET_SW_RES); + if (ret < 0) + { + fusb303_err("ERROR: Failed to reset chip\n"); + ret = -EIO; + } + + /* tRESET max 100 ms. */ + + up_mdelay(100); + + return ret; +} + +/**************************************************************************** + * Name: fusb303_open + * + * Description: + * This function is called whenever the FUSB303 device is opened. + * + ****************************************************************************/ + +static int fusb303_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct fusb303_dev_s *priv = inode->i_private; + uint8_t dev_id; + uint8_t dev_type; + int ret; + + ret = nxsem_wait(&priv->devsem); + if (ret < 0) + { + return ret; + } + + /* Probe device */ + + ret = fusb303_read_device_id(priv, &dev_id, &dev_type); + if (ret < 0) + { + fusb303_err("ERROR: No response at given address 0x%02X\n", priv->addr); + ret = -EFAULT; + } + else + { + fusb303_info("device id: 0x%02X type: 0x%02X\n", dev_id, dev_type); + + (void)fusb303_clear_interrupts(priv); + priv->config->irq_enable(priv->config, true); + } + + nxsem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name: fusb303_close + * + * Description: + * This routine is called when the FUSB303 device is closed. + * + ****************************************************************************/ + +static int fusb303_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct fusb303_dev_s *priv = inode->i_private; + int ret; + + ret = nxsem_wait(&priv->devsem); + if (ret < 0) + { + return ret; + } + + priv->config->irq_enable(priv->config, false); + + nxsem_post(&priv->devsem); + return OK; +} + +/**************************************************************************** + * Name: fusb303_read + * + * Description: + * This routine is called when the FUSB303 device is read. + * + ****************************************************************************/ + +static ssize_t fusb303_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct fusb303_dev_s *priv = inode->i_private; + FAR struct fusb303_result_s *ptr; + irqstate_t flags; + int ret; + + if (buflen < sizeof(struct fusb303_result_s)) + { + return 0; + } + + ptr = (struct fusb303_result_s *)buffer; + + ret = nxsem_wait(&priv->devsem); + if (ret < 0) + { + return ret; + } + + flags = enter_critical_section(); + priv->int_pending = false; + leave_critical_section(flags); + + ptr->status = fusb303_getreg(priv, FUSB303_STATUS_REG); + ptr->status1 = fusb303_getreg(priv, FUSB303_STATUS1_REG); + ptr->dev_type = fusb303_getreg(priv, FUSB303_TYPE_REG); + +#ifdef CONFIG_DEBUG_FUSB303 + fusb303_dumpregs(priv); +#endif + + (void)fusb303_clear_interrupts(priv); + + nxsem_post(&priv->devsem); + return sizeof(struct fusb303_result_s); +} + +/**************************************************************************** + * Name: fusb303_write + * + * Description: + * This routine is called when the FUSB303 device is written to. + * + ****************************************************************************/ + +static ssize_t fusb303_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + ssize_t length = 0; + + return length; +} + +/**************************************************************************** + * Name: fusb303_ioctl + * + * Description: + * This routine is called when ioctl function call is performed for + * the FUSB303 device. + * + ****************************************************************************/ + +static int fusb303_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct fusb303_dev_s *priv = inode->i_private; + int ret; + + ret = nxsem_wait(&priv->devsem); + if (ret < 0) + { + return ret; + } + + fusb303_info("cmd: 0x%02X, arg:%lu\n", cmd, arg); + + switch (cmd) + { + case USBCIOC_READ_DEVID: + { + ret = fusb303_read_device_id(priv, (uint8_t *)arg, NULL); + } + break; + + case USBCIOC_SETUP: + { + ret = fusb303_setup(priv, (struct fusb303_setup_s *)arg); + } + break; + + case USBCIOC_SET_MODE: + { + ret = fusb303_set_mode(priv, (uint8_t)arg); + } + break; + + case USBCIOC_SET_STATE: + { + ret = fusb303_set_state(priv, (uint8_t)arg); + } + break; + + case USBCIOC_READ_STATUS: + { + ret = fusb303_read_status(priv, (uint8_t *)arg); + } + break; + + case USBCIOC_READ_DEVTYPE: + { + ret = fusb303_read_devtype(priv, (uint8_t *)arg); + } + break; + + case USBCIOC_RESET: + { + ret = fusb303_reset(priv); + } + break; + + default: + { + fusb303_err("ERROR: Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + } + break; + } + + nxsem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name: fusb303_poll + * + * Description: + * This routine is called during FUSB303 device poll + * + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int fusb303_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode; + FAR struct fusb303_dev_s *priv; + irqstate_t flags; + int ret = OK; + int i; + + DEBUGASSERT(filep && fds); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct fusb303_dev_s *)inode->i_private; + + ret = nxsem_wait(&priv->devsem); + if (ret < 0) + { + return ret; + } + + if (setup) + { + /* Ignore waits that do not include POLLIN */ + + if ((fds->events & POLLIN) == 0) + { + ret = -EDEADLK; + goto out; + } + + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference. + */ + + for (i = 0; i < CONFIG_FUSB303_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_FUSB303_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto out; + } + + flags = enter_critical_section(); + if (priv->int_pending) + { + fusb303_notify(priv); + } + + leave_critical_section(flags); + } + 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; + } + +out: + nxsem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name: fusb303_notify + * + * Description: + * Notify thread about data to be available + * + ****************************************************************************/ + +static void fusb303_notify(FAR struct fusb303_dev_s *priv) +{ + int i; + + DEBUGASSERT(priv != NULL); + + /* If there are threads waiting on poll() for FUSB303 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. + */ + + for (i = 0; i < CONFIG_FUSB303_NPOLLWAITERS; i++) + { + struct pollfd *fds = priv->fds[i]; + if (fds) + { + fds->revents |= POLLIN; + fusb303_info("Report events: %02x\n", fds->revents); + nxsem_post(fds->sem); + } + } +} +#endif /* !CONFIG_DISABLE_POLL */ + +/**************************************************************************** + * Name: fusb303_callback + * + * Description: + * FUSB303 interrupt handler + * + ****************************************************************************/ + +static int fusb303_int_handler(int irq, FAR void *context, FAR void *arg) +{ + FAR struct fusb303_dev_s *priv = (FAR struct fusb303_dev_s *)arg; + irqstate_t flags; + + DEBUGASSERT(priv != NULL); + + flags = enter_critical_section(); + priv->int_pending = true; + +#ifndef CONFIG_DISABLE_POLL + fusb303_notify(priv); +#endif + leave_critical_section(flags); + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int fusb303_register(FAR const char *devpath, FAR struct i2c_master_s *i2c, + uint8_t addr, FAR struct fusb303_config_s *config) +{ + FAR struct fusb303_dev_s *priv; + int ret; + + DEBUGASSERT(devpath != NULL && i2c != NULL && config != NULL); + + /* Initialize the FUSB303 device structure */ + + priv = (FAR struct fusb303_dev_s *)kmm_zalloc(sizeof(struct fusb303_dev_s)); + if (!priv) + { + fusb303_err("ERROR: Failed to allocate instance\n"); + return -ENOMEM; + } + + /* Initialize device structure semaphore */ + + nxsem_init(&priv->devsem, 0, 1); + + priv->int_pending = false; + priv->i2c = i2c; + priv->addr = addr; + priv->config = config; + + /* Register the character driver */ + + ret = register_driver(devpath, &g_fusb303ops, 0666, priv); + if (ret < 0) + { + fusb303_err("ERROR: Failed to register driver: %d\n", ret); + goto errout_with_priv; + } + + /* Prepare interrupt line and handler. */ + + if (priv->config->irq_clear) + { + priv->config->irq_clear(config); + } + + priv->config->irq_attach(config, fusb303_int_handler, priv); + priv->config->irq_enable(config, false); + + return OK; + +errout_with_priv: + nxsem_destroy(&priv->devsem); + kmm_free(priv); + + return ret; +} diff --git a/include/nuttx/usb/fusb301.h b/include/nuttx/usb/fusb301.h index deecff2b752..12ca8f42812 100644 --- a/include/nuttx/usb/fusb301.h +++ b/include/nuttx/usb/fusb301.h @@ -1,6 +1,6 @@ /**************************************************************************** * include/nuttx/usb/fusb301.h - * FUSB301 USB type-C controller driver + * FUSB301 USB Type-C controller driver * * Copyright (C) 2016-2017 Haltian Ltd. All rights reserved. * Authors: Harri Luhtala @@ -39,9 +39,9 @@ #include -/************************************************************************************ +/**************************************************************************** * Pre-Processor Declarations - ************************************************************************************/ + ****************************************************************************/ #undef EXTERN #if defined(__cplusplus) @@ -52,9 +52,9 @@ extern "C" #define EXTERN extern #endif -/************************************************************************************ +/**************************************************************************** * Pre-processor Definitions - ************************************************************************************/ + ****************************************************************************/ /* IOCTL Commands ***********************************************************/ @@ -66,9 +66,9 @@ extern "C" #define USBCIOC_READ_DEVTYPE _USBCIOC(0x0006) /* Arg: uint8_t* pointer*/ #define USBCIOC_RESET _USBCIOC(0x0007) /* Arg: None */ -/************************************************************************************ +/**************************************************************************** * Public Types - ************************************************************************************/ + ****************************************************************************/ enum fusb301_reg_address_e { @@ -87,8 +87,8 @@ enum fusb301_reg_address_e enum fusb301_devid_mask_e { - DEV_ID_REVISION_MASK = 0x0F, - DEV_ID_VERSION_MASK = 0xF0 + DEV_ID_REVISION_MASK = 0x0f, + DEV_ID_VERSION_MASK = 0xf0 }; #define DEV_ID_VER_A 0x10 @@ -211,9 +211,9 @@ struct fusb301_config_s CODE void (*irq_clear)(FAR struct fusb301_config_s *state); }; -/************************************************************************************ +/**************************************************************************** * Public Function Prototypes - ************************************************************************************/ + ****************************************************************************/ /**************************************************************************** * Name: fusb301_register diff --git a/include/nuttx/usb/fusb303.h b/include/nuttx/usb/fusb303.h new file mode 100644 index 00000000000..62f9cd76c27 --- /dev/null +++ b/include/nuttx/usb/fusb303.h @@ -0,0 +1,340 @@ +/**************************************************************************** + * include/nuttx/usb/fusb303.h + * FUSB303 USB Type-C controller driver + * + * Copyright (C) 2019 Haltian Ltd. All rights reserved. + * Authors: Juha Niskanen + * + * 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 __INCLUDE_NUTTX_USB_FUSB303_H +#define __INCLUDE_NUTTX_USB_FUSB303_H + +#include + +/**************************************************************************** + * Pre-Processor Declarations + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* IOCTL Commands ***********************************************************/ + +#define USBCIOC_READ_DEVID _USBCIOC(0x0001) /* Arg: uint8_t* pointer */ +#define USBCIOC_SETUP _USBCIOC(0x0002) /* Arg: uint8_t* pointer */ +#define USBCIOC_SET_MODE _USBCIOC(0x0003) /* Arg: uint8_t value */ +#define USBCIOC_SET_STATE _USBCIOC(0x0004) /* Arg: uint8_t value */ +#define USBCIOC_READ_STATUS _USBCIOC(0x0005) /* Arg: uint8_t* pointer*/ +#define USBCIOC_READ_DEVTYPE _USBCIOC(0x0006) /* Arg: uint8_t* pointer*/ +#define USBCIOC_RESET _USBCIOC(0x0007) /* Arg: None */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum fusb303_reg_address_e +{ + FUSB303_DEV_ID_REG = 0x01, + FUSB303_DEV_TYPE_REG, + FUSB303_PORTROLE_REG, + FUSB303_CONTROL_REG, + FUSB303_CONTROL1_REG, + FUSB303_MANUAL_REG = 0x09, + FUSB303_RESET_REG, + FUSB303_MASK_REG = 0x0e, + FUSB303_MASK1_REG, + FUSB303_STATUS_REG = 0x11, + FUSB303_STATUS1_REG, + FUSB303_TYPE_REG, + FUSB303_INTERRUPT_REG, + FUSB303_INTERRUPT1_REG +}; + +/* Device ID - 0x01 */ + +enum fusb303_devid_mask_e +{ + DEV_ID_REVISION_MASK = 0x0f, + DEV_ID_VERSION_MASK = 0xf0 +}; + +#define DEV_ID_VER_A 0x10 +#define DEV_ID_REV_A 0x00 + +/* Device Type - 0x02 */ + +#define DEV_TYPE_FUSB303 0x01 +#define DEV_TYPE_FUSB303T 0x02 + +/* Port Roles - 0x03 (called Mode here for fusb301 compatibility) */ + +enum fusb303_mode_e +{ + MODE_SRC = (1 << 0), + MODE_SNK = (1 << 1), + MODE_DRP = (1 << 2), + MODE_AUDIOACC = (1 << 3), + MODE_TRY_SNK = (1 << 4), /* For DRP only */ + MODE_TRY_SRC = (1 << 5), /* For DRP only */ + MODE_ORIENTDEB = (1 << 6), + /* Bit 7 reserved */ +}; + +/* Control - 0x04 */ + +enum fusb303_control_e +{ + CONTROL_INT_ENABLE = (0 << 0), + CONTROL_INT_DISABLE = (1 << 0), + CONTROL_CUR_RESERVED = (0 << 1), /* Do not use */ + CONTROL_CUR_DEFAULT = (1 << 1), /* Default USB Power */ + CONTROL_CUR_1500 = (2 << 1), /* Medium Current Mode */ + CONTROL_CUR_3000 = (3 << 1), /* High Current Mode */ + CONTROL_DCABLE_EN = (1 << 3), + CONTROL_DRPTOGGLE_60_40 = (0 << 4), /* 60% in Unattached.SNK and 40% in Unattached.SRC */ + CONTROL_DRPTOGGLE_50_50 = (1 << 4), /* 50% in Unattached.SNK and 50% in Unattached.SRC */ + CONTROL_DRPTOGGLE_40_60 = (2 << 4), /* 40% in Unattached.SNK and 60% in Unattached.SRC */ + CONTROL_DRPTOGGLE_30_70 = (3 << 4), /* 30% in Unattached.SNK and 70% in Unattached.SRC */ + CONTROL_T_DRP_60MS = (0 << 6), /* Total period of the DRP toggle cycle 60 ms */ + CONTROL_T_DRP_70MS = (1 << 6), /* Total period of the DRP toggle cycle 70 ms */ + CONTROL_T_DRP_80MS = (2 << 6), /* Total period of the DRP toggle cycle 80 ms */ + CONTROL_T_DRP_90MS = (3 << 6), /* Total period of the DRP toggle cycle 90 ms */ +}; + +/* Control 1 - 0x05 */ + +enum fusb303_control1_e +{ + CONTROL1_TCCDEB_120 = (0 << 0), /* Debounce time 120 ms */ + CONTROL1_TCCDEB_130 = (1 << 0), + CONTROL1_TCCDEB_140 = (2 << 0), + CONTROL1_TCCDEB_150 = (3 << 0), + CONTROL1_TCCDEB_160 = (4 << 0), + CONTROL1_TCCDEB_170 = (5 << 0), + CONTROL1_TCCDEB_180 = (6 << 0), + CONTROL1_ENABLE = (1 << 3), + CONTROL1_AUTO_SNK_EN = (1 << 4), + CONTROL1_AUTO_SNK_TH_30 = (0 << 5), /* Weak battery VDD threshold voltage 3.0 V */ + CONTROL1_AUTO_SNK_TH_31 = (1 << 5), + CONTROL1_AUTO_SNK_TH_32 = (2 << 5), + CONTROL1_AUTO_SNK_TH_33 = (3 << 5), + CONTROL1_REMEDY_EN = (1 << 7), +}; + +/* Manual - 0x09 */ + +enum fusb303_manual_e +{ + MANUAL_ERROR_REC = (1 << 0), + MANUAL_DISABLED = (1 << 1), + MANUAL_UNATT_SRC = (1 << 2), + MANUAL_UNATT_SNK = (1 << 3), + MANUAL_FORCE_SNK = (1 << 4), + MANUAL_FORCE_SRC = (1 << 5), + /* Bits 6:7 reserved */ +}; + +/* Reset - 0x0a */ + +enum fusb303_reset_e +{ + RESET_SW_RES = (1 << 0) +}; + +/* Interrupt mask - 0x0e */ + +enum fusb303_int_mask_e +{ + INT_MASK_ATTACK = (1 << 0), + INT_MASK_DETACH = (1 << 1), + INT_MASK_BC_LVL = (1 << 2), + INT_MASK_AUTOSNK = (1 << 3), + INT_MASK_VBUS_CHG = (1 << 4), + INT_MASK_FAULT = (1 << 5), + INT_MASK_ORIENT = (1 << 6), + /* Bit 7 reserved */ +}; + +/* Interrupt mask1 - 0x0f */ + +enum fusb303_int_mask1_e +{ + INT_MASK1_REMEDY = (1 << 0), + INT_MASK1_FRC_SUCC = (1 << 1), + INT_MASK1_FRC_FAIL = (1 << 2), + /* Bit 4 reserved */ + INT_MASK1_REM_FAIL = (1 << 3), + INT_MASK1_REM_VBON = (1 << 5), + INT_MASK1_REM_VBOFF = (1 << 6), + /* Bit 7 reserved */ +}; + +/* Status - 0x11 */ + +enum fusb303_status_e +{ + STATUS_ATTACH = (1 << 0), + STATUS_BC_LVL_UNATT = (0 << 1), + STATUS_BC_LVL_DEF = (1 << 1), + STATUS_BC_LVL_1500 = (2 << 1), + STATUS_BC_LVL_3000 = (3 << 1), + STATUS_VBUS_OK = (1 << 3), + STATUS_CC_NO_CONN = (0 << 4), + STATUS_CC_1 = (1 << 4), + STATUS_CC_2 = (2 << 4), + STATUS_CC_FAULT = (3 << 4), + STATUS_VSAFE0V = (1 << 6), + STATUS_AUTOSNK = (1 << 7), +}; + +/* Status1 - 0x12 */ + +enum fusb303_status1_e +{ + STATUS1_REMEDY = (1 << 0), + STATUS1_FAULT = (1 << 1), +}; + +/* Type - 0x13 */ + +enum fusb303_type_e +{ + TYPE_AUDIO = (1 << 0), + TYPE_AUDIOVBUS = (1 << 1), + TYPE_ACTIVECABLE = (1 << 2), + TYPE_SOURCE = (1 << 3), + TYPE_SINK = (1 << 4), + TYPE_DEBUGSNK = (1 << 5), + TYPE_DEBUGSRC = (1 << 6), + /* Bit 7 reserved */ +}; + +/* Interrupt - 0x14 */ + +enum fusb303_interrupt_e +{ + INTERRUPT_ATTACH = (1 << 0), + INTERRUPT_DETACH = (1 << 1), + INTERRUPT_BC_LVL = (1 << 2), + INTERRUPT_AUTOSNK = (1 << 3), + INTERRUPT_VBUS_CHG = (1 << 4), + INTERRUPT_FAULT = (1 << 5), + INTERRUPT_ORIENT = (1 << 6), + /* Bit 7 reserved */ +}; + +/* Interrupt1 - 0x15 */ + +enum fusb303_interrupt1_e +{ + INTERRUPT1_REMEDY = (1 << 0), + INTERRUPT1_FRC_SUCC = (1 << 1), + INTERRUPT1_FRC_FAIL = (1 << 2), + INTERRUPT1_REM_FAIL = (1 << 3), + /* Bit 4 reserved */ + INTERRUPT1_REM_VBON = (1 << 5), + INTERRUPT1_REM_VBOFF = (1 << 6), + /* Bit 7 reserved */ +}; + +struct fusb303_result_s +{ + uint8_t status; + uint8_t status1; + uint8_t dev_type; +}; + +struct fusb303_setup_s +{ + uint8_t drp_toggle_timing; + uint8_t host_current; + bool dcable_en; + bool remedy_en; + bool auto_snk_en; + uint8_t global_int_mask; + uint8_t int_mask; + uint8_t int_mask1; +}; + +struct fusb303_config_s +{ + /* Device characterization */ + + int irq; + + CODE int (*irq_attach)(FAR struct fusb303_config_s *state, xcpt_t isr, + FAR void *arg); + CODE void (*irq_enable)(FAR struct fusb303_config_s *state, bool enable); + CODE void (*irq_clear)(FAR struct fusb303_config_s *state); +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: fusb303_register + * + * Description: + * Register the FUSB303 character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/usbc0" + * i2c - An instance of the I2C interface to use to communicate with + * FUSB303 + * addr - The I2C address of the FUSB303. The I2C address of the FUSB303 + * is either 0x42 or 0x62. + * config - Pointer to FUSB303 configuration + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int fusb303_register(FAR const char *devpath, FAR struct i2c_master_s *i2c, + uint8_t addr, FAR struct fusb303_config_s *config); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __INCLUDE_NUTTX_USB_FUSB303_H */