/* TI Davinci driver - C64X+ DSP Kernel Module (c) Copyright 2007 Telio AG Written by Olaf Dreesen . All rights reserved. This module is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This library 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 library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define C64X_IRQ MODULE_LICENSE("GPL v2"); //MODULE_LICENSE("Propietary"); MODULE_AUTHOR("Olaf Dreesen "); MODULE_DESCRIPTION("A little c64+ handling module."); #define C_MOD_MAJOR 400 #define C_MOD_NUM_DEV 1 #define C_MOD_NAME "c64x" #define F_NAME "c64x_drv.bin" #define CODE_BASE 0x00800000 /* DDR2: * * transfer buffer */ #define R_BASE DAVINCI_C64X_MEM #define R_LEN 0x02000000 /* L2RAM: * * 0x00800000 - 0x0080FFFF C64x+ * 0x11800000 - 0x1180FFFF ARM */ #define D_BASE 0x11800000 #define D_LEN 0x00010000 /* L1DRAM: * * 0x00F04000 - 0x00F0FFFF C64x+ * 0x11F04000 - 0x11F0FFFF ARM * * Queue controls @ 0x00F04000 (4096 Bytes) */ #define Q_BASE 0x11F04000 #define Q_LEN 0x00001000 #define HQueueDSP (l1dram[0x00>>2]) #define HQueueARM (l1dram[0x04>>2]) #define LQueueDSP (l1dram[0x08>>2]) #define LQueueARM (l1dram[0x0C>>2]) #define DSPidle (l1dram[0x10>>2]) /* IO Register needed: * * 0x01C40008 DSPBOOTADDR DSP Boot Address * 0x01C40010 INTGEN Interrupt Generator * 0x01C40038 CHP_SHRTSW DSP Power * 0x01C4169C MDCFG39 DSP Module config * 0x01C41A9C MDCTL39 DSP Module control */ #define IO_BASE 0x01c40000 #define IO_LEN 0x00010000 #define DSPBOOTADDR (mmr[0x0008>>2]) #define INTGEN (mmr[0x0010>>2]) #define CHP_SHRTSW (mmr[0x0038>>2]) #define MDCFG39 (mmr[0x169C>>2]) #define MDCTL39 (mmr[0x1A9C>>2]) MODULE_FIRMWARE(F_NAME); static dev_t dev_major; static struct cdev*dev_cdev; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) static struct class*dev_class; #else static struct class_simple*dev_class; #endif static volatile unsigned int*mmr=0; static unsigned char*l2ram=0; static volatile unsigned int*l1dram=0; static volatile void*dram=0; static volatile c64xTaskControl*c64xctl=0; static volatile c64xTask*queue_l=0; #ifdef C64X_IRQ static int dev_irq=46; static DECLARE_WAIT_QUEUE_HEAD( wait_irq ); /* IRQ */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21) static irqreturn_t dev_irq_handler(int irq,void*dev_id) { #else static irqreturn_t dev_irq_handler(int irq,void*dev_id,struct pt_regs*regs) { #endif wake_up_all( &wait_irq ); return IRQ_HANDLED; } #endif static u32 opencnt=0; /* char-dev */ static int dev_open(struct inode*inode,struct file*filp) { if (opencnt++==0) { DSPidle=0; MDCTL39=0x00000103; /* Go! Go, go Go! */ while(DSPidle==0); } return 0; } static int dev_release(struct inode*inode,struct file*filp) { if (--opencnt==0) { MDCTL39=0x00000000; /* local reset */ } return 0; } static ssize_t dev_write(struct file*filp,const char __user*buffer,size_t len,loff_t*off) { long ret=0; unsigned long offset=*off; if (offset=D_LEN) { len=D_LEN-offset; } // printk(KERN_INFO "c64x+ : read got offset %08lx %08lx\n",offset,(long)len); ret=len; *off+=len; } return ret; } static ssize_t dev_read(struct file*filp,char __user*buffer,size_t len,loff_t*off) { long ret=0; unsigned long offset=*off; if (offset=D_LEN) { len=D_LEN-offset; } // printk(KERN_INFO "c64x+ : read got offset %08lx %08lx\n",offset,(long)len); ret=len; ret-=copy_to_user(buffer,(l2ram+offset),len); *off+=len; } return ret; } static int dev_mmap(struct file * file, struct vm_area_struct * vma) { size_t size=vma->vm_end-vma->vm_start; if (vma->vm_pgoff) { if (size!=R_LEN) return -EINVAL; #if defined(pgprot_writecombine) vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); #else vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); #endif if (remap_pfn_range(vma, vma->vm_start, R_BASE>>PAGE_SHIFT, size, vma->vm_page_prot)) return -EAGAIN; } else { if (size!=Q_LEN) return -EINVAL; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); if (remap_pfn_range(vma, vma->vm_start, Q_BASE>>PAGE_SHIFT, size, vma->vm_page_prot)) return -EAGAIN; } return 0; } static void c64x_dump( const char *condition ) { static const char *state_names[] = { "DONE", "ERROR", "TODO", "RUNNING" }; uint32_t ql_dsp = c64xctl->QL_dsp; uint32_t ql_arm = c64xctl->QL_arm; uint32_t tl_dsp = queue_l[ql_dsp & C64X_QUEUE_MASK].c64x_function; uint32_t tl_arm = queue_l[ql_arm & C64X_QUEUE_MASK].c64x_function; int dl; dl = ql_arm - ql_dsp; if (dl < 0) dl += C64X_QUEUE_LENGTH; printk( "C64X+ Queue: %s\n" " [DSP %d / %d (%s), ARM %d / %d (%s)] <- %d pending\n", condition, ql_dsp, (tl_dsp >> 2) & 0x3fff, state_names[tl_dsp & 3], ql_arm, (tl_arm >> 2) & 0x3fff, state_names[tl_arm & 3], dl ); } static int c64x_wait_low( void ) { int ret; int num = 0; /* Keep reference values for comparison. */ u32 idle = c64xctl->idlecounter; u32 dsp = c64xctl->QL_dsp; /* Wait for equal pointers... */ while (dsp != c64xctl->QL_arm) { /* ...each time for a 1/50 second... */ ret = wait_event_interruptible_timeout( wait_irq, c64xctl->QL_dsp == c64xctl->QL_arm, HZ/50 ); if (ret < 0) return ret; /* ...if after that 1/50 second still the same command is running... */ if (!ret && c64xctl->QL_dsp == dsp) { /* ...and almost one second elapsed in total, or the DSP felt idle... */ if (++num > 42 || c64xctl->idlecounter != idle) { /* ...timeout! */ printk( KERN_ERR "c64x+ : timeout waiting for idle queue\n" ); c64x_dump( "TIMEOUT!!!" ); return -ETIMEDOUT; } } else { /* Different command running, reset total elapsed time. */ num = 0; } /* Update reference values. */ idle = c64xctl->idlecounter; dsp = c64xctl->QL_dsp; } return 0; } static int dev_ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg) { switch (cmd) { case C64X_IOCTL_RESET: MDCTL39=0x00000000; /* local reset */ mdelay(10); DSPidle=0; MDCTL39=0x00000103; break; case C64X_IOCTL_WAIT_LOW: return c64x_wait_low(); default: printk(KERN_INFO "c64x+ : unknown ioctl : cmd=%08x\n",cmd); return -EAGAIN; break; } return 0; } static struct file_operations dev_file_ops={ .owner = THIS_MODULE, .open = dev_open, .release = dev_release, .read = dev_read, .write = dev_write, .mmap = dev_mmap, .ioctl = dev_ioctl, }; /* INIT */ static __initdata struct device dev_device = { .bus_id = "c64x0", }; static int __init dev_init(void) { int ret=-EIO; u8 *at; const struct firmware*fw = NULL; printk(KERN_INFO "c64x+ : module load\n"); if ((dram=ioremap(R_BASE,R_LEN))==0) { printk(KERN_ERR "c64x+ : module couldn't get memory\n"); goto err0; } printk(KERN_INFO "c64x+ : module got memory @ %p\n",dram); queue_l = dram + 0x01e00000; /* get the 'device' memory */ if ((mmr=ioremap(IO_BASE,IO_LEN))==0) { printk(KERN_ERR "c64x+ : module couldn't get IO-MMR\n"); goto err0; } printk(KERN_INFO "c64x+ : DSP bootaddr: %08x\n",DSPBOOTADDR); printk(KERN_INFO "c64x+ : got mmr %p %08x %08x\n",mmr,MDCTL39,MDCFG39); printk(KERN_INFO "c64x+ : switch state: %08x\n",CHP_SHRTSW); MDCTL39=0x00000000; /* local reset */ mdelay(10); DSPBOOTADDR=CODE_BASE; /* set DSP base address */ // printk(KERN_INFO "c64x+ : check0: %p %08x %08x\n",mmr,MDCTL39,MDCFG39); /* get the 'device' memory */ if ((l1dram=ioremap(Q_BASE,Q_LEN))==0) { printk(KERN_ERR "c64x+ : module couldn't get L1 dsp-memory\n"); goto err1; } printk(KERN_INFO "c64x+ : module got L1D @ %p\n",l1dram); c64xctl = (volatile void*)l1dram; if ((l2ram=ioremap(D_BASE,D_LEN))==0) { printk(KERN_ERR "c64x+ : module couldn't get L2 dsp-memory\n"); goto err2; } printk(KERN_INFO "c64x+ : module got L2 @ %p\n",l2ram); /* request firmware */ device_initialize(&dev_device); ret=device_add(&dev_device); if (ret) { printk(KERN_ERR "c64x+ : device_add failed\n"); goto err3; } printk(KERN_INFO "c64x+ : module requesting firmware '%s'\n",F_NAME); ret=request_firmware(&fw,F_NAME,&dev_device); printk(KERN_INFO "c64x+ : module got fw %p\n",fw); if (ret) { printk(KERN_ERR "c64x+ : no firmware upload (timeout or file not found?)\n"); device_del(&dev_device); goto err3; } printk(KERN_INFO "c64x+ : firmware upload %p %zd\n",fw->data,fw->size); if (fw->size>32767) { printk(KERN_ERR "c64x+ : firmware too big! 32767 is maximum (for now)\n"); release_firmware(fw); device_del(&dev_device); goto err3; } if (memcmp(fw->data+8,"C64x+DV",8)) { printk(KERN_ERR "c64x+ : firmware signature missing\n"); release_firmware(fw); device_del(&dev_device); goto err3; } at = fw->data + fw->size; while ((ulong)--at > (ulong)fw->data) { if (*at == '@') break; } if (at == fw->data) { printk(KERN_ERR "c64x+ : firmware tag missing\n"); release_firmware(fw); device_del(&dev_device); goto err3; } printk(KERN_NOTICE "c64x+ : got firmware of length %d at %p with tag '%*s' of length %d at %p+1\n", fw->size, fw->data, (int)((ulong)(fw->data + fw->size) - (ulong)at - 1), at + 1, (int)((ulong)(fw->data + fw->size) - (ulong)at - 1), at ); /* move firmware into the hardware buffer here. */ memcpy(l2ram,fw->data,fw->size); release_firmware(fw); device_del(&dev_device); #if 0 /* release DSP */ printk(KERN_INFO "c64x+ : check1: %p %08x %08x\n",mmr,MDCTL39,MDCFG39); MDCTL39=0x00000103; /* Hopefully run... */ printk(KERN_INFO "c64x+ : check2: %p %08x %08x\n",mmr,MDCTL39,MDCFG39); printk(KERN_INFO "c64x+ : check3: %08x\n",DSPBOOTADDR); #endif /* register char-dev */ dev_major=MKDEV(C_MOD_MAJOR,0); ret=register_chrdev_region(dev_major,C_MOD_NUM_DEV,C_MOD_NAME); if (ret) { printk(KERN_ERR "c64x+ : can't get chrdev %d\n",C_MOD_MAJOR); goto err3; } /* allocate cdev */ dev_cdev=cdev_alloc(); dev_cdev->ops=&dev_file_ops; /* cdev_init(&dev_data.cdev,&dev_file_ops); */ ret=cdev_add(dev_cdev,dev_major,1); if (ret) { printk(KERN_ERR "c64x+ : can't allocate cdev\n"); goto err4; } #ifdef C64X_IRQ /* allocate interrupt slot */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21) ret=request_irq(dev_irq,dev_irq_handler,IRQF_DISABLED,C_MOD_NAME,NULL); #else ret=request_irq(dev_irq,dev_irq_handler,SA_INTERRUPT ,C_MOD_NAME,NULL); #endif if (ret) { printk(KERN_ERR "c64x+ : can't get IRQ %d\n",dev_irq); goto err5; } #endif /* tell sysfs/udev */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) dev_class=class_create(THIS_MODULE,C_MOD_NAME); #else dev_class=class_simple_create(THIS_MODULE,C_MOD_NAME); #endif if (IS_ERR(dev_class)) { ret=PTR_ERR(dev_class); printk(KERN_ERR "c64x+ : can't allocate class\n"); goto err6; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) class_device_create(dev_class,NULL,dev_major,NULL,C_MOD_NAME"%d",0); #else class_simple_device_add(dev_class,dev_major,NULL,C_MOD_NAME"%d",0); #endif printk(KERN_INFO "c64x+ : module load finished\n"); return 0; /* error out */ err6: #ifdef C64X_IRQ free_irq(dev_irq,0); err5: #endif cdev_del(dev_cdev); err4: unregister_chrdev_region(dev_major,1); err3: iounmap(l2ram); err2: iounmap((void*)l1dram); err1: iounmap((void*)mmr); err0: if (dram) iounmap((void*)dram); return ret; } module_init(dev_init); /* EXIT */ static void __exit dev_exit(void) { /* Put the DSP into Reset */ MDCTL39=0x00000000; /* release the DSP memory */ iounmap((void*)mmr); iounmap((void*)l1dram); iounmap(l2ram); /* release all the other resources */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) class_device_destroy(dev_class,dev_major); class_destroy(dev_class); #else class_simple_device_remove(dev_major); class_simple_destroy(dev_class); #endif #ifdef C64X_IRQ free_irq(dev_irq,0); #endif cdev_del(dev_cdev); unregister_chrdev_region(dev_major,C_MOD_NUM_DEV); } module_exit(dev_exit);