nuttx/drivers/usbmisc/fusb302.c
Catalin Visinescu 081e4c478a drivers/: Multiple Drivers Are Registered With World Writable - Part 2
Permissions (Part 2)

Description:

In kernel builds, any unprivileged process running on the NuttX
device can open /dev/efuse and attempt to read/write fuse content.
Reading the fuses may provide valuable information to an attacker
controlling the user process. The write operation, in extreme cases
where the fuse blocks are not locked, may brick the device.

DISCLAIMER: I tried to be strict with the settings, better to relax them
later if it's needed.

This is part of https://github.com/apache/nuttx/issues/19410

See https://github.com/apache/nuttx/issues/19410

Compiles ok.

Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
2026-07-15 15:27:28 +08:00

1947 lines
56 KiB
C

/****************************************************************************
* drivers/usbmisc/fusb302.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <nuttx/debug.h>
#include <nuttx/compiler.h>
#include <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/wqueue.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/signal.h>
#include <nuttx/usb/fusb302.h>
#include <sys/ioctl.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef FUSB302_I2C_FREQUENCY
# define FUSB302_I2C_FREQUENCY 1000000
#endif
#ifdef CONFIG_DEBUG_USB_ERROR
# define fusb302_err(x, ...) _err(x, ##__VA_ARGS__)
#else
# define fusb302_err(x, ...) uerr(x, ##__VA_ARGS__)
#endif
#if defined(CONFIG_DEBUG_USB_INFO) || defined (CONFIG_DEBUG_FUSB302_REG)
# define fusb302_info(x, ...) _info(x, ##__VA_ARGS__)
#else
# define fusb302_info(x, ...) uinfo(x, ##__VA_ARGS__)
#endif
#define FUSB302_I2C_RETRIES 10
/* Switches0 - 0x02 */
#define SWITCHES0_PULLDOWN_CC1 (1 << 0)
#define SWITCHES0_PULLDOWN_CC2 (1 << 1)
#define SWITCHES0_PULLDOWN_SHIFT (0)
#define SWITCHES0_PULLDOWN_MASK (3 << SWITCHES0_PULLDOWN_SHIFT)
#define SWITCHES0_MEASURE_CC1 (1 << 2)
#define SWITCHES0_MEASURE_CC2 (1 << 3)
#define SWITCHES0_MEASURE_SHIFT (3)
#define SWITCHES0_MEASURE_MASK (3 << SWITCHES0_MEASURE_SHIFT)
#define SWITCHES0_VCONN_CC1 (1 << 4)
#define SWITCHES0_VCONN_CC2 (1 << 5)
#define SWITCHES0_VCONN_SHIFT (4)
#define SWITCHES0_VCONN_MASK (3 << SWITCHES0_VCONN_SHIFT)
#define SWITCHES0_PULLUP_CC1 (1 << 6)
#define SWITCHES0_PULLUP_CC2 (1 << 7)
#define SWITCHES0_PULLUP_SHIFT (6)
#define SWITCHES0_PULLUP_MASK (3 << SWITCHES0_PULLUP_SHIFT)
/* Switches1 */
#define SWITCHES1_TXCC1_MASK (1 << 0)
#define SWITCHES1_TXCC2_MASK (1 << 1)
#define SWITCHES1_AUTO_CRC_MASK (1 << 2)
#define SWITCHES1_DATAROLE_MASK (1 << 3)
#define SWITCHES1_SPECREV_SHIFT (1 << 5) /* Bits 6:5 Specrole */
#define SWITCHES1_SPECREV_MASK (0x03 << 6)
#define SWITCHES1_SPECREV(n) ((uint8_t)(n) << SWITCHES1_SPECREV_SHIFT)
#define SWITCHES1_POWERROLE_MASK (1 << 6)
/* Measure - 0x04 */
#define MEASURE_MDAC_SHIFT (0) /* Bits 5:0 MDAC */
#define MEASURE_MDAC_MASK (0b111111 << MEASURE_MDAC_SHIFT)
#define MEASURE_MDAC(n) ((uint8_t)(n)) << MEASURE_MDAC_SHIFT
#define MEASURE_VBUS_BY_MDAC (1 << 6)
#define SET_MDAC(n) ((uint8_t)(n)) << MEASURE_MDAC_SHIFT)
/* Slice - 0x05 */
#define MEASURE_SDAC_SHIFT (0) /* Bits 5:0 SDAC */
#define MEASURE_SDAC_MASK (0b111111 < MEASURE_SDAC_SHIFT)
#define MEASURE_SDAC(n) ((uint8_t)(n)) << MEASURE_SDAC_SHIFT)
#define MEASURE_SDAC_HYS_SHIFT (6) /* Bits 7:6 SDAC hysteris */
#define MEASURE_SDAC_HYS_MASK (0b11 << MEASURE_SDAC_HYS_SHIFT)
#define SDAC_HYS_VAL(n) ((uint8)t)(n)) << MEASURE_SDAC_HYS_SHIFT)
/* Control0 - 0x06 */
#define CONTROL0_TX_START_MASK (1 << 0)
#define CONTROL0_AUTO_PRE_SHIFT (1)
#define CONTROL0_HOST_CUR_SHIFT (2) /* Bits 3:2 Host Current mode */
#define CONTROL0_HOST_CUR_MASK (3 << CONTROL0_HOST_CUR_SHIFT)
#define HOST_CURRENT_NONE (0) /* no current */
#define HOST_CURRENT_80UA (1) /* default USB power */
#define HOST_CURRENT_180UA (2) /* medium power, 1.5A */
#define HOST_CURRENT_330UA (3) /* high power, 3A */
#define HOST_CURRENT(n) ((uint8_t)(n) << CONTROL0_HOST_CUR_SHIFT)
#define CONTROL0_INT_MASK (1 << 5)
#define CONTROL0_TX_FLUSH (1 << 6)
/* Control1 - 0x07 */
#define CONTROL1_ENSOP1_MASK (1 << 0)
#define CONTROL1_ENSOP2_MASK (1 << 1)
#define CONTROL1_RXFLUSH_MASK (1 << 2)
#define CONTROL1_BIST_MODE2_MASK (1 << 4)
#define CONTROL1_ENSOP1DB_MASK (1 << 5)
#define CONTROL1_ENSOP2DB_MASK (1 << 6)
/* Control2 - 0x08 */
#define CONTROL2_TOGGLE (1 << 0)
#define CONTROL2_MODE_SHIFT (1) /* Bits 2:1 */
#define CONTROL2_MODE_MASK (0b11 << CONTROL2_MODE_SHIFT)
#define SRC_POLLING (0b11)
#define SNK_POLLING (0b10)
#define DRP_POLLING (0b01)
#define SET_POLL_MODE(n) ((uint8_t)(n) << CONTROL2_MODE_SHIFT)
#define CONTROL2_WAKE_EN (1 << 3)
#define CONTROL2_TOG_RD_ONLY (1 << 5)
#define CONTROL2_TOG_SAVE_PWR_SHIFT (6) /* Bits 7:6 */
#define CONTROL2_TOG_SAVE_PWR_MASK (0b11 << CONTROL2_TOG_SAVE_PWR_SHIFT
#define WAIT_NONE (0b00)
#define WAIT_40MS (0b01)
#define WAIT_80MS (0b10)
#define WAIT_160MS (0b11)
#define SET_WAIT_MODE(n) (((uint8_t)(n) << CONTROL2_TOG_SAVE_PWR_SHIFT)
/* Control3 - 0x09 */
#define CONTROL3_AUTO_RETRY (0)
#define CONTROL3_N_RETRIES_SHIFT (1) /* Bits 2:1 */
#define CONTROL3_N_RETRIES_MASK (0b11 << CONTROL3_N_RETRIES_SHIFT)
#define NO_RETRIES (0b00)
#define ONE_RETRIES (0b01)
#define TWO_RETRIES (0b10)
#define THREE_RETRIES (0b11)
#define SET_NUM_RETRIES(n) ((uint8_t(n) << CONTROL3_N_RETRIES_SHIFT)
#define CONTROL3_AUTO_SOFTRESET (1 << 3)
#define CONTROL3_AUTO_HARDRESET (1 << 4)
#define CONTROL3_SEND_HARDRESET (1 << 6)
/* Mask - 0x0A */
#define MASK_BC_LVL (1 << 0)
#define MASK_COLLISION (1 << 1)
#define MASK_WAKE (1 << 2)
#define MASK_ALERT (1 << 3)
#define MASK_CRC_CHK (1 << 4)
#define MASK_COMP_CHNG (1 << 5)
#define MASK_ACTIVITY (1 << 6)
#define MASK_VBUS_OK (1 << 7)
#define MASK_ALL (0xff)
#define MASK_FOR_DISCONNECT (MASK_VBUS_OK | MASK_COMP_CHNG)
/* Power 0x0B */
#define POWER_PWR_SHIFT (0) /* Bits 3:0 */
#define POWER_PWR_MASK (15 << POWER_PWR_SHIFT)
#define POWER_MODE_ALL (15)
#define POWER_MODE_TOGGLING (7)
#define POWER_MODE_BANDGAP_AND_WAKE (1)
#define POWER_MODE_RX_AND_IREF (2)
#define POWER_MODE_MEASURE_BLOCK (4)
#define POWER_MODE_MEASURE_INT_OSC (8)
#define POWER_MODE(n) ((uint8_t)(n) << POWER_PWR_SHIFT)
/* Reset 0x0C */
#define RESET_SW_RESET (1 << 0)
#define RESET_PD_RESET (1 << 1)
/* OCPreg = 0x0D */
#define OCPREG_CUR_SHIFT (0) /* Bits 2:0 */
#define OCPREG_CUR_MASK (0b111 << OCPREG_CUR_SHIFT)
#define X1_MAX_RANGE_DIV8 (0b000)
#define X2_MAX_RANGE_DIV8 (0b001)
#define X3_MAX_RANGE_DIV8 (0b010)
#define X4_MAX_RANGE_DIV8 (0b011)
#define X5_MAX_RANGE_DIV8 (0b100)
#define X6_MAX_RANGE_DIV8 (0b101)
#define X7_MAX_RANGE_D (0b110)
#define MAX_RANGE (0b111)
#define SET_OCP_RANGE(n) ((uint8_t)(n) << OCPREG_CUR_SHIFT)
#define OCPREF_OCP_RANGE (1 << 3)
/* MaskA - 0x0E */
#define MASKA_HARDRST (1 << 0)
#define MASKA_SOFTRST (1 << 1)
#define MASKA_TXSENT (1 << 2)
#define MASKA_HARDSENT (1 << 3)
#define MASKA_RETRYFAIL (1 << 4)
#define MASKA_SOFTFAIL (1 << 5)
#define MASKA_TOGDONE (1 << 6)
#define MASKA_OCP_TEMP (1 << 7)
/* MaskB - 0x0F */
#define MASKB_GCRCSENT (1 << 0)
/* Control4 - 0x10 */
#define CONTROL4_TOG_USRC_EXIT (1 << 0)
/* Status0a - 0x3c */
#define STATUS0A_HARDRST (1 << 0)
#define STATUS0A_SOFTRST (1 << 1)
#define STATUS0A_POWER_SHIFT (2) /* Bits 3:2 */
#define STATUS0A_POWER_MASK (0b11 << STATUS0A_POWER_SHIFT)
#define STATUS0A_RETRYFAIL (1 << 4)
#define STATUS0A_SOFTFAIL (1 << 5)
/* Status1a - 0x3D */
#define STATUS1A_RXSOP (1 << 0)
#define STATUS1A_RXSOP1DB (1 << 1)
#define STATUS1A_RXSOP2DB (1 << 2)
#define STATUS1A_TOGGS_SHIFT (3) /* Bits 5:3 */
#define STATUS1A_TOGGS_MASK (7 << STATUS1A_TOGGS_SHIFT)
#define TOGGS_RUNNING (0)
#define TOGGS_SRC_CC1 (1)
#define TOGGS_SRC_CC2 (2)
#define TOGGS_SNK_CC1 (5)
#define TOGGS_SNK_CC2 (6)
#define TOGGS_AUDIO_ACCESSORY (7)
#define DECODE_TOGGS(n) ((uint8_t)(n) >> STATUS1A_TOGGS_SHIFT)
/* InterruptA - 0x3E */
#define INTERRUPTA_M_HARDRST (1 << 0)
#define INTERRUPTA_M_SOFTRST (1 << 1)
#define INTERRUPTA_M_TXSENT (1 << 2)
#define INTERRUPTA_M_HARDSENT (1 << 3)
#define INTERRUPTA_M_RETRYFAIL (1 << 4)
#define INTERRUPTA_M_SOFTFAIL (1 << 5)
#define INTERRUPTA_M_TOGDONE (1 << 6)
#define INTERRUPTA_M_OCP_TEMP (1 << 7)
/* InterruptB - 0x3F */
#define INTERRUPTB_I_GCRCSENT (1 << 0)
/* Status0 - 0x40 */
#define STATUS0_BC_LVL_SHIFT (0) /* Bits 1:0 */
#define STATUS0_BC_LVL_MASK (0b11 << STATUS0_BC_LVL_SHIFT)
#define STATUS0_WAKE (1 << 2)
#define STATUS0_ALERT (1 << 3)
#define STATUS0_CRC_OK (1 << 4)
#define STATUS0_COMP (1 << 5)
#define STATUS0_ACTIVITY (1 << 6)
#define STATUS0_VBUS_OK (1 << 7)
/* Status1 - 0x41 */
#define STATUS1_OCP (1 << 0)
#define STATUS1_OVRTEMP (1 << 1)
#define STATUS1_TX_FULL (1 << 2)
#define STATUS1_TX_EMPTY (1 << 3)
#define STATUS1_RX_FULL (1 << 4)
#define STATUS1_RX_EMPTY (1 << 5)
#define STATUS1A_RXSOP1 (1 << 6)
#define STATUS1A_RXSOP2 (1 << 7)
/* Interrupt - 0x42 */
#define INTERRUPT_BC_LVL (1 << 0)
#define INTERRUPT_COLLISION_ (1 << 1)
#define INTERRUPT_WAKE (1 << 2)
#define INTERRUPT_ALERT (1 << 3)
#define INTERRUPT_CRC_OK (1 << 4)
#define INTERRUPT_COMP_CHNG (1 << 5)
#define INTERRUPT_ACTIVITY (1 << 6)
#define INTERRUPT_VBUS_OK (1 << 7)
/* FIFOs - 0x43 */
#define FIFOS_TX_RX_TOKEN (1 << 0)
/****************************************************************************
* Private Data Types
****************************************************************************/
enum fusb302_state_e
{
RESET = 0,
WAITING_FOR_DRP_TOGG_I,
WAITING_FOR_SNK_TOGG_I,
WAITING_FOR_SRC_TOGG_I,
WAITING_FOR_VBUS,
WAITING_FOR_COMP_CHNG,
WAITING_FOR_SRC_DISCONNECT,
WAITING_FOR_SNK_DISCONNECT,
WAITING_FOR_HOST_INTERVENTION,
NOT_DEFINED,
};
enum cc_pd_e
{
CC1_PULLDOWN = SWITCHES0_PULLDOWN_CC1,
CC2_PULLDOWN = SWITCHES0_PULLDOWN_CC2,
};
enum cc_pu_e
{
CC1_PULLUP = SWITCHES0_PULLUP_CC1,
CC2_PULLUP = SWITCHES0_PULLUP_CC2,
};
enum cc_vconn_e
{
CC1_VCONN = SWITCHES0_VCONN_CC1,
CC2_VCONN = SWITCHES0_VCONN_CC2,
};
enum cc_meas_e
{
CC1_MEASURE = SWITCHES0_MEASURE_CC1,
CC2_MEASURE = SWITCHES0_MEASURE_CC2,
};
enum src_current_e
{
SRC_CURRENT_DEFAULT,
SRC_CURRENT_MEDIUM,
SRC_CURRENT_HIGH,
};
struct fusb302_dev_s
{
FAR struct pollfd *fds[CONFIG_FUSB302_NPOLLWAITERS];
uint8_t i2c_addr; /* I2C address */
volatile bool int_pending; /* Interrupt pending */
mutex_t devlock; /* Manages exclusive access */
FAR struct fusb302_config_s *config; /* Platform specific config */
struct work_s work; /* Supports interrupt handling */
enum fusb302_state_e state; /* worker state machine */
enum fusb_connect_status_e connect_status; /* USB device connection state */
};
static const uint8_t src_mdac_val[] =
{
[SRC_CURRENT_DEFAULT] = 0x25, /* 1600mV */
[SRC_CURRENT_MEDIUM] = 0x25, /* 1600mV */
[SRC_CURRENT_HIGH] = 0x3d, /* 2600mV */
};
struct fusb302_result_s
{
enum fusb_connect_status_e connected_state;
enum fusb302_state_e state;
};
struct fusb302_setup_s
{
uint8_t drp_toggle_timing;
uint8_t host_current;
uint8_t int_mask;
bool global_int_mask;
};
enum fusb302_reg_address_e
{
FUSB302_DEV_ID_REG = 0x01,
FUSB302_SWITCHES0_REG,
FUSB302_SWITCHES1_REG,
FUSB302_MEASURE_REG,
FUSB302_SLICE_REG,
FUSB302_CONTROL0_REG,
FUSB302_CONTROL1_REG,
FUSB302_CONTROL2_REG,
FUSB302_CONTROL3_REG,
FUSB302_MASK_REG,
FUSB302_POWER_REG,
FUSB302_RESET_REG,
FUSB302_OCPREG_REG,
FUSB302_MASKA_REG,
FUSB302_MASKB_REG,
FUSB302_CONTROL4_REG,
FUSB302_STATUS0A_REG = 0x3c,
FUSB302_STATUS1A_REG,
FUSB302_INTERRUPTA_REG,
FUSB302_INTERRUPTB_REG,
FUSB302_STATUS0_REG,
FUSB302_STATUS1_REG,
FUSB302_INTERRUPT_REG,
FUSB302_FIFOS_REG,
};
/****************************************************************************
* Private Function prototypes
****************************************************************************/
static int fusb302_open(FAR struct file *filep);
static int fusb302_close(FAR struct file *filep);
static ssize_t fusb302_read(FAR struct file *, FAR char *, size_t);
static ssize_t fusb302_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int fusb302_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
static int fusb302_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
static int fusb302_putreg(FAR struct fusb302_dev_s *priv,
uint8_t regaddr, uint8_t regval);
static int fusb302_getreg(FAR struct fusb302_dev_s *priv, uint8_t reg);
#ifdef CONFIG_DEBUG_FUSB302_REG
static void fusb302_dump_registers(FAR struct fusb302_dev_s *priv,
FAR const char *msg);
#else
# define fusb302_dump_registers(priv, msg);
#endif
static int fusb302_clear_interrupts(FAR struct fusb302_dev_s * priv);
static int fusb302_reset(FAR struct fusb302_dev_s *priv);
static int fusb302_set_mode(FAR struct fusb302_dev_s *priv,
enum fusb302_mode_e mode);
static int fusb302_int_handler(int irq, FAR void *context,
FAR void *arg);
static void fusb302_worker(FAR void *arg);
static int fusb302_schedule(FAR struct fusb302_dev_s *priv);
void enableccpd(FAR struct fusb302_dev_s *priv,
enum cc_pd_e pulldown, bool enable);
void enableccpu(FAR struct fusb302_dev_s *priv,
enum cc_pu_e pullup, bool enable);
void enablevconn(FAR struct fusb302_dev_s *priv,
enum cc_vconn_e vconn, bool enable);
void enableccmeas(FAR struct fusb302_dev_s *priv,
enum cc_meas_e pullup, bool enable);
void set_switches(FAR struct fusb302_dev_s *priv,
uint8_t toggsval);
static int set_int_mask(struct fusb302_dev_s *priv);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_fusb302ops =
{
fusb302_open, /* open */
fusb302_close, /* close */
fusb302_read, /* read */
fusb302_write, /* write */
NULL, /* seek */
fusb302_ioctl, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
fusb302_poll, /* poll */
};
/****************************************************************************
* Private Functions
****************************************************************************/
static int set_int_mask(struct fusb302_dev_s *priv)
{
int ret = OK;
switch (priv->state)
{
case WAITING_FOR_DRP_TOGG_I:
case WAITING_FOR_SNK_TOGG_I:
case WAITING_FOR_SRC_TOGG_I:
ret = fusb302_putreg(priv, FUSB302_MASK_REG,
(uint8_t) ~MASKA_TOGDONE);
ret = fusb302_putreg(priv, FUSB302_MASKA_REG,
(uint8_t) MASKA_HARDRST);
ret = fusb302_putreg(priv, FUSB302_MASKB_REG,
(uint8_t) MASKB_GCRCSENT);
break;
case WAITING_FOR_VBUS:
ret = fusb302_putreg(priv, FUSB302_MASK_REG,
((uint8_t) ~MASK_VBUS_OK) |
((uint8_t) ~MASK_BC_LVL) |
((uint8_t) ~MASK_COMP_CHNG));
break;
case WAITING_FOR_COMP_CHNG:
ret = fusb302_putreg(priv, FUSB302_MASK_REG,
(uint8_t) ~MASK_COMP_CHNG);
break;
case WAITING_FOR_SRC_DISCONNECT:
ret = fusb302_putreg(priv, FUSB302_MASK_REG,
(uint8_t) ~MASK_COMP_CHNG);
break;
case WAITING_FOR_SNK_DISCONNECT:
ret = fusb302_putreg(priv, FUSB302_MASK_REG,
(uint8_t) ~MASK_VBUS_OK);
break;
case WAITING_FOR_HOST_INTERVENTION:
case NOT_DEFINED:
default:
break;
}
return ret;
}
/****************************************************************************
* Name: enableccpd
*
* Description:
* Enable/disable CCx pulldown resistors
*
* Input Parameters:
* priv - pointer to device structure
* pulldown - CC1 or CC2
* enable - enable or disable it
*
* Returned Value:
* none
*
****************************************************************************/
void enableccpd(struct fusb302_dev_s *priv, enum cc_pd_e pulldown,
bool enable)
{
int regval;
regval = fusb302_getreg(priv, FUSB302_SWITCHES0_REG);
if (enable)
{
regval |= pulldown;
}
else
{
regval &= ~pulldown;
}
fusb302_putreg(priv, FUSB302_SWITCHES0_REG, regval);
}
/****************************************************************************
* Name: enableccpu
*
* Description:
* Enable/disable CCx pulldown resistors
*
* Input Parameters:
* priv - pointer to device structure
* pulldown - CC1 or CC2
* enable - enable or disable it
*
* Returned Value:
* none
*
****************************************************************************/
void enableccpu(struct fusb302_dev_s *priv, enum cc_pu_e pullup, bool enable)
{
int regval;
regval = fusb302_getreg(priv, FUSB302_SWITCHES0_REG);
if (enable)
{
regval |= pullup;
}
else
{
regval &= ~pullup;
}
fusb302_putreg(priv, FUSB302_SWITCHES0_REG, regval);
}
/****************************************************************************
* Name: enableccmeas
*
* Description:
* Enable/disable CC measurement
*
* Input Parameters:
* priv - pointer to device structure
* measure - CC1 or CC2
* enable - enable or disable it
*
* Returned Value:
* none
*
****************************************************************************/
void enableccmeas(struct fusb302_dev_s *priv, enum cc_meas_e measure,
bool enable)
{
int regval;
regval = fusb302_getreg(priv, FUSB302_SWITCHES0_REG);
if (enable)
{
regval |= measure;
}
else
{
regval &= ~measure;
}
fusb302_putreg(priv, FUSB302_SWITCHES0_REG, regval);
}
/****************************************************************************
* Name: setmdac
*
* Description:
* Set MDAC measurement threshold
*
* Input Parameters:
* priv - pointer to device structure
* threshold - MDAC threshold value
*
* Returned Value:
* none
*
****************************************************************************/
void setmdac(struct fusb302_dev_s *priv, enum src_current_e thresh)
{
int regval;
regval = fusb302_getreg(priv, FUSB302_MEASURE_REG);
regval &= ~MEASURE_MDAC_MASK;
regval |= MEASURE_MDAC(src_mdac_val[thresh]);
fusb302_putreg(priv, FUSB302_MEASURE_REG, regval);
nxsched_usleep(150);
}
/****************************************************************************
* Name: enablevconn
*
* Description:
* Enable/disable Vconn supply for host mode
*
* Input Parameters:
* priv - pointer to device structure
* vconn - CC1 or CC2
* enable - enable or disable it
*
* Returned Value:
* none
*
****************************************************************************/
void enablevconn(FAR struct fusb302_dev_s *priv, enum cc_vconn_e vconn,
bool enable)
{
int regval;
regval = fusb302_getreg(priv, FUSB302_SWITCHES0_REG);
if (enable)
{
regval |= vconn;
}
else
{
regval &= ~vconn;
}
fusb302_putreg(priv, FUSB302_SWITCHES0_REG, regval);
}
/****************************************************************************
* Name: set_switches
*
* Description:
* Set up switches for required operational mode
*
* Input Parameters:
* priv - pointer to device structure
* toggsval - the value read after toggling is done
* enable - enable or disable it
*
* Returned Value:
* none
*
****************************************************************************/
void set_switches(FAR struct fusb302_dev_s *priv,
uint8_t toggsval)
{
/* what device detect type was seen */
switch (toggsval)
{
case TOGGS_SRC_CC1:
{
enableccpd(priv, CC1_PULLDOWN, false);
enableccpd(priv, CC2_PULLDOWN, false);
enableccpu(priv, CC1_PULLUP, true);
enableccpu(priv, CC2_PULLUP, false);
enablevconn(priv, CC1_VCONN, false);
enablevconn(priv, CC2_VCONN, true);
enableccmeas(priv, CC1_MEASURE, true);
enableccmeas(priv, CC2_MEASURE, false);
setmdac(priv, SRC_CURRENT_DEFAULT);
}
break;
case TOGGS_SRC_CC2:
{
enableccpd(priv, CC1_PULLDOWN, false);
enableccpd(priv, CC2_PULLDOWN, false);
enableccpu(priv, CC1_PULLUP, false);
enableccpu(priv, CC2_PULLUP, true);
enablevconn(priv, CC1_VCONN, true);
enablevconn(priv, CC2_VCONN, false);
enableccmeas(priv, CC1_MEASURE, false);
enableccmeas(priv, CC2_MEASURE, true);
setmdac(priv, SRC_CURRENT_DEFAULT);
}
break;
case TOGGS_SNK_CC1:
{
enablevconn(priv, CC1_VCONN, false);
enablevconn(priv, CC2_VCONN, false);
enableccpu(priv, CC1_PULLUP, false);
enableccpd(priv, CC2_PULLUP, false);
enablevconn(priv, CC1_VCONN, false);
enablevconn(priv, CC2_VCONN, false);
enableccpd(priv, CC1_PULLDOWN, true);
enableccpd(priv, CC2_PULLDOWN, false);
}
break;
case TOGGS_SNK_CC2:
{
enablevconn(priv, CC1_VCONN, false);
enablevconn(priv, CC2_VCONN, false);
enableccpu(priv, CC1_PULLUP, false);
enableccpu(priv, CC2_PULLUP, false);
enablevconn(priv, CC1_VCONN, false);
enablevconn(priv, CC2_VCONN, false);
enableccpd(priv, CC1_PULLDOWN, false);
enableccpd(priv, CC2_PULLDOWN, true);
}
break;
case TOGGS_AUDIO_ACCESSORY:
enableccpd(priv, CC1_PULLDOWN, false);
enableccpd(priv, CC2_PULLDOWN, false);
enableccpu(priv, CC1_PULLUP, true);
enableccpu(priv, CC2_PULLUP, false);
enablevconn(priv, CC1_VCONN, false);
enablevconn(priv, CC2_VCONN, true);
enableccmeas(priv, CC1_MEASURE, true);
enableccmeas(priv, CC2_MEASURE, false);
setmdac(priv, SRC_CURRENT_DEFAULT);
break;
default:
fusb302_err("ERROR: invalid TOGGS VAL read:%d\n", toggsval);
break;
}
}
/****************************************************************************
* Name: fusb302_worker
*
* Description:
* Worker task to deal with device state machine
*
* Input Parameters:
* arg - Pointer to device
*
* Returned Value:
* OK
*
****************************************************************************/
static void fusb302_worker(FAR void *arg)
{
FAR struct fusb302_dev_s *priv = (FAR struct fusb302_dev_s *)arg;
FAR struct fusb302_config_s *config;
int regval;
int toggsval;
int interrupt;
int interrupta;
DEBUGASSERT(priv != NULL);
config = priv->config;
DEBUGASSERT(config != NULL);
nxmutex_lock(&priv->devlock);
/* just to double check we really should be here */
if (!priv->int_pending)
{
fusb302_err("ERROR: worker task run with no interrupt\n");
goto error_exit;
}
else
{
/* do statemachine */
interrupt = fusb302_getreg(priv, FUSB302_INTERRUPT_REG);
interrupta = fusb302_getreg(priv, FUSB302_INTERRUPTA_REG);
switch (priv->state)
{
case WAITING_FOR_DRP_TOGG_I:
if (interrupta & INTERRUPTA_M_TOGDONE)
{
/* What has connected? */
regval = fusb302_getreg(priv, FUSB302_STATUS1A_REG);
toggsval = DECODE_TOGGS(regval & STATUS1A_TOGGS_MASK);
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
fusb302_info("INFO: toggs val=%x\n", toggsval);
/* set everything up depending on what's been detected */
set_switches(priv, toggsval);
/* Power up Internal Oscillator as well now */
fusb302_putreg(priv, FUSB302_POWER_REG,
POWER_MODE_ALL);
fusb302_dump_registers(priv,
"After DRP toggle detect handling");
switch (toggsval)
{
case TOGGS_RUNNING:
break;
case TOGGS_SNK_CC1:
case TOGGS_SNK_CC2:
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
if ((interrupt & INTERRUPT_VBUS_OK) ||
(regval & STATUS0_VBUS_OK) ||
(regval & STATUS0_BC_LVL_MASK))
{
priv->state = WAITING_FOR_SNK_DISCONNECT;
priv->connect_status = SNK_DEVICE_CONNECTED;
}
else
{
priv->state = WAITING_FOR_VBUS;
}
break;
case TOGGS_SRC_CC1:
case TOGGS_SRC_CC2:
case TOGGS_AUDIO_ACCESSORY:
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
priv->state = WAITING_FOR_SRC_DISCONNECT;
priv->connect_status = SRC_DEVICE_CONNECTED;
break;
default:
priv->connect_status = UNKNOWN_CONNECTED;
goto error_exit;
break;
}
/* turn off toggling */
regval = fusb302_getreg(priv, FUSB302_CONTROL2_REG);
regval &= ~CONTROL2_TOGGLE;
fusb302_putreg(priv, FUSB302_CONTROL2_REG, regval);
/* Send new interrupt mask */
set_int_mask(priv);
}
break;
case WAITING_FOR_VBUS:
if (interrupt & INTERRUPT_VBUS_OK)
{
if (priv->connect_status == NOTHING_CONNECTED)
{
priv->state = WAITING_FOR_SNK_DISCONNECT;
priv->connect_status = SNK_DEVICE_CONNECTED;
}
else if (priv->connect_status == SNK_DEVICE_CONNECTED)
{
priv->state = WAITING_FOR_HOST_INTERVENTION;
priv->connect_status = SNK_DETACH_DETECTED;
}
}
else
{
priv->state = NOT_DEFINED;
goto error_exit;
}
/* Send new interrupt mask */
set_int_mask(priv);
break;
case WAITING_FOR_COMP_CHNG:
if (interrupt & INTERRUPT_COMP_CHNG)
{
if (priv->connect_status == NOTHING_CONNECTED)
{
priv->state = WAITING_FOR_SRC_DISCONNECT;
priv->connect_status = SRC_DEVICE_CONNECTED;
}
else if (priv->connect_status == SRC_DEVICE_CONNECTED)
{
priv->state = WAITING_FOR_HOST_INTERVENTION;
priv->connect_status = SRC_DETACH_DETECTED;
}
}
else
{
priv->state = NOT_DEFINED;
goto error_exit;
}
break;
case WAITING_FOR_SNK_TOGG_I:
if (interrupta & INTERRUPTA_M_TOGDONE)
{
/* Check what has connected? */
regval = fusb302_getreg(priv, FUSB302_STATUS1A_REG);
toggsval = DECODE_TOGGS(regval & STATUS1A_TOGGS_MASK);
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
fusb302_info("INFO: toggs val=%x\n", toggsval);
/* set everything up depending on what's been detected */
set_switches(priv, toggsval);
/* Power up Internal Oscillator as well now */
fusb302_putreg(priv, FUSB302_POWER_REG,
POWER_MODE_ALL);
fusb302_dump_registers(priv,
"After SNK toggle detect handling");
switch (toggsval)
{
case TOGGS_RUNNING:
break;
case TOGGS_SNK_CC1:
case TOGGS_SNK_CC2:
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
if ((interrupt & INTERRUPT_VBUS_OK) ||
(regval & STATUS0_VBUS_OK) ||
(regval & STATUS0_BC_LVL_MASK))
{
priv->state = WAITING_FOR_SNK_DISCONNECT;
priv->connect_status = SNK_DEVICE_CONNECTED;
}
else
{
priv->state = WAITING_FOR_VBUS;
}
break;
case TOGGS_SRC_CC1:
case TOGGS_SRC_CC2:
case TOGGS_AUDIO_ACCESSORY:
default:
priv->connect_status = UNKNOWN_CONNECTED;
goto error_exit;
break;
}
/* turn off toggling */
regval = fusb302_getreg(priv, FUSB302_CONTROL2_REG);
regval &= ~CONTROL2_TOGGLE;
fusb302_putreg(priv, FUSB302_CONTROL2_REG, regval);
/* Send new interrupt mask */
set_int_mask(priv);
}
break;
case WAITING_FOR_SRC_TOGG_I:
if (interrupta & INTERRUPTA_M_TOGDONE)
{
/* Check what has connected? */
regval = fusb302_getreg(priv, FUSB302_STATUS1A_REG);
toggsval = DECODE_TOGGS(regval & STATUS1A_TOGGS_MASK);
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
fusb302_info("INFO: toggs val=%x\n", toggsval);
/* set everything up depending on what's been detected */
set_switches(priv, toggsval);
/* Power up Internal Oscillator as well now */
fusb302_putreg(priv, FUSB302_POWER_REG,
POWER_MODE_ALL);
fusb302_dump_registers(priv,
"After SRC toggle detect handling");
switch (toggsval)
{
case TOGGS_RUNNING:
break;
case TOGGS_SRC_CC1:
case TOGGS_SRC_CC2:
case TOGGS_AUDIO_ACCESSORY:
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
priv->state = WAITING_FOR_SRC_DISCONNECT;
priv->connect_status = SRC_DEVICE_CONNECTED;
break;
case TOGGS_SNK_CC1:
case TOGGS_SNK_CC2:
default:
priv->connect_status = UNKNOWN_CONNECTED;
goto error_exit;
break;
}
/* turn off toggling */
regval = fusb302_getreg(priv, FUSB302_CONTROL2_REG);
regval &= ~CONTROL2_TOGGLE;
fusb302_putreg(priv, FUSB302_CONTROL2_REG, regval);
/* Send new interrupt mask */
set_int_mask(priv);
}
break;
case WAITING_FOR_SRC_DISCONNECT:
if ((priv->connect_status != SRC_DEVICE_CONNECTED) &&
(priv->connect_status != AUDIO_ACCESSORY_CONNECTED))
{
priv->connect_status = CONNECT_ERROR;
priv->state = WAITING_FOR_HOST_INTERVENTION;
}
else if (interrupt & INTERRUPT_COMP_CHNG)
{
priv->connect_status = SRC_DETACH_DETECTED;
priv->state = WAITING_FOR_HOST_INTERVENTION;
}
else if (interrupt & INTERRUPT_BC_LVL)
{
priv->connect_status = BC_LEVEL_CHANGE_REQUSTED;
}
break;
case WAITING_FOR_SNK_DISCONNECT:
regval = fusb302_getreg(priv, FUSB302_STATUS0_REG);
if (regval & STATUS0_VBUS_OK)
/* A USB3 device can cause a spurious VBUS OK here.
* A USB2 device doesn't.
*/
break;
if (priv->connect_status != SNK_DEVICE_CONNECTED)
{
priv->connect_status = CONNECT_ERROR;
priv->state = WAITING_FOR_HOST_INTERVENTION;
}
else if (interrupt & INTERRUPT_VBUS_OK)
{
priv->connect_status = SNK_DETACH_DETECTED;
priv->state = WAITING_FOR_HOST_INTERVENTION;
}
else if (interrupt & INTERRUPT_BC_LVL)
{
priv->connect_status = BC_LEVEL_CHANGE_REQUSTED;
}
break;
case WAITING_FOR_HOST_INTERVENTION:
/* the higher level software has not dealt with the previous
* response yet so we notify this as an error
*/
priv->connect_status = CONNECT_ERROR;
break;
default:
fusb302_info("state not defined yet\n");
fusb302_reset(priv);
break;
}
/* notify any waiters that there's a new state */
fusb302_info("INFO: connection or state change: %d\n",
priv->connect_status);
poll_notify(priv->fds, CONFIG_FUSB302_NPOLLWAITERS, POLLIN);
}
error_exit:
fusb302_clear_interrupts(priv);
/* re-enable interrupt */
priv->int_pending = false;
config->irq_enable(config, true);
/* release lock */
nxmutex_unlock(&priv->devlock);
}
/****************************************************************************
* Name: fusb302_schedule
*
* Description:
* Schedule the state machine work
*
* Input Parameters:
* priv - A reference to the FUSB302 peripheral state
*
* Returned Value:
* OK
*
****************************************************************************/
static int fusb302_schedule(FAR struct fusb302_dev_s *priv)
{
FAR struct fusb302_config_s *config;
int ret;
config = priv->config;
DEBUGASSERT(config != NULL);
DEBUGASSERT(priv->work.worker == NULL);
ret = work_queue(HPWORK, &priv->work, fusb302_worker, priv, 0);
if (ret != 0)
{
fusb302_err("ERROR: Failed to queue work: %d\n", ret);
}
return OK;
}
/****************************************************************************
* Name: fusb302_dumpregs
*
* Description:
* Dump the contents of all FUSB302 registers
*
* Input Parameters:
* priv - A reference to the FUSB302 peripheral state
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_DEBUG_FUSB302_REG
static void fusb302_dump_registers(FAR struct fusb302_dev_s *priv,
FAR const char *msg)
{
fusb302_info("\nFUSB302 Registers: %s\n", msg);
fusb302_info
(" Device id: %02x Switches0: %02x Switch1: %02x Measure: %02x\n",
fusb302_getreg(priv, FUSB302_DEV_ID_REG),
fusb302_getreg(priv, FUSB302_SWITCHES0_REG),
fusb302_getreg(priv, FUSB302_SWITCHES1_REG),
fusb302_getreg(priv, FUSB302_MEASURE_REG));
fusb302_info
(" Slice: %02x Control0: %02x Control1:%02x Control2: %02x\n",
fusb302_getreg(priv, FUSB302_SLICE_REG),
fusb302_getreg(priv, FUSB302_CONTROL0_REG),
fusb302_getreg(priv, FUSB302_CONTROL1_REG),
fusb302_getreg(priv, FUSB302_CONTROL2_REG));
fusb302_info
(" Control3: %02x Mask: %02x Power:%02x Reset: %02x\n",
fusb302_getreg(priv, FUSB302_CONTROL3_REG),
fusb302_getreg(priv, FUSB302_MASK_REG),
fusb302_getreg(priv, FUSB302_POWER_REG),
fusb302_getreg(priv, FUSB302_RESET_REG));
fusb302_info
(" OCPreg: %02x MaskA: %02x MaskB:%02x Control4: %02x\n",
fusb302_getreg(priv, FUSB302_OCPREG_REG),
fusb302_getreg(priv, FUSB302_MASKA_REG),
fusb302_getreg(priv, FUSB302_MASKB_REG),
fusb302_getreg(priv, FUSB302_CONTROL4_REG));
fusb302_info
(" Status0a: %02x Status1a: %02x InterruptA:%02x InterruptB: %02x\n",
fusb302_getreg(priv, FUSB302_STATUS0A_REG),
fusb302_getreg(priv, FUSB302_STATUS1A_REG),
fusb302_getreg(priv, FUSB302_INTERRUPTA_REG),
fusb302_getreg(priv, FUSB302_INTERRUPTB_REG));
fusb302_info
(" Status0: %02x Status1: %02x Interrupt:%02x FIFOs: %02x\n",
fusb302_getreg(priv, FUSB302_STATUS0_REG),
fusb302_getreg(priv, FUSB302_STATUS1_REG),
fusb302_getreg(priv, FUSB302_INTERRUPT_REG),
fusb302_getreg(priv, FUSB302_FIFOS_REG));
}
#endif
/****************************************************************************
* Name: fusb302_getreg
*
* Description:
* Read from an 8-bit FUSB302 register
*
* Input Parameters:
* priv - pointer to FUSB302 Private Structure
* reg - register to read
*
* Returned Value:
* Returns positive register value in case of success, otherwise ERROR
****************************************************************************/
static int fusb302_getreg(FAR struct fusb302_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 = FUSB302_I2C_FREQUENCY;
msg[0].addr = priv->config->i2c_addr;
msg[0].flags = 0;
msg[0].buffer = &reg;
msg[0].length = 1;
msg[1].frequency = FUSB302_I2C_FREQUENCY;
msg[1].addr = priv->config->i2c_addr;
msg[1].flags = I2C_M_READ;
msg[1].buffer = &regval;
msg[1].length = 1;
/* Perform the transfer */
for (retries = 0; retries < FUSB302_I2C_RETRIES; retries++)
{
ret = I2C_TRANSFER(priv->config->i2c, msg, 2);
if (ret >= 0)
{
return regval;
}
else
{
/* Some error. Try to reset I2C bus and keep trying. */
#ifdef CONFIG_I2C_RESET
if (retries == FUSB302_I2C_RETRIES - 1)
{
break;
}
ret = I2C_RESET(priv->config->i2c);
if (ret < 0)
{
fusb302_err("ERROR: I2C_RESET failed: %d\n", ret);
return ret;
}
#endif
}
}
fusb302_info("reg:%02X, error:%d\n", reg, ret);
return ret;
}
/****************************************************************************
* Name: fusb302_putreg
*
* Description:
* Write a value to an 8-bit FUSB302 register
*
* Input Parameters:
* priv - pointer to FUSB302 Private Structure
* regaddr - register to read
* regval - value to be written
*
* Returned Value:
* None
****************************************************************************/
static int fusb302_putreg(FAR struct fusb302_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 FUSB302 address write message */
msg.frequency = FUSB302_I2C_FREQUENCY;
msg.addr = priv->config->i2c_addr;
msg.flags = 0;
msg.buffer = txbuffer;
msg.length = 2;
/* Perform the transfer */
for (retries = 0; retries < FUSB302_I2C_RETRIES; retries++)
{
ret = I2C_TRANSFER(priv->config->i2c, &msg, 1);
if (ret == OK)
{
return OK;
}
else
{
/* Some error. Try to reset I2C bus and keep trying. */
#ifdef CONFIG_I2C_RESET
if (retries == FUSB302_I2C_RETRIES - 1)
{
break;
}
ret = I2C_RESET(priv->config->i2c;
if (ret < 0)
{
fusb302_err("ERROR: I2C_RESET failed: %d\n", ret);
return ret;
}
#endif
}
}
fusb302_err("ERROR: failed reg:%02X, value:%02X, error:%d\n",
regaddr, regval, ret);
return ret;
}
/****************************************************************************
* Name: fusb302_read_device_id
*
* Description:
* Read device version and revision IDs
*
****************************************************************************/
static int fusb302_read_device_id(FAR struct fusb302_dev_s * priv,
FAR uint8_t * arg)
{
int ret;
ret = fusb302_getreg(priv, FUSB302_DEV_ID_REG);
if (ret < 0)
{
fusb302_err("ERROR: Failed to read device ID\n");
return -EIO;
}
*arg = ret;
return OK;
}
/****************************************************************************
* Name: fusb302_clear_interrupts
*
* Description:
* Clear interrupts from FUSB302 chip
*
****************************************************************************/
static int fusb302_clear_interrupts(FAR struct fusb302_dev_s * priv)
{
int ret = OK;
ret = fusb302_getreg(priv, FUSB302_INTERRUPT_REG);
if (ret >= 0)
{
ret = fusb302_getreg(priv, FUSB302_INTERRUPTA_REG);
}
if (ret >= 0)
{
ret = fusb302_getreg(priv, FUSB302_INTERRUPTB_REG);
}
if (ret < 0)
{
fusb302_err("ERROR: Failed to clear interrupts\n");
return -EIO;
}
return ret;
}
/****************************************************************************
* Name: fusb302_setup
*
* Description:
* Setup FUSB302 chip
*
****************************************************************************/
static int fusb302_setup(FAR struct fusb302_dev_s *priv,
struct fusb302_setup_s *setup)
{
/* Not implemented yet */
return -ENOTTY;
}
/****************************************************************************
* Name: fusb302_set_mode
*
* Description:
* Configure supported device modes (sink, source, DRP, accessory)
*
****************************************************************************/
static int fusb302_set_mode(FAR struct fusb302_dev_s *priv,
enum fusb302_mode_e mode)
{
int ret;
int regval;
ret = OK;
priv->int_pending = false;
priv->config->irq_enable(priv->config, false);
/* Best to start with a known, clean, set of register settings.
* This sets:
* Host current to default
* Vbus measure off
* Switches0 to defaults (Vconn_CC off etc)
*/
if (priv->state != RESET)
{
fusb302_reset(priv);
}
regval = fusb302_clear_interrupts(priv);
switch (mode)
{
case MODE_DRP_POLL_AUTO:
{
/* set up FUSB302 to automatically poll for device or host */
ret = fusb302_putreg(priv, FUSB302_CONTROL2_REG,
(SET_POLL_MODE(DRP_POLLING) |
CONTROL2_TOGGLE |
SET_WAIT_MODE(WAIT_80MS))));
priv->state = WAITING_FOR_DRP_TOGG_I;
priv->connect_status = TOGGLING;
set_int_mask(priv);
}
break;
case MODE_SNK_POLL_AUTO:
{
/* set up FUSB302 to automatically poll for device */
ret = fusb302_putreg(priv, FUSB302_CONTROL2_REG,
(SET_POLL_MODE(SNK_POLLING) |
CONTROL2_TOGGLE));
priv->state = WAITING_FOR_SNK_TOGG_I;
priv->connect_status = TOGGLING;
set_int_mask(priv);
}
break;
case MODE_SRC_POLL_AUTO:
{
/* set up FUSB302 to automatically poll for device */
ret = fusb302_putreg(priv, FUSB302_CONTROL2_REG,
(SET_POLL_MODE(SRC_POLLING) |
CONTROL2_TOGGLE));
priv->state = WAITING_FOR_SRC_TOGG_I;
priv->connect_status = TOGGLING;
set_int_mask(priv);
}
break;
default:
return -EINVAL;
break;
}
ret = fusb302_putreg(priv, FUSB302_POWER_REG, POWER_MODE_TOGGLING);
/* enable interrupts */
regval = fusb302_getreg(priv, FUSB302_CONTROL0_REG);
regval &= ~CONTROL0_INT_MASK;
ret = fusb302_putreg(priv, FUSB302_CONTROL0_REG, regval);
if (ret == OK)
{
priv->config->irq_enable(priv->config, true);
}
else
{
priv->config->irq_enable(priv->config, false);
}
return ret;
}
/****************************************************************************
* Name: fusb302_read_status
*
* Description:
* Read status register
*
****************************************************************************/
static int fusb302_read_status(FAR struct fusb302_dev_s *priv,
FAR uint8_t *arg)
{
int ret;
ret = fusb302_getreg(priv, FUSB302_STATUS0_REG);
if (ret < 0)
{
fusb302_err("ERROR: Failed to read status\n");
return -EIO;
}
*arg = ret;
return OK;
}
/****************************************************************************
* Name: fusb302_reset
*
* Description:
* Reset FUSB302 HW and clear I2C registers
*
****************************************************************************/
static int fusb302_reset(FAR struct fusb302_dev_s *priv)
{
int ret = OK;
ret = fusb302_putreg(priv, FUSB302_RESET_REG, RESET_SW_RESET);
if (ret < 0)
{
fusb302_err("ERROR: FUSB302 did not respond to soft reset\n");
return -EFAULT;
}
else
{
fusb302_info("INFO: fusb302 soft reset executed\n");
}
priv->state = RESET;
priv->connect_status = NOTHING_CONNECTED;
return ret;
}
/****************************************************************************
* Name: fusb302_open
*
* Description:
* This function is called whenever the FUSB302 device is opened.
*
****************************************************************************/
static int fusb302_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct fusb302_dev_s *priv = inode->i_private;
int ret = OK;
/* Probe device */
ret = fusb302_getreg(priv, FUSB302_DEV_ID_REG);
if (ret < 0)
{
fusb302_err("ERROR: No response at given address 0x%02X\n",
priv->config->i2c_addr);
ret = -EFAULT;
}
/* Error exit */
return ret;
}
/****************************************************************************
* Name: fusb302_close
*
* Description:
* This routine is called when the FUSB302 device is closed.
*
****************************************************************************/
static int fusb302_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct fusb302_dev_s *priv = inode->i_private;
fusb302_dump_registers(priv, "Registers before close");
priv->config->irq_enable(priv->config, false);
return OK;
}
/****************************************************************************
* Name: fusb302_read
* Description:
* This routine is called when the FUSB302 device is read.
****************************************************************************/
static ssize_t fusb302_read(FAR struct file *filep,
FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode = filep->f_inode;
FAR struct fusb302_dev_s *priv = inode->i_private;
FAR struct fusb302_result_s *ptr;
irqstate_t flags;
int ret;
if (buflen < sizeof(struct fusb302_result_s))
{
return 0;
}
ptr = (struct fusb302_result_s *)buffer;
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
return ret;
}
flags = enter_critical_section();
ptr->connected_state = priv->connect_status;
ptr->state = priv->state;
if (priv->int_pending)
{
fusb302_dump_registers(priv, "Registers after attach detected");
}
priv->int_pending = false;
leave_critical_section(flags);
nxmutex_unlock(&priv->devlock);
return sizeof(struct fusb302_result_s);
}
/****************************************************************************
* Name: fusb302_write
* Description:
* This routine is called when the FUSB302 device is written to.
****************************************************************************/
static ssize_t fusb302_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
ssize_t length = 0;
return length;
}
/****************************************************************************
* Name: fusb302_ioctl
* Description:
* This routine is called when ioctl function call is performed for
* the FUSB302 device.
****************************************************************************/
static int fusb302_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct fusb302_dev_s *priv = inode->i_private;
int ret;
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
return ret;
}
fusb302_info("cmd: 0x%02X, arg:%lu\n", cmd, arg);
switch (cmd)
{
case USBCIOC_READ_DEVID:
{
ret = fusb302_read_device_id(priv, (FAR uint8_t *)arg);
}
break;
case USBCIOC_SETUP:
{
ret = fusb302_setup(priv, (struct fusb302_setup_s *)arg);
}
break;
case USBCIOC_SET_POLL_MODE:
{
ret = fusb302_set_mode(priv, (uint8_t)arg);
}
break;
case USBCIOC_READ_STATUS:
{
ret = fusb302_read_status(priv, (FAR uint8_t *)arg);
}
break;
case USBCIOC_RESET:
{
fusb302_dump_registers(priv, "Before reset");
ret = fusb302_reset(priv);
fusb302_dump_registers(priv, "After reset");
}
break;
default:
{
fusb302_err("ERROR: Unrecognized cmd: %d\n", cmd);
ret = -ENOTTY;
}
break;
}
fusb302_dump_registers(priv, "After ioctl");
nxmutex_unlock(&priv->devlock);
return ret;
}
/****************************************************************************
* Name: fusb302_poll
* Description:
* This routine is called during FUSB302 device poll
****************************************************************************/
static int fusb302_poll(FAR struct file *filep,
FAR struct pollfd *fds,
bool setup)
{
FAR struct inode *inode;
FAR struct fusb302_dev_s *priv;
int ret = OK;
int i;
DEBUGASSERT(fds);
inode = filep->f_inode;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
ret = nxmutex_lock(&priv->devlock);
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_FUSB302_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_FUSB302_NPOLLWAITERS)
{
fds->priv = NULL;
ret = -EBUSY;
goto out;
}
}
else if (fds->priv)
{
/* This is a request to tear down the poll. */
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
DEBUGASSERT(slot != NULL);
/* Remove all memory of the poll setup */
*slot = NULL;
fds->priv = NULL;
}
out:
nxmutex_unlock(&priv->devlock);
return ret;
}
/****************************************************************************
* Name: fusb302_callback
*
* Description:
* FUSB302 interrupt handler
*
****************************************************************************/
static int fusb302_int_handler(int irq, FAR void *context, FAR void *arg)
{
FAR struct fusb302_dev_s *priv = (FAR struct fusb302_dev_s *)arg;
FAR struct fusb302_config_s *config;
int ret;
ret = OK;
DEBUGASSERT(priv != NULL);
config = priv->config;
DEBUGASSERT(config != NULL);
priv->int_pending = true;
/* Disable interrupts */
config->irq_enable(config, false);
/* run worker scheduler */
ret = fusb302_schedule(priv);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: fusb302_register
*
* Description:
* Register the FUSB302 character driver as "devpath"
*
* Input Parameters
* devpath - the full path to the driver to register, e.g. "/dev/fusb302"
*
* Returned Value
*
* Zero (OK) on success; a negated errno value on failure
*
****************************************************************************/
int fusb302_register(FAR const char *devpath,
FAR struct fusb302_config_s *config)
{
int ret;
DEBUGASSERT(devpath != NULL && config != NULL);
/* Initialize the FUSB302 device structure */
FAR struct fusb302_dev_s *priv;
priv = (FAR struct fusb302_dev_s *)
kmm_zalloc(sizeof(struct fusb302_dev_s));
if (!priv)
{
fusb302_err("ERROR: Failed to allocate instance\n");
return -ENOMEM;
}
/* Initialize device structure mutex */
nxmutex_init(&priv->devlock);
priv->int_pending = false;
priv->config = config;
/* probe the FUSB302 */
ret = fusb302_getreg(priv, FUSB302_DEV_ID_REG);
if (ret < 0)
{
fusb302_err("ERROR: FUSB302 did not respond\n");
return -ENODEV;
}
/* Register the character driver */
ret = register_driver(devpath, &g_fusb302ops, 0600, priv);
if (ret < 0)
{
fusb302_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, fusb302_int_handler, priv);
priv->config->irq_enable(config, false);
return OK;
errout_with_priv:
nxmutex_destroy(&priv->devlock);
kmm_free(priv);
return ret;
}