You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1143 lines
25 KiB
1143 lines
25 KiB
/* |
|
* Copyright (c) 2022 Nordic Semiconductor ASA |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/* |
|
* MAX3421E USB Peripheral/Host Controller with SPI Interface. |
|
* NOTE: Driver supports only host mode yet. |
|
*/ |
|
|
|
#define DT_DRV_COMPAT maxim_max3421e_spi |
|
|
|
#include <string.h> |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/init.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/drivers/usb/uhc.h> |
|
|
|
#include "uhc_common.h" |
|
#include "uhc_max3421e.h" |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(max3421e, CONFIG_UHC_DRIVER_LOG_LEVEL); |
|
|
|
static K_KERNEL_STACK_DEFINE(drv_stack, CONFIG_MAX3421E_THREAD_STACK_SIZE); |
|
static struct k_thread drv_stack_data; |
|
|
|
#define MAX3421E_STATE_BUS_RESET 0 |
|
#define MAX3421E_STATE_BUS_RESUME 1 |
|
|
|
struct max3421e_data { |
|
struct gpio_callback gpio_cb; |
|
struct uhc_transfer *last_xfer; |
|
struct k_sem irq_sem; |
|
atomic_t state; |
|
uint16_t tog_in; |
|
uint16_t tog_out; |
|
uint8_t addr; |
|
uint8_t hirq; |
|
uint8_t hien; |
|
uint8_t mode; |
|
uint8_t hxfr; |
|
uint8_t hrsl; |
|
}; |
|
|
|
struct max3421e_config { |
|
struct spi_dt_spec dt_spi; |
|
struct gpio_dt_spec dt_int; |
|
struct gpio_dt_spec dt_rst; |
|
}; |
|
|
|
static int max3421e_read_hirq(const struct device *dev, |
|
const uint8_t reg, |
|
uint8_t *const data, |
|
const uint32_t count, |
|
bool update_hirq) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
const struct max3421e_config *config = dev->config; |
|
uint8_t cmd = MAX3421E_CMD_SPI_READ(reg); |
|
uint8_t hirq; |
|
int ret; |
|
|
|
const struct spi_buf cmd_buf = { |
|
.buf = &cmd, |
|
.len = sizeof(cmd) |
|
}; |
|
const struct spi_buf rx_buf[] = { |
|
{ |
|
.buf = &hirq, |
|
.len = sizeof(hirq) |
|
}, |
|
{ |
|
.buf = data, |
|
.len = count |
|
} |
|
}; |
|
|
|
const struct spi_buf_set tx = { |
|
.buffers = &cmd_buf, |
|
.count = 1 |
|
}; |
|
const struct spi_buf_set rx = { |
|
.buffers = rx_buf, |
|
.count = ARRAY_SIZE(rx_buf) |
|
}; |
|
|
|
ret = spi_transceive_dt(&config->dt_spi, &tx, &rx); |
|
if (unlikely(update_hirq)) { |
|
priv->hirq = hirq; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int max3421e_read(const struct device *dev, |
|
const uint8_t reg, |
|
uint8_t *const data, |
|
const uint32_t count) |
|
{ |
|
|
|
return max3421e_read_hirq(dev, reg, data, count, false); |
|
} |
|
|
|
static int max3421e_write_byte(const struct device *dev, |
|
const uint8_t reg, |
|
const uint8_t val) |
|
{ |
|
const struct max3421e_config *config = dev->config; |
|
uint8_t buf[2] = {MAX3421E_CMD_SPI_WRITE(reg), val}; |
|
|
|
const struct spi_buf cmd_buf = { |
|
.buf = &buf, |
|
.len = sizeof(buf) |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &cmd_buf, |
|
.count = 1 |
|
}; |
|
|
|
return spi_write_dt(&config->dt_spi, &tx); |
|
} |
|
|
|
static int max3421e_write(const struct device *dev, |
|
const uint8_t reg, |
|
uint8_t *const data, |
|
const size_t count) |
|
{ |
|
const struct max3421e_config *config = dev->config; |
|
uint8_t cmd = MAX3421E_CMD_SPI_WRITE(reg); |
|
|
|
const struct spi_buf cmd_buf[] = { |
|
{ |
|
.buf = &cmd, |
|
.len = sizeof(cmd), |
|
}, |
|
{ |
|
.buf = data, |
|
.len = count, |
|
}, |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = cmd_buf, |
|
.count = ARRAY_SIZE(cmd_buf), |
|
}; |
|
|
|
return spi_write_dt(&config->dt_spi, &tx); |
|
} |
|
|
|
static int max3421e_lock(const struct device *dev) |
|
{ |
|
struct uhc_data *data = dev->data; |
|
|
|
return k_mutex_lock(&data->mutex, K_FOREVER); |
|
} |
|
|
|
static int max3421e_unlock(const struct device *dev) |
|
{ |
|
struct uhc_data *data = dev->data; |
|
|
|
return k_mutex_unlock(&data->mutex); |
|
} |
|
|
|
/* Disable Host Interrupt */ |
|
static ALWAYS_INLINE int max3421e_hien_disable(const struct device *dev, |
|
const uint8_t hint) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
|
|
priv->hien &= ~hint; |
|
|
|
return max3421e_write_byte(dev, MAX3421E_REG_HIEN, priv->hien); |
|
} |
|
|
|
/* Set peripheral (device) address to be used in next transfer */ |
|
static ALWAYS_INLINE int max3421e_peraddr(const struct device *dev, |
|
const uint8_t addr) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
int ret = 0; |
|
|
|
if (priv->addr != addr) { |
|
/* |
|
* TODO: Consider how to force the update of toggle values |
|
* for the next transfer. Necessary if we want to support |
|
* multiple peripherals. |
|
*/ |
|
ret = max3421e_write_byte(dev, MAX3421E_REG_PERADDR, addr); |
|
if (ret == 0) { |
|
priv->addr = addr; |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/* Update driver's knowledge about DATA PID */ |
|
static ALWAYS_INLINE void max3421e_tgl_update(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
uint8_t ep_idx = MAX3421E_EP(priv->hxfr); |
|
|
|
if (priv->hxfr & MAX3421E_OUTNIN) { |
|
if (priv->hrsl & MAX3421E_SNDTOGRD) { |
|
priv->tog_out |= BIT(ep_idx); |
|
} else { |
|
priv->tog_out &= ~BIT(ep_idx); |
|
} |
|
} else { |
|
if (priv->hrsl & MAX3421E_RCVTOGRD) { |
|
priv->tog_in |= BIT(ep_idx); |
|
} else { |
|
priv->tog_in &= ~BIT(ep_idx); |
|
} |
|
} |
|
|
|
LOG_DBG("tog_in 0x%02x tog_out 0x%02x last-hxfr 0x%02x hrsl 0x%02x", |
|
priv->tog_in, priv->tog_out, priv->hxfr, priv->hrsl); |
|
} |
|
|
|
/* Get DATA PID to be used for the next transfer */ |
|
static ALWAYS_INLINE uint8_t max3421e_tgl_next(const struct device *dev, |
|
const uint8_t hxfr) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
uint8_t ep_idx = MAX3421E_EP(hxfr); |
|
uint8_t hctl; |
|
|
|
/* Force DATA1 PID for the data stage of control transfer */ |
|
if (hxfr & MAX3421E_SETUP) { |
|
priv->tog_in |= BIT(0); |
|
priv->tog_out |= BIT(0); |
|
} |
|
|
|
if (hxfr & MAX3421E_OUTNIN) { |
|
hctl = (priv->tog_out & BIT(ep_idx)) ? MAX3421E_SNDTOG1 : |
|
MAX3421E_SNDTOG0; |
|
} else { |
|
hctl = (priv->tog_in & BIT(ep_idx)) ? MAX3421E_RCVTOG1 : |
|
MAX3421E_RCVTOG0; |
|
} |
|
|
|
return hctl; |
|
} |
|
|
|
static ALWAYS_INLINE int max3421e_hxfr_start(const struct device *dev, |
|
const uint8_t hxfr) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
|
|
if (priv->hxfr != hxfr) { |
|
uint8_t reg[2] = {0, hxfr}; |
|
|
|
/* Update DATA PID if transfer parameter changes */ |
|
max3421e_tgl_update(dev); |
|
reg[0] = max3421e_tgl_next(dev, hxfr); |
|
priv->hxfr = hxfr; |
|
LOG_DBG("hctl 0x%02x hxfr 0x%02x", reg[0], reg[1]); |
|
|
|
return max3421e_write(dev, MAX3421E_REG_HCTL, |
|
reg, sizeof(reg)); |
|
} |
|
|
|
return max3421e_write_byte(dev, MAX3421E_REG_HXFR, priv->hxfr); |
|
} |
|
|
|
static int max3421e_xfer_data(const struct device *dev, |
|
struct net_buf *const buf, |
|
const uint8_t ep) |
|
{ |
|
const uint8_t ep_idx = USB_EP_GET_IDX(ep); |
|
int ret; |
|
|
|
if (USB_EP_DIR_IS_IN(ep)) { |
|
LOG_DBG("bulk in %p %u", buf, net_buf_tailroom(buf)); |
|
ret = max3421e_hxfr_start(dev, MAX3421E_HXFR_BULKIN(ep_idx)); |
|
} else { |
|
size_t len; |
|
|
|
len = MIN(MAX3421E_MAX_EP_SIZE, buf->len); |
|
LOG_DBG("bulk out %p %u", buf, len); |
|
|
|
ret = max3421e_write(dev, MAX3421E_REG_SNDFIFO, buf->data, len); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
ret = max3421e_write_byte(dev, MAX3421E_REG_SNDBC, len); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
/* |
|
* FIXME: Pull should happen after device ACKs the data, |
|
* move to max3421e_hrslt_success(). |
|
*/ |
|
net_buf_pull(buf, len); |
|
ret = max3421e_hxfr_start(dev, MAX3421E_HXFR_BULKOUT(ep_idx)); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int max3421e_xfer_control(const struct device *dev, |
|
struct uhc_transfer *const xfer, |
|
const uint8_t hrsl) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
struct net_buf *buf = xfer->buf; |
|
int ret; |
|
|
|
/* Just restart if device NAKed packet */ |
|
if (HRSLT_IS_NAK(hrsl)) { |
|
return max3421e_hxfr_start(dev, priv->hxfr); |
|
} |
|
|
|
|
|
if (xfer->stage == UHC_CONTROL_STAGE_SETUP) { |
|
LOG_DBG("Handle SETUP stage"); |
|
ret = max3421e_write(dev, MAX3421E_REG_SUDFIFO, |
|
xfer->setup_pkt, sizeof(xfer->setup_pkt)); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
ret = max3421e_hxfr_start(dev, MAX3421E_HXFR_SETUP(0)); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
if (buf != NULL && xfer->stage == UHC_CONTROL_STAGE_DATA) { |
|
LOG_DBG("Handle DATA stage"); |
|
return max3421e_xfer_data(dev, buf, xfer->ep); |
|
} |
|
|
|
if (xfer->stage == UHC_CONTROL_STAGE_STATUS) { |
|
LOG_DBG("Handle STATUS stage"); |
|
if (USB_EP_DIR_IS_IN(xfer->ep)) { |
|
ret = max3421e_hxfr_start(dev, MAX3421E_HXFR_HSOUT(0)); |
|
} else { |
|
ret = max3421e_hxfr_start(dev, MAX3421E_HXFR_HSIN(0)); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
return -EINVAL; |
|
} |
|
|
|
static int max3421e_xfer_bulk(const struct device *dev, |
|
struct uhc_transfer *const xfer, |
|
const uint8_t hrsl) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
struct net_buf *buf = xfer->buf; |
|
|
|
/* Just restart if device NAKed packet */ |
|
if (HRSLT_IS_NAK(hrsl)) { |
|
return max3421e_hxfr_start(dev, priv->hxfr); |
|
} |
|
|
|
if (buf == NULL) { |
|
LOG_ERR("No buffer to handle"); |
|
return -ENODATA; |
|
} |
|
|
|
return max3421e_xfer_data(dev, buf, xfer->ep); |
|
} |
|
|
|
static int max3421e_schedule_xfer(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
uint8_t hrsl = priv->hrsl; |
|
|
|
if (priv->last_xfer == NULL) { |
|
int ret; |
|
|
|
/* Do not restart last transfer */ |
|
hrsl = 0; |
|
|
|
priv->last_xfer = uhc_xfer_get_next(dev); |
|
if (priv->last_xfer == NULL) { |
|
LOG_DBG("Nothing to transfer"); |
|
return 0; |
|
} |
|
|
|
LOG_DBG("Next transfer %p", priv->last_xfer); |
|
ret = max3421e_peraddr(dev, priv->last_xfer->udev->addr); |
|
if (ret) { |
|
return ret; |
|
} |
|
} |
|
|
|
/* |
|
* TODO: currently we only support control transfers and |
|
* treat all others as bulk. |
|
*/ |
|
if (USB_EP_GET_IDX(priv->last_xfer->ep) == 0) { |
|
return max3421e_xfer_control(dev, priv->last_xfer, hrsl); |
|
} |
|
|
|
return max3421e_xfer_bulk(dev, priv->last_xfer, hrsl); |
|
} |
|
|
|
static void max3421e_xfer_drop_active(const struct device *dev, int err) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
|
|
if (priv->last_xfer) { |
|
uhc_xfer_return(dev, priv->last_xfer, err); |
|
priv->last_xfer = NULL; |
|
} |
|
} |
|
|
|
static void max3421e_xfer_cleanup_cancelled(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
struct uhc_data *data = dev->data; |
|
struct uhc_transfer *tmp; |
|
|
|
if (priv->last_xfer != NULL && priv->last_xfer->err == -ECONNRESET) { |
|
max3421e_xfer_drop_active(dev, -ECONNRESET); |
|
} |
|
|
|
SYS_DLIST_FOR_EACH_CONTAINER(&data->ctrl_xfers, tmp, node) { |
|
if (tmp->err == -ECONNRESET) { |
|
uhc_xfer_return(dev, tmp, -ECONNRESET); |
|
} |
|
} |
|
} |
|
|
|
static int max3421e_hrslt_success(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
struct uhc_transfer *const xfer = priv->last_xfer; |
|
struct net_buf *buf = xfer->buf; |
|
bool finished = false; |
|
int err = 0; |
|
size_t len; |
|
uint8_t bc; |
|
|
|
switch (MAX3421E_HXFR_TYPE(priv->hxfr)) { |
|
case MAX3421E_HXFR_TYPE_SETUP: |
|
if (xfer->buf != NULL) { |
|
xfer->stage = UHC_CONTROL_STAGE_DATA; |
|
} else { |
|
xfer->stage = UHC_CONTROL_STAGE_STATUS; |
|
} |
|
break; |
|
case MAX3421E_HXFR_TYPE_HSOUT: |
|
LOG_DBG("HSOUT"); |
|
finished = true; |
|
break; |
|
case MAX3421E_HXFR_TYPE_HSIN: |
|
LOG_DBG("HSIN"); |
|
finished = true; |
|
break; |
|
case MAX3421E_HXFR_TYPE_ISOOUT: |
|
LOG_ERR("ISO OUT is not implemented"); |
|
k_panic(); |
|
break; |
|
case MAX3421E_HXFR_TYPE_ISOIN: |
|
LOG_ERR("ISO IN is not implemented"); |
|
k_panic(); |
|
break; |
|
case MAX3421E_HXFR_TYPE_BULKOUT: |
|
if (buf->len == 0) { |
|
LOG_INF("hrslt bulk out %u", buf->len); |
|
if (xfer->ep == USB_CONTROL_EP_OUT) { |
|
xfer->stage = UHC_CONTROL_STAGE_STATUS; |
|
} else { |
|
finished = true; |
|
} |
|
} |
|
break; |
|
case MAX3421E_HXFR_TYPE_BULKIN: |
|
err = max3421e_read(dev, MAX3421E_REG_RCVBC, &bc, sizeof(bc)); |
|
if (err) { |
|
break; |
|
} |
|
|
|
if (bc > net_buf_tailroom(buf)) { |
|
LOG_WRN("%u received bytes will be dropped", |
|
bc - net_buf_tailroom(buf)); |
|
} |
|
|
|
len = MIN(net_buf_tailroom(buf), bc); |
|
err = max3421e_read(dev, MAX3421E_REG_RCVFIFO, |
|
net_buf_add(buf, len), len); |
|
if (err) { |
|
break; |
|
} |
|
|
|
LOG_INF("bc %u tr %u", bc, net_buf_tailroom(buf)); |
|
|
|
if (bc < MAX3421E_MAX_EP_SIZE || !net_buf_tailroom(buf)) { |
|
LOG_INF("hrslt bulk in %u, %u", bc, len); |
|
if (xfer->ep == USB_CONTROL_EP_IN) { |
|
xfer->stage = UHC_CONTROL_STAGE_STATUS; |
|
} else { |
|
finished = true; |
|
} |
|
} |
|
break; |
|
} |
|
|
|
if (finished) { |
|
LOG_DBG("Transfer finished"); |
|
uhc_xfer_return(dev, xfer, 0); |
|
priv->last_xfer = NULL; |
|
} |
|
|
|
if (err) { |
|
max3421e_xfer_drop_active(dev, err); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int max3421e_handle_hxfrdn(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
struct uhc_transfer *const xfer = priv->last_xfer; |
|
const uint8_t hrsl = priv->hrsl; |
|
int ret = 0; |
|
|
|
if (xfer == NULL) { |
|
LOG_ERR("No transfers to handle"); |
|
return -ENODATA; |
|
} |
|
|
|
switch (MAX3421E_HRSLT(hrsl)) { |
|
case MAX3421E_HR_NAK: |
|
break; |
|
case MAX3421E_HR_STALL: |
|
max3421e_xfer_drop_active(dev, -EPIPE); |
|
break; |
|
case MAX3421E_HR_TOGERR: |
|
LOG_WRN("Toggle error"); |
|
break; |
|
case MAX3421E_HR_SUCCESS: |
|
ret = max3421e_hrslt_success(dev); |
|
break; |
|
default: |
|
/* TODO: Handle all reasonalbe result codes */ |
|
max3421e_xfer_drop_active(dev, -EINVAL); |
|
ret = -EINVAL; |
|
break; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static void max3421e_handle_condet(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
const uint8_t jk = priv->hrsl & MAX3421E_JKSTATUS_MASK; |
|
enum uhc_event_type type = UHC_EVT_ERROR; |
|
|
|
/* |
|
* JSTATUS:KSTATUS 0:0 - SE0 |
|
* JSTATUS:KSTATUS 0:1 - K (Resume) |
|
* JSTATUS:KSTATUS 1:0 - J (Idle) |
|
*/ |
|
if (jk == 0) { |
|
/* Device disconnected */ |
|
type = UHC_EVT_DEV_REMOVED; |
|
} |
|
|
|
if (jk == MAX3421E_JSTATUS) { |
|
/* Device connected */ |
|
type = UHC_EVT_DEV_CONNECTED_FS; |
|
} |
|
|
|
if (jk == MAX3421E_KSTATUS) { |
|
/* Device connected */ |
|
type = UHC_EVT_DEV_CONNECTED_LS; |
|
} |
|
|
|
uhc_submit_event(dev, type, 0); |
|
} |
|
|
|
static void max3421e_bus_event(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
|
|
if (atomic_test_and_clear_bit(&priv->state, |
|
MAX3421E_STATE_BUS_RESUME)) { |
|
/* Resume operation done event */ |
|
uhc_submit_event(dev, UHC_EVT_RESUMED, 0); |
|
} |
|
|
|
if (atomic_test_and_clear_bit(&priv->state, |
|
MAX3421E_STATE_BUS_RESET)) { |
|
/* Reset operation done event */ |
|
uhc_submit_event(dev, UHC_EVT_RESETED, 0); |
|
} |
|
} |
|
|
|
static int max3421e_update_hrsl_hirq(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
int err; |
|
|
|
err = max3421e_read_hirq(dev, MAX3421E_REG_HRSL, &priv->hrsl, 1, true); |
|
/* Consider only enabled interrupts and RCVDAV bit (see RCVBC description) */ |
|
priv->hirq &= priv->hien | MAX3421E_RCVDAV; |
|
LOG_DBG("HIRQ 0x%02x HRSLT %d", priv->hirq, MAX3421E_HRSLT(priv->hrsl)); |
|
|
|
return err; |
|
} |
|
|
|
static int max3421e_clear_hirq(const struct device *dev, const uint8_t hirq) |
|
{ |
|
return max3421e_write_byte(dev, MAX3421E_REG_HIRQ, hirq); |
|
} |
|
|
|
static int max3421e_handle_bus_irq(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
const uint8_t hirq = priv->hirq; |
|
int ret = 0; |
|
|
|
/* Suspend operation Done Interrupt (bus suspended) */ |
|
if (hirq & MAX3421E_SUSDN) { |
|
ret = max3421e_hien_disable(dev, MAX3421E_SUSDN); |
|
uhc_submit_event(dev, UHC_EVT_SUSPENDED, 0); |
|
} |
|
|
|
/* Peripheral Connect/Disconnect Interrupt */ |
|
if (hirq & MAX3421E_CONDET) { |
|
max3421e_handle_condet(dev); |
|
} |
|
|
|
/* Remote Wakeup Interrupt */ |
|
if (hirq & MAX3421E_RWU) { |
|
uhc_submit_event(dev, UHC_EVT_RWUP, 0); |
|
} |
|
|
|
/* Bus Reset or Bus Resume event */ |
|
if (hirq & MAX3421E_BUSEVENT) { |
|
max3421e_bus_event(dev); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static void uhc_max3421e_thread(void *p1, void *p2, void *p3) |
|
{ |
|
ARG_UNUSED(p2); |
|
ARG_UNUSED(p3); |
|
|
|
const struct device *dev = p1; |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
|
|
LOG_DBG("MAX3421E thread started"); |
|
|
|
while (true) { |
|
bool schedule = false; |
|
int err; |
|
|
|
k_sem_take(&priv->irq_sem, K_FOREVER); |
|
|
|
max3421e_lock(dev); |
|
|
|
/* |
|
* Get HRSL and HIRQ values, do not perform any operation |
|
* that changes the state of the bus yet. |
|
*/ |
|
err = max3421e_update_hrsl_hirq(dev); |
|
if (unlikely(err)) { |
|
uhc_submit_event(dev, UHC_EVT_ERROR, err); |
|
} |
|
|
|
/* Host Transfer Done Interrupt */ |
|
if (priv->hirq & MAX3421E_HXFRDN) { |
|
err = max3421e_handle_hxfrdn(dev); |
|
if (unlikely(err)) { |
|
uhc_submit_event(dev, UHC_EVT_ERROR, err); |
|
} |
|
} |
|
|
|
/* Frame Generator Interrupt */ |
|
if (priv->hirq & MAX3421E_FRAME) { |
|
schedule = HRSLT_IS_BUSY(priv->hrsl) ? false : true; |
|
} |
|
|
|
/* Shorten the if path a little */ |
|
if (priv->hirq & ~(MAX3421E_FRAME | MAX3421E_HXFRDN)) { |
|
err = max3421e_handle_bus_irq(dev); |
|
if (unlikely(err)) { |
|
uhc_submit_event(dev, UHC_EVT_ERROR, err); |
|
} |
|
} |
|
|
|
/* Clear interrupts and schedule new bus transfer */ |
|
err = max3421e_clear_hirq(dev, priv->hirq); |
|
if (unlikely(err)) { |
|
uhc_submit_event(dev, UHC_EVT_ERROR, err); |
|
} |
|
|
|
max3421e_xfer_cleanup_cancelled(dev); |
|
|
|
if (schedule) { |
|
err = max3421e_schedule_xfer(dev); |
|
if (unlikely(err)) { |
|
uhc_submit_event(dev, UHC_EVT_ERROR, err); |
|
} |
|
} |
|
|
|
max3421e_unlock(dev); |
|
} |
|
} |
|
|
|
static void max3421e_gpio_cb(const struct device *dev, |
|
struct gpio_callback *cb, |
|
uint32_t pins) |
|
{ |
|
struct max3421e_data *priv = |
|
CONTAINER_OF(cb, struct max3421e_data, gpio_cb); |
|
|
|
k_sem_give(&priv->irq_sem); |
|
} |
|
|
|
/* Enable SOF generator */ |
|
static int max3421e_sof_enable(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
|
|
if (priv->mode & MAX3421E_SOFKAENAB) { |
|
return -EALREADY; |
|
} |
|
|
|
priv->mode |= MAX3421E_SOFKAENAB; |
|
|
|
return max3421e_write_byte(dev, MAX3421E_REG_MODE, priv->mode); |
|
} |
|
|
|
/* Disable SOF generator and suspend bus */ |
|
static int max3421e_bus_suspend(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
|
|
if (!(priv->mode & MAX3421E_SOFKAENAB)) { |
|
return -EALREADY; |
|
} |
|
|
|
priv->hien |= MAX3421E_SUSDN; |
|
priv->mode &= ~MAX3421E_SOFKAENAB; |
|
|
|
uint8_t tmp[3] = {MAX3421E_SUSDN, priv->hien, priv->mode}; |
|
|
|
return max3421e_write(dev, MAX3421E_REG_HIRQ, tmp, sizeof(tmp)); |
|
} |
|
|
|
/* Signal bus reset, 50ms SE0 signal */ |
|
static int max3421e_bus_reset(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
int ret; |
|
|
|
if (atomic_test_bit(&priv->state, MAX3421E_STATE_BUS_RESUME)) { |
|
return -EBUSY; |
|
} |
|
|
|
ret = max3421e_write_byte(dev, MAX3421E_REG_HCTL, MAX3421E_BUSRST); |
|
atomic_set_bit(&priv->state, MAX3421E_STATE_BUS_RESET); |
|
|
|
return ret; |
|
} |
|
|
|
/* Signal bus resume event, 20ms K-state + low-speed EOP */ |
|
static int max3421e_bus_resume(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
int ret; |
|
|
|
if (atomic_test_bit(&priv->state, MAX3421E_STATE_BUS_RESET)) { |
|
return -EBUSY; |
|
} |
|
|
|
ret = max3421e_write_byte(dev, MAX3421E_REG_HCTL, MAX3421E_SIGRSM); |
|
atomic_set_bit(&priv->state, MAX3421E_STATE_BUS_RESUME); |
|
|
|
return ret; |
|
} |
|
|
|
static int max3421e_enqueue(const struct device *dev, |
|
struct uhc_transfer *const xfer) |
|
{ |
|
return uhc_xfer_append(dev, xfer); |
|
} |
|
|
|
static int max3421e_dequeue(const struct device *dev, |
|
struct uhc_transfer *const xfer) |
|
{ |
|
struct uhc_data *data = dev->data; |
|
struct uhc_transfer *tmp; |
|
unsigned int key; |
|
|
|
key = irq_lock(); |
|
SYS_DLIST_FOR_EACH_CONTAINER(&data->ctrl_xfers, tmp, node) { |
|
if (xfer == tmp) { |
|
tmp->err = -ECONNRESET; |
|
} |
|
} |
|
|
|
irq_unlock(key); |
|
|
|
return 0; |
|
} |
|
|
|
static int max3421e_reset(const struct device *dev) |
|
{ |
|
const struct max3421e_config *config = dev->config; |
|
int ret; |
|
|
|
if (config->dt_rst.port) { |
|
gpio_pin_set_dt(&config->dt_rst, 1); |
|
gpio_pin_set_dt(&config->dt_rst, 0); |
|
} else { |
|
LOG_DBG("Reset MAX3421E using CHIPRES"); |
|
ret = max3421e_write_byte(dev, MAX3421E_REG_USBCTL, MAX3421E_CHIPRES); |
|
ret |= max3421e_write_byte(dev, MAX3421E_REG_USBCTL, 0); |
|
|
|
if (ret) { |
|
return ret; |
|
} |
|
} |
|
|
|
for (int i = 0; i < CONFIG_MAX3421E_OSC_WAIT_RETRIES; i++) { |
|
uint8_t usbirq; |
|
|
|
ret = max3421e_read(dev, MAX3421E_REG_USBIRQ, |
|
&usbirq, sizeof(usbirq)); |
|
|
|
LOG_DBG("USBIRQ 0x%02x", usbirq); |
|
if (usbirq & MAX3421E_OSCOKIRQ) { |
|
return 0; |
|
} |
|
|
|
k_msleep(3); |
|
} |
|
|
|
return -EIO; |
|
} |
|
|
|
static int max3421e_pinctl_setup(const struct device *dev) |
|
{ |
|
/* Full-Duplex SPI, INT pin edge active, GPX pin signals SOF */ |
|
const uint8_t pinctl = MAX3421E_FDUPSPI | MAX3421E_GPXB | MAX3421E_GPXA; |
|
uint8_t tmp; |
|
int ret; |
|
|
|
ret = max3421e_write_byte(dev, MAX3421E_REG_PINCTL, pinctl); |
|
if (unlikely(ret)) { |
|
return ret; |
|
} |
|
|
|
ret = max3421e_read(dev, MAX3421E_REG_PINCTL, &tmp, sizeof(tmp)); |
|
if (unlikely(ret)) { |
|
return ret; |
|
} |
|
|
|
if (unlikely(tmp != pinctl)) { |
|
LOG_ERR("Failed to verify PINCTL register 0x%02x vs 0x%02x", |
|
pinctl, tmp); |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Cache MODE and HIEN register values to avoid having to read it |
|
* before modifying register bits. |
|
*/ |
|
static int max3421e_mode_setup(const struct device *dev) |
|
{ |
|
/* |
|
* MODE register defaults: |
|
* host mode, connect internal D+ and D- pulldown resistors to ground |
|
*/ |
|
const uint8_t mode = MAX3421E_DPPULLDN | MAX3421E_DMPULLDN | |
|
MAX3421E_DELAYISO | MAX3421E_HOST; |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
uint8_t tmp; |
|
int ret; |
|
|
|
ret = max3421e_write_byte(dev, MAX3421E_REG_MODE, mode); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
ret = max3421e_read(dev, MAX3421E_REG_MODE, &tmp, sizeof(tmp)); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
if (tmp != mode) { |
|
LOG_ERR("Failed to verify MODE register 0x%02x vs 0x%02x", |
|
mode, tmp); |
|
return -EIO; |
|
} |
|
|
|
priv->mode = mode; |
|
|
|
return 0; |
|
} |
|
|
|
static int max3421e_hien_setup(const struct device *dev) |
|
{ |
|
/* Host interrupts enabled by default */ |
|
const uint8_t hien = MAX3421E_HXFRDN | MAX3421E_FRAME | |
|
MAX3421E_CONDET | MAX3421E_RWU | |
|
MAX3421E_BUSEVENT; |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
uint8_t tmp; |
|
int ret; |
|
|
|
ret = max3421e_write_byte(dev, MAX3421E_REG_HIEN, hien); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
ret = max3421e_read(dev, MAX3421E_REG_HIEN, &tmp, sizeof(tmp)); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
if (tmp != hien) { |
|
LOG_ERR("Failed to verify HIEN register 0x%02x vs 0x%02x", |
|
hien, tmp); |
|
return -EIO; |
|
} |
|
|
|
priv->hien = hien; |
|
|
|
return 0; |
|
} |
|
|
|
static int max3421e_enable_int_output(const struct device *dev) |
|
{ |
|
const uint8_t cpuctl = MAX3421E_IE; |
|
uint8_t tmp; |
|
int ret; |
|
|
|
/* Enable MAX3421E INT output pin */ |
|
ret = max3421e_write_byte(dev, MAX3421E_REG_CPUCTL, cpuctl); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
ret = max3421e_read(dev, MAX3421E_REG_CPUCTL, &tmp, sizeof(tmp)); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
if (tmp != cpuctl) { |
|
LOG_ERR("Failed to verify CPUCTL register 0x%02x vs 0x%02x", |
|
cpuctl, tmp); |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int uhc_max3421e_init(const struct device *dev) |
|
{ |
|
struct max3421e_data *priv = uhc_get_private(dev); |
|
uint8_t rev; |
|
int ret; |
|
|
|
ret = max3421e_pinctl_setup(dev); |
|
if (ret) { |
|
LOG_ERR("Failed to setup pinctl"); |
|
return ret; |
|
} |
|
|
|
ret = max3421e_read(dev, MAX3421E_REG_REVISION, &rev, sizeof(rev)); |
|
if (ret) { |
|
LOG_ERR("Failed to read revision"); |
|
return ret; |
|
} |
|
|
|
ret = max3421e_reset(dev); |
|
if (ret) { |
|
LOG_ERR("Failed to reset MAX3421E"); |
|
return ret; |
|
} |
|
|
|
ret = max3421e_mode_setup(dev); |
|
if (ret) { |
|
LOG_ERR("Failed to setup controller mode"); |
|
return ret; |
|
} |
|
|
|
ret = max3421e_hien_setup(dev); |
|
if (ret) { |
|
LOG_ERR("Failed to setup interrupts"); |
|
return ret; |
|
} |
|
|
|
ret = max3421e_enable_int_output(dev); |
|
if (ret) { |
|
LOG_ERR("Failed to enable INT output"); |
|
return ret; |
|
} |
|
|
|
LOG_INF("REV 0x%x, MODE 0x%02x, HIEN 0x%02x", |
|
rev, priv->mode, priv->hien); |
|
|
|
priv->addr = 0; |
|
|
|
/* Sample bus if device is already connected */ |
|
return max3421e_write_byte(dev, MAX3421E_REG_HCTL, MAX3421E_SAMPLEBUS); |
|
} |
|
|
|
static int uhc_max3421e_enable(const struct device *dev) |
|
{ |
|
/* TODO */ |
|
return 0; |
|
} |
|
|
|
static int uhc_max3421e_disable(const struct device *dev) |
|
{ |
|
/* TODO */ |
|
return 0; |
|
} |
|
|
|
static int uhc_max3421e_shutdown(const struct device *dev) |
|
{ |
|
/* TODO */ |
|
return 0; |
|
} |
|
|
|
static int max3421e_driver_init(const struct device *dev) |
|
{ |
|
const struct max3421e_config *config = dev->config; |
|
struct uhc_data *data = dev->data; |
|
struct max3421e_data *priv = data->priv; |
|
int ret; |
|
|
|
if (config->dt_rst.port) { |
|
if (!gpio_is_ready_dt(&config->dt_rst)) { |
|
LOG_ERR("GPIO device %s not ready", |
|
config->dt_rst.port->name); |
|
return -EIO; |
|
} |
|
|
|
ret = gpio_pin_configure_dt(&config->dt_rst, |
|
GPIO_OUTPUT_INACTIVE); |
|
if (ret) { |
|
LOG_ERR("Failed to configure GPIO pin %u", |
|
config->dt_rst.pin); |
|
return ret; |
|
} |
|
} |
|
|
|
if (!spi_is_ready_dt(&config->dt_spi)) { |
|
LOG_ERR("SPI device %s not ready", config->dt_spi.bus->name); |
|
return -EIO; |
|
} |
|
|
|
if (!gpio_is_ready_dt(&config->dt_int)) { |
|
LOG_ERR("GPIO device %s not ready", config->dt_int.port->name); |
|
return -EIO; |
|
} |
|
|
|
ret = gpio_pin_configure_dt(&config->dt_int, GPIO_INPUT); |
|
if (ret) { |
|
LOG_ERR("Failed to configure GPIO pin %u", config->dt_int.pin); |
|
return ret; |
|
} |
|
|
|
gpio_init_callback(&priv->gpio_cb, max3421e_gpio_cb, |
|
BIT(config->dt_int.pin)); |
|
ret = gpio_add_callback(config->dt_int.port, &priv->gpio_cb); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
ret = gpio_pin_interrupt_configure_dt(&config->dt_int, |
|
GPIO_INT_EDGE_TO_ACTIVE); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
k_mutex_init(&data->mutex); |
|
k_thread_create(&drv_stack_data, drv_stack, |
|
K_KERNEL_STACK_SIZEOF(drv_stack), |
|
uhc_max3421e_thread, |
|
(void *)dev, NULL, NULL, |
|
K_PRIO_COOP(2), 0, K_NO_WAIT); |
|
k_thread_name_set(&drv_stack_data, "uhc_max3421e"); |
|
|
|
LOG_DBG("MAX3421E CPU interface initialized"); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct uhc_api max3421e_uhc_api = { |
|
.lock = max3421e_lock, |
|
.unlock = max3421e_unlock, |
|
.init = uhc_max3421e_init, |
|
.enable = uhc_max3421e_enable, |
|
.disable = uhc_max3421e_disable, |
|
.shutdown = uhc_max3421e_shutdown, |
|
|
|
.bus_reset = max3421e_bus_reset, |
|
.sof_enable = max3421e_sof_enable, |
|
.bus_suspend = max3421e_bus_suspend, |
|
.bus_resume = max3421e_bus_resume, |
|
|
|
.ep_enqueue = max3421e_enqueue, |
|
.ep_dequeue = max3421e_dequeue, |
|
}; |
|
|
|
static struct max3421e_data max3421e_data = { |
|
.irq_sem = Z_SEM_INITIALIZER(max3421e_data.irq_sem, 0, 1), |
|
}; |
|
|
|
static struct uhc_data max3421e_uhc_data = { |
|
.priv = &max3421e_data, |
|
}; |
|
|
|
static const struct max3421e_config max3421e_cfg = { |
|
.dt_spi = SPI_DT_SPEC_INST_GET(0, SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 0), |
|
.dt_int = GPIO_DT_SPEC_INST_GET(0, int_gpios), |
|
.dt_rst = GPIO_DT_SPEC_INST_GET_OR(0, reset_gpios, {0}), |
|
}; |
|
|
|
DEVICE_DT_INST_DEFINE(0, max3421e_driver_init, NULL, |
|
&max3421e_uhc_data, &max3421e_cfg, |
|
POST_KERNEL, 99, |
|
&max3421e_uhc_api);
|
|
|