/* * linux/drivers/char/de2fpga.c -- FPGA driver for the DragonEngine * * Copyright (C) 2003 Georges Menie * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of this archive for * more details. */ #include #include #include #include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Georges Menie"); MODULE_DESCRIPTION("Spartan II FPGA driver for the DragonEngine"); #define MDL_MAJOR 61 #define MDL_NAME "de2fpga" #undef PDEBUG #ifdef MDL_DEBUG #ifdef __KERNEL__ #define PDEBUG(fmt,args...) printk(KERN_ALERT MDL_NAME ": " fmt, ##args); #else #define PDEBUG(fmt,args...) fprintf(stderr, MDL_NAME ": " fmt, ##args); #endif #else #define PDEBUG(fmt,args...) /* void */ #endif #undef IPDEBUG #define IPDEBUG(fmt,args...) /* void */ static int major = MDL_MAJOR; MODULE_PARM(major, "i"); static struct device_tag { long addr; int status; int state; int offs; int prog; char *file; char *part; char *date; char *time; char buf[DE2FPGA_MAX_TAGLEN]; } drvinfo = { 0x08000000, /* fpga registers start address */ }; static const unsigned char bs_head[] = { 0, 9, 15, 240, 15, 240, 15, 240, 15, 240, 0, 0, 1 }; #define BIT(x) (1<<(x)) static inline void de2fpga_setup_hw(void) { /* program the PROG pin (PB7) as output, and set high */ PBSEL |= BIT(7); PBDIR |= BIT(7); PBDATA |= BIT(7); /* program the CS/WR pin (PF7) as output, and set high */ PFSEL |= BIT(7); PFDIR |= BIT(7); PFDATA |= BIT(7); /* program the DONE pin (PM5) as input */ PMSEL |= BIT(5); PMDIR &= ~BIT(5); /* Select PF2 pin function as CLKO output pin, disabled */ PFSEL &= ~BIT(2); PLLCR |= PLLCR_CLKEN; } static inline void de2fpa_fpga_select(void) { /* CS low */ PFDATA &= ~BIT(7); } static inline void de2fpa_fpga_deselect(void) { /* CS high */ PFDATA |= BIT(7); } static inline int de2fpa_start_prog(void) { int i, ret; de2fpa_fpga_select(); /* pulse PROG low */ PBDATA &= ~BIT(7); for (i = 0; i < 5; ++i); PBDATA |= BIT(7); /* Check status */ for (i = 0; i < 20; ++i) { ret = PMDATA & BIT(5); if (ret == 0) break; } if (ret) { /* error, CS high */ de2fpa_fpga_deselect(); return 1; } return 0; } static inline void de2fpa_prog(unsigned char byte) { /* reverse the bits order */ byte = ((byte & 0xAA) >> 1) | ((byte & 0x55) << 1); byte = ((byte & 0xCC) >> 2) | ((byte & 0x33) << 2); byte = (byte >> 4) | (byte << 4); BYTE_REF(drvinfo.addr+1) = byte; } static inline int de2fpa_end_prog(void) { int i, ret; /* Check status */ for (i = 0; i < 20; ++i) { ret = PMDATA & BIT(5); if (ret != 0) break; } de2fpa_fpga_deselect(); if (ret != 0) { /* Enable the CLKO output buffer */ PLLCR &= ~PLLCR_CLKEN; return 0; } return 1; } static void de2fpga_reset_prog_data(void) { drvinfo.file = drvinfo.part = drvinfo.buf; drvinfo.date = drvinfo.time = drvinfo.buf; drvinfo.buf[0] = '\0'; drvinfo.status = DE2FPGA_STS_UNKNOWN; } static int de2fpga_load_bitstream(int byte) { static int tagsize; static int bitsize; static char *tag; switch (drvinfo.state) { case 0: /* read header */ if (drvinfo.offs >= sizeof bs_head || byte != bs_head[drvinfo.offs]) return -1; if (drvinfo.offs == sizeof bs_head - 1) { drvinfo.state = 1; tag = drvinfo.buf; } break; case 1: /* read tag letter */ if (byte >= 'a' && byte <= 'd') { drvinfo.state = 2; switch (byte) { case 'a': drvinfo.file = tag; break; case 'b': drvinfo.part = tag; break; case 'c': drvinfo.date = tag; break; case 'd': drvinfo.time = tag; break; } } else if (byte == 'e') { drvinfo.state = 5; } else { return -2; } break; case 2: /* read tag size MSB */ drvinfo.state = 3; tagsize = byte << 8; break; case 3: /* read tag size LSB */ drvinfo.state = 4; tagsize += byte; break; case 4: /* read tag content */ if (tag - drvinfo.buf >= DE2FPGA_MAX_TAGLEN) return -3; *tag++ = byte; --tagsize; if (tagsize == 0) { drvinfo.state = 1; } break; case 5: /* read bitstream size MSB */ drvinfo.state = 6; bitsize = byte << 24; break; case 6: drvinfo.state = 7; bitsize += byte << 16; break; case 7: drvinfo.state = 8; bitsize += byte << 8; break; case 8: /* read bitstream size LSB */ drvinfo.state = 9; bitsize += byte; if (bitsize == 0) return -4; break; case 9: /* start program bitstream */ drvinfo.state = 10; drvinfo.status = DE2FPGA_STS_UNKNOWN; if (de2fpa_start_prog()) return -5; drvinfo.prog = 1; /* Fall through */ case 10: /* program bitstream */ de2fpa_prog(byte); --bitsize; if (bitsize == 0) { drvinfo.state = -1; if (de2fpa_end_prog()) return -6; drvinfo.prog = 0; drvinfo.status = DE2FPGA_STS_OK; } break; default: return -7; } ++drvinfo.offs; return 0; } static ssize_t de2fpga_write(struct file *filp, const char *buf, size_t count, loff_t * offp) { int i, e; unsigned char c; /* Can't seek on this device */ if (offp != &filp->f_pos) return -ESPIPE; for (i = 0; i < count; ++i) { if (get_user(c, buf++)) { PDEBUG("get_user error\n"); if (drvinfo.prog) { de2fpga_reset_prog_data(); drvinfo.status = DE2FPGA_STS_ERROR; } return -EFAULT; } if ((e = de2fpga_load_bitstream(c)) != 0) { PDEBUG("load_bitstream error %d @ %d\n", e, drvinfo.offs); drvinfo.status = DE2FPGA_STS_ERROR; if (drvinfo.prog) { de2fpga_reset_prog_data(); drvinfo.status = DE2FPGA_STS_ERROR; } return -EFAULT; } } *offp += count; return count; } static int de2fpga_mmap(struct file *filp, struct vm_area_struct *vma) { /* this is uClinux (no MMU) specific code */ vma->vm_flags |= VM_RESERVED; vma->vm_start = drvinfo.addr; return 0; } static int de2fpga_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { int i; char *p_in; char *p_out; struct de2fpga_info param; switch (cmd) { case DE2FPGA_PARAMS_GET: if (verify_area(VERIFY_WRITE, (char *) arg, sizeof(struct de2fpga_info))) return -EBADRQC; param.status = drvinfo.status; param.file = drvinfo.file - drvinfo.buf; param.part = drvinfo.part - drvinfo.buf; param.date = drvinfo.file - drvinfo.buf; param.time = drvinfo.time - drvinfo.buf; memcpy(param.buf, drvinfo.buf, DE2FPGA_MAX_TAGLEN); p_in = (char *) ¶m; p_out = (char *) arg; for (i = 0; i < sizeof(struct de2fpga_info); i++) put_user(p_in[i], p_out + i); return 0; default: break; } return -ENOIOCTLCMD; } static int de2fpga_open(struct inode *inode, struct file *filp) { /* initialize state machine to read the bitstream file */ drvinfo.state = 0; drvinfo.offs = 0; drvinfo.prog = 0; return 0; } static int de2fpga_release(struct inode *inode, struct file *filp) { if (drvinfo.prog) { de2fpa_fpga_deselect(); de2fpga_reset_prog_data(); drvinfo.status = DE2FPGA_STS_ERROR; } return 0; } struct file_operations de2fpga_fops = { owner:THIS_MODULE, write:de2fpga_write, mmap:de2fpga_mmap, ioctl:de2fpga_ioctl, open:de2fpga_open, release:de2fpga_release, }; static int de2fpga_proc_read(char *buf, char **start, off_t offset, int count, int *eof, void *data) { int len = 0; len += sprintf(buf + len, "FPGA data\n"); len += sprintf(buf + len, "status: %s\n", drvinfo.status == DE2FPGA_STS_OK ? "ok" : drvinfo.status == DE2FPGA_STS_ERROR ? "error" : "unknown"); if (drvinfo.status == DE2FPGA_STS_OK) { len += sprintf(buf + len, "file: %s\n", drvinfo.file); len += sprintf(buf + len, "part: %s\n", drvinfo.part); len += sprintf(buf + len, "date: %s\n", drvinfo.date); len += sprintf(buf + len, "time: %s\n", drvinfo.time); } *eof = 1; return len; } static int __init de2fpga_init(void) { int result; result = register_chrdev(major, MDL_NAME, &de2fpga_fops); if (result < 0) { printk(KERN_WARNING "%s: can't get major %d\n", MDL_NAME, major); return result; } if (major == 0) major = result; create_proc_read_entry(MDL_NAME, 0, NULL, de2fpga_proc_read, NULL); printk(KERN_INFO "%s: Spartan II fpga driver installed (c,%d,0).\n", MDL_NAME, major); de2fpga_reset_prog_data(); de2fpga_setup_hw(); return 0; } static void __exit de2fpga_cleanup(void) { remove_proc_entry(MDL_NAME, NULL); unregister_chrdev(major, MDL_NAME); } module_init(de2fpga_init); module_exit(de2fpga_cleanup);