mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-02-12 10:15:54 +00:00
996 lines
24 KiB
C
996 lines
24 KiB
C
|
/*
|
|||
|
* usb-mkl27z.c - USB driver for MKL27Z
|
|||
|
*
|
|||
|
* Copyright (C) 2016, 2018 Flying Stone Technology
|
|||
|
* Author: NIIBE Yutaka <gniibe@fsij.org>
|
|||
|
*
|
|||
|
* This file is a part of Chopstx, a thread library for embedded.
|
|||
|
*
|
|||
|
* Chopstx is free software: you can redistribute it and/or modify it
|
|||
|
* under the terms of the GNU General Public License as published by
|
|||
|
* the Free Software Foundation, either version 3 of the License, or
|
|||
|
* (at your option) any later version.
|
|||
|
*
|
|||
|
* Chopstx is distributed in the hope that it will be useful, but
|
|||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|||
|
* General Public License for more details.
|
|||
|
*
|
|||
|
* You should have received a copy of the GNU General Public License
|
|||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
*
|
|||
|
* As additional permission under GNU GPL version 3 section 7, you may
|
|||
|
* distribute non-source form of the Program without the copy of the
|
|||
|
* GNU GPL normally required by section 4, provided you inform the
|
|||
|
* receipents of GNU GPL by a written offer.
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
#include <stdint.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <string.h>
|
|||
|
|
|||
|
#include "usb_lld.h"
|
|||
|
|
|||
|
#define DATA0 0
|
|||
|
#define DATA1 1
|
|||
|
|
|||
|
struct endpoint_ctl {
|
|||
|
uint32_t rx_odd: 1;
|
|||
|
uint32_t tx_odd: 1;
|
|||
|
};
|
|||
|
static struct endpoint_ctl ep[16];
|
|||
|
|
|||
|
#if 0
|
|||
|
struct USB_CONF {
|
|||
|
const uint8_t PERID; /* Peripheral ID register */
|
|||
|
uint8_t rsvd0[3]; /* */
|
|||
|
const uint8_t IDCOMP; /* Peripheral ID Complement register */
|
|||
|
uint8_t rsvd1[3]; /* */
|
|||
|
const uint8_t REV; /* Peripheral Revision register */
|
|||
|
uint8_t rsvd2[3]; /* */
|
|||
|
volatile uint8_t ADDINFO; /* Peripheral Additional Info register */
|
|||
|
};
|
|||
|
static struct USB_CONF *const USB_CONF = (struct USB_CONF *)0x40072000;
|
|||
|
#endif
|
|||
|
|
|||
|
struct USB_CTRL0 {
|
|||
|
volatile uint8_t OTGCTL; /* OTG Control register */
|
|||
|
};
|
|||
|
static struct USB_CTRL0 *const USB_CTRL0 = (struct USB_CTRL0 *)0x4007201c;
|
|||
|
|
|||
|
struct USB_CTRL1 {
|
|||
|
volatile uint8_t ISTAT; /* Interrupt Status register */
|
|||
|
uint8_t rsvd5[3]; /* */
|
|||
|
volatile uint8_t INTEN; /* Interrupt Enable register */
|
|||
|
uint8_t rsvd6[3]; /* */
|
|||
|
volatile uint8_t ERRSTAT; /* Error Interrupt Status register */
|
|||
|
uint8_t rsvd7[3]; /* */
|
|||
|
volatile uint8_t ERREN; /* Error Interrupt Enable register */
|
|||
|
uint8_t rsvd8[3]; /* */
|
|||
|
volatile uint8_t STAT; /* Status register */
|
|||
|
uint8_t rsvd9[3]; /* */
|
|||
|
volatile uint8_t CTL; /* Control register */
|
|||
|
uint8_t rsvd10[3]; /* */
|
|||
|
volatile uint8_t ADDR; /* Address register */
|
|||
|
uint8_t rsvd11[3]; /* */
|
|||
|
volatile uint8_t BDTPAGE1; /* BDT Page register 1 */
|
|||
|
uint8_t rsvd12[3]; /* */
|
|||
|
volatile uint8_t FRMNUML; /* Frame Number register Low */
|
|||
|
uint8_t rsvd13[3]; /* */
|
|||
|
volatile uint8_t FRMNUMH; /* Frame Number register High */
|
|||
|
uint8_t rsvd14[11]; /* */
|
|||
|
volatile uint8_t BDTPAGE2; /* BDT Page Register 2 */
|
|||
|
uint8_t rsvd15[3]; /* */
|
|||
|
volatile uint8_t BDTPAGE3; /* BDT Page Register 3 */
|
|||
|
};
|
|||
|
static struct USB_CTRL1 *const USB_CTRL1 = (struct USB_CTRL1 *)0x40072080;
|
|||
|
|
|||
|
/* Interrupt source bits */
|
|||
|
#define USB_IS_STALL (1 << 7)
|
|||
|
#define USB_IS_RESUME (1 << 5)
|
|||
|
#define USB_IS_SLEEP (1 << 4)
|
|||
|
#define USB_IS_TOKDNE (1 << 3)
|
|||
|
#define USB_IS_SOFTOK (1 << 2)
|
|||
|
#define USB_IS_ERROR (1 << 1)
|
|||
|
#define USB_IS_USBRST (1 << 0)
|
|||
|
|
|||
|
|
|||
|
struct USB_ENDPT {
|
|||
|
volatile uint8_t EP; /* Endpoint Control register */
|
|||
|
uint8_t rsvd17[3];
|
|||
|
};
|
|||
|
static struct USB_ENDPT *const USB_ENDPT = (struct USB_ENDPT *)0x400720c0;
|
|||
|
|
|||
|
struct USB_CTRL2 {
|
|||
|
volatile uint8_t USBCTRL; /* USB Control register */
|
|||
|
uint8_t rsvd33[3]; /* */
|
|||
|
volatile uint8_t OBSERVE; /* USB OTG Observe register */
|
|||
|
uint8_t rsvd34[3]; /* */
|
|||
|
volatile uint8_t CONTROL; /* USB OTG Control register */
|
|||
|
uint8_t rsvd35[3]; /* */
|
|||
|
volatile uint8_t USBTRC0; /* USB Transceiver Control register 0 */
|
|||
|
uint8_t rsvd36[7]; /* */
|
|||
|
volatile uint8_t USBFRMADJUST; /* Frame Adjut Register */
|
|||
|
};
|
|||
|
static struct USB_CTRL2 *const USB_CTRL2 = (struct USB_CTRL2 *)0x40072100;
|
|||
|
|
|||
|
/* Buffer Descriptor */
|
|||
|
struct BD {
|
|||
|
volatile uint32_t ctrl;
|
|||
|
volatile void *buf;
|
|||
|
};
|
|||
|
/*
|
|||
|
uint32_t rsvd0 : 2;
|
|||
|
volatile uint32_t STALL: 1;
|
|||
|
volatile uint32_t DTS: 1;
|
|||
|
|
|||
|
volatile uint32_t NINC: 1;
|
|||
|
volatile uint32_t KEEP: 1;
|
|||
|
volatile uint32_t DATA01: 1;
|
|||
|
volatile uint32_t OWN: 1;
|
|||
|
|
|||
|
uint32_t rsvd1: 8;
|
|||
|
volatile uint32_t BC: 10;
|
|||
|
uint32_t rsvd2: 6;
|
|||
|
*/
|
|||
|
#define TOK_PID(ctrl) ((ctrl >> 2) & 0x0f)
|
|||
|
|
|||
|
extern uint8_t __usb_bdt__;
|
|||
|
|
|||
|
static struct BD *const BD_table = (struct BD *)&__usb_bdt__;
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_usb_init (void)
|
|||
|
{
|
|||
|
int i;
|
|||
|
|
|||
|
memset (ep, 0, sizeof (ep));
|
|||
|
memset (BD_table, 0, 16 * 2 * 2 * sizeof (struct BD));
|
|||
|
|
|||
|
/* D+ pull up */
|
|||
|
USB_CTRL0->OTGCTL = 0x80;
|
|||
|
|
|||
|
USB_CTRL1->ERREN = 0xff;
|
|||
|
|
|||
|
USB_CTRL1->BDTPAGE1 = ((uint32_t)BD_table) >> 8;
|
|||
|
USB_CTRL1->BDTPAGE2 = ((uint32_t)BD_table) >> 16;
|
|||
|
USB_CTRL1->BDTPAGE3 = ((uint32_t)BD_table) >> 24;
|
|||
|
|
|||
|
/* Not suspended, Pull-down disabled. */
|
|||
|
USB_CTRL2->USBCTRL = 0x00;
|
|||
|
/* DP Pullup in non-OTG device mode. */
|
|||
|
USB_CTRL2->CONTROL = 0x10;
|
|||
|
|
|||
|
/* Disable all endpoints. */
|
|||
|
for (i = 0; i < 16; i++)
|
|||
|
USB_ENDPT[i].EP = 0;
|
|||
|
|
|||
|
/*
|
|||
|
* Enable USB FS communication module, clearing all ODD-bits
|
|||
|
* for BDT.
|
|||
|
*/
|
|||
|
USB_CTRL1->CTL = 0x03;
|
|||
|
|
|||
|
/* ??? How we can ask re-enumeration? Is only hard RESET enough? */
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_set_daddr (uint8_t daddr)
|
|||
|
{
|
|||
|
USB_CTRL1->ADDR = daddr;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_prepare_ep0_setup (struct usb_dev *dev)
|
|||
|
{
|
|||
|
/* Endpoint 0, TX=0. */
|
|||
|
BD_table[ep[0].rx_odd].ctrl = 0x00080088; /* Len=8, OWN=1, DATA01=0, DTS=1 */
|
|||
|
BD_table[ep[0].rx_odd].buf = &dev->dev_req;
|
|||
|
|
|||
|
BD_table[!ep[0].rx_odd].ctrl = 0x0000; /* OWN=0 */
|
|||
|
BD_table[!ep[0].rx_odd].buf = NULL;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_prepare_ep0_in (const void *buf, uint8_t len, int data01)
|
|||
|
{
|
|||
|
/* Endpoint 0, TX=1 *//* OWN=1, DTS=1 */
|
|||
|
BD_table[2+ep[0].tx_odd].ctrl = (len << 16) | 0x0088 | (data01 << 6);
|
|||
|
BD_table[2+ep[0].tx_odd].buf = (void *)buf;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_prepare_ep0_out (void *buf, uint8_t len, int data01)
|
|||
|
{
|
|||
|
/* Endpoint 0, TX=0 *//* OWN=1, DTS=1 */
|
|||
|
BD_table[ep[0].rx_odd].ctrl = (len << 16) | 0x0088 | (data01 << 6);
|
|||
|
BD_table[ep[0].rx_odd].buf = buf;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
kl27z_ep_is_disabled (uint8_t n)
|
|||
|
{
|
|||
|
return (USB_ENDPT[n].EP == 0);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
kl27z_ep_is_stall (uint8_t n)
|
|||
|
{
|
|||
|
return (USB_ENDPT[n].EP & 0x02) >> 1;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_ep_stall (uint8_t n)
|
|||
|
{
|
|||
|
USB_ENDPT[n].EP |= 0x02;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_ep_clear_stall (uint8_t n)
|
|||
|
{
|
|||
|
USB_ENDPT[n].EP &= ~0x02;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
kl27z_ep_clear_dtog (int rx, uint8_t n)
|
|||
|
{
|
|||
|
uint32_t config;
|
|||
|
|
|||
|
if (!kl27z_ep_is_stall (n))
|
|||
|
/* Just in case, when the endpoint is active */
|
|||
|
kl27z_ep_stall (n);
|
|||
|
|
|||
|
if (rx)
|
|||
|
{
|
|||
|
config = BD_table[4*n+ep[n].rx_odd].ctrl;
|
|||
|
|
|||
|
BD_table[4*n+!ep[n].rx_odd].ctrl &= ~(1 << 6);
|
|||
|
if ((config & 0x0080)) /* OWN already? */
|
|||
|
{
|
|||
|
/*
|
|||
|
* How to update BDT entry which is owned by USBFS seems to
|
|||
|
* be not clearly documented. It would be just OK to update
|
|||
|
* it as long as the endpoint is stalled (BDT entry is
|
|||
|
* actually not in use). We write 0 at first and then write
|
|||
|
* value with OWN, to avoid possible failure.
|
|||
|
*/
|
|||
|
BD_table[4*n+ep[n].rx_odd].ctrl = 0;
|
|||
|
BD_table[4*n+ep[n].rx_odd].ctrl = (config & ~(1 << 6));
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
config = BD_table[4*n+2+ep[n].tx_odd].ctrl;
|
|||
|
|
|||
|
BD_table[4*n+2+!ep[n].tx_odd].ctrl &= ~(1 << 6);
|
|||
|
if ((config & 0x0080)) /* OWN already? */
|
|||
|
{
|
|||
|
BD_table[4*n+2+ep[n].tx_odd].ctrl = 0;
|
|||
|
BD_table[4*n+2+ep[n].tx_odd].ctrl = (config & ~(1 << 6));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
kl27z_ep_clear_stall (n);
|
|||
|
}
|
|||
|
|
|||
|
#include "usb_lld_driver.h"
|
|||
|
|
|||
|
static int handle_transaction (struct usb_dev *dev, uint8_t stat);
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_stall (int n)
|
|||
|
{
|
|||
|
kl27z_ep_stall (n);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_ctrl_error (struct usb_dev *dev)
|
|||
|
{
|
|||
|
dev->state = STALLED;
|
|||
|
kl27z_ep_stall (ENDP0);
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
usb_lld_ctrl_ack (struct usb_dev *dev)
|
|||
|
{
|
|||
|
/* Zero length packet for ACK. */
|
|||
|
dev->state = WAIT_STATUS_IN;
|
|||
|
kl27z_prepare_ep0_in (&dev->dev_req, 0, DATA1);
|
|||
|
return USB_EVENT_OK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_init (struct usb_dev *dev, uint8_t feature)
|
|||
|
{
|
|||
|
usb_lld_set_configuration (dev, 0);
|
|||
|
dev->feature = feature;
|
|||
|
dev->state = WAIT_SETUP;
|
|||
|
|
|||
|
kl27z_set_daddr (0);
|
|||
|
kl27z_usb_init ();
|
|||
|
|
|||
|
/* Enable the endpoint 0. */
|
|||
|
USB_ENDPT[0].EP = 0x0d;
|
|||
|
|
|||
|
/* Clear Interrupt Status Register, and enable interrupt for USB */
|
|||
|
USB_CTRL1->ISTAT = 0xff; /* All clear */
|
|||
|
|
|||
|
USB_CTRL1->INTEN = USB_IS_STALL | USB_IS_TOKDNE
|
|||
|
| USB_IS_ERROR | USB_IS_USBRST;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#define USB_MAKE_EV(event) (event<<24)
|
|||
|
#define USB_MAKE_TXRX(ep_num,txrx,len) ((txrx? (1<<23):0)|(ep_num<<16)|len)
|
|||
|
|
|||
|
int
|
|||
|
usb_lld_event_handler (struct usb_dev *dev)
|
|||
|
{
|
|||
|
uint8_t istat_value = USB_CTRL1->ISTAT;
|
|||
|
uint8_t stat = USB_CTRL1->STAT;
|
|||
|
|
|||
|
if ((istat_value & USB_IS_USBRST))
|
|||
|
{
|
|||
|
USB_CTRL1->ISTAT = USB_IS_USBRST;
|
|||
|
return USB_MAKE_EV (USB_EVENT_DEVICE_RESET);
|
|||
|
}
|
|||
|
else if ((istat_value & USB_IS_TOKDNE))
|
|||
|
return handle_transaction (dev, stat);
|
|||
|
else if ((istat_value & USB_IS_ERROR))
|
|||
|
{ /* Clear Errors. */
|
|||
|
USB_CTRL1->ERRSTAT = USB_CTRL1->ERRSTAT;
|
|||
|
USB_CTRL1->ISTAT = USB_IS_ERROR;
|
|||
|
}
|
|||
|
else if ((istat_value & USB_IS_STALL))
|
|||
|
{
|
|||
|
/* Does STAT have ENDPOINT info in this case?: No, it doesn't. */
|
|||
|
|
|||
|
if (kl27z_ep_is_stall (0))
|
|||
|
{ /* It's endpoint 0, recover from erorr. */
|
|||
|
dev->state = WAIT_SETUP;
|
|||
|
kl27z_ep_clear_stall (0);
|
|||
|
kl27z_prepare_ep0_setup (dev);
|
|||
|
}
|
|||
|
|
|||
|
USB_CTRL1->ISTAT = USB_IS_STALL;
|
|||
|
}
|
|||
|
|
|||
|
return USB_EVENT_OK;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
handle_datastage_out (struct usb_dev *dev, uint8_t stat)
|
|||
|
{
|
|||
|
struct ctrl_data *data_p = &dev->ctrl_data;
|
|||
|
int odd = (stat >> 2)&1;
|
|||
|
int data01 = !((BD_table[odd].ctrl >> 6)&1);
|
|||
|
uint32_t len = (BD_table[odd].ctrl >> 16)&0x3ff;
|
|||
|
|
|||
|
data_p->len -= len;
|
|||
|
data_p->addr += len;
|
|||
|
|
|||
|
len = data_p->len;
|
|||
|
if (len > USB_MAX_PACKET_SIZE)
|
|||
|
len = USB_MAX_PACKET_SIZE;
|
|||
|
|
|||
|
if (data_p->len == 0)
|
|||
|
{
|
|||
|
/* No more data to receive, proceed to send acknowledge for IN. */
|
|||
|
dev->state = WAIT_STATUS_IN;
|
|||
|
kl27z_prepare_ep0_in (&dev->dev_req, 0, DATA1);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
dev->state = OUT_DATA;
|
|||
|
kl27z_prepare_ep0_out (data_p->addr, len, data01);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
handle_datastage_in (struct usb_dev *dev, uint8_t stat)
|
|||
|
{
|
|||
|
struct ctrl_data *data_p = &dev->ctrl_data;
|
|||
|
int odd = (stat >> 2)&1;
|
|||
|
int data01 = !((BD_table[2+odd].ctrl >> 6)&1);
|
|||
|
uint32_t len = USB_MAX_PACKET_SIZE;
|
|||
|
|
|||
|
if ((data_p->len == 0) && (dev->state == LAST_IN_DATA))
|
|||
|
{
|
|||
|
if (data_p->require_zlp)
|
|||
|
{
|
|||
|
data_p->require_zlp = 0;
|
|||
|
|
|||
|
/* No more data to send. Send empty packet */
|
|||
|
kl27z_prepare_ep0_in (&dev->dev_req, 0, data01);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* No more data to send, proceed to receive OUT acknowledge. */
|
|||
|
dev->state = WAIT_STATUS_OUT;
|
|||
|
kl27z_prepare_ep0_out (&dev->dev_req, 0, DATA1);
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dev->state = (data_p->len <= len) ? LAST_IN_DATA : IN_DATA;
|
|||
|
|
|||
|
if (len > data_p->len)
|
|||
|
len = data_p->len;
|
|||
|
|
|||
|
kl27z_prepare_ep0_in (data_p->addr, len, data01);
|
|||
|
data_p->len -= len;
|
|||
|
data_p->addr += len;
|
|||
|
}
|
|||
|
|
|||
|
typedef int (*HANDLER) (struct usb_dev *dev);
|
|||
|
|
|||
|
static int
|
|||
|
std_none (struct usb_dev *dev)
|
|||
|
{
|
|||
|
(void)dev;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static uint16_t status_info;
|
|||
|
|
|||
|
static int
|
|||
|
std_get_status (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (arg->value != 0 || arg->len != 2 || (arg->index >> 8) != 0
|
|||
|
|| USB_SETUP_SET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
if (rcp == DEVICE_RECIPIENT)
|
|||
|
{
|
|||
|
if (arg->index == 0)
|
|||
|
{
|
|||
|
/* Get Device Status */
|
|||
|
uint8_t feature = dev->feature;
|
|||
|
|
|||
|
/* Remote Wakeup enabled */
|
|||
|
if ((feature & (1 << 5)))
|
|||
|
status_info |= 2;
|
|||
|
else
|
|||
|
status_info &= ~2;
|
|||
|
|
|||
|
/* Bus-powered */
|
|||
|
if ((feature & (1 << 6)))
|
|||
|
status_info |= 1;
|
|||
|
else /* Self-powered */
|
|||
|
status_info &= ~1;
|
|||
|
|
|||
|
return usb_lld_ctrl_send (dev, &status_info, 2);
|
|||
|
}
|
|||
|
}
|
|||
|
else if (rcp == INTERFACE_RECIPIENT)
|
|||
|
{
|
|||
|
if (dev->configuration == 0)
|
|||
|
return -1;
|
|||
|
|
|||
|
return USB_EVENT_GET_STATUS_INTERFACE;
|
|||
|
}
|
|||
|
else if (rcp == ENDPOINT_RECIPIENT)
|
|||
|
{
|
|||
|
uint8_t n = (arg->index & 0x0f);
|
|||
|
|
|||
|
if ((arg->index & 0x70) || n == ENDP0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (kl27z_ep_is_disabled (n))
|
|||
|
return -1;
|
|||
|
|
|||
|
status_info = kl27z_ep_is_stall (n);
|
|||
|
return usb_lld_ctrl_send (dev, &status_info, 2);
|
|||
|
}
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_clear_feature (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (USB_SETUP_GET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
if (rcp == DEVICE_RECIPIENT)
|
|||
|
{
|
|||
|
if (arg->len != 0 || arg->index != 0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (arg->value == FEATURE_DEVICE_REMOTE_WAKEUP)
|
|||
|
{
|
|||
|
dev->feature &= ~(1 << 5);
|
|||
|
return USB_EVENT_CLEAR_FEATURE_DEVICE;
|
|||
|
}
|
|||
|
}
|
|||
|
else if (rcp == ENDPOINT_RECIPIENT)
|
|||
|
{
|
|||
|
uint8_t n = (arg->index & 0x0f);
|
|||
|
|
|||
|
if (dev->configuration == 0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (arg->len != 0 || (arg->index >> 8) != 0
|
|||
|
|| arg->value != FEATURE_ENDPOINT_HALT || n == ENDP0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (kl27z_ep_is_disabled (n))
|
|||
|
return -1;
|
|||
|
|
|||
|
kl27z_ep_clear_dtog ((arg->index & 0x80) == 0, n);
|
|||
|
|
|||
|
return USB_EVENT_CLEAR_FEATURE_ENDPOINT;
|
|||
|
}
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_set_feature (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (USB_SETUP_GET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
if (rcp == DEVICE_RECIPIENT)
|
|||
|
{
|
|||
|
if (arg->len != 0 || arg->index != 0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (arg->value == FEATURE_DEVICE_REMOTE_WAKEUP)
|
|||
|
{
|
|||
|
dev->feature |= 1 << 5;
|
|||
|
return USB_EVENT_SET_FEATURE_DEVICE;
|
|||
|
}
|
|||
|
}
|
|||
|
else if (rcp == ENDPOINT_RECIPIENT)
|
|||
|
{
|
|||
|
uint8_t n = (arg->index & 0x0f);
|
|||
|
|
|||
|
if (dev->configuration == 0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (arg->len != 0 || (arg->index >> 8) != 0
|
|||
|
|| arg->value != FEATURE_ENDPOINT_HALT || n == ENDP0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (kl27z_ep_is_disabled (n))
|
|||
|
return -1;
|
|||
|
|
|||
|
kl27z_ep_stall (n);
|
|||
|
|
|||
|
return USB_EVENT_SET_FEATURE_ENDPOINT;
|
|||
|
}
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_set_address (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (USB_SETUP_GET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
if (rcp == DEVICE_RECIPIENT && arg->len == 0 && arg->value <= 127
|
|||
|
&& arg->index == 0 && dev->configuration == 0)
|
|||
|
return usb_lld_ctrl_ack (dev);
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_get_descriptor (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
if (USB_SETUP_SET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
return USB_EVENT_GET_DESCRIPTOR;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_get_configuration (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (USB_SETUP_SET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
if (arg->value != 0 || arg->index != 0 || arg->len != 1)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (rcp == DEVICE_RECIPIENT)
|
|||
|
return usb_lld_ctrl_send (dev, &dev->configuration, 1);
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_set_configuration (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (USB_SETUP_GET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
if (rcp == DEVICE_RECIPIENT && arg->index == 0 && arg->len == 0)
|
|||
|
return USB_EVENT_SET_CONFIGURATION;
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_get_interface (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (USB_SETUP_SET (arg->type))
|
|||
|
return -1;
|
|||
|
|
|||
|
if (arg->value != 0 || (arg->index >> 8) != 0 || arg->len != 1)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (dev->configuration == 0)
|
|||
|
return -1;
|
|||
|
|
|||
|
if (rcp == INTERFACE_RECIPIENT)
|
|||
|
return USB_EVENT_GET_INTERFACE;
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
std_set_interface (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct device_req *arg = &dev->dev_req;
|
|||
|
uint8_t rcp = arg->type & RECIPIENT;
|
|||
|
|
|||
|
if (USB_SETUP_GET (arg->type) || rcp != INTERFACE_RECIPIENT
|
|||
|
|| arg->len != 0 || (arg->index >> 8) != 0
|
|||
|
|| (arg->value >> 8) != 0 || dev->configuration == 0)
|
|||
|
return -1;
|
|||
|
|
|||
|
return USB_EVENT_SET_INTERFACE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static int
|
|||
|
handle_setup0 (struct usb_dev *dev)
|
|||
|
{
|
|||
|
struct ctrl_data *data_p = &dev->ctrl_data;
|
|||
|
int r = -1;
|
|||
|
HANDLER handler;
|
|||
|
|
|||
|
data_p->addr = NULL;
|
|||
|
data_p->len = 0;
|
|||
|
data_p->require_zlp = 0;
|
|||
|
|
|||
|
if ((dev->dev_req.type & REQUEST_TYPE) == STANDARD_REQUEST)
|
|||
|
{
|
|||
|
switch (dev->dev_req.request)
|
|||
|
{
|
|||
|
case 0: handler = std_get_status; break;
|
|||
|
case 1: handler = std_clear_feature; break;
|
|||
|
case 3: handler = std_set_feature; break;
|
|||
|
case 5: handler = std_set_address; break;
|
|||
|
case 6: handler = std_get_descriptor; break;
|
|||
|
case 8: handler = std_get_configuration; break;
|
|||
|
case 9: handler = std_set_configuration; break;
|
|||
|
case 10: handler = std_get_interface; break;
|
|||
|
case 11: handler = std_set_interface; break;
|
|||
|
default: handler = std_none; break;
|
|||
|
}
|
|||
|
|
|||
|
if ((r = (*handler) (dev)) < 0)
|
|||
|
{
|
|||
|
usb_lld_ctrl_error (dev);
|
|||
|
return USB_EVENT_OK;
|
|||
|
}
|
|||
|
else
|
|||
|
return r;
|
|||
|
}
|
|||
|
else
|
|||
|
return USB_EVENT_CTRL_REQUEST;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
handle_in0 (struct usb_dev *dev, uint8_t stat)
|
|||
|
{
|
|||
|
int r = 0;
|
|||
|
|
|||
|
if (dev->state == IN_DATA || dev->state == LAST_IN_DATA)
|
|||
|
handle_datastage_in (dev, stat);
|
|||
|
else if (dev->state == WAIT_STATUS_IN)
|
|||
|
{ /* Control WRITE transfer done successfully. */
|
|||
|
uint16_t value = dev->dev_req.value;
|
|||
|
|
|||
|
if ((dev->dev_req.request == SET_ADDRESS) &&
|
|||
|
((dev->dev_req.type & (REQUEST_TYPE | RECIPIENT))
|
|||
|
== (STANDARD_REQUEST | DEVICE_RECIPIENT)))
|
|||
|
{
|
|||
|
kl27z_set_daddr (value);
|
|||
|
ep[0].rx_odd = 0;
|
|||
|
r = USB_EVENT_DEVICE_ADDRESSED;
|
|||
|
}
|
|||
|
else
|
|||
|
r = USB_EVENT_CTRL_WRITE_FINISH;
|
|||
|
dev->state = WAIT_SETUP;
|
|||
|
kl27z_prepare_ep0_setup (dev);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
dev->state = STALLED;
|
|||
|
kl27z_ep_stall (ENDP0);
|
|||
|
}
|
|||
|
|
|||
|
return r;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
handle_out0 (struct usb_dev *dev, uint8_t stat)
|
|||
|
{
|
|||
|
if (dev->state == OUT_DATA)
|
|||
|
/* It's normal control WRITE transfer. */
|
|||
|
handle_datastage_out (dev, stat);
|
|||
|
else if (dev->state == WAIT_STATUS_OUT)
|
|||
|
{ /* Control READ transfer done successfully. */
|
|||
|
dev->state = WAIT_SETUP;
|
|||
|
kl27z_prepare_ep0_setup (dev);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/*
|
|||
|
* dev->state == IN_DATA || dev->state == LAST_IN_DATA
|
|||
|
* (Host aborts the transfer before finish)
|
|||
|
* Or else, unexpected state.
|
|||
|
* STALL the endpoint, until we receive the next SETUP token.
|
|||
|
*/
|
|||
|
dev->state = STALLED;
|
|||
|
kl27z_ep_stall (ENDP0);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#define USB_TOKEN_ACK 0x02
|
|||
|
#define USB_TOKEN_IN 0x09
|
|||
|
#define USB_TOKEN_SETUP 0x0d
|
|||
|
|
|||
|
static int
|
|||
|
handle_transaction (struct usb_dev *dev, uint8_t stat)
|
|||
|
{
|
|||
|
int odd = (stat >> 2)&1;
|
|||
|
uint8_t ep_num = (stat >> 4);
|
|||
|
int r;
|
|||
|
|
|||
|
if (ep_num == 0)
|
|||
|
{
|
|||
|
if ((stat & 0x08) == 0)
|
|||
|
{
|
|||
|
ep[0].rx_odd ^= 1;
|
|||
|
if (TOK_PID (BD_table[odd].ctrl) == USB_TOKEN_SETUP)
|
|||
|
{
|
|||
|
r = handle_setup0 (dev);
|
|||
|
USB_CTRL1->ISTAT = USB_IS_TOKDNE;
|
|||
|
USB_CTRL1->CTL = 0x01; /* Clear TXSUSPENDTOKENBUSY. */
|
|||
|
return USB_MAKE_EV (r);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
handle_out0 (dev, stat);
|
|||
|
USB_CTRL1->ISTAT = USB_IS_TOKDNE;
|
|||
|
return USB_EVENT_OK;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ep[0].tx_odd ^= 1;
|
|||
|
r = handle_in0 (dev, stat);
|
|||
|
USB_CTRL1->ISTAT = USB_IS_TOKDNE;
|
|||
|
return USB_MAKE_EV (r);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
uint16_t len;
|
|||
|
|
|||
|
if ((stat & 0x08) == 0)
|
|||
|
{
|
|||
|
len = (BD_table[4*ep_num+odd].ctrl >> 16)&0x3ff;
|
|||
|
ep[ep_num].rx_odd ^= 1;
|
|||
|
USB_CTRL1->ISTAT = USB_IS_TOKDNE;
|
|||
|
return USB_MAKE_TXRX (ep_num, 0, len);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/*
|
|||
|
* IN transaction is usually in a sequence like:
|
|||
|
*
|
|||
|
* -----time------>
|
|||
|
* host: IN ACK
|
|||
|
* device: DATA0/1
|
|||
|
*
|
|||
|
* Although it is not described in the specification (it's
|
|||
|
* ambiguous), it is actually possible for some host
|
|||
|
* implementation to send back a NAK on erroneous case like
|
|||
|
* a device sent oversized data.
|
|||
|
*
|
|||
|
* -----time------>
|
|||
|
* host: IN NAK
|
|||
|
* device: DATA0/1
|
|||
|
*
|
|||
|
* We do our best to distinguish successful tx and tx with
|
|||
|
* failure.
|
|||
|
*
|
|||
|
*/
|
|||
|
uint32_t dmaerr = (USB_CTRL1->ERRSTAT & (1 << 5));
|
|||
|
int success = (dmaerr == 0);
|
|||
|
|
|||
|
len = (BD_table[4*ep_num+2+odd].ctrl >> 16)&0x3ff;
|
|||
|
if (!success)
|
|||
|
USB_CTRL1->ERRSTAT = dmaerr; /* Clear error. */
|
|||
|
|
|||
|
ep[ep_num].tx_odd ^= 1;
|
|||
|
USB_CTRL1->ISTAT = USB_IS_TOKDNE;
|
|||
|
return USB_MAKE_TXRX (ep_num, 1, len);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_reset (struct usb_dev *dev, uint8_t feature)
|
|||
|
{
|
|||
|
usb_lld_set_configuration (dev, 0);
|
|||
|
dev->feature = feature;
|
|||
|
dev->state = WAIT_SETUP;
|
|||
|
|
|||
|
/* Reset USB */
|
|||
|
USB_CTRL2->USBTRC0 = 0xc0;
|
|||
|
|
|||
|
USB_CTRL1->CTL = 0x00; /* Disable USB FS communication module */
|
|||
|
|
|||
|
kl27z_set_daddr (0);
|
|||
|
kl27z_usb_init ();
|
|||
|
|
|||
|
/* Clear Interrupt Status Register, and enable interrupt for USB */
|
|||
|
USB_CTRL1->ISTAT = 0xff; /* All clear */
|
|||
|
|
|||
|
USB_CTRL1->INTEN = USB_IS_STALL | USB_IS_TOKDNE
|
|||
|
| USB_IS_ERROR | USB_IS_USBRST;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_setup_endp (struct usb_dev *dev, int n, int rx_en, int tx_en)
|
|||
|
{
|
|||
|
if (n == 0)
|
|||
|
{
|
|||
|
/* Enable the endpoint 0. */
|
|||
|
USB_ENDPT[0].EP = 0x0d;
|
|||
|
kl27z_prepare_ep0_setup (dev);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Enable the endpoint. */
|
|||
|
USB_ENDPT[n].EP = (rx_en << 3)|(tx_en << 2)|0x11;
|
|||
|
|
|||
|
/* Configure BDT entry so that it starts with DATA0. */
|
|||
|
|
|||
|
/* RX */
|
|||
|
BD_table[4*n+ep[n].rx_odd].ctrl = 0x0000;
|
|||
|
BD_table[4*n+ep[n].rx_odd].buf = NULL;
|
|||
|
BD_table[4*n+!ep[n].rx_odd].ctrl = 0x0040;
|
|||
|
BD_table[4*n+!ep[n].rx_odd].buf = NULL;
|
|||
|
|
|||
|
/* TX */
|
|||
|
BD_table[4*n+2+ep[n].tx_odd].ctrl = 0x0000;
|
|||
|
BD_table[4*n+2+ep[n].tx_odd].buf = NULL;
|
|||
|
BD_table[4*n+2+!ep[n].tx_odd].ctrl = 0x0040;
|
|||
|
BD_table[4*n+2+!ep[n].tx_odd].buf = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_set_configuration (struct usb_dev *dev, uint8_t config)
|
|||
|
{
|
|||
|
dev->configuration = config;
|
|||
|
}
|
|||
|
|
|||
|
uint8_t
|
|||
|
usb_lld_current_configuration (struct usb_dev *dev)
|
|||
|
{
|
|||
|
return dev->configuration;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
usb_lld_ctrl_recv (struct usb_dev *dev, void *p, size_t len)
|
|||
|
{
|
|||
|
struct ctrl_data *data_p = &dev->ctrl_data;
|
|||
|
data_p->addr = (uint8_t *)p;
|
|||
|
data_p->len = len;
|
|||
|
if (len > USB_MAX_PACKET_SIZE)
|
|||
|
len = USB_MAX_PACKET_SIZE;
|
|||
|
|
|||
|
kl27z_prepare_ep0_out (p, len, DATA1);
|
|||
|
dev->state = OUT_DATA;
|
|||
|
return USB_EVENT_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* BUF: Pointer to data memory. Data memory should not be allocated
|
|||
|
* on stack when BUFLEN > USB_MAX_PACKET_SIZE.
|
|||
|
*
|
|||
|
* BUFLEN: size of the data.
|
|||
|
*/
|
|||
|
int
|
|||
|
usb_lld_ctrl_send (struct usb_dev *dev, const void *buf, size_t buflen)
|
|||
|
{
|
|||
|
struct ctrl_data *data_p = &dev->ctrl_data;
|
|||
|
uint32_t len_asked = dev->dev_req.len;
|
|||
|
uint32_t len;
|
|||
|
|
|||
|
data_p->addr = (void *)buf;
|
|||
|
data_p->len = buflen;
|
|||
|
|
|||
|
/* Restrict the data length to be the one host asks for */
|
|||
|
if (data_p->len >= len_asked)
|
|||
|
data_p->len = len_asked;
|
|||
|
/* ZLP is only required when host doesn't expect the end of packets. */
|
|||
|
else if (data_p->len != 0 && (data_p->len % USB_MAX_PACKET_SIZE) == 0)
|
|||
|
data_p->require_zlp = 1;
|
|||
|
|
|||
|
if (data_p->len < USB_MAX_PACKET_SIZE)
|
|||
|
{
|
|||
|
len = data_p->len;
|
|||
|
dev->state = LAST_IN_DATA;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
len = USB_MAX_PACKET_SIZE;
|
|||
|
dev->state = IN_DATA;
|
|||
|
}
|
|||
|
|
|||
|
kl27z_prepare_ep0_in (data_p->addr, len, DATA1);
|
|||
|
|
|||
|
data_p->len -= len;
|
|||
|
data_p->addr += len;
|
|||
|
|
|||
|
return USB_EVENT_OK;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_rx_enable_buf (int n, void *buf, size_t len)
|
|||
|
{
|
|||
|
int data01 = !((BD_table[4*n+!ep[n].rx_odd].ctrl >> 6)&1);
|
|||
|
|
|||
|
BD_table[4*n+ep[n].rx_odd].ctrl = (len << 16) | 0x0088 | (data01 << 6);
|
|||
|
BD_table[4*n+ep[n].rx_odd].buf = buf;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
usb_lld_tx_enable_buf (int n, const void *buf, size_t len)
|
|||
|
{
|
|||
|
int data01 = !((BD_table[4*n+2+!ep[n].tx_odd].ctrl >> 6)&1);
|
|||
|
|
|||
|
BD_table[4*n+2+ep[n].tx_odd].ctrl = (len << 16) | 0x0088 | (data01 << 6);
|
|||
|
BD_table[4*n+2+ep[n].tx_odd].buf = (void *)buf;
|
|||
|
}
|