diff options
author | Jiri Slaby <jirislaby@gmail.com> | 2007-07-17 04:05:16 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-17 10:23:10 -0700 |
commit | 054f5b0aaa58dfc841635e52b6c1cc2b14ec37fc (patch) | |
tree | f2356ddebc9e11d1eb5c178d4443dcf3e9612088 /drivers/char | |
parent | cf3a386c083c0cc4eb9f01a8818b9499ae56e73f (diff) | |
download | lwn-054f5b0aaa58dfc841635e52b6c1cc2b14ec37fc.tar.gz lwn-054f5b0aaa58dfc841635e52b6c1cc2b14ec37fc.zip |
Char: cyclades, add firmware loading
cyclades, add firmware loading
Signed-off-by: Jiri Slaby <jirislaby@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/cyclades.c | 351 |
1 files changed, 328 insertions, 23 deletions
diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index e04005b5f8a6..b9bd4f67efb4 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -646,6 +646,7 @@ #include <linux/delay.h> #include <linux/spinlock.h> #include <linux/bitops.h> +#include <linux/firmware.h> #include <asm/system.h> #include <asm/io.h> @@ -680,6 +681,44 @@ static void cy_send_xchar(struct tty_struct *tty, char ch); #define STD_COM_FLAGS (0) +/* firmware stuff */ +#define ZL_MAX_BLOCKS 16 +#define DRIVER_VERSION 0x02010203 +#define RAM_SIZE 0x80000 + +#define Z_FPGA_LOADED(X) ((readl(&(X)->init_ctrl) & (1<<17)) != 0) + +enum zblock_type { + ZBLOCK_PRG = 0, + ZBLOCK_FPGA = 1 +}; + +struct zfile_header { + char name[64]; + char date[32]; + char aux[32]; + u32 n_config; + u32 config_offset; + u32 n_blocks; + u32 block_offset; + u32 reserved[9]; +} __attribute__ ((packed)); + +struct zfile_config { + char name[64]; + u32 mailbox; + u32 function; + u32 n_blocks; + u32 block_list[ZL_MAX_BLOCKS]; +} __attribute__ ((packed)); + +struct zfile_block { + u32 type; + u32 file_offset; + u32 ram_offset; + u32 size; +} __attribute__ ((packed)); + static struct tty_driver *cy_serial_driver; #ifdef CONFIG_ISA @@ -4735,17 +4774,295 @@ static int __init cy_detect_isa(void) } /* cy_detect_isa */ #ifdef CONFIG_PCI -static void __devinit plx_init(void __iomem * addr, __u32 initctl) +static inline int __devinit cyc_isfwstr(const char *str, unsigned int size) +{ + unsigned int a; + + for (a = 0; a < size && *str; a++, str++) + if (*str & 0x80) + return -EINVAL; + + for (; a < size; a++, str++) + if (*str) + return -EINVAL; + + return 0; +} + +static inline void __devinit cyz_fpga_copy(void __iomem *fpga, u8 *data, + unsigned int size) +{ + for (; size > 0; size--) { + cy_writel(fpga, *data++); + udelay(10); + } +} + +static void __devinit plx_init(struct pci_dev *pdev, int irq, + struct RUNTIME_9060 __iomem *addr) { /* Reset PLX */ - cy_writel(addr + initctl, readl(addr + initctl) | 0x40000000); + cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) | 0x40000000); udelay(100L); - cy_writel(addr + initctl, readl(addr + initctl) & ~0x40000000); + cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) & ~0x40000000); /* Reload Config. Registers from EEPROM */ - cy_writel(addr + initctl, readl(addr + initctl) | 0x20000000); + cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) | 0x20000000); udelay(100L); - cy_writel(addr + initctl, readl(addr + initctl) & ~0x20000000); + cy_writel(&addr->init_ctrl, readl(&addr->init_ctrl) & ~0x20000000); + + /* For some yet unknown reason, once the PLX9060 reloads the EEPROM, + * the IRQ is lost and, thus, we have to re-write it to the PCI config. + * registers. This will remain here until we find a permanent fix. + */ + pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, irq); +} + +static int __devinit __cyz_load_fw(const struct firmware *fw, + const char *name, const u32 mailbox, void __iomem *base, + void __iomem *fpga) +{ + void *ptr = fw->data; + struct zfile_header *h = ptr; + struct zfile_config *c, *cs; + struct zfile_block *b, *bs; + unsigned int a, tmp, len = fw->size; +#define BAD_FW KERN_ERR "Bad firmware: " + if (len < sizeof(*h)) { + printk(BAD_FW "too short: %u<%zu\n", len, sizeof(*h)); + return -EINVAL; + } + + cs = ptr + h->config_offset; + bs = ptr + h->block_offset; + + if ((void *)(cs + h->n_config) > ptr + len || + (void *)(bs + h->n_blocks) > ptr + len) { + printk(BAD_FW "too short"); + return -EINVAL; + } + + if (cyc_isfwstr(h->name, sizeof(h->name)) || + cyc_isfwstr(h->date, sizeof(h->date))) { + printk(BAD_FW "bad formatted header string\n"); + return -EINVAL; + } + + if (strncmp(name, h->name, sizeof(h->name))) { + printk(BAD_FW "bad name '%s' (expected '%s')\n", h->name, name); + return -EINVAL; + } + + tmp = 0; + for (c = cs; c < cs + h->n_config; c++) { + for (a = 0; a < c->n_blocks; a++) + if (c->block_list[a] > h->n_blocks) { + printk(BAD_FW "bad block ref number in cfgs\n"); + return -EINVAL; + } + if (c->mailbox == mailbox && c->function == 0) /* 0 is normal */ + tmp++; + } + if (!tmp) { + printk(BAD_FW "nothing appropriate\n"); + return -EINVAL; + } + + for (b = bs; b < bs + h->n_blocks; b++) + if (b->file_offset + b->size > len) { + printk(BAD_FW "bad block data offset\n"); + return -EINVAL; + } + + /* everything is OK, let's seek'n'load it */ + for (c = cs; c < cs + h->n_config; c++) + if (c->mailbox == mailbox && c->function == 0) + break; + + for (a = 0; a < c->n_blocks; a++) { + b = &bs[c->block_list[a]]; + if (b->type == ZBLOCK_FPGA) { + if (fpga != NULL) + cyz_fpga_copy(fpga, ptr + b->file_offset, + b->size); + } else { + if (base != NULL) + memcpy_toio(base + b->ram_offset, + ptr + b->file_offset, b->size); + } + } +#undef BAD_FW + return 0; +} + +static int __devinit cyz_load_fw(struct pci_dev *pdev, void __iomem *base_addr, + struct RUNTIME_9060 __iomem *ctl_addr, int irq) +{ + const struct firmware *fw; + struct FIRM_ID __iomem *fid = base_addr + ID_ADDRESS; + struct CUSTOM_REG __iomem *cust = base_addr; + struct ZFW_CTRL __iomem *pt_zfwctrl; + u8 *tmp; + u32 mailbox, status; + unsigned int i; + int retval; + + retval = request_firmware(&fw, "cyzfirm.bin", &pdev->dev); + if (retval) { + dev_err(&pdev->dev, "can't get firmware\n"); + goto err; + } + + /* Check whether the firmware is already loaded and running. If + positive, skip this board */ + if (Z_FPGA_LOADED(ctl_addr) && readl(&fid->signature) == ZFIRM_ID) { + u32 cntval = readl(base_addr + 0x190); + + udelay(100); + if (cntval != readl(base_addr + 0x190)) { + /* FW counter is working, FW is running */ + dev_dbg(&pdev->dev, "Cyclades-Z FW already loaded. " + "Skipping board.\n"); + retval = 0; + goto err_rel; + } + } + + /* start boot */ + cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) & + ~0x00030800UL); + + mailbox = readl(&ctl_addr->mail_box_0); + + if (mailbox == 0 || Z_FPGA_LOADED(ctl_addr)) { + /* stops CPU and set window to beginning of RAM */ + cy_writel(&ctl_addr->loc_addr_base, WIN_CREG); + cy_writel(&cust->cpu_stop, 0); + cy_writel(&ctl_addr->loc_addr_base, WIN_RAM); + udelay(100); + } + + plx_init(pdev, irq, ctl_addr); + + if (mailbox != 0) { + /* load FPGA */ + retval = __cyz_load_fw(fw, "Cyclom-Z", mailbox, NULL, + base_addr); + if (retval) + goto err_rel; + if (!Z_FPGA_LOADED(ctl_addr)) { + dev_err(&pdev->dev, "fw upload successful, but fw is " + "not loaded\n"); + goto err_rel; + } + } + + /* stops CPU and set window to beginning of RAM */ + cy_writel(&ctl_addr->loc_addr_base, WIN_CREG); + cy_writel(&cust->cpu_stop, 0); + cy_writel(&ctl_addr->loc_addr_base, WIN_RAM); + udelay(100); + + /* clear memory */ + for (tmp = base_addr; (void *)tmp < base_addr + RAM_SIZE; tmp++) + cy_writeb(tmp, 255); + if (mailbox != 0) { + /* set window to last 512K of RAM */ + cy_writel(&ctl_addr->loc_addr_base, WIN_RAM + RAM_SIZE); + //sleep(1); + for (tmp = base_addr; (void *)tmp < base_addr + RAM_SIZE; tmp++) + cy_writeb(tmp, 255); + /* set window to beginning of RAM */ + cy_writel(&ctl_addr->loc_addr_base, WIN_RAM); + //sleep(1); + } + + retval = __cyz_load_fw(fw, "Cyclom-Z", mailbox, base_addr, NULL); + release_firmware(fw); + if (retval) + goto err; + + /* finish boot and start boards */ + cy_writel(&ctl_addr->loc_addr_base, WIN_CREG); + cy_writel(&cust->cpu_start, 0); + cy_writel(&ctl_addr->loc_addr_base, WIN_RAM); + i = 0; + while ((status = readl(&fid->signature)) != ZFIRM_ID && i++ < 40) + msleep(100); + if (status != ZFIRM_ID) { + if (status == ZFIRM_HLT) { + dev_err(&pdev->dev, "you need an external power supply " + "for this number of ports. Firmware halted and " + "board reset.\n"); + retval = -EIO; + goto err; + } + dev_warn(&pdev->dev, "fid->signature = 0x%x... Waiting " + "some more time\n", status); + while ((status = readl(&fid->signature)) != ZFIRM_ID && + i++ < 200) + msleep(100); + if (status != ZFIRM_ID) { + dev_err(&pdev->dev, "Board not started in 20 seconds! " + "Giving up. (fid->signature = 0x%x)\n", + status); + dev_info(&pdev->dev, "*** Warning ***: if you are " + "upgrading the FW, please power cycle the " + "system before loading the new FW to the " + "Cyclades-Z.\n"); + + if (Z_FPGA_LOADED(ctl_addr)) + plx_init(pdev, irq, ctl_addr); + + retval = -EIO; + goto err; + } + dev_dbg(&pdev->dev, "Firmware started after %d seconds.\n", + i / 10); + } + pt_zfwctrl = base_addr + readl(&fid->zfwctrl_addr); + + dev_dbg(&pdev->dev, "fid=> %p, zfwctrl_addr=> %x, npt_zfwctrl=> %p\n", + base_addr + ID_ADDRESS, readl(&fid->zfwctrl_addr), + base_addr + readl(&fid->zfwctrl_addr)); + + dev_info(&pdev->dev, "Cyclades-Z FW loaded: version = %x, ports = %u\n", + readl(&pt_zfwctrl->board_ctrl.fw_version), + readl(&pt_zfwctrl->board_ctrl.n_channel)); + + if (readl(&pt_zfwctrl->board_ctrl.n_channel) == 0) { + dev_warn(&pdev->dev, "no Cyclades-Z ports were found. Please " + "check the connection between the Z host card and the " + "serial expanders.\n"); + + if (Z_FPGA_LOADED(ctl_addr)) + plx_init(pdev, irq, ctl_addr); + + dev_info(&pdev->dev, "Null number of ports detected. Board " + "reset.\n"); + retval = 0; + goto err; + } + + cy_writel(&pt_zfwctrl->board_ctrl.op_system, C_OS_LINUX); + cy_writel(&pt_zfwctrl->board_ctrl.dr_version, DRIVER_VERSION); + + /* + Early firmware failed to start looking for commands. + This enables firmware interrupts for those commands. + */ + cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) | + (1 << 17)); + cy_writel(&ctl_addr->intr_ctrl_stat, readl(&ctl_addr->intr_ctrl_stat) | + 0x00030800UL); + + plx_init(pdev, irq, ctl_addr); + + return 0; +err_rel: + release_firmware(fw); +err: + return retval; } static int __devinit cy_pci_probe(struct pci_dev *pdev, @@ -4827,16 +5144,9 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, } /* Disable interrupts on the PLX before resetting it */ - cy_writew(addr0 + 0x68, - readw(addr0 + 0x68) & ~0x0900); + cy_writew(addr0 + 0x68, readw(addr0 + 0x68) & ~0x0900); - plx_init(addr0, 0x6c); - /* For some yet unknown reason, once the PLX9060 reloads - the EEPROM, the IRQ is lost and, thus, we have to - re-write it to the PCI config. registers. - This will remain here until we find a permanent - fix. */ - pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, irq); + plx_init(pdev, irq, addr0); mailbox = (u32)readl(&ctl_addr->mail_box_0); @@ -4877,6 +5187,9 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, if ((mailbox == ZO_V1) || (mailbox == ZO_V2)) cy_writel(addr2 + ID_ADDRESS, 0L); + retval = cyz_load_fw(pdev, addr2, addr0, irq); + if (retval) + goto err_unmap; /* This must be a Cyclades-8Zo/PCI. The extendable version will have a different device_id and will be allocated its maximum number of ports. */ @@ -4953,15 +5266,7 @@ static int __devinit cy_pci_probe(struct pci_dev *pdev, case PLX_9060: case PLX_9080: default: /* Old boards, use PLX_9060 */ - - plx_init(addr0, 0x6c); - /* For some yet unknown reason, once the PLX9060 reloads - the EEPROM, the IRQ is lost and, thus, we have to - re-write it to the PCI config. registers. - This will remain here until we find a permanent - fix. */ - pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, irq); - + plx_init(pdev, irq, addr0); cy_writew(addr0 + 0x68, readw(addr0 + 0x68) | 0x0900); break; } |