/*************************************************************************** * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 * * by Terraneo Federico * * * * 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. * * * * As a special exception, if other files instantiate templates or use * * macros or inline functions from this file, or you compile this file * * and link it with other works to produce a work based on this file, * * this file does not by itself cause the resulting work to be covered * * by the GNU General Public License. However the source code for this * * file must still be made available in accordance with the GNU General * * Public License. This exception does not invalidate any other reasons * * why a work based on this file might be covered by the GNU General * * Public License. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #include #include #include #include "serial_lpc2000.h" #include "kernel/sync.h" #include "kernel/scheduler/scheduler.h" #include "interfaces/portability.h" #include "filesystem/ioctl.h" #include "LPC213x.h" namespace miosix { /// Pointer to serial port classes to let interrupts access the classes static LPC2000Serial *ports[2]={0}; /** * \internal interrupt routine for usart0 actual implementation */ void __attribute__((noinline)) usart0irqImpl() { if(ports[0]) ports[0]->IRQhandleInterrupt(); VICVectAddr=0xff;//Restart VIC } /** * \internal interrupt routine for usart0 */ void __attribute__((interrupt("IRQ"),naked)) usart0irq() { saveContextFromIrq(); asm volatile("bl _ZN6miosix13usart0irqImplEv"); restoreContext(); } /** * \internal interrupt routine for usart1 actual implementation */ void __attribute__((noinline)) usart1irqImpl() { if(ports[1]) ports[1]->IRQhandleInterrupt(); VICVectAddr=0xff;//Restart VIC } /** * \internal interrupt routine for usart1 */ void __attribute__ ((interrupt("IRQ"),naked)) usart1irq() { saveContextFromIrq(); asm volatile("bl _ZN6miosix13usart1irqImplEv"); restoreContext(); } // // class LPC2000Serial // // A note on the baudrate/500: the buffer is selected so as to withstand // 20ms of full data rate. In the 8N1 format one char is made of 10 bits. // So (baudrate/10)*0.02=baudrate/500 LPC2000Serial::LPC2000Serial(int id, int baudrate) : Device(Device::TTY), rxQueue(hwRxQueueLen+baudrate/500), rxWaiting(0), idle(true) { InterruptDisableLock dLock; if(id<0 || id>1 || ports[id]!=0) errorHandler(UNEXPECTED); ports[id]=this; if(id==0) { serial=reinterpret_cast(0xe000c000); PCONP|=(1<<3);//Enable UART0 peripheral PINSEL0&=~(0xf);//Clear bits 0 to 3 of PINSEL0 PINSEL0|=0x5;//Set p0.0 as TXD and p0.1 as RXD //Init VIC VICSoftIntClr=(1<<6);//Clear uart0 interrupt flag (if previously set) VICIntSelect&=~(1<<6);//uart0=IRQ VICIntEnable=(1<<6);//uart0 interrupt ON VICVectCntl2=0x20 | 0x6;//Slot 2 of VIC used by uart0 VICVectAddr2=reinterpret_cast(&usart0irq); } else { serial=reinterpret_cast(0xe0010000); PCONP|=(1<<4);//Enable UART1 peripheral PINSEL0&=~(0xf0000);//Clear bits 16 to 19 of PINSEL0 PINSEL0|=0x50000;//Set p0.8 as TXD and p0.9 as RXD //Init VIC VICSoftIntClr=(1<<7);//Clear uart1 interrupt flag (if previously set) VICIntSelect&=~(1<<7);//uart1=IRQ VICIntEnable=(1<<7);//uart1 interrupt ON VICVectCntl3=0x20 | 0x7;//Slot 3 of VIC used by uart1 VICVectAddr3=reinterpret_cast(&usart1irq); } serial->LCR=0x3;//DLAB disabled //0x07= fifo enabled, reset tx and rx hardware fifos //0x80= uart rx fifo trigger level 8 characters serial->FCR=0x07 | 0x80; serial->LCR=0x83;//8data bit, 1stop, no parity, DLAB enabled int div=TIMER_CLOCK/16/baudrate; serial->DLL=div & 0xff; serial->DLM=(div>>8) & 0xff; serial->LCR=0x3;//DLAB disabled serial->IER=0x7;//Enable RLS, RDA, CTI and THRE interrupts } ssize_t LPC2000Serial::readBlock(void *buffer, size_t size, off_t where) { Lock l(rxMutex); char *buf=reinterpret_cast(buffer); size_t result=0; FastInterruptDisableLock dLock; for(;;) { //Try to get data from the queue for(;result0) break; if(result==size) break; //Wait for data in the queue do { rxWaiting=Thread::IRQgetCurrentThread(); Thread::IRQwait(); { FastInterruptEnableLock eLock(dLock); Thread::yield(); } } while(rxWaiting); } return result; } ssize_t LPC2000Serial::writeBlock(const void *buffer, size_t size, off_t where) { Lock l(txMutex); FastInterruptDisableLock dLock; size_t len=size; const char *buf=reinterpret_cast(buffer); while(len>0) { //If no data in software and hardware queue if((serial->LSR & (1<<5)) && (txQueue.isEmpty())) { //Fill hardware queue first for(int i=0;iTHR=*buf++; len--; if(len==0) break; } } else { if(txQueue.IRQput(*buf)==true) { buf++; len--; } else { FastInterruptEnableLock eLock(dLock); txQueue.waitUntilNotFull(); } } } return size; } void LPC2000Serial::IRQwrite(const char *str) { while((*str)!='\0') { //Wait until the hardware fifo is ready to accept one char while(!(serial->LSR & (1<<5))) ; //wait serial->THR=*str++; } waitSerialTxFifoEmpty(); } int LPC2000Serial::ioctl(int cmd, void* arg) { if(reinterpret_cast(arg) & 0b11) return -EFAULT; //Unaligned termios *t=reinterpret_cast(arg); switch(cmd) { case IOCTL_SYNC: waitSerialTxFifoEmpty(); return 0; case IOCTL_TCGETATTR: t->c_iflag=IGNBRK | IGNPAR; t->c_oflag=0; t->c_cflag=CS8; t->c_lflag=0; return 0; case IOCTL_TCSETATTR_NOW: case IOCTL_TCSETATTR_DRAIN: case IOCTL_TCSETATTR_FLUSH: //Changing things at runtime unsupported, so do nothing, but don't //return error as console_device.h implements some attribute changes return 0; default: return -ENOTTY; //Means the operation does not apply to this descriptor } } void LPC2000Serial::IRQhandleInterrupt() { char c; bool hppw=false; bool wakeup=false; switch(serial->IIR & 0xf) { case 0x6: //RLS c=serial->LSR;//Read LSR to clear interrupt c=serial->RBR;//Read RBR to discard char that caused error break; case 0x4: //RDA //Note: read one less char than HARDWARE_RX_QUEUE_LENGTH as the //CTI interrupt only occurs if there's at least one character in //the buffer, and we always want the CTI interrupt for(int i=0;iRBR)==false) /*fifo overflow*/; wakeup=true; idle=false; break; case 0xc: //CTI while(serial->LSR & (1<<0)) if(rxQueue.tryPut(serial->RBR)==false) /*fifo overflow*/; wakeup=true; idle=true; break; case 0x2: //THRE for(int i=0;iTHR=c; } break; } if(wakeup && rxWaiting) { rxWaiting->IRQwakeup(); if(rxWaiting->IRQgetPriority()> Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true; rxWaiting=0; } if(hppw) Scheduler::IRQfindNextThread(); } LPC2000Serial::~LPC2000Serial() { waitSerialTxFifoEmpty(); InterruptDisableLock dLock; //Disable UART0 serial->LCR=0;//DLAB disabled serial->FCR=0; int id=0; if(ports[0]==this) id=0; else if(ports[1]==this) id=1; else errorHandler(UNEXPECTED); ports[id]=0; if(id==0) { //Disable VIC VICIntEnClr=(1<<6); //Disable PIN PINSEL0&=~(0xf);//Clear bits 0 to 3 of PINSEL0 } else { //Disable VIC VICIntEnClr=(1<<7); //Disable PIN PINSEL0&=~(0xf0000);//Clear bits 16 to 19 of PINSEL0 } } } //namespace miosix