/*
* linux/drivers/net/cs8900.c
*
* Cirrus Logic CS8900A driver for Linux 2.6.27,
* fitting SMDK2410 board only.
*
* Author: Jianying Liu, <rssn@163.com>
* Date: 2010-2-18
* ( Please DO NOT remove these messages while redistributing. )
*
* This source code 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.
*
*/
#define DRIVER_NAME "CS8900 Driver for S3C2410, v1.0"
//#define CS8900_DEBUG
#define FULL_DUPLEX
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/irq.h>
#include <asm/mach-types.h>
#include <mach/irqs.h>
#ifdef CONFIG_ARCH_S3C2410
#include <mach/regs-mem.h>
#include <mach/regs-gpio.h>
#include <asm/plat-s3c24xx/common-smdk.h>
#include <asm/plat-s3c24xx/smdk2410.h>
#endif
#include "cs8900.h"
typedef struct cs8900_priv
{
int iomem_sz;
spinlock_t lock;
struct net_device_stats stats;
struct timer_list timer;
} cs8900_priv_t;
/* --- Declarations start --- */
static u16 cs8900_readw(struct net_device *dev, u16 reg_addr);
static void cs8900_writew(struct net_device *dev, u16 reg_addr, u16 value);
static void cs8900_unsetw(struct net_device *dev, u16 reg_addr, u16 mask);
static void cs8900_rx(struct net_device *dev);
static irqreturn_t cs8900_interrupt(int irq, void *id);
static int cs8900_start_xmit(struct sk_buff *skb, struct net_device *dev);
static int cs8900_open(struct net_device *dev);
static int cs8900_stop(struct net_device *dev);
static int cs8900_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
static struct net_device_stats *cs8900_stats(struct net_device *dev);
static void cs8900_tx_timeout(struct net_device *dev);
static int set_mac_address(struct net_device *dev, void *addr);
static int cs8900_init(struct net_device *dev);
static void cs8900_timer(unsigned long data);
/* --- Declarations end --- */
static struct net_device *cs8900_dev = NULL;
#ifdef CS8900_DEBUG
static void cs8900_watchw(struct net_device *dev, u16 reg_addr)
{
printk("%s: @0x%x = 0x%x\n", dev->name, reg_addr, cs8900_readw(dev, reg_addr) );
}
#endif
static u16 cs8900_readw(struct net_device *dev, u16 reg_addr)
{
outw(reg_addr, dev->base_addr + PP_Address);
return inw(dev->base_addr + PP_Data);
}
static void cs8900_writew(struct net_device *dev, u16 reg_addr, u16 value)
{
outw(reg_addr, dev->base_addr + PP_Address);
outw(value, dev->base_addr + PP_Data);
}
static void cs8900_setw(struct net_device *dev, u16 reg_addr, u16 mask)
{
cs8900_writew(dev, reg_addr, cs8900_readw(dev, reg_addr) | mask);
}
static void cs8900_unsetw(struct net_device *dev, u16 reg_addr, u16 mask)
{
cs8900_writew(dev, reg_addr, cs8900_readw(dev, reg_addr) & ~mask);
}
static void cs8900_rx(struct net_device *dev)
{
cs8900_priv_t *priv = netdev_priv(dev);
u16 status, len;
struct sk_buff *skb;
status = cs8900_readw(dev, PP_RxStatus);
len = cs8900_readw(dev, PP_RxLength);
if(!(status & RxOK))
{
priv->stats.rx_errors++;
if((status & (Runt | Extradata))) priv->stats.rx_length_errors++;
if((status & CRCerror)) priv->stats.rx_crc_errors++;
return;
}
/* Allocate sk_buff for received packet data */
if((skb = dev_alloc_skb(len + 4)) == NULL)
{
priv->stats.rx_dropped++;
return;
}
skb->dev = dev;
skb_reserve(skb, 2);
ioread16_rep((void *)dev->base_addr, skb_put(skb, len), (len + 1) / 2);
skb->protocol = eth_type_trans(skb, dev);
/* Notify the higher level with it... */
netif_rx(skb);
//printk("%s: %s(): status: 0x%x, len: %d\n", dev->name, __FUNCTION__, status, len);
dev->last_rx = jiffies;
priv->stats.rx_packets++;
priv->stats.rx_bytes += len;
/* */
}
static irqreturn_t cs8900_interrupt(int irq, void *id)
{
struct net_device *dev = (struct net_device *)id;
cs8900_priv_t *priv = netdev_priv(dev);
u16 status;
int handled = 0;
while((status = cs8900_readw(dev, PP_ISQ)))
{
handled = 1;
switch(RegNum(status))
{
case RxEvent:
cs8900_rx(dev);
#ifdef CS8900_DEBUG
printk("%s: In interrupt [RxEvent]\n", dev->name);
#endif
break;
case TxEvent:
#ifdef CS8900_DEBUG
printk("%s: In interrupt [TxEvent]\n", dev->name);
#endif
priv->stats.collisions += ColCount(cs8900_readw(dev, PP_TxCOL));
if(!(RegContent(status) & TxOK))
{
priv->stats.tx_errors++;
if((RegContent(status) & Out_of_window))
priv->stats.tx_window_errors++;
if((RegContent(status) & Jabber))
priv->stats.tx_aborted_errors++;
break;
}
netif_wake_queue(dev);
break;
case BufEvent:
#ifdef CS8900_DEBUG
printk("%s: In interrupt [BufEvent]\n", dev->name);
#endif
if(RegContent(status) & RxMiss)
{
u16 missed = MissCount(cs8900_readw(dev, PP_RxMISS));
priv->stats.rx_errors++;
priv->stats.rx_missed_errors += missed;
}
if(RegContent(status) & TxUnderrun)
{
priv->stats.tx_errors++;
priv->stats.tx_fifo_errors++;
netif_wake_queue(dev);
}
break;
case TxCOL:
#ifdef CS8900_DEBUG
printk("%s: In interrupt [TxCOL]\n", dev->name);
#endif
priv->stats.collisions += ColCount(cs8900_readw(dev, PP_TxCOL));
break;
case RxMISS:
#ifdef CS8900_DEBUG
printk("%s: In interrupt [RxMISS]\n", dev->name);
#endif
status = MissCount(cs8900_readw(dev, PP_RxMISS));
priv->stats.rx_errors += status;
priv->stats.rx_missed_errors += status;
break;
default:
#ifdef CS8900_DEBUG
printk("%s: In interrupt [type: 0x%x]\n", dev->name, status);
#endif
break;
}
}
return IRQ_RETVAL(handled);
}
static int cs8900_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
cs8900_priv_t *priv = (cs8900_priv_t *)netdev_priv(dev);
u16 busst;
//int i;
spin_lock_irq(&priv->lock);
netif_stop_queue(dev);
/* Send TxStart command */
cs8900_writew(dev, PP_TxCMD, TxStart(After5));
cs8900_writew(dev, PP_TxLength, skb->len);
busst = cs8900_readw(dev, PP_BusST);
if(!(busst & Rdy4TxNOW))
{
netif_wake_queue(dev);
spin_unlock_irq(&priv->lock);
#ifdef CS8900_DEBUG
printk(KERN_WARNING "%s: Chip not ready for sending.\n", dev->name);
#endif
priv->stats.tx_errors++;
return 1;
}
/* Send packet data */
iowrite16_rep((void *)dev->base_addr, skb->data, (skb->len + 1) / 2);
//for(i=0; i<(skb->len + 1) / 2; i++)
// cs8900_writew(dev, 0x0, *((u16 *)skb->data + i) );
spin_unlock_irq(&priv->lock);
dev->trans_start = jiffies;
//#if 1
//while(!(cs8900_readw(dev, PP_BusST) & Rdy4TxNOW));
priv->stats.tx_packets++;
priv->stats.tx_bytes += skb->len;
//netif_wake_queue(dev);
//#endif
dev_kfree_skb(skb);
return 0;
}
static int cs8900_open(struct net_device *dev)
{
#ifdef CONFIG_ARCH_S3C2410
/* Enable Ethernet controller */
//cs8900_writew(dev, PP_BusCTL, cs8900_readw(dev, PP_BusCTL) | MemoryE);
//cs8900_watchw(dev, PP_RxCTL);
//cs8900_watchw(dev, PP_RxCFG);
cs8900_setw(dev, PP_RxCFG, RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE);
cs8900_setw(dev, PP_RxCTL, RxOKA | IndividualA | BroadcastA);
cs8900_setw(dev, PP_BufCFG, Rdy4TxiE | RxMissiE | TxUnderruniE | TxColOvfiE | MissOvfloiE);
cs8900_setw(dev, PP_TxCFG, TxOKiE | Out_of_windowiE | JabberiE);
cs8900_setw(dev, PP_LineCTL, SerRxON | SerTxON);
/* Enable interrupt */
set_irq_type(dev->irq, IRQ_TYPE_EDGE_RISING /*(1<<1)||(1<<0)*/ );
cs8900_setw(dev, PP_BusCTL, EnableRQ | MemoryE);
#ifdef FULL_DUPLEX
cs8900_setw(dev, PP_TestCTL, FDX);
#endif /* #ifdef FULL_DUPLEX */
/* Register interrupt handler */
udelay(200);
if(request_irq(dev->irq, cs8900_interrupt, 0, dev->name, dev) < 0)
{
printk(KERN_ERR "%s: request_irq() %d error.\n", dev->name, dev->irq);
return -1;
}
//printk(KERN_ERR "%s: request_irq() %d success.\n", dev->name, dev->irq);
/* Setup interrupt number */
#ifdef CS8900_DEBUG
cs8900_watchw(dev, PP_IntNum);
#endif
cs8900_writew(dev, PP_IntNum, 0); /* IRQ map: {dev->irq, 0, 0, 0} */
#endif
netif_start_queue(dev);
return 0;
}
static int cs8900_stop(s