From: Kuninori Morimoto Date: Mon, 5 Apr 2010 18:46:12 +0000 (+0000) Subject: net/irda: Add SuperH IrDA driver support X-Git-Tag: MMI-PSA29.97-13-9~23873^2~559 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=17a328ca8c04cf88be9a9ef1cb74f359c59f1802;p=GitHub%2FMotorolaMobilityLLC%2Fkernel-slsi.git net/irda: Add SuperH IrDA driver support This is very simple driver for SuperH Mobile IrDA which support SIR/MIR/FIR. This patch add only SIR support for now. Signed-off-by: Kuninori Morimoto Signed-off-by: David S. Miller --- diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig index af10e97345ce..25bb2a015e18 100644 --- a/drivers/net/irda/Kconfig +++ b/drivers/net/irda/Kconfig @@ -397,5 +397,11 @@ config MCS_FIR To compile it as a module, choose M here: the module will be called mcs7780. +config SH_IRDA + tristate "SuperH IrDA driver" + depends on IRDA && ARCH_SHMOBILE + help + Say Y here if your want to enable SuperH IrDA devices. + endmenu diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile index e030d47e2793..dfc64537f62f 100644 --- a/drivers/net/irda/Makefile +++ b/drivers/net/irda/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_VIA_FIR) += via-ircc.o obj-$(CONFIG_PXA_FICP) += pxaficp_ir.o obj-$(CONFIG_MCS_FIR) += mcs7780.o obj-$(CONFIG_AU1000_FIR) += au1k_ir.o +obj-$(CONFIG_SH_IRDA) += sh_irda.o # SIR drivers obj-$(CONFIG_IRTTY_SIR) += irtty-sir.o sir-dev.o obj-$(CONFIG_BFIN_SIR) += bfin_sir.o diff --git a/drivers/net/irda/sh_irda.c b/drivers/net/irda/sh_irda.c new file mode 100644 index 000000000000..9a828b06a57e --- /dev/null +++ b/drivers/net/irda/sh_irda.c @@ -0,0 +1,865 @@ +/* + * SuperH IrDA Driver + * + * Copyright (C) 2010 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on sh_sir.c + * Copyright (C) 2009 Renesas Solutions Corp. + * Copyright 2006-2009 Analog Devices Inc. + * + * This program 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. + */ + +/* + * CAUTION + * + * This driver is very simple. + * So, it doesn't have below support now + * - MIR/FIR support + * - DMA transfer support + * - FIFO mode support + */ +#include +#include +#include +#include +#include + +#define DRIVER_NAME "sh_irda" + +#if defined(CONFIG_ARCH_SH7367) || defined(CONFIG_ARCH_SH7377) +#define __IRDARAM_LEN 0x13FF +#else +#define __IRDARAM_LEN 0x1039 +#endif + +#define IRTMR 0x1F00 /* Transfer mode */ +#define IRCFR 0x1F02 /* Configuration */ +#define IRCTR 0x1F04 /* IR control */ +#define IRTFLR 0x1F20 /* Transmit frame length */ +#define IRTCTR 0x1F22 /* Transmit control */ +#define IRRFLR 0x1F40 /* Receive frame length */ +#define IRRCTR 0x1F42 /* Receive control */ +#define SIRISR 0x1F60 /* SIR-UART mode interrupt source */ +#define SIRIMR 0x1F62 /* SIR-UART mode interrupt mask */ +#define SIRICR 0x1F64 /* SIR-UART mode interrupt clear */ +#define SIRBCR 0x1F68 /* SIR-UART mode baud rate count */ +#define MFIRISR 0x1F70 /* MIR/FIR mode interrupt source */ +#define MFIRIMR 0x1F72 /* MIR/FIR mode interrupt mask */ +#define MFIRICR 0x1F74 /* MIR/FIR mode interrupt clear */ +#define CRCCTR 0x1F80 /* CRC engine control */ +#define CRCIR 0x1F86 /* CRC engine input data */ +#define CRCCR 0x1F8A /* CRC engine calculation */ +#define CRCOR 0x1F8E /* CRC engine output data */ +#define FIFOCP 0x1FC0 /* FIFO current pointer */ +#define FIFOFP 0x1FC2 /* FIFO follow pointer */ +#define FIFORSMSK 0x1FC4 /* FIFO receive status mask */ +#define FIFORSOR 0x1FC6 /* FIFO receive status OR */ +#define FIFOSEL 0x1FC8 /* FIFO select */ +#define FIFORS 0x1FCA /* FIFO receive status */ +#define FIFORFL 0x1FCC /* FIFO receive frame length */ +#define FIFORAMCP 0x1FCE /* FIFO RAM current pointer */ +#define FIFORAMFP 0x1FD0 /* FIFO RAM follow pointer */ +#define BIFCTL 0x1FD2 /* BUS interface control */ +#define IRDARAM 0x0000 /* IrDA buffer RAM */ +#define IRDARAM_LEN __IRDARAM_LEN /* - 8/16/32 (read-only for 32) */ + +/* IRTMR */ +#define TMD_MASK (0x3 << 14) /* Transfer Mode */ +#define TMD_SIR (0x0 << 14) +#define TMD_MIR (0x3 << 14) +#define TMD_FIR (0x2 << 14) + +#define FIFORIM (1 << 8) /* FIFO receive interrupt mask */ +#define MIM (1 << 4) /* MIR/FIR Interrupt Mask */ +#define SIM (1 << 0) /* SIR Interrupt Mask */ +#define xIM_MASK (FIFORIM | MIM | SIM) + +/* IRCFR */ +#define RTO_SHIFT 8 /* shift for Receive Timeout */ +#define RTO (0x3 << RTO_SHIFT) + +/* IRTCTR */ +#define ARMOD (1 << 15) /* Auto-Receive Mode */ +#define TE (1 << 0) /* Transmit Enable */ + +/* IRRFLR */ +#define RFL_MASK (0x1FFF) /* mask for Receive Frame Length */ + +/* IRRCTR */ +#define RE (1 << 0) /* Receive Enable */ + +/* + * SIRISR, SIRIMR, SIRICR, + * MFIRISR, MFIRIMR, MFIRICR + */ +#define FRE (1 << 15) /* Frame Receive End */ +#define TROV (1 << 11) /* Transfer Area Overflow */ +#define xIR_9 (1 << 9) +#define TOT xIR_9 /* for SIR Timeout */ +#define ABTD xIR_9 /* for MIR/FIR Abort Detection */ +#define xIR_8 (1 << 8) +#define FER xIR_8 /* for SIR Framing Error */ +#define CRCER xIR_8 /* for MIR/FIR CRC error */ +#define FTE (1 << 7) /* Frame Transmit End */ +#define xIR_MASK (FRE | TROV | xIR_9 | xIR_8 | FTE) + +/* SIRBCR */ +#define BRC_MASK (0x3F) /* mask for Baud Rate Count */ + +/* CRCCTR */ +#define CRC_RST (1 << 15) /* CRC Engine Reset */ +#define CRC_CT_MASK 0x0FFF /* mask for CRC Engine Input Data Count */ + +/* CRCIR */ +#define CRC_IN_MASK 0x0FFF /* mask for CRC Engine Input Data */ + +/************************************************************************ + + + enum / structure + + +************************************************************************/ +enum sh_irda_mode { + SH_IRDA_NONE = 0, + SH_IRDA_SIR, + SH_IRDA_MIR, + SH_IRDA_FIR, +}; + +struct sh_irda_self; +struct sh_irda_xir_func { + int (*xir_fre) (struct sh_irda_self *self); + int (*xir_trov) (struct sh_irda_self *self); + int (*xir_9) (struct sh_irda_self *self); + int (*xir_8) (struct sh_irda_self *self); + int (*xir_fte) (struct sh_irda_self *self); +}; + +struct sh_irda_self { + void __iomem *membase; + unsigned int irq; + struct clk *clk; + + struct net_device *ndev; + + struct irlap_cb *irlap; + struct qos_info qos; + + iobuff_t tx_buff; + iobuff_t rx_buff; + + enum sh_irda_mode mode; + spinlock_t lock; + + struct sh_irda_xir_func *xir_func; +}; + +/************************************************************************ + + + common function + + +************************************************************************/ +static void sh_irda_write(struct sh_irda_self *self, u32 offset, u16 data) +{ + unsigned long flags; + + spin_lock_irqsave(&self->lock, flags); + iowrite16(data, self->membase + offset); + spin_unlock_irqrestore(&self->lock, flags); +} + +static u16 sh_irda_read(struct sh_irda_self *self, u32 offset) +{ + unsigned long flags; + u16 ret; + + spin_lock_irqsave(&self->lock, flags); + ret = ioread16(self->membase + offset); + spin_unlock_irqrestore(&self->lock, flags); + + return ret; +} + +static void sh_irda_update_bits(struct sh_irda_self *self, u32 offset, + u16 mask, u16 data) +{ + unsigned long flags; + u16 old, new; + + spin_lock_irqsave(&self->lock, flags); + old = ioread16(self->membase + offset); + new = (old & ~mask) | data; + if (old != new) + iowrite16(data, self->membase + offset); + spin_unlock_irqrestore(&self->lock, flags); +} + +/************************************************************************ + + + mode function + + +************************************************************************/ +/*===================================== + * + * common + * + *=====================================*/ +static void sh_irda_rcv_ctrl(struct sh_irda_self *self, int enable) +{ + struct device *dev = &self->ndev->dev; + + sh_irda_update_bits(self, IRRCTR, RE, enable ? RE : 0); + dev_dbg(dev, "recv %s\n", enable ? "enable" : "disable"); +} + +static int sh_irda_set_timeout(struct sh_irda_self *self, int interval) +{ + struct device *dev = &self->ndev->dev; + + if (SH_IRDA_SIR != self->mode) + interval = 0; + + if (interval < 0 || interval > 2) { + dev_err(dev, "unsupported timeout interval\n"); + return -EINVAL; + } + + sh_irda_update_bits(self, IRCFR, RTO, interval << RTO_SHIFT); + return 0; +} + +static int sh_irda_set_baudrate(struct sh_irda_self *self, int baudrate) +{ + struct device *dev = &self->ndev->dev; + u16 val; + + if (baudrate < 0) + return 0; + + if (SH_IRDA_SIR != self->mode) { + dev_err(dev, "it is not SIR mode\n"); + return -EINVAL; + } + + /* + * Baud rate (bits/s) = + * (48 MHz / 26) / (baud rate counter value + 1) x 16 + */ + val = (48000000 / 26 / 16 / baudrate) - 1; + dev_dbg(dev, "baudrate = %d, val = 0x%02x\n", baudrate, val); + + sh_irda_update_bits(self, SIRBCR, BRC_MASK, val); + + return 0; +} + +static int xir_get_rcv_length(struct sh_irda_self *self) +{ + return RFL_MASK & sh_irda_read(self, IRRFLR); +} + +/*===================================== + * + * NONE MODE + * + *=====================================*/ +static int xir_fre(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + dev_err(dev, "none mode: frame recv\n"); + return 0; +} + +static int xir_trov(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + dev_err(dev, "none mode: buffer ram over\n"); + return 0; +} + +static int xir_9(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + dev_err(dev, "none mode: time over\n"); + return 0; +} + +static int xir_8(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + dev_err(dev, "none mode: framing error\n"); + return 0; +} + +static int xir_fte(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + dev_err(dev, "none mode: frame transmit end\n"); + return 0; +} + +static struct sh_irda_xir_func xir_func = { + .xir_fre = xir_fre, + .xir_trov = xir_trov, + .xir_9 = xir_9, + .xir_8 = xir_8, + .xir_fte = xir_fte, +}; + +/*===================================== + * + * MIR/FIR MODE + * + * MIR/FIR are not supported now + *=====================================*/ +static struct sh_irda_xir_func mfir_func = { + .xir_fre = xir_fre, + .xir_trov = xir_trov, + .xir_9 = xir_9, + .xir_8 = xir_8, + .xir_fte = xir_fte, +}; + +/*===================================== + * + * SIR MODE + * + *=====================================*/ +static int sir_fre(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + u16 data16; + u8 *data = (u8 *)&data16; + int len = xir_get_rcv_length(self); + int i, j; + + if (len > IRDARAM_LEN) + len = IRDARAM_LEN; + + dev_dbg(dev, "frame recv length = %d\n", len); + + for (i = 0; i < len; i++) { + j = i % 2; + if (!j) + data16 = sh_irda_read(self, IRDARAM + i); + + async_unwrap_char(self->ndev, &self->ndev->stats, + &self->rx_buff, data[j]); + } + self->ndev->last_rx = jiffies; + + sh_irda_rcv_ctrl(self, 1); + + return 0; +} + +static int sir_trov(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + + dev_err(dev, "buffer ram over\n"); + sh_irda_rcv_ctrl(self, 1); + return 0; +} + +static int sir_tot(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + + dev_err(dev, "time over\n"); + sh_irda_set_baudrate(self, 9600); + sh_irda_rcv_ctrl(self, 1); + return 0; +} + +static int sir_fer(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + + dev_err(dev, "framing error\n"); + sh_irda_rcv_ctrl(self, 1); + return 0; +} + +static int sir_fte(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + + dev_dbg(dev, "frame transmit end\n"); + netif_wake_queue(self->ndev); + + return 0; +} + +static struct sh_irda_xir_func sir_func = { + .xir_fre = sir_fre, + .xir_trov = sir_trov, + .xir_9 = sir_tot, + .xir_8 = sir_fer, + .xir_fte = sir_fte, +}; + +static void sh_irda_set_mode(struct sh_irda_self *self, enum sh_irda_mode mode) +{ + struct device *dev = &self->ndev->dev; + struct sh_irda_xir_func *func; + const char *name; + u16 data; + + switch (mode) { + case SH_IRDA_SIR: + name = "SIR"; + data = TMD_SIR; + func = &sir_func; + break; + case SH_IRDA_MIR: + name = "MIR"; + data = TMD_MIR; + func = &mfir_func; + break; + case SH_IRDA_FIR: + name = "FIR"; + data = TMD_FIR; + func = &mfir_func; + break; + default: + name = "NONE"; + data = 0; + func = &xir_func; + break; + } + + self->mode = mode; + self->xir_func = func; + sh_irda_update_bits(self, IRTMR, TMD_MASK, data); + + dev_dbg(dev, "switch to %s mode", name); +} + +/************************************************************************ + + + irq function + + +************************************************************************/ +static void sh_irda_set_irq_mask(struct sh_irda_self *self) +{ + u16 tmr_hole; + u16 xir_reg; + + /* set all mask */ + sh_irda_update_bits(self, IRTMR, xIM_MASK, xIM_MASK); + sh_irda_update_bits(self, SIRIMR, xIR_MASK, xIR_MASK); + sh_irda_update_bits(self, MFIRIMR, xIR_MASK, xIR_MASK); + + /* clear irq */ + sh_irda_update_bits(self, SIRICR, xIR_MASK, xIR_MASK); + sh_irda_update_bits(self, MFIRICR, xIR_MASK, xIR_MASK); + + switch (self->mode) { + case SH_IRDA_SIR: + tmr_hole = SIM; + xir_reg = SIRIMR; + break; + case SH_IRDA_MIR: + case SH_IRDA_FIR: + tmr_hole = MIM; + xir_reg = MFIRIMR; + break; + default: + tmr_hole = 0; + xir_reg = 0; + break; + } + + /* open mask */ + if (xir_reg) { + sh_irda_update_bits(self, IRTMR, tmr_hole, 0); + sh_irda_update_bits(self, xir_reg, xIR_MASK, 0); + } +} + +static irqreturn_t sh_irda_irq(int irq, void *dev_id) +{ + struct sh_irda_self *self = dev_id; + struct sh_irda_xir_func *func = self->xir_func; + u16 isr = sh_irda_read(self, SIRISR); + + /* clear irq */ + sh_irda_write(self, SIRICR, isr); + + if (isr & FRE) + func->xir_fre(self); + if (isr & TROV) + func->xir_trov(self); + if (isr & xIR_9) + func->xir_9(self); + if (isr & xIR_8) + func->xir_8(self); + if (isr & FTE) + func->xir_fte(self); + + return IRQ_HANDLED; +} + +/************************************************************************ + + + CRC function + + +************************************************************************/ +static void sh_irda_crc_reset(struct sh_irda_self *self) +{ + sh_irda_write(self, CRCCTR, CRC_RST); +} + +static void sh_irda_crc_add(struct sh_irda_self *self, u16 data) +{ + sh_irda_write(self, CRCIR, data & CRC_IN_MASK); +} + +static u16 sh_irda_crc_cnt(struct sh_irda_self *self) +{ + return CRC_CT_MASK & sh_irda_read(self, CRCCTR); +} + +static u16 sh_irda_crc_out(struct sh_irda_self *self) +{ + return sh_irda_read(self, CRCOR); +} + +static int sh_irda_crc_init(struct sh_irda_self *self) +{ + struct device *dev = &self->ndev->dev; + int ret = -EIO; + u16 val; + + sh_irda_crc_reset(self); + + sh_irda_crc_add(self, 0xCC); + sh_irda_crc_add(self, 0xF5); + sh_irda_crc_add(self, 0xF1); + sh_irda_crc_add(self, 0xA7); + + val = sh_irda_crc_cnt(self); + if (4 != val) { + dev_err(dev, "CRC count error %x\n", val); + goto crc_init_out; + } + + val = sh_irda_crc_out(self); + if (0x51DF != val) { + dev_err(dev, "CRC result error%x\n", val); + goto crc_init_out; + } + + ret = 0; + +crc_init_out: + + sh_irda_crc_reset(self); + return ret; +} + +/************************************************************************ + + + iobuf function + + +************************************************************************/ +static void sh_irda_remove_iobuf(struct sh_irda_self *self) +{ + kfree(self->rx_buff.head); + + self->tx_buff.head = NULL; + self->tx_buff.data = NULL; + self->rx_buff.head = NULL; + self->rx_buff.data = NULL; +} + +static int sh_irda_init_iobuf(struct sh_irda_self *self, int rxsize, int txsize) +{ + if (self->rx_buff.head || + self->tx_buff.head) { + dev_err(&self->ndev->dev, "iobuff has already existed."); + return -EINVAL; + } + + /* rx_buff */ + self->rx_buff.head = kmalloc(rxsize, GFP_KERNEL); + if (!self->rx_buff.head) + return -ENOMEM; + + self->rx_buff.truesize = rxsize; + self->rx_buff.in_frame = FALSE; + self->rx_buff.state = OUTSIDE_FRAME; + self->rx_buff.data = self->rx_buff.head; + + /* tx_buff */ + self->tx_buff.head = self->membase + IRDARAM; + self->tx_buff.truesize = IRDARAM_LEN; + + return 0; +} + +/************************************************************************ + + + net_device_ops function + + +************************************************************************/ +static int sh_irda_hard_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct sh_irda_self *self = netdev_priv(ndev); + struct device *dev = &self->ndev->dev; + int speed = irda_get_next_speed(skb); + int ret; + + dev_dbg(dev, "hard xmit\n"); + + netif_stop_queue(ndev); + sh_irda_rcv_ctrl(self, 0); + + ret = sh_irda_set_baudrate(self, speed); + if (ret < 0) + return ret; + + self->tx_buff.len = 0; + if (skb->len) { + unsigned long flags; + + spin_lock_irqsave(&self->lock, flags); + self->tx_buff.len = async_wrap_skb(skb, + self->tx_buff.head, + self->tx_buff.truesize); + spin_unlock_irqrestore(&self->lock, flags); + + if (self->tx_buff.len > self->tx_buff.truesize) + self->tx_buff.len = self->tx_buff.truesize; + + sh_irda_write(self, IRTFLR, self->tx_buff.len); + sh_irda_write(self, IRTCTR, ARMOD | TE); + } + + dev_kfree_skb(skb); + + return 0; +} + +static int sh_irda_ioctl(struct net_device *ndev, struct ifreq *ifreq, int cmd) +{ + /* + * FIXME + * + * This function is needed for irda framework. + * But nothing to do now + */ + return 0; +} + +static struct net_device_stats *sh_irda_stats(struct net_device *ndev) +{ + struct sh_irda_self *self = netdev_priv(ndev); + + return &self->ndev->stats; +} + +static int sh_irda_open(struct net_device *ndev) +{ + struct sh_irda_self *self = netdev_priv(ndev); + int err; + + clk_enable(self->clk); + err = sh_irda_crc_init(self); + if (err) + goto open_err; + + sh_irda_set_mode(self, SH_IRDA_SIR); + sh_irda_set_timeout(self, 2); + sh_irda_set_baudrate(self, 9600); + + self->irlap = irlap_open(ndev, &self->qos, DRIVER_NAME); + if (!self->irlap) { + err = -ENODEV; + goto open_err; + } + + netif_start_queue(ndev); + sh_irda_rcv_ctrl(self, 1); + sh_irda_set_irq_mask(self); + + dev_info(&ndev->dev, "opened\n"); + + return 0; + +open_err: + clk_disable(self->clk); + + return err; +} + +static int sh_irda_stop(struct net_device *ndev) +{ + struct sh_irda_self *self = netdev_priv(ndev); + + /* Stop IrLAP */ + if (self->irlap) { + irlap_close(self->irlap); + self->irlap = NULL; + } + + netif_stop_queue(ndev); + + dev_info(&ndev->dev, "stoped\n"); + + return 0; +} + +static const struct net_device_ops sh_irda_ndo = { + .ndo_open = sh_irda_open, + .ndo_stop = sh_irda_stop, + .ndo_start_xmit = sh_irda_hard_xmit, + .ndo_do_ioctl = sh_irda_ioctl, + .ndo_get_stats = sh_irda_stats, +}; + +/************************************************************************ + + + platform_driver function + + +************************************************************************/ +static int __devinit sh_irda_probe(struct platform_device *pdev) +{ + struct net_device *ndev; + struct sh_irda_self *self; + struct resource *res; + char clk_name[8]; + unsigned int irq; + int err = -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || irq < 0) { + dev_err(&pdev->dev, "Not enough platform resources.\n"); + goto exit; + } + + ndev = alloc_irdadev(sizeof(*self)); + if (!ndev) + goto exit; + + self = netdev_priv(ndev); + self->membase = ioremap_nocache(res->start, resource_size(res)); + if (!self->membase) { + err = -ENXIO; + dev_err(&pdev->dev, "Unable to ioremap.\n"); + goto err_mem_1; + } + + err = sh_irda_init_iobuf(self, IRDA_SKB_MAX_MTU, IRDA_SIR_MAX_FRAME); + if (err) + goto err_mem_2; + + snprintf(clk_name, sizeof(clk_name), "irda%d", pdev->id); + self->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(self->clk)) { + dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); + goto err_mem_3; + } + + irda_init_max_qos_capabilies(&self->qos); + + ndev->netdev_ops = &sh_irda_ndo; + ndev->irq = irq; + + self->ndev = ndev; + self->qos.baud_rate.bits &= IR_9600; /* FIXME */ + self->qos.min_turn_time.bits = 1; /* 10 ms or more */ + spin_lock_init(&self->lock); + + irda_qos_bits_to_value(&self->qos); + + err = register_netdev(ndev); + if (err) + goto err_mem_4; + + platform_set_drvdata(pdev, ndev); + + if (request_irq(irq, sh_irda_irq, IRQF_DISABLED, "sh_irda", self)) { + dev_warn(&pdev->dev, "Unable to attach sh_irda interrupt\n"); + goto err_mem_4; + } + + dev_info(&pdev->dev, "SuperH IrDA probed\n"); + + goto exit; + +err_mem_4: + clk_put(self->clk); +err_mem_3: + sh_irda_remove_iobuf(self); +err_mem_2: + iounmap(self->membase); +err_mem_1: + free_netdev(ndev); +exit: + return err; +} + +static int __devexit sh_irda_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct sh_irda_self *self = netdev_priv(ndev); + + if (!self) + return 0; + + unregister_netdev(ndev); + clk_put(self->clk); + sh_irda_remove_iobuf(self); + iounmap(self->membase); + free_netdev(ndev); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver sh_irda_driver = { + .probe = sh_irda_probe, + .remove = __devexit_p(sh_irda_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init sh_irda_init(void) +{ + return platform_driver_register(&sh_irda_driver); +} + +static void __exit sh_irda_exit(void) +{ + platform_driver_unregister(&sh_irda_driver); +} + +module_init(sh_irda_init); +module_exit(sh_irda_exit); + +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_DESCRIPTION("SuperH IrDA driver"); +MODULE_LICENSE("GPL");