/**************************************************************************** * drivers/usbmisc/fusb302.c * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * 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 * threhold - 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); nxsig_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 = ® 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 = ®val; 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, 0666, 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; }