+/*
+ * HW init
+ */
+
+static int moxa_check_fw_model(struct moxa_board_conf *brd, u8 model)
+{
+ switch (brd->boardType) {
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ if (model != 1)
+ goto err;
+ break;
+ case MOXA_BOARD_CP204J:
+ if (model != 3)
+ goto err;
+ break;
+ default:
+ if (model != 2)
+ goto err;
+ break;
+ }
+ return 0;
+err:
+ return -EINVAL;
+}
+
+static int moxa_check_fw(const void *ptr)
+{
+ const __le16 *lptr = ptr;
+
+ if (*lptr != cpu_to_le16(0x7980))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int moxa_load_bios(struct moxa_board_conf *brd, const u8 *buf,
+ size_t len)
+{
+ void __iomem *baseAddr = brd->basemem;
+ u16 tmp;
+
+ writeb(HW_reset, baseAddr + Control_reg); /* reset */
+ msleep(10);
+ memset_io(baseAddr, 0, 4096);
+ memcpy_toio(baseAddr, buf, len); /* download BIOS */
+ writeb(0, baseAddr + Control_reg); /* restart */
+
+ msleep(2000);
+
+ switch (brd->boardType) {
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ tmp = readw(baseAddr + C218_key);
+ if (tmp != C218_KeyCode)
+ goto err;
+ break;
+ case MOXA_BOARD_CP204J:
+ tmp = readw(baseAddr + C218_key);
+ if (tmp != CP204J_KeyCode)
+ goto err;
+ break;
+ default:
+ tmp = readw(baseAddr + C320_key);
+ if (tmp != C320_KeyCode)
+ goto err;
+ tmp = readw(baseAddr + C320_status);
+ if (tmp != STS_init) {
+ printk(KERN_ERR "MOXA: bios upload failed -- CPU/Basic "
+ "module not found\n");
+ return -EIO;
+ }
+ break;
+ }
+
+ return 0;
+err:
+ printk(KERN_ERR "MOXA: bios upload failed -- board not found\n");
+ return -EIO;
+}
+
+static int moxa_load_320b(struct moxa_board_conf *brd, const u8 *ptr,
+ size_t len)
+{
+ void __iomem *baseAddr = brd->basemem;
+
+ if (len < 7168) {
+ printk(KERN_ERR "MOXA: invalid 320 bios -- too short\n");
+ return -EINVAL;
+ }
+
+ writew(len - 7168 - 2, baseAddr + C320bapi_len);
+ writeb(1, baseAddr + Control_reg); /* Select Page 1 */
+ memcpy_toio(baseAddr + DynPage_addr, ptr, 7168);
+ writeb(2, baseAddr + Control_reg); /* Select Page 2 */
+ memcpy_toio(baseAddr + DynPage_addr, ptr + 7168, len - 7168);
+
+ return 0;
+}
+
+static int moxa_real_load_code(struct moxa_board_conf *brd, const void *ptr,
+ size_t len)
+{
+ void __iomem *baseAddr = brd->basemem;
+ const __le16 *uptr = ptr;
+ size_t wlen, len2, j;
+ unsigned long key, loadbuf, loadlen, checksum, checksum_ok;
+ unsigned int i, retry;
+ u16 usum, keycode;
+
+ keycode = (brd->boardType == MOXA_BOARD_CP204J) ? CP204J_KeyCode :
+ C218_KeyCode;
+
+ switch (brd->boardType) {
+ case MOXA_BOARD_CP204J:
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ key = C218_key;
+ loadbuf = C218_LoadBuf;
+ loadlen = C218DLoad_len;
+ checksum = C218check_sum;
+ checksum_ok = C218chksum_ok;
+ break;
+ default:
+ key = C320_key;
+ keycode = C320_KeyCode;
+ loadbuf = C320_LoadBuf;
+ loadlen = C320DLoad_len;
+ checksum = C320check_sum;
+ checksum_ok = C320chksum_ok;
+ break;
+ }
+
+ usum = 0;
+ wlen = len >> 1;
+ for (i = 0; i < wlen; i++)
+ usum += le16_to_cpu(uptr[i]);
+ retry = 0;
+ do {
+ wlen = len >> 1;
+ j = 0;
+ while (wlen) {
+ len2 = (wlen > 2048) ? 2048 : wlen;
+ wlen -= len2;
+ memcpy_toio(baseAddr + loadbuf, ptr + j, len2 << 1);
+ j += len2 << 1;
+
+ writew(len2, baseAddr + loadlen);
+ writew(0, baseAddr + key);
+ for (i = 0; i < 100; i++) {
+ if (readw(baseAddr + key) == keycode)
+ break;
+ msleep(10);
+ }
+ if (readw(baseAddr + key) != keycode)
+ return -EIO;
+ }
+ writew(0, baseAddr + loadlen);
+ writew(usum, baseAddr + checksum);
+ writew(0, baseAddr + key);
+ for (i = 0; i < 100; i++) {
+ if (readw(baseAddr + key) == keycode)
+ break;
+ msleep(10);
+ }
+ retry++;
+ } while ((readb(baseAddr + checksum_ok) != 1) && (retry < 3));
+ if (readb(baseAddr + checksum_ok) != 1)
+ return -EIO;
+
+ writew(0, baseAddr + key);
+ for (i = 0; i < 600; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ msleep(10);
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code)
+ return -EIO;
+
+ if (MOXA_IS_320(brd)) {
+ if (brd->busType == MOXA_BUS_TYPE_PCI) { /* ASIC board */
+ writew(0x3800, baseAddr + TMS320_PORT1);
+ writew(0x3900, baseAddr + TMS320_PORT2);
+ writew(28499, baseAddr + TMS320_CLOCK);
+ } else {
+ writew(0x3200, baseAddr + TMS320_PORT1);
+ writew(0x3400, baseAddr + TMS320_PORT2);
+ writew(19999, baseAddr + TMS320_CLOCK);
+ }
+ }
+ writew(1, baseAddr + Disable_IRQ);
+ writew(0, baseAddr + Magic_no);
+ for (i = 0; i < 500; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ msleep(10);
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code)
+ return -EIO;
+
+ if (MOXA_IS_320(brd)) {
+ j = readw(baseAddr + Module_cnt);
+ if (j <= 0)
+ return -EIO;
+ brd->numPorts = j * 8;
+ writew(j, baseAddr + Module_no);
+ writew(0, baseAddr + Magic_no);
+ for (i = 0; i < 600; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ msleep(10);
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code)
+ return -EIO;
+ }
+ brd->intNdx = baseAddr + IRQindex;
+ brd->intPend = baseAddr + IRQpending;
+ brd->intTable = baseAddr + IRQtable;
+
+ return 0;
+}
+
+static int moxa_load_code(struct moxa_board_conf *brd, const void *ptr,
+ size_t len)
+{
+ void __iomem *ofsAddr, *baseAddr = brd->basemem;
+ struct moxa_port *port;
+ int retval, i;
+
+ if (len % 2) {
+ printk(KERN_ERR "MOXA: bios length is not even\n");
+ return -EINVAL;
+ }
+
+ retval = moxa_real_load_code(brd, ptr, len); /* may change numPorts */
+ if (retval)
+ return retval;
+
+ switch (brd->boardType) {
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ case MOXA_BOARD_CP204J:
+ port = brd->ports;
+ for (i = 0; i < brd->numPorts; i++, port++) {
+ port->board = brd;
+ port->DCDState = 0;
+ port->tableAddr = baseAddr + Extern_table +
+ Extern_size * i;
+ ofsAddr = port->tableAddr;
+ writew(C218rx_mask, ofsAddr + RX_mask);
+ writew(C218tx_mask, ofsAddr + TX_mask);
+ writew(C218rx_spage + i * C218buf_pageno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C218rx_pageno, ofsAddr + EndPage_rxb);
+
+ writew(C218tx_spage + i * C218buf_pageno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb) + C218tx_pageno, ofsAddr + EndPage_txb);
+
+ }
+ break;
+ default:
+ port = brd->ports;
+ for (i = 0; i < brd->numPorts; i++, port++) {
+ port->board = brd;
+ port->DCDState = 0;
+ port->tableAddr = baseAddr + Extern_table +
+ Extern_size * i;
+ ofsAddr = port->tableAddr;
+ switch (brd->numPorts) {
+ case 8:
+ writew(C320p8rx_mask, ofsAddr + RX_mask);
+ writew(C320p8tx_mask, ofsAddr + TX_mask);
+ writew(C320p8rx_spage + i * C320p8buf_pgno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C320p8rx_pgno, ofsAddr + EndPage_rxb);
+ writew(C320p8tx_spage + i * C320p8buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb) + C320p8tx_pgno, ofsAddr + EndPage_txb);
+
+ break;
+ case 16:
+ writew(C320p16rx_mask, ofsAddr + RX_mask);
+ writew(C320p16tx_mask, ofsAddr + TX_mask);
+ writew(C320p16rx_spage + i * C320p16buf_pgno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C320p16rx_pgno, ofsAddr + EndPage_rxb);
+ writew(C320p16tx_spage + i * C320p16buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb) + C320p16tx_pgno, ofsAddr + EndPage_txb);
+ break;
+
+ case 24:
+ writew(C320p24rx_mask, ofsAddr + RX_mask);
+ writew(C320p24tx_mask, ofsAddr + TX_mask);
+ writew(C320p24rx_spage + i * C320p24buf_pgno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C320p24rx_pgno, ofsAddr + EndPage_rxb);
+ writew(C320p24tx_spage + i * C320p24buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb);
+ break;
+ case 32:
+ writew(C320p32rx_mask, ofsAddr + RX_mask);
+ writew(C320p32tx_mask, ofsAddr + TX_mask);
+ writew(C320p32tx_ofs, ofsAddr + Ofs_txb);
+ writew(C320p32rx_spage + i * C320p32buf_pgno, ofsAddr + Page_rxb);
+ writew(readb(ofsAddr + Page_rxb), ofsAddr + EndPage_rxb);
+ writew(C320p32tx_spage + i * C320p32buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb);
+ break;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+static int moxa_load_fw(struct moxa_board_conf *brd, const struct firmware *fw)
+{
+ const void *ptr = fw->data;
+ char rsn[64];
+ u16 lens[5];
+ size_t len;
+ unsigned int a, lenp, lencnt;
+ int ret = -EINVAL;
+ struct {
+ __le32 magic; /* 0x34303430 */
+ u8 reserved1[2];
+ u8 type; /* UNIX = 3 */
+ u8 model; /* C218T=1, C320T=2, CP204=3 */
+ u8 reserved2[8];
+ __le16 len[5];
+ } const *hdr = ptr;
+
+ BUILD_BUG_ON(ARRAY_SIZE(hdr->len) != ARRAY_SIZE(lens));
+
+ if (fw->size < MOXA_FW_HDRLEN) {
+ strcpy(rsn, "too short (even header won't fit)");
+ goto err;
+ }
+ if (hdr->magic != cpu_to_le32(0x30343034)) {
+ sprintf(rsn, "bad magic: %.8x", le32_to_cpu(hdr->magic));
+ goto err;
+ }
+ if (hdr->type != 3) {
+ sprintf(rsn, "not for linux, type is %u", hdr->type);
+ goto err;
+ }
+ if (moxa_check_fw_model(brd, hdr->model)) {
+ sprintf(rsn, "not for this card, model is %u", hdr->model);
+ goto err;
+ }
+
+ len = MOXA_FW_HDRLEN;
+ lencnt = hdr->model == 2 ? 5 : 3;
+ for (a = 0; a < ARRAY_SIZE(lens); a++) {
+ lens[a] = le16_to_cpu(hdr->len[a]);
+ if (lens[a] && len + lens[a] <= fw->size &&
+ moxa_check_fw(&fw->data[len]))
+ printk(KERN_WARNING "MOXA firmware: unexpected input "
+ "at offset %u, but going on\n", (u32)len);
+ if (!lens[a] && a < lencnt) {
+ sprintf(rsn, "too few entries in fw file");
+ goto err;
+ }
+ len += lens[a];
+ }
+
+ if (len != fw->size) {
+ sprintf(rsn, "bad length: %u (should be %u)", (u32)fw->size,
+ (u32)len);
+ goto err;
+ }
+
+ ptr += MOXA_FW_HDRLEN;
+ lenp = 0; /* bios */
+
+ strcpy(rsn, "read above");
+
+ ret = moxa_load_bios(brd, ptr, lens[lenp]);
+ if (ret)
+ goto err;
+
+ /* we skip the tty section (lens[1]), since we don't need it */
+ ptr += lens[lenp] + lens[lenp + 1];
+ lenp += 2; /* comm */
+
+ if (hdr->model == 2) {
+ ret = moxa_load_320b(brd, ptr, lens[lenp]);
+ if (ret)
+ goto err;
+ /* skip another tty */
+ ptr += lens[lenp] + lens[lenp + 1];
+ lenp += 2;
+ }
+
+ ret = moxa_load_code(brd, ptr, lens[lenp]);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ printk(KERN_ERR "firmware failed to load, reason: %s\n", rsn);
+ return ret;
+}
+
+static int moxa_init_board(struct moxa_board_conf *brd, struct device *dev)
+{
+ const struct firmware *fw;
+ const char *file;
+ struct moxa_port *p;
+ unsigned int i;
+ int ret;
+
+ brd->ports = kcalloc(MAX_PORTS_PER_BOARD, sizeof(*brd->ports),
+ GFP_KERNEL);
+ if (brd->ports == NULL) {
+ printk(KERN_ERR "cannot allocate memory for ports\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ for (i = 0, p = brd->ports; i < MAX_PORTS_PER_BOARD; i++, p++) {
+ tty_port_init(&p->port);
+ p->type = PORT_16550A;
+ p->cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+ }
+
+ switch (brd->boardType) {
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ file = "c218tunx.cod";
+ break;
+ case MOXA_BOARD_CP204J:
+ file = "cp204unx.cod";
+ break;
+ default:
+ file = "c320tunx.cod";
+ break;
+ }
+
+ ret = request_firmware(&fw, file, dev);
+ if (ret) {
+ printk(KERN_ERR "MOXA: request_firmware failed. Make sure "
+ "you've placed '%s' file into your firmware "
+ "loader directory (e.g. /lib/firmware)\n",
+ file);
+ goto err_free;
+ }
+
+ ret = moxa_load_fw(brd, fw);
+
+ release_firmware(fw);
+
+ if (ret)
+ goto err_free;
+
+ spin_lock_bh(&moxa_lock);
+ brd->ready = 1;
+ if (!timer_pending(&moxaTimer))
+ mod_timer(&moxaTimer, jiffies + HZ / 50);
+ spin_unlock_bh(&moxa_lock);
+
+ return 0;
+err_free:
+ kfree(brd->ports);
+err:
+ return ret;
+}
+
+static void moxa_board_deinit(struct moxa_board_conf *brd)
+{
+ unsigned int a, opened;
+
+ mutex_lock(&moxa_openlock);
+ spin_lock_bh(&moxa_lock);
+ brd->ready = 0;
+ spin_unlock_bh(&moxa_lock);
+
+ /* pci hot-un-plug support */
+ for (a = 0; a < brd->numPorts; a++)
+ if (brd->ports[a].port.flags & ASYNC_INITIALIZED) {
+ struct tty_struct *tty = tty_port_tty_get(
+ &brd->ports[a].port);
+ if (tty) {
+ tty_hangup(tty);
+ tty_kref_put(tty);
+ }
+ }
+ while (1) {
+ opened = 0;
+ for (a = 0; a < brd->numPorts; a++)
+ if (brd->ports[a].port.flags & ASYNC_INITIALIZED)
+ opened++;
+ mutex_unlock(&moxa_openlock);
+ if (!opened)
+ break;
+ msleep(50);
+ mutex_lock(&moxa_openlock);
+ }
+
+ iounmap(brd->basemem);
+ brd->basemem = NULL;
+ kfree(brd->ports);
+}
+