最近在开发板上搞2.6.30内核移植,从网上下载了不少别人移植好的驱动,我下载下来却不能用。终于下定决心自己改写一个适合的驱动,搞了几天,终于搞定了。是基于platform_driver的,发表出来大家共享一下。头文件是用的网上的,只需要修改./drivers/net下的Kconfig文件和Makfile,把cs8900.c加进去即可,具体方法可参考网上的文章。别的不需要修改任何文件。以下是完整的cs8900.c:
/*
* linux/drivers/net/cs8900.c
*
* Author: Abraham van der Merwe <abraham at 2d3d.co.za>
*
* A Cirrus Logic CS8900A driver for Linux
* based on the cs89x0 driver written by Russell Nelson,
* Donald Becker, and others.
*
* 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.
*
* History:
* 22-May-2002 Initial version (Abraham vd Merwe)
* 30-May-2002 Added char device support for eeprom (Frank Becker)
* 24-Jan-2004 Fixups for 2.6 (Frank Becker)
* 15-July-2004 Modified for SMDK2410 (Roc Wu pwu at jadechip.com)
*/
#define VERSION_STRING "Cirrus Logic CS8900A driver for Linux (Modified for SMDK2410)"
/*
* At the moment the driver does not support memory mode operation.
* It is trivial to implement this, but not worth the effort.
*/
/*
* TODO:
*
* 1. Sort out ethernet checksum
* 2. If !ready in send_start(), queue buffer and send it in interrupt handler
* when we receive a BufEvent with Rdy4Tx, send it again. dangerous!
* 3. how do we prevent interrupt handler destroying integrity of get_stats()?
* 4. Change reset code to check status.
* 5. Implement set_mac_address and remove fake mac address
* 7. Link status detection stuff
* 8. Write utility to write EEPROM, do self testing, etc.
* 9. Implement DMA routines (I need a board w/ DMA support for that)
* 10. Power management
* 11. Add support for multiple ethernet chips
*/
// added BSt
//#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <asm/irq.h>
//#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <asm/mach-types.h>
#include "cs8900.h"
#define CARDNAME "cs8900"
#define DRV_VERSION "1.01"
#define res_size(_r) (((_r)->end - (_r)->start) + 1)
u8 MAC_Addr[6] = {0x08,0x00,0x3e,0x26,0x0a,0x5b};
u16 txlen;
struct cs8900_t {
struct net_device_stats stats;
// u16 txlen;
int char_devnum;
void __iomem *io_addr; /* Register I/O base address */
void __iomem *irq_addr; /* Register I/O base address */
unsigned int irq;
unsigned int flags;
unsigned int in_suspend :1;
struct net_device *ndev;
struct device *dev; /* parent device */
struct resource *io_res; /* resources found */
struct resource *irq_res; /* resources found */
struct resource *irq_area; /* resources found */
struct resource *addr_req;
struct mutex addr_lock; /* phy and eeprom access lock */
spinlock_t lock;
} ;
#define IO_DATA 0x00
#define IO_CMD 0x04
#define IO_LEN 0x06
#define IO_ISQ 0x08
#define IO_PPTR 0x0a
#define IO_PDATA 0x0c
static u16 read_ISQ(struct net_device *dev)
{
return readw(dev->base_addr + IO_ISQ);
}
static void write_TXCMD(struct net_device *dev,u16 cmd)
{
writew(cmd,dev->base_addr + IO_CMD);
}
static void write_TXLEN(struct net_device *dev,u16 len)
{
writew(len,dev->base_addr + IO_LEN);
}
static void write_cs8900(struct net_device *dev,u16 reg,u16 value)
{
writew(reg,dev->base_addr + IO_PPTR);
writew(value,dev->base_addr + IO_PDATA);
}
static u16 read_cs8900(struct net_device *dev,u16 reg)
{
writew(reg,dev->base_addr + IO_PPTR);
return readw(dev->base_addr + IO_PDATA);
}
static inline void read_Frame (struct net_device *dev,struct sk_buff *skb,u16 length)
{
insw (dev->base_addr,skb_put (skb,length),(length + 1) / 2);
}
static inline void write_Frame (struct net_device *dev,struct sk_buff *skb)
{
outsw (dev->base_addr,skb->data,(skb->len + 1) / 2);
}
static void cs8900_receive (struct net_device *dev)
{
struct sk_buff *skb;
u16 status,length;
status = read_cs8900 (dev,PP_RxStatus);
length = read_cs8900 (dev,PP_RxLength);
if (!(status & RxOK)) {
dev->stats.rx_errors++;
if ((status & (Runt | Extradata))) dev->stats.rx_length_errors++;
if ((status & CRCerror)) dev->stats.rx_crc_errors++;
return;
}
if ((skb = dev_alloc_skb (length + 4)) == NULL) {
dev->stats.rx_dropped++;
return;
}
skb->dev = dev;
skb_reserve (skb,2);
read_Frame (dev,skb,length);
#ifdef FULL_DUPLEX
dump_packet (dev,skb,"recv");
#endif /* #ifdef FULL_DUPLEX */
skb->protocol = eth_type_trans (skb,dev);
netif_rx (skb);
dev->last_rx = jiffies;
dev->stats.rx_packets++;
dev->stats.rx_bytes += length;
}
static irqreturn_t s3c24xx_cs8900_irq(int irqno, void *id)//struct pt_regs *regs
{
struct net_device *dev = id;
struct cs8900_t *priv = netdev_priv(dev);
volatile u16 status;
unsigned long flags;
spin_lock_irqsave(&priv->lock,flags);
while (status = read_ISQ(dev))
{
switch (RegNum (status)) {
case RxEvent:
cs8900_receive (dev);
break;
case TxEvent:
dev->stats.collisions += ColCount (read_cs8900 (dev,PP_TxCOL));
if (!(RegContent (status) & TxOK)) {
dev->stats.tx_errors++;
if ((RegContent (status) & Out_of_window)) dev->stats.tx_window_errors++;
if ((RegContent (status) & Jabber)) dev->stats.tx_aborted_errors++;
break;
} else if (txlen) {
dev->stats.tx_packets++;
dev->stats.tx_bytes += txlen;
}
txlen = 0;
netif_wake_queue (dev);
break;
case BufEvent:
if ((RegContent (status) & RxMiss)) {
u16 missed = MissCount (read_cs8900 (dev,PP_RxMISS));
dev->stats.rx_errors += missed;
dev->stats.rx_missed_errors += missed;
}
if ((RegContent (status) & TxUnderrun)) {
dev->stats.tx_errors++;
dev->stats.tx_fifo_errors++;
txlen = 0;
netif_wake_queue (dev);
}
/* if Rdy4Tx, transmit last sent packet (if any) */
break;
case TxCOL:
dev->stats.collisions += ColCount (read_cs8900 (dev,PP_TxCOL));
break;
case RxMISS:
status = MissCount (read_cs8900 (dev,PP_RxMISS));
dev->stats.rx_errors += status;
dev->stats.rx_missed_errors += status;
break;
}
}
spin_unlock_irqrestore(&priv->lock, flags);
return IRQ_HANDLED;
}
#define __IRQT_RISEDGE (1 << 1)
#define __IRQT_HIGHLVL (1 << 3)
#define IRQT_RISING (__IRQT_RISEDGE)
#define IRQT_HIGH (__IRQT_HIGHLVL)
static int
cs8900_open(struct net_device *dev)
{
int ret;
struct cs8900_t *priv = netdev_priv(dev);
ret = request_irq(dev->irq, s3c24xx_cs8900_irq, priv->flags,dev->name, dev);
if (ret != 0) {
printk ("%s: could not register interrupt %d/n",dev->name, dev->irq);
return -ENOENT;
}
write_cs8900 (dev,PP_RxCFG, 0x03|RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE);
write_cs8900 (dev,PP_RxCTL, 0x05|RxOKA | IndividualA | BroadcastA);
write_cs8900 (dev,PP_TxCFG, 0x07|TxOKiE | Out_of_windowiE | JabberiE);
write_cs8900 (dev,PP_BufCFG, 0x0b|Rdy4TxiE | RxMissiE | TxUnderruniE | TxColOvfiE | MissOvfloiE);
write_cs8900 (dev,PP_LineCTL, 0x13|SerRxON | SerTxON);
write_cs8900 (dev,PP_BusCTL, 0x17|EnableRQ);
writew(0x10,priv->irq_addr+0x8c);
// writew(readw(priv->irq_addr+0x60)|0x8,priv->irq_addr+0x60);
/* start the queue */
netif_start_queue (dev);
return (0);
}
static void cs8900_timeout(struct net_device *dev)
{
// struct cs8900_t *priv = netdev_priv(dev);
dev->stats.tx_errors++;
dev->stats.tx_heartbeat_errors++;
txlen = 0;
netif_wake_queue (dev);
}
static struct net_device_stats *cs8900_get_stats (struct net_device *dev)
{
// struct cs8900_t *priv = netdev_priv(dev);
return (&dev->stats);
}
static int
cs8900_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct cs8900_t *priv = netdev_priv(dev);
unsigned long flags;
u16 status;
int i;
spin_lock_irqsave(&priv->lock,flags);
netif_stop_queue (dev);
write_TXCMD (dev,TxStart (After5));
write_TXLEN (dev,skb->len);
txlen = skb->len;
status = read_cs8900 (dev,PP_BusST);
// printk (KERN_ERR "line status: 0x%x/n",read_cs8900 (dev,0x134));
if ((status & TxBidErr)) {
spin_unlock_irqrestore(&priv->lock,flags);
printk (KERN_WARNING "%s: Invalid frame size %d!/n",dev->name,skb->len);
dev->stats.tx_errors++;
dev->stats.tx_aborted_errors++;
txlen = 0;
return (1);
}
if (!(status & Rdy4TxNOW)) {
spin_unlock_irqrestore(&priv->lock,flags);
printk (KERN_WARNING "%s: Transmit buffer not free!/n",dev->name);
dev->stats.tx_errors++;
txlen = 0;
/* store skb and send it in interrupt handler */
return (1);
}
write_Frame (dev,skb);
spin_unlock_irqrestore(&priv->lock,flags);
#ifdef DEBUG
// dump_packet (dev,skb,"send");
#endif /* #ifdef DEBUG */
dev->trans_start = jiffies;
dev_kfree_skb (skb);
/* udelay(2000);
while(status=read_ISQ(dev))
printk("the txevent :%x",status);
printk("the txevent :%x",read_cs8900(dev,PP_TxEvent));
// priv->txlen = skb->len;
// printk ("Packegt tranlated...");
*/ return (0);
}
static int
cs8900_stop(struct net_device *dev)
{
/* disable ethernet controller */
write_cs8900 (dev,PP_BusCTL,0);
write_cs8900 (dev,PP_TestCTL,0);
write_cs8900 (dev,PP_SelfCTL,0);
write_cs8900 (dev,PP_LineCTL,0);
write_cs8900 (dev,PP_BufCFG,0);
write_cs8900 (dev,PP_TxCFG,0);
write_cs8900 (dev,PP_RxCTL,0);
write_cs8900 (dev,PP_RxCFG,0);
/* uninstall interrupt handler */
free_irq (dev->irq,dev);
/* stop the queue */
netif_stop_queue (dev);
return (0);
}
static void cs8900_set_receive_mode (struct net_device *dev)
{
if ((dev->flags & IFF_PROMISC))
write_cs8900 (dev,PP_RxCTL,read_cs8900(dev,PP_RxCTL)|PromiscuousA);
else
write_cs8900 (dev,PP_RxCTL,read_cs8900(dev,PP_RxCTL)&~PromiscuousA);
if ((dev->flags & IFF_ALLMULTI) && dev->mc_list)
write_cs8900 (dev,PP_RxCTL,read_cs8900(dev,PP_RxCTL)|MulticastA);
else
write_cs8900 (dev,PP_RxCTL,read_cs8900(dev,PP_RxCTL)&~MulticastA);
}
static struct net_device_ops cs8900_ops = {
.ndo_open = &cs8900_open,
.ndo_start_xmit = &cs8900_start_xmit,
.ndo_tx_timeout = &cs8900_timeout,
.ndo_set_multicast_list = &cs8900_set_receive_mode,
.ndo_stop = &cs8900_stop,
.ndo_get_stats = &cs8900_get_stats,
};
static int __devinit
cs8900_probe(struct platform_device *pdev)
{
struct cs8900_t *priv;
int i,ret;
u16 value;
struct net_device *ndev;
int iosize;
ndev = alloc_etherdev(sizeof(struct cs8900_t));
if (!ndev) {
dev_err(&pdev->dev, "no memory for state/n");
return -ENOMEM;
}
SET_NETDEV_DEV(ndev, &pdev->dev);
priv = netdev_priv(ndev);
memset(priv, 0, sizeof(*priv));
priv->dev = &pdev->dev;
priv->ndev = ndev;
spin_lock_init(&priv->lock);
mutex_init(&priv->addr_lock);
priv->io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (priv->io_res == NULL){
dev_err(&pdev->dev, "insufficient resources/n");
ret = -ENOENT;
goto err_ioarea;
}
iosize = res_size(priv->io_res);
priv->addr_req = request_mem_region(priv->io_res->start, iosize,pdev->name);
if (priv->addr_req == NULL) {
dev_err(&pdev->dev, "cannot request IO/n");
ret = -ENXIO;
goto err_iomap;
}
priv->io_addr = ioremap(priv->io_res->start, (priv->io_res->end - priv->io_res->start)+1);
if (priv->io_res == NULL) {
dev_err(&pdev->dev, "cannot map IO/n");
ret = -ENXIO;
goto err_iomap;
}
priv->ndev->base_addr = (unsigned long)priv->io_addr;
priv->ndev->irq = platform_get_irq(pdev, 0);
priv->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
priv->flags = priv->irq_res->flags;
//test if the chip is cs8900A
if ((value = read_cs8900 (priv->ndev,PP_ProductID)) != EISA_REG_CODE) {
printk (KERN_ERR "%s: incorrect signature 0x%.4x/n",pdev->name,value);
return (-ENXIO);
}
printk (KERN_ERR "%s: signature 0x%.4x/n",pdev->name,value);
/* verify chip version */
value = read_cs8900 (priv->ndev,PP_ProductID + 2);
if (VERSION (value) != CS8900A) {
printk (KERN_ERR "%s: unknown chip version 0x%.8x/n",pdev->name,VERSION (value));
return (-ENXIO);
}
priv->irq_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (priv->irq_res == NULL){
dev_err(&pdev->dev, "insufficient resources/n");
ret = -ENOENT;
goto err_ioarea;
}
iosize = res_size(priv->irq_res);
priv->irq_addr = request_mem_region(priv->irq_res->start, iosize,pdev->name);
if (priv->irq_addr == NULL) {
dev_err(&pdev->dev, "cannot request IO/n");
ret = -ENXIO;
goto err_iomap;
}
priv->io_addr = ioremap(priv->io_res->start, (priv->io_res->end - priv->io_res->start)+1);
if (priv->io_res == NULL) {
dev_err(&pdev->dev, "cannot map IO/n");
ret = -ENXIO;
goto err_iomap;
}
ether_setup(priv->ndev);
priv->ndev->netdev_ops = &cs8900_ops;
priv->ndev->watchdog_timeo = msecs_to_jiffies(5000);
priv->ndev->watchdog_timeo = HZ;
priv->ndev->dev_addr[0] = MAC_Addr[0];
priv->ndev->dev_addr[1] = MAC_Addr[1];
priv->ndev->dev_addr[2] = MAC_Addr[2];
priv->ndev->dev_addr[3] = MAC_Addr[3];
priv->ndev->dev_addr[4] = MAC_Addr[4];
priv->ndev->dev_addr[5] = MAC_Addr[5];
platform_set_drvdata(pdev, priv->ndev);
ret = register_netdev(priv->ndev);
if (ret == 0)
printk(KERN_INFO "cs8900 driver register succeed/n");
/* write_cs8900 (ndev,PP_SelfCTL,read_cs8900(ndev,PP_RxCTL)|RESET);
while(!read_cs8900(ndev,PP_SelfST)&INITD); //wait for reset complete
*/
write_cs8900 (priv->ndev,PP_IntNum,0);
for (i = 0; i < ETH_ALEN; i += 2)
write_cs8900 (ndev,PP_IA + i,ndev->dev_addr[i] | (ndev->dev_addr[i + 1] << 8));
return (0);
//device_create_file(struct device *device, struct device_attribute *entry);
err_iomap:
iounmap(priv->io_addr);
err_ioarea:
release_resource(priv->io_res);
kfree(priv->io_res);
return ret;
}
static int __devexit
cs8900_drv_remove(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
unregister_netdev(ndev);
free_netdev(ndev); /* free device structure */
return 0;
}
static int
cs8900_drv_suspend(struct platform_device *dev, pm_message_t state)
{
return 0;
}
static int
cs8900_drv_resume(struct platform_device *dev)
{
return 0;
}
static struct platform_driver cs8900_driver = {
.driver = {
.name = "cs8900",
.owner = THIS_MODULE,
},
.probe = cs8900_probe,
.remove = __devexit_p(cs8900_drv_remove),
.suspend = cs8900_drv_suspend,
.resume = cs8900_drv_resume,
};
#define S3C2410_CPUIRQ_OFFSET (16)
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
#define IRQ_EINT9 S3C2410_IRQ(37)
static struct resource s3c_eth0_resource[] = {
[0] = {
.start = 0x19000000,
.end = 0x19000000 + 16,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 0x56000000,
.end = 0x56000000 + 256,
.flags = IORESOURCE_MEM,
},
[2] = {
.start = IRQ_EINT9,
.end = IRQ_EINT9,
.flags = IORESOURCE_IRQ|IRQF_TRIGGER_HIGH,
},
};
struct platform_device s3c_device_eth0 = {
.name = "cs8900",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_eth0_resource),
.resource = s3c_eth0_resource,
};
static struct platform_device __initdata *smdk_eths[] = {
&s3c_device_eth0,
};
static int __init
cs8900_init(void)
{
printk(KERN_INFO "%s Ethernet Driver, V%s/n", CARDNAME, DRV_VERSION);
platform_add_devices(smdk_eths, ARRAY_SIZE(smdk_eths));
return platform_driver_register(&cs8900_driver);
}
static void __exit
cs8900_cleanup(void)
{
platform_driver_unregister(&cs8900_driver);
}
MODULE_AUTHOR ("Abraham van der Merwe <abraham at 2d3d.co.za>");
MODULE_DESCRIPTION (VERSION_STRING);
MODULE_LICENSE ("GPL");
module_init (cs8900_init);
module_exit (cs8900_cleanup);