mspdebug/transport/ti3410.c

629 lines
14 KiB
C

/* MSPDebug - debugging tool for the eZ430
* Copyright (C) 2009, 2010 Daniel Beer
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if !defined(__Windows__) || defined(__MINGW32__)
#include <usb.h>
#else
#include <lusb0_usb.h>
#endif
#include <time.h>
#include "ti3410.h"
#include "util.h"
#include "usbutil.h"
#include "output.h"
#include "ihex.h"
/************************************************************************
* Definitions taken from drivers/usb/serial/ti_usb_3410_5052.h in the
* Linux kernel (GPLv2+).
*/
/* Configuration ids */
#define TI_BOOT_CONFIG 1
#define TI_ACTIVE_CONFIG 2
/* Pipe transfer mode and timeout */
#define TI_PIPE_MODE_CONTINOUS 0x01
#define TI_PIPE_MODE_MASK 0x03
#define TI_PIPE_TIMEOUT_MASK 0x7C
#define TI_PIPE_TIMEOUT_ENABLE 0x80
/* Module identifiers */
#define TI_I2C_PORT 0x01
#define TI_IEEE1284_PORT 0x02
#define TI_UART1_PORT 0x03
#define TI_UART2_PORT 0x04
#define TI_RAM_PORT 0x05
/* Purge modes */
#define TI_PURGE_OUTPUT 0x00
#define TI_PURGE_INPUT 0x80
/* Commands */
#define TI_GET_VERSION 0x01
#define TI_GET_PORT_STATUS 0x02
#define TI_GET_PORT_DEV_INFO 0x03
#define TI_GET_CONFIG 0x04
#define TI_SET_CONFIG 0x05
#define TI_OPEN_PORT 0x06
#define TI_CLOSE_PORT 0x07
#define TI_START_PORT 0x08
#define TI_STOP_PORT 0x09
#define TI_TEST_PORT 0x0A
#define TI_PURGE_PORT 0x0B
#define TI_RESET_EXT_DEVICE 0x0C
#define TI_WRITE_DATA 0x80
#define TI_READ_DATA 0x81
#define TI_REQ_TYPE_CLASS 0x82
/* Bits per character */
#define TI_UART_5_DATA_BITS 0x00
#define TI_UART_6_DATA_BITS 0x01
#define TI_UART_7_DATA_BITS 0x02
#define TI_UART_8_DATA_BITS 0x03
/* Parity */
#define TI_UART_NO_PARITY 0x00
#define TI_UART_ODD_PARITY 0x01
#define TI_UART_EVEN_PARITY 0x02
#define TI_UART_MARK_PARITY 0x03
#define TI_UART_SPACE_PARITY 0x04
/* Stop bits */
#define TI_UART_1_STOP_BITS 0x00
#define TI_UART_1_5_STOP_BITS 0x01
#define TI_UART_2_STOP_BITS 0x02
/* Modem control */
#define TI_MCR_LOOP 0x04
#define TI_MCR_DTR 0x10
#define TI_MCR_RTS 0x20
/* Read/Write data */
#define TI_RW_DATA_ADDR_SFR 0x10
#define TI_RW_DATA_ADDR_IDATA 0x20
#define TI_RW_DATA_ADDR_XDATA 0x30
#define TI_RW_DATA_ADDR_CODE 0x40
#define TI_RW_DATA_ADDR_GPIO 0x50
#define TI_RW_DATA_ADDR_I2C 0x60
#define TI_RW_DATA_ADDR_FLASH 0x70
#define TI_RW_DATA_ADDR_DSP 0x80
#define TI_RW_DATA_UNSPECIFIED 0x00
#define TI_RW_DATA_BYTE 0x01
#define TI_RW_DATA_WORD 0x02
#define TI_RW_DATA_DOUBLE_WORD 0x04
#define TI_TRANSFER_TIMEOUT 2
#define TI_FIRMWARE_BUF_SIZE 16284
#define TI_DOWNLOAD_MAX_PACKET_SIZE 64
/************************************************************************/
struct ti3410_transport {
struct transport base;
struct usb_dev_handle *hnd;
};
#define USB_FET_VENDOR 0x0451
#define USB_FET_PRODUCT 0xf430
#define USB_FET_INTERFACE 0
#define USB_FET_IN_EP 0x81
#define USB_FET_OUT_EP 0x01
#define USB_FET_INT_EP 0x83
#define USB_FDL_INTERFACE 0
#define USB_FDL_OUT_EP 0x01
#define TIMEOUT 1000
#define READ_TIMEOUT 5000
static int open_device(struct ti3410_transport *tr,
struct usb_device *dev)
{
struct usb_dev_handle *hnd;
hnd = usb_open(dev);
if (!hnd) {
pr_error("ti3410: failed to open USB device");
return -1;
}
#if defined(__linux__)
if (usb_detach_kernel_driver_np(hnd, USB_FET_INTERFACE) < 0)
pr_error("ti3410: warning: can't "
"detach kernel driver");
#endif
/* This device has two configurations -- we need the one which
* has two bulk endpoints and a control.
*/
if (dev->config->bConfigurationValue == TI_BOOT_CONFIG) {
printc_dbg("TI3410 device is in boot config, "
"setting active\n");
if (usb_set_configuration(hnd, TI_ACTIVE_CONFIG) < 0) {
pr_error("ti3410: failed to set active config");
usb_close(hnd);
return -1;
}
}
if (usb_claim_interface(hnd, USB_FET_INTERFACE) < 0) {
pr_error("ti3410: can't claim interface");
usb_close(hnd);
return -1;
}
tr->hnd = hnd;
return 0;
}
static int set_termios(struct ti3410_transport *tr)
{
static const uint8_t tios_data[10] = {
0x00, 0x02, /* 460800 bps */
0x60, 0x00, /* flags = ENABLE_MS_INTS | AUTO_START_DMA */
TI_UART_8_DATA_BITS,
TI_UART_NO_PARITY,
TI_UART_1_STOP_BITS,
0x00, /* cXon */
0x00, /* cXoff */
0x00 /* UART mode = RS232 */
};
if (usb_control_msg(tr->hnd,
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TI_SET_CONFIG,
0,
TI_UART1_PORT, (char *)tios_data, sizeof(tios_data), TIMEOUT) < 0) {
pr_error("ti3410: TI_SET_CONFIG failed");
return -1;
}
return 0;
}
static int set_mcr(struct ti3410_transport *tr)
{
static const uint8_t wb_data[9] = {
TI_RW_DATA_ADDR_XDATA,
TI_RW_DATA_BYTE,
1, /* byte count */
0x00, 0x00, 0xff, 0xa4, /* base address */
TI_MCR_LOOP | TI_MCR_RTS | TI_MCR_DTR, /* mask */
TI_MCR_RTS | TI_MCR_DTR /* data */
};
if (usb_control_msg(tr->hnd,
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TI_WRITE_DATA,
0,
TI_RAM_PORT, (char *)wb_data, sizeof(wb_data), TIMEOUT) < 0) {
pr_error("ti3410: TI_SET_CONFIG failed");
return -1;
}
return 0;
}
static int do_open_start(struct ti3410_transport *tr)
{
if (set_termios(tr) < 0)
return -1;
if (set_mcr(tr) < 0)
return -1;
if (usb_control_msg(tr->hnd,
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TI_OPEN_PORT,
TI_PIPE_MODE_CONTINOUS | TI_PIPE_TIMEOUT_ENABLE |
(TI_TRANSFER_TIMEOUT << 2),
TI_UART1_PORT, NULL, 0, TIMEOUT) < 0) {
pr_error("ti3410: TI_OPEN_PORT failed");
return -1;
}
if (usb_control_msg(tr->hnd,
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TI_START_PORT,
0,
TI_UART1_PORT, NULL, 0, TIMEOUT) < 0) {
pr_error("ti3410: TI_START_PORT failed");
return -1;
}
return 0;
}
static int interrupt_flush(struct ti3410_transport *tr)
{
uint8_t buf[2];
return usb_interrupt_read(tr->hnd, USB_FET_INT_EP,
(char *)buf, 2, TIMEOUT);
}
static int setup_port(struct ti3410_transport *tr)
{
interrupt_flush(tr);
if (do_open_start(tr) < 0)
return -1;
if (usb_control_msg(tr->hnd,
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TI_PURGE_PORT,
TI_PURGE_INPUT,
TI_UART1_PORT, NULL, 0, TIMEOUT) < 0) {
pr_error("ti3410: TI_PURGE_PORT (input) failed");
return -1;
}
interrupt_flush(tr);
interrupt_flush(tr);
if (usb_control_msg(tr->hnd,
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TI_PURGE_PORT,
TI_PURGE_OUTPUT,
TI_UART1_PORT, NULL, 0, TIMEOUT) < 0) {
pr_error("ti3410: TI_PURGE_PORT (output) failed");
return -1;
}
interrupt_flush(tr);
if (usb_clear_halt(tr->hnd, USB_FET_IN_EP) < 0 ||
usb_clear_halt(tr->hnd, USB_FET_OUT_EP) < 0) {
pr_error("ti3410: failed to clear halt status");
return -1;
}
if (do_open_start(tr) < 0)
return -1;
return 0;
}
static void teardown_port(struct ti3410_transport *tr)
{
if (usb_control_msg(tr->hnd,
USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TI_CLOSE_PORT,
0,
TI_UART1_PORT, NULL, 0, TIMEOUT) < 0)
pr_error("ti3410: warning: TI_CLOSE_PORT failed");
}
static int ti3410_send(transport_t tr_base, const uint8_t *data, int len)
{
struct ti3410_transport *tr = (struct ti3410_transport *)tr_base;
int sent;
while (len) {
sent = usb_bulk_write(tr->hnd, USB_FET_OUT_EP,
(char *)data, len, TIMEOUT);
if (sent <= 0) {
pr_error("ti3410: can't send data");
return -1;
}
data += sent;
len -= sent;
}
return 0;
}
static int ti3410_recv(transport_t tr_base, uint8_t *databuf, int max_len)
{
struct ti3410_transport *tr = (struct ti3410_transport *)tr_base;
time_t deadline = time(NULL) + READ_TIMEOUT / 1000;
int rlen;
while (time(NULL) < deadline) {
rlen = usb_bulk_read(tr->hnd, USB_FET_IN_EP, (char *)databuf,
max_len, READ_TIMEOUT);
if (rlen > 0)
return rlen;
if (rlen < 0) {
printc_err("ti3410: usb_bulk_read: %s\n",
usb_strerror());
return -1;
}
}
printc_err("ti3410: read timeout\n");
return -1;
}
static void ti3410_destroy(transport_t tr_base)
{
struct ti3410_transport *tr = (struct ti3410_transport *)tr_base;
teardown_port(tr);
free(tr);
}
struct firmware {
uint8_t buf[TI_FIRMWARE_BUF_SIZE];
unsigned int size;
};
static FILE *find_firmware(void)
{
char path[256];
const char *env;
FILE *in;
printc_dbg("Searching for firmware for TI3410...\n");
env = getenv("MSPDEBUG_TI3410_FW");
if (env) {
snprintf(path, sizeof(path), "%s", env);
printc_dbg(" - checking %s\n", path);
in = fopen(path, "r");
if (in)
return in;
}
snprintf(path, sizeof(path), "%s",
LIB_DIR "/mspdebug/ti_3410.fw.ihex");
printc_dbg(" - checking %s\n", path);
in = fopen(path, "r");
if (in)
return in;
snprintf(path, sizeof(path), "%s", "ti_3410.fw.ihex");
printc_dbg(" - checking %s\n", path);
in = fopen(path, "r");
if (in)
return in;
printc_err("ti3410: unable to locate firmware\n");
return NULL;
}
static int do_extract(void *user_data, const struct binfile_chunk *ch)
{
struct firmware *f = (struct firmware *)user_data;
if (f->size != ch->addr) {
printc_err("ti3410: firmware gap at 0x%x (ends at 0x%0x)\n",
f->size, ch->addr);
return -1;
}
if (f->size + ch->len > sizeof(f->buf)) {
printc_err("ti3410: maximum firmware size exceeded\n");
return -1;
}
memcpy(f->buf + f->size, ch->data, ch->len);
f->size += ch->len;
return 0;
}
static int load_firmware(struct firmware *f)
{
FILE *in = find_firmware();
if (!in)
return -1;
if (!ihex_check(in)) {
printc_err("ti3410: not a valid IHEX file\n");
fclose(in);
return -1;
}
memset(f, 0, sizeof(*f));
if (ihex_extract(in, do_extract, f) < 0) {
printc_err("ti3410: failed to load firmware\n");
fclose(in);
return -1;
}
fclose(in);
return 0;
}
static void prepare_firmware(struct firmware *f)
{
uint8_t cksum = 0;
uint16_t real_size = f->size - 3;
int i;
for (i = 3; i < f->size; i++)
cksum += f->buf[i];
f->buf[0] = real_size & 0xff;
f->buf[1] = real_size >> 8;
f->buf[2] = cksum;
printc_dbg("Loaded %d byte firmware image (checksum = 0x%02x)\n",
f->size, cksum);
}
static int do_download(struct usb_device *dev, const struct firmware *f)
{
struct usb_dev_handle *hnd;
int offset = 0;
printc_dbg("Starting download...\n");
hnd = usb_open(dev);
if (!hnd) {
pr_error("ti3410: failed to open USB device");
return -1;
}
#if defined(__linux__)
if (usb_detach_kernel_driver_np(hnd, USB_FDL_INTERFACE) < 0)
pr_error("ti3410: warning: can't "
"detach kernel driver");
#endif
if (usb_claim_interface(hnd, USB_FDL_INTERFACE) < 0) {
pr_error("ti3410: can't claim interface");
usb_close(hnd);
return -1;
}
while (offset < f->size) {
int plen = f->size - offset;
int r;
if (plen > TI_DOWNLOAD_MAX_PACKET_SIZE)
plen = TI_DOWNLOAD_MAX_PACKET_SIZE;
r = usb_bulk_write(hnd, USB_FDL_OUT_EP,
(char *)f->buf + offset, plen, TIMEOUT);
if (r < 0) {
pr_error("ti3410: bulk write failed");
usb_close(hnd);
return -1;
}
offset += r;
}
delay_ms(100);
if (usb_reset(hnd) < 0)
pr_error("ti3410: warning: reset failed");
usb_close(hnd);
return 0;
}
static int download_firmware(struct usb_device *dev)
{
struct firmware frm;
if (load_firmware(&frm) < 0)
return -1;
prepare_firmware(&frm);
if (do_download(dev, &frm) < 0)
return -1;
printc_dbg("Waiting for TI3410 reset...\n");
delay_s(2);
return 0;
}
static int ti3410_flush(transport_t tr_base)
{
return 0;
}
static int ti3410_set_modem(transport_t tr_base, transport_modem_t state)
{
printc_err("ti3410: unsupported operation: set_modem\n");
return -1;
}
static const struct transport_class ti3410_transport = {
.destroy = ti3410_destroy,
.send = ti3410_send,
.recv = ti3410_recv,
.flush = ti3410_flush,
.set_modem = ti3410_set_modem
};
transport_t ti3410_open(const char *devpath, const char *requested_serial,
int has_vid_pid, uint16_t vid, uint16_t pid)
{
struct ti3410_transport *tr = malloc(sizeof(*tr));
struct usb_device *dev;
if (!tr) {
pr_error("ti3410: can't allocate memory");
return NULL;
}
tr->base.ops = &ti3410_transport;
usb_init();
usb_find_busses();
usb_find_devices();
if (devpath)
dev = usbutil_find_by_loc(devpath);
else
dev = usbutil_find_by_id(has_vid_pid ? vid : USB_FET_VENDOR,
has_vid_pid ? pid : USB_FET_PRODUCT,
requested_serial);
if (!dev) {
free(tr);
return NULL;
}
if (dev->descriptor.bNumConfigurations == 1) {
if (download_firmware(dev) < 0) {
printc_err("ti3410: firmware download failed\n");
free(tr);
return NULL;
}
usb_find_devices();
if (devpath)
dev = usbutil_find_by_loc(devpath);
else
dev = usbutil_find_by_id(USB_FET_VENDOR,
USB_FET_PRODUCT,
requested_serial);
if (!dev) {
free(tr);
return NULL;
}
}
if (open_device(tr, dev) < 0) {
printc_err("ti3410: failed to open TI3410 device\n");
free(tr);
return NULL;
}
if (setup_port(tr) < 0) {
printc_err("ti3410: failed to set up port\n");
teardown_port(tr);
usb_close(tr->hnd);
free(tr);
return NULL;
}
return (transport_t)tr;
}