/* c_can_irq.c - Hynix HMS30c7202 ARM IRQ handling code
 * Linux CAN-bus device driver.
 * Written by Sebastian Stolzenberg email:stolzi@sebastian-stolzenberg.de
 * Based on code from Arnaud Westenberg email:arnaud@wanadoo.nl
 * and Ake Hedman, eurosource, akhe@eurosource.se
 * Rewritten for new CAN queues by Pavel Pisa - OCERA team member
 * email:pisa@cmp.felk.cvut.cz
 * This software is released under the GPL-License.
 * Version lincan-0.3  17 Jun 2004
 */
 
#include "../include/can.h"
#include "../include/can_sysdep.h"
#include "../include/main.h"
#include "../include/c_can.h"

union c_can_data
{
   unsigned short wdata[4];
   unsigned char bdata[8];
};



// prototypes
inline void c_can_irq_read_handler( struct canchip_t *pchip, int idxobj, u32 msgid  );

inline void c_can_irq_write_handler( struct canchip_t *pchip, int idxobj);

void c_can_irq_rtr_handler( struct canchip_t *pchip, int idxobj, u32 msgid );

u16 readMaskCM = IFXCM_ARB | IFXCM_CNTRL | IFXCM_CLRINTPND
  | IFXCM_TRND | IFXCM_DA | IFXCM_DB;

u16 msgLstReadMaskCM = IFXCM_CNTRL;
u16 msgLstWriteMaskCM =  IFXCM_CNTRL | IFXCM_WRRD;

///////////////////////////////////////////////////////////////////////////////
// c_can_irq_write_handler
//
// Send a message from the output fifo ( if any ).
//

inline void c_can_irq_write_handler( struct canchip_t *pchip, int idxobj)
{
	int cmd;
	struct msgobj_t *pmsgobj = pchip->msgobj[idxobj];

	DEBUGMSG("(c%dm%d)calling c_can_irq_write_handler(...)\n",
		pchip->chip_idx, pmsgobj->object);

	if(pmsgobj->tx_slot){
		/* Do local transmitted message distribution if enabled */
		if (processlocal){
			/* fill CAN message timestamp */
			can_filltimestamp(&pmsgobj->tx_slot->msg.timestamp);

			pmsgobj->tx_slot->msg.flags |= MSG_LOCAL;
			canque_filter_msg2edges(pmsgobj->qends, &pmsgobj->tx_slot->msg);
		}
		/* Free transmitted slot */
		canque_free_outslot(pmsgobj->qends, pmsgobj->tx_qedge, pmsgobj->tx_slot);
		pmsgobj->tx_slot=NULL;
	}
   
	// Get ready to send next message
	spin_lock( &c_can_spwlock );

	cmd=canque_test_outslot(pmsgobj->qends, &pmsgobj->tx_qedge, &pmsgobj->tx_slot);
	if(cmd<0){
		DEBUGMSG("(c%dm%d)Nothin to write\n",
			pchip->chip_idx, pmsgobj->object);
		spin_unlock( &c_can_spwlock );
		return;
	}

	// Send the message
	if ( pchip->chipspecops->send_msg( pchip, pmsgobj,&pmsgobj->tx_slot->msg) ) {
		pmsgobj->ret = -1;
		canque_notify_inends(pmsgobj->tx_qedge, CANQUEUE_NOTIFY_ERRTX_SEND);
		canque_free_outslot(pmsgobj->qends, pmsgobj->tx_qedge, pmsgobj->tx_slot);
		pmsgobj->tx_slot=NULL;
		spin_unlock( &c_can_spwlock );
		DEBUGMSG("(c%dm%d)c_can_irq_handler: Unable to send message\n",
			pchip->chip_idx, pmsgobj->object );
		return;
	} else {
		// Another message sent
	    #ifdef CAN_WITH_STATISTICS
		pchip->stat.cntTxPkt++;
		pchip->stat.cntTxData += pmsgobj->tx_slot->length;
	    #endif /*CAN_WITH_STATISTICS*/
	}
	spin_unlock( &c_can_spwlock );

	// Wake up any waiting writer
	return;
}

///////////////////////////////////////////////////////////////////////////////
// c_can_irq_read_handler
//
// Message received form the line. Write it in the input fifo->
//

inline void c_can_irq_read_handler( struct canchip_t *pchip,
				    int idxobj, u32 msgid  )
{
	int i=0;
	u16 bDataAvail=1 ;
	u16 msgCntlReg = 0;
	union c_can_data readData;
	struct msgobj_t *pmsgobj = pchip->msgobj[idxobj];
		
	DEBUGMSG("(c%dm%d)calling c_can_irq_read_handler(...)\n",
		pchip->chip_idx, pmsgobj->object);
	
	while ( bDataAvail ) {
	     
	    #ifdef CAN_WITH_STATISTICS
		pchip->stat.cntRxFifoOvr++;
	    #endif /*CAN_WITH_STATISTICS*/
		// Message length
		msgCntlReg = c_can_read_reg_w( pchip, CCIF1DMC );
	
		pmsgobj->rx_msg.length = msgCntlReg & 0x000F;
	
		// Message id
		pmsgobj->rx_msg.id = (u32)msgid;
	
		// Fetch message bytes
		if (pmsgobj->rx_msg.length > 0)
			readData.wdata[0] = c_can_read_reg_w(pchip, CCIF1DA1);
		if (pmsgobj->rx_msg.length > 2)
			readData.wdata[1] = c_can_read_reg_w(pchip, CCIF1DA2);
		if (pmsgobj->rx_msg.length > 4)
			readData.wdata[2] = c_can_read_reg_w(pchip, CCIF1DB1);
		if (pmsgobj->rx_msg.length > 6)
			readData.wdata[3] = c_can_read_reg_w(pchip, CCIF1DB2);
		
		for ( i=0; i < pmsgobj->rx_msg.length; i++ ) {
			pmsgobj->rx_msg.data[ i ] = readData.bdata[i];
		}
		DEBUGMSG("(c%dm%d)Received Message:\n",
		      pchip->chip_idx, pmsgobj->object);
		DEBUGMSG(" id = %ld\n",
		      pmsgobj->rx_msg.id);
		DEBUGMSG(" length = %d\n",
		      pmsgobj->rx_msg.length);
		for ( i=0; i < pmsgobj->rx_msg.length; i++ )
			DEBUGMSG(" data[%d] = 0x%.2x\n", i, pmsgobj->rx_msg.data[i]);
		
		/* fill CAN message timestamp */
		can_filltimestamp(&pmsgobj->rx_msg.timestamp);

		canque_filter_msg2edges(pmsgobj->qends, &pmsgobj->rx_msg);
	    
	    #ifdef CAN_WITH_STATISTICS
		// Another received packet
		pchip->stat.cntRxPkt++;

		// Add databytes read to statistics block
		pchip->stat.cntRxData += pmsgobj->rx_msg.length;
	    #endif /*CAN_WITH_STATISTICS*/

		spin_unlock( &c_can_sprlock );

		// Check if new data arrived
		if (c_can_if1_busycheck(pchip)) ;
		c_can_write_reg_w(pchip, readMaskCM, CCIF1CM);
		c_can_write_reg_w(pchip, idxobj+1, CCIF1CR);
		if (c_can_if1_busycheck(pchip)) ;
		if ( !( ( bDataAvail = c_can_read_reg_w( pchip, CCIF1DMC ) ) &
			IFXMC_NEWDAT ) ) {
			break;
		}

		if ( bDataAvail & IFXMC_MSGLST ) {
			CANMSG("(c%dm%d)c-can fifo full: Message lost!\n",
			pchip->chip_idx, pmsgobj->object);
		}

	}
	// while
}

void c_can_irq_sync_activities(struct canchip_t *chip, struct msgobj_t *obj)
{
	while(!can_msgobj_test_and_set_fl(obj,TX_LOCK)) {

		/*if(can_msgobj_test_and_clear_fl(obj,TX_REQUEST)) {
			if(canobj_read_reg(chip,obj,iMSGCTL1)&TXRQ_RES)
				i82527_irq_write_handler(chip, obj);
		}

		if(!obj->tx_slot) {
			if(can_msgobj_test_and_clear_fl(obj,FILTCH_REQUEST)) {
				i82527_irq_update_filter(chip, obj);
			}
		}*/
		/* FIXME: these functionality has to be implemented to start TX */

		can_msgobj_clear_fl(obj,TX_LOCK);
		if(can_msgobj_test_fl(obj,TX_REQUEST))
			continue;
		if(can_msgobj_test_fl(obj,FILTCH_REQUEST) && !obj->tx_slot)
			continue;
		break;
	}
}


///////////////////////////////////////////////////////////////////////////////
// c_can_irq_handler
//

int c_can_irq_handler(int irq, struct canchip_t *chip)
{
	struct rtr_id *rtr_search = hardware_p->rtr_queue;
	u16 chip_status;
	int id0=0, id1=0;
	u16 errcount = 0;
	u16 irqreg = 0;
	u32 msgid = 0;
	u16 tempCntlReg = 0;
	//#ifdef CAN_DEBUG
	// u32 intCntrVAddr = 0;
	//#endif
	//unsigned short flags = 0;

	//if (pchip->ntype != CAN_CHIPTYPE_C_CAN)	{
	//	DEBUGMSG("\n(c%d)IRQ not for c_can_irq_handler(...)\n", pchip->chip_idx);
	//	return;
	//}

	irqreg = c_can_read_reg_w( pchip, CCINTR );

	if(!irqreg) {
		DEBUGMSG( "\n(c%d)IRQ handler: addr=%.8lx spurious interrupt\n",
			pchip->chip_idx,
			(long)( pchip->/*v*/chip_base_addr/* + CCSR*/));
		return CANCHIP_IRQ_NONE;
	}
	
	DEBUGMSG( "\n(c%d)IRQ handler: addr=%.8lx irqreg=0x%.4x\n",
	     pchip->chip_idx,
	     (long)( pchip->/*v*/chip_base_addr/* + CCSR*/),
	     irqreg);


	
    #ifdef REGDUMP
	c_can_registerdump(pchip);
    #endif

/*
#ifdef CAN_DEBUG
	if ( (!( intCntrVAddr = (u32)ioremap( 0x80024000, 0xCD ) ))) {
		DEBUGMSG("Failed to map Interrupt Controller IO-memory\n");
	}
	else {

   DEBUGMSG( "Mapped Interrupt Controller IO-memory: 0x%lx - 0x%lx to 0x%lx\n",
	   	(unsigned long)0X80024000,
	      (unsigned long)0X800240CC,
	      (unsigned long)intCntrVAddr);
	}

	DEBUGMSG("Current Interrupt Status Register (ISR): 0x%4.4lx\n",
				(long)readl(intCntrVAddr + 4));
	DEBUGMSG("Current Interrupt ID: %d\n",
				(int)(readl(intCntrVAddr + 0x90) & 0xF));
	iounmap( (void*)intCntrVAddr);
	DEBUGMSG( "Unmapped Interrupt Controller IO-memory: 0x%lx\n",
	      (unsigned long)intCntrVAddr);
#endif
*/
	while ( irqreg ){
		// Handle change in status register
	    
		if ( irqreg == INT_STAT ) {
			chip_status = c_can_read_reg_w( pchip, CCSR );
			DEBUGMSG( "(c%d)Status register: 0x%x\n",
				pchip->chip_idx, chip_status );
		
			if ( chip_status & SR_EWARN ) {
				// There is an abnormal # of errors
			    #ifdef CAN_WITH_STATISTICS
				pchip->stat.cntWarnings++;
			    #endif /*CAN_WITH_STATISTICS*/
				errcount = c_can_read_reg_w( pchip, CCEC);
				DEBUGMSG("(c%d)stat: c_can_irq_handler: Abnormal number of Errors Warning\n"
					 "       txErr=%d, rxErr=%d\n",
					pchip->chip_idx, (errcount&0x00ff), ((errcount&0x7f00)>>8));

				/*
				// this code deactivates the chip if the transmiterrorcounter grew above 127
				if ((pchip->stat.cntWarnings > 100) && ((errcount&0x00ff) > 127))
				{
					CANMSG("(c%d)to much Errornumber warnings (>100), deactivating chip",
					pchip->chip_idx);
					pchip->config_irqs(pchip, 0);
					pchip->enable_configuration(pchip);
					pchip->clear_objects(pchip);
					pchip->flags &= ~CHANNEL_CONFIGURED;
					return;
				}*/
			}

			if ( chip_status & SR_EPASS ) {
				// There is an abnormal # of errors
			    #ifdef CAN_WITH_STATISTICS
				pchip->stat.cntErrPassive++;
			    #endif /*CAN_WITH_STATISTICS*/
				DEBUGMSG("(c%d)stat: c_can_irq_handler: Chip entering Error Passive Mode\n",
				pchip->chip_idx);
			}

			if ( chip_status & SR_BOFF ) {
				// We have a bus off condition
			    #ifdef CAN_WITH_STATISTICS
				pchip->stat.cntBusOff++;
			    #endif /*CAN_WITH_STATISTICS*/
				//pchip->fifo->tx_in_progress = 0;
				//reset init bit
				CANMSG("(c%d)stat: c_can_irq_handler: Bus Off\n",
					pchip->chip_idx);
				/*if (pchip->stat.cntBusOff > 100)
				{
				CANMSG("(c%d)to much busoff warnings (>100), deactivating chip",
					pchip->chip_idx);
				pchip->config_irqs(pchip, 0);
				pchip->enable_configuration(pchip);
				pchip->clear_objects(pchip);
				pchip->flags &= ~CHANNEL_CONFIGURED;
				return;
				}
				else*/
				CANMSG("(c%d)try to reconnect",
					pchip->chip_idx);
				pchip->chipspecops->disable_configuration(pchip);
			}

			if (chip_status & SR_TXOK) {
				DEBUGMSG("(c%d)stat: Transmitted a Message successfully\n",
					pchip->chip_idx);
				c_can_write_reg_w(pchip, chip_status & ~SR_TXOK, CCSR);
			}
		
			if (chip_status & SR_RXOK) {
				DEBUGMSG("(c%d)stat: Received a Message successfully\n",
					pchip->chip_idx);
				c_can_write_reg_w(pchip, chip_status & ~SR_RXOK, CCSR);
			}
		
		    #ifdef CAN_WITH_STATISTICS
			// Errors to statistics
			switch( chip_status & 0x07 )
			{
				case SRLEC_NE: // No error
					break;
				case SRLEC_SE: // Stuff error
					pchip->stat.cntStuffErr++;
					break;
				case SRLEC_FE: // Form error
					pchip->stat.cntFormErr++;
					break;
				case SRLEC_AE: // Ack error
					pchip->stat.cntAckErr++;
					break;
				case SRLEC_B1: // Bit 1 error
					pchip->stat.cntBit1Err++;
					break;
				case SRLEC_B0: // Bit 0 error
					pchip->stat.cntBit0Err++;
					break;
				case SRLEC_CR: // CRC error
					pchip->stat.cntCrcErr++;
					break;
				case 7: // unused
					break;
			}
		    #endif /*CAN_WITH_STATISTICS*/
			//return; // continue?
		} else {
			if (irqreg >0 && irqreg <33) {
				struct msgobj_t *pmsgobj;
				int idxobj;
				
				//get id
				idxobj = irqreg-1;
				pmsgobj=pchip->msgobj[idxobj];
				
				//DEBUGMSG( "Interrupt handler: addr=%lx devid=%lx irqreq=%x status=0x%x\n",
				//	      (unsigned long)pchip->vbase_addr + iIRQ,
				//      (unsigned long)dev_id,
				//      irqreg,
				//      statreg );
				//
				spin_lock( &c_can_if1lock );
		
				//Message Lost Check
				if (c_can_if1_busycheck(pchip)) ; /*??????????*/
				c_can_write_reg_w(pchip, msgLstReadMaskCM, CCIF1CM);
				c_can_write_reg_w(pchip, idxobj+1, CCIF1CR);
		
				if (c_can_if1_busycheck(pchip)) ; /*??????????*/
				tempCntlReg = c_can_read_reg_w( pchip, CCIF1DMC );

				if (tempCntlReg & IFXMC_MSGLST) {
					CANMSG("(c%dm%d)Chip lost a message\n",
						pchip->chip_idx, pmsgobj->object);
				    #ifdef CAN_WITH_STATISTICS
					pchip->stat.cntMsgLst++;
				    #endif /*CAN_WITH_STATISTICS*/
		       
					//Reset Message Lost Bit
					tempCntlReg = tempCntlReg & (~IFXMC_MSGLST);
					c_can_write_reg_w(pchip, tempCntlReg, CCIF1DMC);
					c_can_write_reg_w(pchip, msgLstWriteMaskCM, CCIF1CM);
					c_can_write_reg_w(pchip, idxobj+1, CCIF1CR);
				}

				//transfer Message Object to IF1 Buffer
				if (c_can_if1_busycheck(pchip)) ;
				c_can_write_reg_w(pchip, readMaskCM, CCIF1CM);
				c_can_write_reg_w(pchip, idxobj+1, CCIF1CR);

				if (c_can_if1_busycheck(pchip)) ;
				if (c_can_read_reg_w(pchip, CCIF1A2) & IFXARB2_DIR) {
					spin_unlock( &c_can_if1lock );
					c_can_irq_write_handler(pchip,idxobj);
				} else {
					if (can_msgobj_test_fl(pmsgobj,RX_MODE_EXT)) {
						id0=c_can_read_reg_w(pchip, CCIF1A1);
						id1=(c_can_read_reg_w(pchip, CCIF1A2)&0x1FFF)<<16;
						msgid= id0|id1;
					} else {
						msgid=((c_can_read_reg_w(pchip, CCIF1A2)&0x1FFC)>>2)&0x7FF;
					}
					spin_unlock( &c_can_if1lock );

					spin_lock(&hardware_p->rtr_lock);
					while ( rtr_search != NULL )
					{
						if ( rtr_search->id == msgid ) {
							break;
						}
						rtr_search = rtr_search->next;
					}
					spin_unlock(&hardware_p->rtr_lock);

					spin_lock( &c_can_if1lock );

					//transfer Message Object to IF1 Buffer
					if (c_can_if1_busycheck(pchip)) ;
					c_can_write_reg_w(pchip, readMaskCM, CCIF1CM);
					c_can_write_reg_w(pchip, idxobj+1, CCIF1CR);
			
					if (c_can_if1_busycheck(pchip)) ;
			
					if ( ( rtr_search != NULL ) && (rtr_search->id == msgid ) ) {
						c_can_irq_rtr_handler( pchip, idxobj, msgid );
					} else {
						c_can_irq_read_handler( pchip, idxobj, msgid );
					}
					spin_unlock( &c_can_if1lock );
			
					//}
				}
			//else
			}
		//if
		}
		// Get irq status again
		irqreg = c_can_read_reg_w( pchip, CCINTR );
	}
	return CANCHIP_IRQ_HANDLED;
}

///////////////////////////////////////////////////////////////////////////////
// c_can_irq_rtr_handler
//

void c_can_irq_rtr_handler( struct canchip_t *pchip, int idxobj, u32 msgid   )
{
	short int i=0;
	struct rtr_id *prtr_search = hardware_p->rtr_queue;
	union c_can_data rtrData;
	
	spin_lock( &hardware_p->rtr_lock );
	
	prtr_search->rtr_message->id = msgid;
	prtr_search->rtr_message->length =
		( c_can_read_reg_w( pchip, CCIF1DMC ) & 0x000f);
	
	// Fetch message bytes
	if (prtr_search->rtr_message->length > 0)
		rtrData.wdata[0] = c_can_read_reg_w(pchip, CCIF1DA1);
	if (prtr_search->rtr_message->length > 2)
		rtrData.wdata[1] = c_can_read_reg_w(pchip, CCIF1DA2);
	if (prtr_search->rtr_message->length > 4)
		rtrData.wdata[2] = c_can_read_reg_w(pchip, CCIF1DB1);
	if (prtr_search->rtr_message->length > 6)
		rtrData.wdata[3] = c_can_read_reg_w(pchip, CCIF1DB2);
	
	for ( i=0; i<prtr_search->rtr_message->length; i++ ) {
		prtr_search->rtr_message->data[ i ] = rtrData.bdata[i];
	}
	
	spin_unlock( &hardware_p->rtr_lock );
	wake_up_interruptible( &prtr_search->rtr_wq );
	return;
}

