blob: 5ca367e9f626a75c0b67ab9bc096310e877d8736 [file] [log] [blame]
/**
* \file
*
* \brief USB Device Communication Device Class (CDC) interface.
*
* Copyright (C) 2009 Atmel Corporation. All rights reserved.
*
* \page License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel AVR product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "conf_usb.h"
#include "usb_protocol.h"
#include "usb_protocol_cdc.h"
#include "udd.h"
#include "udc.h"
#include "udi_cdc.h"
/**
* \addtogroup udi_cdc_group
*
* @{
*/
/**
* \name Interface for UDC
*/
//@{
bool udi_cdc_comm_enable(void);
void udi_cdc_comm_disable(void);
bool udi_cdc_comm_setup(void);
bool udi_cdc_data_enable(void);
void udi_cdc_data_disable(void);
bool udi_cdc_data_setup(void);
uint8_t udi_cdc_getsetting(void);
//! Global structure which contains standard UDI API for UDC
UDC_DESC_STORAGE udi_api_t udi_api_cdc_comm = {
.enable = udi_cdc_comm_enable,
.disable = udi_cdc_comm_disable,
.setup = udi_cdc_comm_setup,
.getsetting = udi_cdc_getsetting,
};
UDC_DESC_STORAGE udi_api_t udi_api_cdc_data = {
.enable = udi_cdc_data_enable,
.disable = udi_cdc_data_disable,
.setup = udi_cdc_data_setup,
.getsetting = udi_cdc_getsetting,
};
//@}
//! Information about configuration of communication line
usb_cdc_line_coding_t udi_cdc_line_coding;
bool udi_cdc_serial_state_msg_ongoing;
static le16_t udi_cdc_state;
static usb_cdc_notify_serial_state_t uid_cdc_state_msg = {
.header.bmRequestType =
USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS |
USB_REQ_RECIP_INTERFACE,
.header.bNotification = USB_REQ_CDC_NOTIFY_SERIAL_STATE,
.header.wValue = LE16(0),
.header.wIndex = LE16(UDI_CDC_COMM_IFACE_NUMBER),
.header.wLength = LE16(2),
};
/**
* \name Variables to manage RX/TX transfer requests
* Two buffers for each sense are used to optimize the speed.
*/
//@{
//! Buffer to receive data
COMPILER_WORD_ALIGNED static uint8_t udi_cdc_rx_buf[2][UDI_CDC_DATA_EPS_SIZE];
//! Data available in RX buffers
static volatile uint8_t udi_cdc_rx_buf_nb[2];
//! Read position in current RX buffer
static uint8_t udi_cdc_rx_pos;
//! Give the current RX buffer used (rx0 if 0, rx1 if 1)
static uint8_t udi_cdc_rx_buf_sel;
//! Give current RX buffer used by the transfer
//! (rx0 if 0, rx1 if 1, no trans if UDI_CDC_TRANS_HALTED)
static volatile uint8_t udi_cdc_rx_trans_sel;
//! Buffer to send data
COMPILER_WORD_ALIGNED static uint8_t udi_cdc_tx_buf[2][UDI_CDC_DATA_EPS_SIZE];
//! Data available in TX buffers
static uint8_t udi_cdc_tx_buf_nb[2];
//! Give current TX buffer used (tx0 if 0, tx1 if 1)
static volatile uint8_t udi_cdc_tx_buf_sel;
//! Give current TX buffer used by the transfer
//! (tx0 if 0, tx1 if 1, no trans if UDI_CDC_TRANS_HALTED)
static volatile uint8_t udi_cdc_tx_trans_sel;
//! To enable/disable the automatic send of next TX buffer
//! after current sent finish
static volatile bool udi_cdc_tx_b_trans_reload;
//! Define a transfer halted
#define UDI_CDC_TRANS_HALTED 2
//@}
/**
* \name Internal routines
*/
//@{
/**
* \name Routines to control serial line
*/
//@{
/**
* \brief Sends lien coding to application
*
* Called after SETUP request when line coding data is received.
*/
void udi_cdc_line_coding_received(void);
/**
* \brief Records new state and eventually notify the USB host
*
* \param b_set State is enabled if true, else disabled
* \param bit_mask Field to process (see CDC_SERIAL_STATE_ defines)
*/
static void udi_cdc_ctrl_state_change(bool b_set, le16_t bit_mask);
/**
* \brief Ack sent of serial state message
* Callback called after serial state message sent
*
* \param status UDD_EP_TRANSFER_OK, if transfer finished
* \param status UDD_EP_TRANSFER_ABORT, if transfer aborted
* \param n number of data transfered
*/
void udi_cdc_serial_state_msg_sent(udd_ep_status_t status, iram_size_t n);
//@}
/**
* \name Routines to process data transfer
*/
//@{
/**
* \brief Enable the reception of data from the USB host
*
* The value udi_cdc_rx_trans_sel indicate the RX buffer to fill.
*
* \return \c 1 if function was successfully done, otherwise \c 0.
*/
static bool udi_cdc_rx_start(void);
/**
* \brief Update RX buffer management with a new data
* Callback called after data reception on USB line
*
* \param status UDD_EP_TRANSFER_OK, if transfer finish
* \param status UDD_EP_TRANSFER_ABORT, if transfer aborted
* \param n number of data received
*/
void udi_cdc_data_recevied(udd_ep_status_t status, iram_size_t n);
/**
* \brief Sends a TX buffer to the USB host
*
* The value udi_cdc_tx_trans_sel indicates the TX buffer's content to send.
*
* \return \c 1 if function was successfully done, otherwise \c 0.
*/
static bool udi_cdc_tx_start(void);
/**
* \brief Valid TX buffer sent
* Callback called after data transfer on USB line
*
* \param status UDD_EP_TRANSFER_OK, if transfer finished
* \param status UDD_EP_TRANSFER_ABORT, if transfer aborted
* \param n number of data transfered
*/
void udi_cdc_data_sent(udd_ep_status_t status, iram_size_t n);
//@}
//@}
bool udi_cdc_comm_enable(void)
{
// Initialize control signal management
udi_cdc_state = CPU_TO_LE16(0);
uid_cdc_state_msg.value = CPU_TO_LE16(0);
udi_cdc_line_coding.dwDTERate = CPU_TO_LE32(UDI_CDC_DEFAULT_RATE);
udi_cdc_line_coding.bCharFormat = UDI_CDC_DEFAULT_STOPBITS;
udi_cdc_line_coding.bParityType = UDI_CDC_DEFAULT_PARITY;
udi_cdc_line_coding.bDataBits = UDI_CDC_DEFAULT_DATABITS;
UDI_CDC_SET_CODING_EXT((&udi_cdc_line_coding));
// Call application callback
// to initialize memories or indicate that interface is enabled
return UDI_CDC_ENABLE_EXT();
}
bool udi_cdc_data_enable(void)
{
// Initialize control signal management
udi_cdc_state = CPU_TO_LE16(0);
uid_cdc_state_msg.value = CPU_TO_LE16(0);
// Initialize TX management
udi_cdc_tx_buf_nb[0] = 0;
udi_cdc_tx_buf_nb[1] = 0;
udi_cdc_tx_buf_sel = 0;
udi_cdc_tx_trans_sel = UDI_CDC_TRANS_HALTED;
// Initialize RX management
udi_cdc_rx_buf_nb[0] = 0;
udi_cdc_rx_buf_nb[1] = 0;
udi_cdc_rx_pos = 0;
udi_cdc_rx_buf_sel = 0;
udi_cdc_rx_trans_sel = 0;
return udi_cdc_rx_start();
}
void udi_cdc_comm_disable(void)
{
UDI_CDC_DISABLE_EXT();
}
void udi_cdc_data_disable(void)
{
}
bool udi_cdc_comm_setup(void)
{
if (Udd_setup_is_in()) {
// GET Interface Requests
if (Udd_setup_type() == USB_REQ_TYPE_CLASS) {
// Requests Class Interface Get
switch (udd_g_ctrlreq.req.bRequest) {
case USB_REQ_CDC_GET_LINE_CODING:
// Get configuration of CDC line
if (sizeof(usb_cdc_line_coding_t) !=
udd_g_ctrlreq.req.wLength)
return false; // Error for USB host
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_cdc_line_coding;
udd_g_ctrlreq.payload_size =
sizeof(udi_cdc_line_coding);
return true;
}
}
}
if (Udd_setup_is_out()) {
// SET Interface Requests
if (Udd_setup_type() == USB_REQ_TYPE_CLASS) {
// Requests Class Interface Set
switch (udd_g_ctrlreq.req.bRequest) {
case USB_REQ_CDC_SET_LINE_CODING:
// Change configuration of CDC line
if (sizeof(usb_cdc_line_coding_t) !=
udd_g_ctrlreq.req.wLength)
return false; // Error for USB host
udd_g_ctrlreq.callback =
udi_cdc_line_coding_received;
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_cdc_line_coding;
udd_g_ctrlreq.payload_size =
sizeof(udi_cdc_line_coding);
return true;
case USB_REQ_CDC_SET_CONTROL_LINE_STATE:
// According cdc spec 1.1 chapter 6.2.14
UDI_CDC_SET_DTR_EXT(
(0 != (udd_g_ctrlreq.req.wValue & CDC_CTRL_SIGNAL_DTE_PRESENT)));
UDI_CDC_SET_RTS_EXT(
(0 != (udd_g_ctrlreq.req.wValue & CDC_CTRL_SIGNAL_ACTIVATE_CARRIER)));
return true;
}
}
}
return false; // request Not supported
}
bool udi_cdc_data_setup(void)
{
return false; // request Not supported
}
uint8_t udi_cdc_getsetting(void)
{
return 0; // CDC don't have multiple alternate setting
}
//-------------------------------------------------
//------- Internal routines to control serial line
void udi_cdc_line_coding_received(void)
{
// Send line coding to component associated to CDC
UDI_CDC_SET_CODING_EXT((&udi_cdc_line_coding));
}
static void udi_cdc_ctrl_state_change(bool b_set, le16_t bit_mask)
{
// Update state
if (b_set) {
udi_cdc_state |= bit_mask;
} else {
udi_cdc_state &= ~bit_mask;
}
// Send it if possible and state changed
if ((!udi_cdc_serial_state_msg_ongoing)
&& (udi_cdc_state != uid_cdc_state_msg.value)) {
// Fill notification message
uid_cdc_state_msg.value = udi_cdc_state;
// Send notification message
udi_cdc_serial_state_msg_ongoing =
udd_ep_run(UDI_CDC_COMM_EP,
false,
(uint8_t *) & uid_cdc_state_msg,
sizeof(uid_cdc_state_msg),
udi_cdc_serial_state_msg_sent);
}
}
void udi_cdc_serial_state_msg_sent(udd_ep_status_t status, iram_size_t n)
{
// For the irregular signals like break, the incoming ring signal,
// or the overrun error state, this will reset their values to zero
// and again will not send another notification until their state changes.
uid_cdc_state_msg.value &= ~(CDC_SERIAL_STATE_BREAK |
CDC_SERIAL_STATE_RING |
CDC_SERIAL_STATE_FRAMING |
CDC_SERIAL_STATE_PARITY | CDC_SERIAL_STATE_OVERRUN);
udi_cdc_serial_state_msg_ongoing = false;
}
//-------------------------------------------------
//------- Internal routines to process data transfer
static bool udi_cdc_rx_start(void)
{
if (!udd_ep_run(UDI_CDC_DATA_EP_OUT,
true,
udi_cdc_rx_buf[udi_cdc_rx_trans_sel],
UDI_CDC_DATA_EPS_SIZE,
udi_cdc_data_recevied)) {
udi_cdc_rx_trans_sel = UDI_CDC_TRANS_HALTED;
return false;
}
return true;
}
void udi_cdc_data_recevied(udd_ep_status_t status, iram_size_t n)
{
if (UDD_EP_TRANSFER_OK != status) {
// Abort reception
return;
}
if (0 == n) {
// Empty packet then restart reception on same buffer
udi_cdc_rx_start();
return;
}
// Update the buffer's number
udi_cdc_rx_buf_nb[udi_cdc_rx_trans_sel] = n;
// Go to next buffer
udi_cdc_rx_trans_sel = (udi_cdc_rx_trans_sel + 1) % 2;
// Check if next buffer is free
if (udi_cdc_rx_trans_sel == udi_cdc_rx_buf_sel) {
// No buffer free; stop reception
udi_cdc_rx_trans_sel = UDI_CDC_TRANS_HALTED;
} else {
// Restart reception on next buffer
udi_cdc_rx_start();
}
}
static bool udi_cdc_tx_start(void)
{
uint8_t tx_trans_sel = udi_cdc_tx_trans_sel;
// Switch current buffer
udi_cdc_tx_buf_sel = (udi_cdc_tx_trans_sel + 1) % 2;
udi_cdc_tx_buf_nb[udi_cdc_tx_buf_sel] = 0;
// Send the other buffer
if (!udd_ep_run(UDI_CDC_DATA_EP_IN,
true,
udi_cdc_tx_buf[tx_trans_sel],
udi_cdc_tx_buf_nb[tx_trans_sel],
udi_cdc_data_sent)) {
udi_cdc_tx_trans_sel = UDI_CDC_TRANS_HALTED;
return false;
}
return true;
}
void udi_cdc_data_sent(udd_ep_status_t status, iram_size_t n)
{
status = status; n = n;
if (!udi_cdc_tx_b_trans_reload) {
// Automatic reload stopped (case of putc on going)
udi_cdc_tx_trans_sel = UDI_CDC_TRANS_HALTED;
return;
}
// Go to next buffer
udi_cdc_tx_trans_sel = (udi_cdc_tx_trans_sel + 1) % 2;
// Check, if next buffer is not empty
if (0 == udi_cdc_tx_buf_nb[udi_cdc_tx_trans_sel]) {
// No data available; stop sending data
udi_cdc_tx_trans_sel = UDI_CDC_TRANS_HALTED;
} else {
// Send next buffer
udi_cdc_tx_start();
}
}
//---------------------------------------------
//------- Application interface
void udi_cdc_ctrl_signal_dcd(bool b_set)
{
udi_cdc_ctrl_state_change(b_set, CDC_SERIAL_STATE_DCD);
}
void udi_cdc_ctrl_signal_dsr(bool b_set)
{
udi_cdc_ctrl_state_change(b_set, CDC_SERIAL_STATE_DSR);
}
void udi_cdc_signal_framing_error(void)
{
udi_cdc_ctrl_state_change(true, CDC_SERIAL_STATE_FRAMING);
}
void udi_cdc_signal_parity_error(void)
{
udi_cdc_ctrl_state_change(true, CDC_SERIAL_STATE_PARITY);
}
void udi_cdc_signal_overrun(void)
{
udi_cdc_ctrl_state_change(true, CDC_SERIAL_STATE_OVERRUN);
}
bool udi_cdc_is_rx_ready(void)
{
return (0 != udi_cdc_rx_buf_nb[udi_cdc_rx_buf_sel]);
}
int udi_cdc_getc(void)
{
int rx_data = 0;
bool b_databit_9;
b_databit_9 = (9 == udi_cdc_line_coding.bDataBits);
udi_cdc_getc_process_one_byte:
// Waiting for data
while (!udi_cdc_is_rx_ready()) {
if (UDI_CDC_TRANS_HALTED == udi_cdc_rx_trans_sel)
return 0; // Error system
}
// Read data
rx_data |= udi_cdc_rx_buf[udi_cdc_rx_buf_sel][udi_cdc_rx_pos];
udi_cdc_rx_pos++;
// Check if buffer empty
if (udi_cdc_rx_pos == udi_cdc_rx_buf_nb[udi_cdc_rx_buf_sel]) {
// Initialize again current buffer
udi_cdc_rx_buf_nb[udi_cdc_rx_buf_sel] = 0;
// Switch to next buffer
udi_cdc_rx_pos = 0;
udi_cdc_rx_buf_sel = (udi_cdc_rx_buf_sel + 1) % 2;
// Check if reception is halted
if (UDI_CDC_TRANS_HALTED == udi_cdc_rx_trans_sel) {
// Restart RX reception
udi_cdc_rx_trans_sel = (udi_cdc_rx_buf_sel + 1) % 2;
udi_cdc_rx_start();
}
}
if (b_databit_9) {
// Receive MSB
b_databit_9 = false;
rx_data = rx_data << 8;
goto udi_cdc_getc_process_one_byte;
}
return rx_data;
}
bool udi_cdc_is_tx_ready(void)
{
return (UDI_CDC_DATA_EPS_SIZE != udi_cdc_tx_buf_nb[udi_cdc_tx_buf_sel]);
}
int udi_cdc_putc(int value)
{
uint8_t buf_sel;
bool b_databit_9;
b_databit_9 = (9 == udi_cdc_line_coding.bDataBits);
udi_cdc_putc_process_one_byte:
while (UDI_CDC_DATA_EPS_SIZE == udi_cdc_tx_buf_nb[udi_cdc_tx_buf_sel]);
// Stop automatic transfer reload
udi_cdc_tx_b_trans_reload = false;
// Store volatile to no volatile to save code
buf_sel = udi_cdc_tx_buf_sel;
udi_cdc_tx_buf[buf_sel][udi_cdc_tx_buf_nb[buf_sel]] = value & 0xFF;
udi_cdc_tx_buf_nb[buf_sel]++;
// Restart automatic transfer reload
udi_cdc_tx_b_trans_reload = true;
if (UDI_CDC_TRANS_HALTED == udi_cdc_tx_trans_sel) {
// Transfer is halted --> restart it with current buffer
udi_cdc_tx_trans_sel = buf_sel;
if (!udi_cdc_tx_start())
return false;
}
if (b_databit_9) {
// Send MSB
b_databit_9 = false;
value = value >> 8;
goto udi_cdc_putc_process_one_byte;
}
return true;
}
//@}