lkml.org 
[lkml]   [2007]   [Dec]   [5]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH] max3100 driver
    Date
    This patch adds support for the MAX3100 SPI UART.

    Generated on 20071205 against v2.6.23

    Signed-off-by: Christian Pellegrin <chripell@fsfe.org>
    ---
    drivers/serial/Kconfig | 7 +
    drivers/serial/Makefile | 1 +
    drivers/serial/max3100.c | 960 ++++++++++++++++++++++++++++++++++++++++
    include/linux/serial_max3100.h | 25 +
    4 files changed, 993 insertions(+), 0 deletions(-)

    diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
    index 81b52b7..4e7111b 100644
    --- a/drivers/serial/Kconfig
    +++ b/drivers/serial/Kconfig
    @@ -461,6 +461,13 @@ config SERIAL_S3C2410_CONSOLE
    your boot loader about how to pass options to the kernel at
    boot time.)

    +config SERIAL_MAX3100
    + tristate "MAX3100 support"
    + depends on SPI
    + help
    + MAX3100 chip support
    +
    +
    config SERIAL_DZ
    bool "DECstation DZ serial driver"
    depends on MACH_DECSTATION && 32BIT
    diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile
    index af6377d..9f67e52 100644
    --- a/drivers/serial/Makefile
    +++ b/drivers/serial/Makefile
    @@ -29,6 +29,7 @@ obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o
    obj-$(CONFIG_SERIAL_SA1100) += sa1100.o
    obj-$(CONFIG_SERIAL_BFIN) += bfin_5xx.o
    obj-$(CONFIG_SERIAL_S3C2410) += s3c2410.o
    +obj-$(CONFIG_SERIAL_MAX3100) += max3100.o
    obj-$(CONFIG_SERIAL_SUNCORE) += suncore.o
    obj-$(CONFIG_SERIAL_SUNHV) += sunhv.o
    obj-$(CONFIG_SERIAL_SUNZILOG) += sunzilog.o
    diff --git a/drivers/serial/max3100.c b/drivers/serial/max3100.c
    new file mode 100644
    index 0000000..dd48258
    --- /dev/null
    +++ b/drivers/serial/max3100.c
    @@ -0,0 +1,960 @@
    +
    +/*
    + *
    + * Copyright (C) 2007 Christian Pellegrin <chripell@evolware.org>
    + *
    + * 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.
    + *
    + * version 0.1 31/5/2006 first public release
    + * version 0.2 11/11/2007 ported to kernel 2.6.23
    + *
    + *
    + *
    + * Notes: the MAX3100 doesn't provide an interrupt on CTS
    + * so we have to use polling for flow control. TX empty
    + * IRQ is unusable, since writing conf clears FIFO buffer.
    + *
    + * Example platform data:
    +
    +static struct plat_max3100 max3100_plat_data = {
    + .loopback = 0,
    + .crystal = 0,
    + .only_edge_irq = 0,
    +};
    +
    +static struct spi_board_info spi_board_info[] = {
    + {
    + .modalias = "max3100",
    + .platform_data = &max3100_plat_data,
    + .irq = IRQ_EINT12,
    + .max_speed_hz = 5*1000*1000,
    + .chip_select = 0,
    + },
    +};
    +
    + */
    +
    +#include <linux/bitops.h>
    +#include <linux/console.h>
    +#include <linux/delay.h>
    +#include <linux/device.h>
    +#include <linux/errno.h>
    +#include <linux/fcntl.h>
    +#include <linux/init.h>
    +#include <linux/interrupt.h>
    +#include <linux/irq.h>
    +#include <linux/kernel.h>
    +#include <linux/keyboard.h>
    +#include <linux/major.h>
    +#include <linux/mm.h>
    +#include <linux/module.h>
    +#include <linux/pm.h>
    +#include <linux/reboot.h>
    +#include <linux/sched.h>
    +#include <linux/serial_core.h>
    +#include <linux/signal.h>
    +#include <linux/spi/spi.h>
    +#include <linux/string.h>
    +#include <linux/timer.h>
    +#include <linux/tty.h>
    +#include <linux/tty_flip.h>
    +#include <linux/workqueue.h>
    +
    +#include <asm/delay.h>
    +#include <asm/io.h>
    +#include <asm/irq.h>
    +#include <asm/semaphore.h>
    +#include <asm/system.h>
    +#include <asm/uaccess.h>
    +
    +#include <linux/serial_max3100.h>
    +
    +#define MAX3100_C (1<<14)
    +#define MAX3100_D (0<<14)
    +#define MAX3100_W (1<<15)
    +#define MAX3100_RX (0<<15)
    +
    +#define MAX3100_WC (MAX3100_W | MAX3100_C)
    +#define MAX3100_RC (MAX3100_RX| MAX3100_C)
    +#define MAX3100_WD (MAX3100_W | MAX3100_D)
    +#define MAX3100_RD (MAX3100_RX| MAX3100_D)
    +#define MAX3100_CMD (3 << 14)
    +
    +#define MAX3100_T (1<<14)
    +#define MAX3100_R (1<<15)
    +
    +#define MAX3100_FEN (1<<13)
    +#define MAX3100_SHDN (1<<12)
    +#define MAX3100_TM (1<<11)
    +#define MAX3100_RM (1<<10)
    +#define MAX3100_PM (1<<9)
    +#define MAX3100_RAM (1<<8)
    +#define MAX3100_IR (1<<7)
    +#define MAX3100_ST (1<<6)
    +#define MAX3100_PE (1<<5)
    +#define MAX3100_L (1<<4)
    +#define MAX3100_BAUD (0xf)
    +
    +#define MAX3100_TE (1<<10)
    +#define MAX3100_RAFE (1<<10)
    +#define MAX3100_RTS (1<<9)
    +#define MAX3100_CTS (1<<9)
    +#define MAX3100_PT (1<<8)
    +#define MAX3100_DATA (0xff)
    +
    +#define MAX3100_RT (MAX3100_R | MAX3100_T)
    +#define MAX3100_RTC (MAX3100_RT | MAX3100_CTS | MAX3100_RAFE)
    +
    +struct max3100_port_s {
    + struct uart_port port;
    + struct spi_device *spi;
    + struct tty_struct *tty;
    +
    + struct semaphore spi_txrx; /* protects access to the hw */
    +
    + int rts; /* rts status, can be MAX3100_RTS or 0 */
    + int conf; /* configuration for the MAX31000
    + * (bits 0-7, bits 8-11 are irqs) */
    + int last_cts_rx; /* last CTS received for flow ctrl */
    +
    + int tx_buf_cur; /* current char to tx */
    + int tx_buf_tot; /* current number of chars in tx buf */
    + unsigned char *tx_buf;
    + spinlock_t tx_buf_lock; /* shared tx buffer spinlock (no sem
    + * since write may sleep) */
    +
    + int tx_stopped; /* when we should not send chars */
    + int parity; /* keeps track if we should work parity */
    +#define MAX3100_PARITY_ON 1
    +#define MAX3100_PARITY_ODD 2
    +#define MAX3100_7BIT 4
    +
    + int irq; /* irq assigned to the max3100 */
    +
    + int minor; /* minor number */
    + int crystal; /* 1 if 3.6864Mhz crystal 0 for 1.8432 */
    + int loopback; /* 1 if we are in loopback mode */
    + int only_edge_irq; /* 1 if we have only edge irqs (like PXA) */
    +
    + int ref_count; /* how many users */
    + struct semaphore sem; /* protects us during open/close */
    +
    + struct workqueue_struct *workqueue;
    + /* for handling irqs: need workqueue since we do spi_sync */
    + struct work_struct work;
    + int force_end_work;
    + /* set to 1 to make the worknadler exit as soon as possible */
    +
    + int irq_status;
    + spinlock_t irq_lock;
    + /* these are for IRQ on/off counting */
    +};
    +
    +static struct tty_driver *serial_driver = NULL;
    +/* global since we do register the driver only once */
    +
    +#define MAX_MAX3100 4 /* 4 MAX3100s should be enough for everyone */
    +static struct max3100_port_s * max3100s[MAX_MAX3100]; /* the chips */
    +
    +#define MAX3100_TX_BUF_N 16
    +/* our buffer for sendind chars */
    +
    +#define MAX3100_FLIP_EVERY 8
    +/* how many chars we wait befor flipping tty buffer */
    +
    +static void irq_state(struct max3100_port_s *s, int on) {
    + unsigned long flags;
    +
    + spin_lock_irqsave(&s->irq_lock, flags);
    + if (s->irq_status != on) {
    + if (on) {
    + enable_irq(s->irq);
    + }
    + else {
    + disable_irq(s->irq);
    + }
    + s->irq_status = on;
    + }
    + spin_unlock_irqrestore(&s->irq_lock, flags);
    +}
    +
    +
    +static int max3100_do_parity(struct max3100_port_s *s, u16 c)
    +{
    + int parity;
    + int i,n;
    +
    + if (s->parity & MAX3100_PARITY_ODD)
    + parity = 0;
    + else
    + parity = 1;
    +
    + if (s->parity & MAX3100_7BIT)
    + n = 7;
    + else
    + n = 8;
    +
    + for(i=0;i<n;i++) {
    + parity = parity ^ ( (c>>i) & 1);
    + }
    + return parity;
    +}
    +
    +static inline int max3100_check_parity(struct max3100_port_s *s, u16 c)
    +{
    + return max3100_do_parity(s, c) == ( (c>>9) & 1);
    +}
    +
    +static inline void max3100_calc_parity(struct max3100_port_s *s, u16 *c)
    +{
    + *c &= ~MAX3100_PT;
    + *c |= max3100_do_parity(s, *c)<<8;
    +}
    +
    +
    +static void max3100_work(struct work_struct *w);
    +
    +static inline void max3100_dowork(struct max3100_port_s *s)
    +{
    + if (!s->force_end_work) {
    + PREPARE_WORK(&s->work, max3100_work);
    + queue_work(s->workqueue, &s->work);
    + }
    +}
    +
    +static int max3100_sr(struct max3100_port_s *s, u16 tx, u16 *rx, int push)
    +{
    + /* note: we suppose that the T bit fron conf_status is in sync
    + * with the hw one, so all the communication must go through
    + * this function */
    + struct spi_message message;
    + struct spi_transfer x;
    + int status, flag;
    + u16 etx,erx;
    + int got_char = 0;
    +
    + down(&s->spi_txrx);
    +
    + if (s->loopback) {
    + if ((tx & MAX3100_CMD) == MAX3100_RC)
    + tx |= 1;
    + }
    + etx = htons(tx);
    + spi_message_init(&message);
    + memset(&x, 0, sizeof(x));
    + x.tx_buf = &etx;
    + x.rx_buf = &erx;
    + x.len = 2;
    + spi_message_add_tail(&x, &message);
    + status = spi_sync(s->spi, &message);
    + if (status) {
    + dev_warn(&s->spi->dev, "error while calling spi_sync\n");
    + return 0;
    + }
    + *rx = ntohs(erx);
    + //printk("%04x - %04x\n", tx, *rx);
    +
    + if ((tx & MAX3100_CMD) == MAX3100_RD ||
    + (tx & MAX3100_CMD) == MAX3100_WD) {
    + s->last_cts_rx = *rx;
    + }
    +
    + if (*rx & MAX3100_R &&
    + ((tx & MAX3100_CMD) == MAX3100_RD ||
    + (tx & MAX3100_CMD) == MAX3100_WD)) {
    + unsigned int flags;
    +
    + got_char = 1;
    + if (tty_buffer_request_room(s->tty, 1) == 0) {
    + dev_warn(&s->spi->dev, "no room in tty buffer\n");
    + tty_schedule_flip(s->tty);
    + }
    +
    + flag = TTY_NORMAL;
    + if (*rx & MAX3100_RAFE) {
    + s->port.icount.frame++;
    + flags = TTY_FRAME;
    + }
    + if (s->parity & MAX3100_PARITY_ON && max3100_check_parity(s, *rx)) {
    + s->port.icount.parity++;
    + flags = TTY_PARITY;
    + }
    + tty_insert_flip_char(s->tty, *rx & MAX3100_DATA, flag);
    + }
    +
    + up(&s->spi_txrx);
    +
    + if (push && got_char) {
    + tty_schedule_flip(s->tty);
    + }
    + return got_char;
    +}
    +
    +
    +static void max3100_send_conf(struct max3100_port_s *s)
    +{
    + u16 tx=0,rx;
    +
    + tx = MAX3100_WC | (s->conf & 0x0fff);
    + max3100_sr(s, tx, &rx, 1);
    +}
    +
    +static inline int max3100_ok_to_send(struct max3100_port_s *s)
    +{
    + if (C_CRTSCTS(s->tty)) {
    + if ((s->last_cts_rx & MAX3100_CTS) == 0)
    + return 0;
    + }
    + return ! s->tx_stopped;
    +}
    +
    +static void max3100_work(struct work_struct *w)
    +{
    + struct max3100_port_s *s = container_of(w, struct max3100_port_s, work);
    + u16 tx,rx;
    + unsigned long flags;
    + int nchars; /* number of char waiting in the send buf */
    + int rxchars = 0; /* chars received */
    +
    + do {
    + tx = MAX3100_RD;
    + rxchars += max3100_sr(s, tx, &rx, 0);
    + /* rx a char so we get MAX3100_T and CTS current */
    +
    + spin_lock_irqsave(&s->tx_buf_lock, flags);
    + nchars = s->tx_buf_tot - s->tx_buf_cur;
    + tx = MAX3100_WD | s->rts;
    + tx |= s->tx_buf[s->tx_buf_cur];
    + spin_unlock_irqrestore(&s->tx_buf_lock, flags);
    +
    + if ((rx & MAX3100_T) && /* tx buffer empty*/
    + max3100_ok_to_send(s) && /* fw ctrl decision */
    + nchars > 0 && /* more to send */
    + ! s->force_end_work) {
    + if (s->parity & MAX3100_PARITY_ON)
    + max3100_calc_parity(s, &tx);
    + rxchars += max3100_sr(s, tx, &rx, 0);
    + nchars -= 1;
    + spin_lock_irqsave(&s->tx_buf_lock, flags);
    + s->tx_buf_cur += 1;
    + spin_unlock_irqrestore(&s->tx_buf_lock, flags);
    + if (nchars == 0) {
    + tty_wakeup(s->tty);
    + }
    + }
    + if (rxchars > MAX3100_FLIP_EVERY) {
    + tty_schedule_flip(s->tty);
    + rxchars = 0;
    + }
    + }
    + while ((nchars > 0 || /* more to send */
    + rx & MAX3100_R) && /* try to empy all the RX buffer */
    + ! s->force_end_work);
    + if (rxchars) {
    + tty_schedule_flip(s->tty);
    + }
    + if (! s->only_edge_irq && ! s->force_end_work)
    + irq_state(s, 1);
    +}
    +
    +static irqreturn_t max3100_irq(int irqno, void *dev_id)
    +{
    + struct max3100_port_s *s = dev_id;
    +
    + if (! s->only_edge_irq)
    + irq_state(s, 0); /* avoid irq storm */
    + max3100_dowork(s);
    + return IRQ_HANDLED;
    +}
    +
    +static void rs_stop(struct tty_struct *tty)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    +
    + s->tx_stopped = 1;
    +}
    +
    +static void rs_start(struct tty_struct *tty)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    +
    + s->tx_stopped = 0;
    +}
    +
    +static void rs_flush_chars(struct tty_struct *tty)
    +{
    +}
    +
    +static void rs_waint_until_sent(struct tty_struct *tty, int timeout)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + unsigned long jstop;
    +
    + jstop = jiffies + timeout;
    + do {
    + if (s->tx_buf_tot == 0 || s->tx_buf_tot == s->tx_buf_cur)
    + return;
    + schedule();
    + } while (!timeout || time_before(jiffies, jstop));
    +}
    +
    +static void rs_flush_buffer(struct tty_struct *tty)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + unsigned long flags;
    +
    + spin_lock_irqsave(&s->tx_buf_lock, flags);
    + s->tx_buf_cur = 0;
    + s->tx_buf_tot = 0;
    + spin_unlock_irqrestore(&s->tx_buf_lock, flags);
    +}
    +
    +static int rs_write(struct tty_struct * tty,
    + const unsigned char *buf, int count)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + unsigned long flags;
    +
    + spin_lock_irqsave(&s->tx_buf_lock, flags);
    + if (s->tx_buf_cur < s->tx_buf_tot) {
    + count = 0;
    + }
    + else {
    + if (count > MAX3100_TX_BUF_N)
    + count = MAX3100_TX_BUF_N;
    + memcpy(s->tx_buf, buf, count);
    + s->tx_buf_cur = 0;
    + s->tx_buf_tot = count;
    + }
    + spin_unlock_irqrestore(&s->tx_buf_lock, flags);
    + max3100_dowork(s);
    + return count;
    +}
    +
    +static int rs_write_room(struct tty_struct *tty)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + int ret;
    + unsigned long flags;
    +
    + spin_lock_irqsave(&s->tx_buf_lock, flags);
    + if (s->tx_buf_cur < s->tx_buf_tot)
    + ret = 0;
    + else
    + ret = MAX3100_TX_BUF_N;
    + spin_unlock_irqrestore(&s->tx_buf_lock, flags);
    + return ret;
    +}
    +
    +static int rs_chars_in_buffer(struct tty_struct *tty)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + int ret;
    + unsigned long flags;
    +
    + spin_lock_irqsave(&s->tx_buf_lock, flags);
    + if (s->tx_buf_cur < s->tx_buf_tot)
    + ret = MAX3100_TX_BUF_N;
    + else
    + ret = 0;
    + spin_unlock_irqrestore(&s->tx_buf_lock, flags);
    + return ret;
    +}
    +
    +static void internal_throttle(struct tty_struct * tty, char ch, int rts)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + u16 tx=0,rx;
    + int old_tx_stopped;
    +
    + old_tx_stopped = s->tx_stopped;
    + s->tx_stopped = 1;
    + tx = MAX3100_RD;
    + do { /* wait fot tx buf empty */
    + max3100_sr(s, tx, &rx, 1);
    + } while ((rx & MAX3100_T) == 0);
    +
    + if (I_IXOFF(tty)) {
    + tx = MAX3100_WD | s->rts | (ch&0xff);
    + }
    + if (C_CRTSCTS(tty)) {
    + s->rts = rts ? (MAX3100_RTS) : 0;
    + tx = MAX3100_WD | s->rts | MAX3100_TE;
    + }
    + if (tx) {
    + max3100_sr(s, tx, &rx, 1);
    + }
    + s->tx_stopped = old_tx_stopped;
    +}
    +
    +static void rs_throttle(struct tty_struct * tty)
    +{
    + internal_throttle(tty, STOP_CHAR(tty), 0);
    +}
    +
    +static void rs_unthrottle(struct tty_struct * tty)
    +{
    + internal_throttle(tty, START_CHAR(tty), 1);
    +}
    +
    +static int get_lsr_info(struct max3100_port_s *s, unsigned int *value)
    +{
    + unsigned char status;
    + u16 tx=0,rx;
    +
    + tx = MAX3100_RD;
    + max3100_sr(s, tx, &rx, 1);
    + status = (rx & MAX3100_CTS) > 0;
    + put_user(status,value);
    + return 0;
    +}
    +
    +static int rs_ioctl(struct tty_struct *tty, struct file * file,
    + unsigned int cmd, unsigned long arg)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + int retval = 0;
    +
    + switch (cmd) {
    +
    + case TIOCSERGETLSR: /* Get line status register */
    + if (access_ok(VERIFY_WRITE, (void *) arg, sizeof(unsigned int)))
    + return get_lsr_info(s, (unsigned int *) arg);
    + return -EFAULT;
    +
    + default:
    + retval = -ENOIOCTLCMD ;
    + }
    + return retval;
    +}
    +
    +static void change_speed(struct max3100_port_s *s)
    +{
    + unsigned cflag;
    + unsigned i;
    + u32 param_new;
    + u32 param_mask;
    +
    + if (!s->tty || !s->tty->termios)
    + return;
    + cflag = s->tty->termios->c_cflag;
    + param_new = param_mask = 0;
    +
    + i = cflag & CBAUD;
    + /* note: supposed 3.6864 Mhz xtal */
    + switch(i) {
    + case B300:
    + if (s->crystal) {
    + param_new = 16;
    + }
    + else {
    + dev_warn(&s->spi->dev, "unsupported baud rate!\n");
    + param_new = 15;
    + }
    + break;
    + case B600:
    + param_new = 15;
    + break;
    + case B1200:
    + param_new = 14;
    + break;
    + case B2400:
    + param_new = 13;
    + break;
    + case B4800:
    + param_new = 12;
    + break;
    + case B9600:
    + param_new = 11;
    + break;
    + case B19200:
    + param_new = 10;
    + break;
    + case B38400:
    + param_new = 9;
    + break;
    + case B57600:
    + param_new = 2;
    + break;
    + case B115200:
    + param_new = 1;
    + break;
    + case B230400:
    + if (s->crystal) {
    + param_new = 1;
    + dev_warn(&s->spi->dev, "unsupported baud rate!\n");
    + }
    + else {
    + param_new = 0;
    + }
    + break;
    + default:
    + param_new = 1;
    + dev_warn(&s->spi->dev, "invalid baudrate\n");
    + }
    + param_new = (param_new - s->crystal) & MAX3100_BAUD;
    + param_mask |= MAX3100_BAUD;
    +
    + if ((cflag & CSIZE) == CS8) {
    + param_new &= ~MAX3100_L;
    + s->parity &= ~MAX3100_7BIT;
    + }
    + else {
    + param_new |= MAX3100_L;
    + s->parity |= MAX3100_7BIT;
    + }
    + param_mask |= MAX3100_L;
    +
    + if (cflag & CSTOPB)
    + param_new |= MAX3100_ST;
    + else
    + param_new &= ~MAX3100_ST;
    + param_mask |= MAX3100_ST;
    +
    + if (cflag & PARENB) {
    + param_new |= MAX3100_PE;
    + s->parity |= MAX3100_PARITY_ON;
    + }
    + else {
    + param_new &= ~MAX3100_PE;
    + s->parity &= ~MAX3100_PARITY_ON;
    + }
    + param_mask |= MAX3100_PE;
    +
    + if (cflag & PARODD) {
    + s->parity |= MAX3100_PARITY_ODD;
    + }
    + else {
    + s->parity &=~ MAX3100_PARITY_ODD;
    + }
    +
    + s->conf = (s->conf & ~param_mask) | (param_new & param_mask);
    + max3100_send_conf(s);
    +
    + return;
    +}
    +
    +
    +static void rs_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    +
    + if (tty->termios->c_cflag == old_termios->c_cflag)
    + return;
    +
    + change_speed(s);
    +
    + if ((old_termios->c_cflag & CRTSCTS) &&
    + !(tty->termios->c_cflag & CRTSCTS)) {
    + max3100_dowork(s);
    + }
    +}
    +
    +static void shutdown(struct max3100_port_s *s)
    +{
    + s->force_end_work = 1;
    + if (s->workqueue) {
    + flush_workqueue(s->workqueue);
    + destroy_workqueue(s->workqueue);
    + }
    + if (s->irq)
    + free_irq(s->irq, s);
    + kfree(s->tx_buf);
    + s->tx_buf = NULL;
    + if (s->tty)
    + set_bit(TTY_IO_ERROR, &s->tty->flags);
    +}
    +
    +static void rs_hangup(struct tty_struct *tty)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    +
    + shutdown(s);
    +}
    +
    +static int startup(struct max3100_port_s *s)
    +{
    + char b[10];
    + s->tx_buf = kmalloc(MAX3100_TX_BUF_N, GFP_KERNEL);
    + s->tx_buf_cur = 0;
    + s->tx_buf_tot = 0;
    +
    + s->force_end_work = 0;
    + s->parity = 0;
    + s->rts = 0;
    + s->tx_stopped = 0;
    + s->conf = MAX3100_RM;
    +
    + sprintf(b, "max3100-%d", s->minor);
    + s->workqueue = create_singlethread_workqueue(b);
    + if (!s->workqueue) {
    + dev_warn(&s->spi->dev, "cannot create workqueue\n");
    + return -EBUSY;
    + }
    + INIT_WORK(&s->work, max3100_work);
    +
    + s->irq_status = 1; /* IRQS are on after request */
    + if (request_irq(s->irq, max3100_irq, 0, "max3100", s) < 0) {
    + dev_warn(&s->spi->dev, "cannot allocate irq %d\n", s->irq);
    + s->irq = 0;
    + return -EBUSY;
    + }
    + if (s->only_edge_irq)
    + set_irq_type(s->irq, IRQT_FALLING);
    + else
    + set_irq_type(s->irq, IRQT_LOW);
    +
    + if (s->tty)
    + clear_bit(TTY_IO_ERROR, &s->tty->flags);
    +
    + change_speed(s);
    +
    + if (s->loopback) {
    + u16 tx,rx;
    + tx = 0x4001;
    + max3100_sr(s, tx, &rx, 1);
    + }
    +
    + return 0;
    +}
    +
    +int rs_open(struct tty_struct *tty, struct file * filp)
    +{
    + struct max3100_port_s *s;
    + int retval=0, line;
    +
    + line = tty->index;
    +
    + if (line >= MAX_MAX3100 || line < 0)
    + return -ENODEV;
    +
    + s = max3100s[line];
    +
    + if (!s) {
    + printk("inexistent max3100 for index %d\n", line);
    + return -ENODEV;
    + }
    +
    + down(&s->sem);
    +
    + s->ref_count++;
    +
    + if (s->ref_count == 1) {
    + tty->driver_data = s;
    + s->tty = tty;
    +
    + retval = startup(s);
    + }
    +
    + up(&s->sem);
    +
    + return retval;
    +}
    +
    +static void rs_close(struct tty_struct *tty, struct file * filp)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    +
    + down(&s->sem);
    +
    + s->ref_count--;
    + if (s->ref_count == 0) {
    + rs_waint_until_sent(tty, 5*HZ);
    + shutdown(s);
    + }
    +
    + up(&s->sem);
    +}
    +
    +static int rs_tiocmget(struct tty_struct *tty, struct file *file)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + u16 tx,rx;
    + unsigned int result = 0;
    +
    + tx = MAX3100_RD;
    + max3100_sr(s, tx, &rx, 1);
    +
    + result = ((s->rts) ? TIOCM_RTS : 0)
    + | ((rx & MAX3100_CTS) ? TIOCM_CTS : 0);
    + return result;
    +}
    +
    +static int rs_tiocmset(struct tty_struct *tty, struct file *file,
    + unsigned int set, unsigned int clear)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) tty->driver_data;
    + int old_rts = s->rts;
    + u16 tx,rx;
    +
    + if (set & TIOCM_RTS)
    + s->rts = MAX3100_RTS;
    + if (clear & TIOCM_RTS)
    + s->rts = 0;
    +
    + if (s->rts != old_rts) {
    + tx = MAX3100_WD | s->rts | MAX3100_TE;
    + max3100_sr(s, tx, &rx, 1);
    + }
    + return 0;
    +}
    +
    +struct tty_operations rs_ops = {
    + .open = rs_open,
    + .close = rs_close,
    + .write = rs_write,
    + .flush_chars = rs_flush_chars,
    + .wait_until_sent = rs_waint_until_sent,
    + .write_room = rs_write_room,
    + .chars_in_buffer = rs_chars_in_buffer,
    + .flush_buffer = rs_flush_buffer,
    + .ioctl = rs_ioctl,
    + .throttle = rs_throttle,
    + .unthrottle = rs_unthrottle,
    + .set_termios = rs_set_termios,
    + .stop = rs_stop,
    + .start = rs_start,
    + .hangup = rs_hangup,
    + .tiocmget = rs_tiocmget,
    + .tiocmset = rs_tiocmset,
    +};
    +
    +static int __devinit max3100_probe(struct spi_device *spi)
    +{
    + int i;
    + struct plat_max3100 *pdata;
    +
    + if (!serial_driver) {
    + serial_driver = alloc_tty_driver(MAX_MAX3100);
    + if (!serial_driver) {
    + printk("Cannot allocate serial driver\n");
    + return -ENOMEM;
    + }
    +
    + serial_driver->name = "ttyMAX";
    + serial_driver->driver_name = "ttyMAX";
    + serial_driver->major = TTY_MAJOR;
    + serial_driver->minor_start = 128; /* this should prevent clashes */
    + serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
    + serial_driver->subtype = SERIAL_TYPE_NORMAL;
    + serial_driver->init_termios = tty_std_termios;
    + serial_driver->init_termios.c_cflag =
    + B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    + serial_driver->flags = TTY_DRIVER_REAL_RAW |TTY_DRIVER_DYNAMIC_DEV;
    + tty_set_operations(serial_driver, &rs_ops);
    +
    + if (tty_register_driver(serial_driver)) {
    + put_tty_driver(serial_driver);
    + printk("Couldn't register serial driver\n");
    + return -EINVAL;
    + }
    + }
    +
    + for(i=0; i<MAX_MAX3100; i++)
    + if (!max3100s[i])
    + break;
    + if (i == MAX_MAX3100) {
    + dev_warn(&spi->dev, "too many MAX3100 chips\n");
    + return -ENOMEM;
    + }
    +
    + max3100s[i] = kmalloc(sizeof(struct max3100_port_s), GFP_KERNEL);
    + if (!max3100s[i]) {
    + dev_warn(&spi->dev, "kmalloc for max3100 structure %d failed!\n", i);
    + return -ENOMEM;
    + }
    + memset(max3100s[i], 0, sizeof(struct max3100_port_s));
    + max3100s[i]->spi = spi;
    + max3100s[i]->irq = spi->irq;
    + init_MUTEX(&max3100s[i]->sem);
    + init_MUTEX(&max3100s[i]->spi_txrx);
    + spin_lock_init(&max3100s[i]->tx_buf_lock);
    + spin_lock_init(&max3100s[i]->irq_lock);
    + dev_set_drvdata(&spi->dev, &max3100s[i]);
    + pdata = spi->dev.platform_data;
    + max3100s[i]->crystal = pdata->crystal;
    + max3100s[i]->loopback = pdata->loopback;
    + max3100s[i]->only_edge_irq = pdata->only_edge_irq;
    + max3100s[i]->minor = i;
    + tty_register_device(serial_driver, i, &spi->dev);
    +
    + return 0;
    +}
    +
    +static int __devexit max3100_remove(struct spi_device *spi)
    +{
    + int i;
    +
    + i = 0;
    +
    + for(i=0; i<MAX_MAX3100; i++)
    + if (max3100s[i] && max3100s[i]->ref_count > 0)
    + return -EBUSY;
    +
    + for(i=0; i<MAX_MAX3100; i++)
    + if (max3100s[i]) {
    + tty_unregister_device(serial_driver, i);
    + kfree(max3100s[i]);
    + max3100s[i] = NULL;
    + }
    +
    + if (serial_driver)
    + tty_unregister_driver(serial_driver);
    + return 0;
    +}
    +
    +#ifdef CONFIG_PM
    +static int max3100_suspend(struct spi_device *spi, pm_message_t state)
    +{
    + struct max3100_port_s *s = (struct max3100_port_s *) dev_get_drvdata(&spi->dev);
    + u16 tx,rx;
    +
    + tx = MAX3100_WC | MAX3100_SHDN;
    + max3100_sr(s, tx, &rx, 0);
    + return 0;
    +}
    +
    +static int max3100_resume(struct spi_device *spi) {
    + struct max3100_port_s *s = (struct max3100_port_s *) dev_get_drvdata(&spi->dev);
    +
    + max3100_send_conf(s);
    +
    + return 0;
    +}
    +
    +#else
    +#define max3100_suspend NULL
    +#define max3100_resume NULL
    +#endif
    +
    +static struct spi_driver max3100_driver = {
    + .driver = {
    + .name = "max3100",
    + .bus = &spi_bus_type,
    + .owner = THIS_MODULE,
    + },
    +
    + .probe = max3100_probe,
    + .remove = __devexit_p(max3100_remove),
    + .suspend = max3100_suspend,
    + .resume = max3100_resume,
    +};
    +
    +
    +static int __init max3100_init(void)
    +{
    + return spi_register_driver(&max3100_driver);
    +}
    +module_init(max3100_init);
    +
    +static void __exit max3100_exit(void)
    +{
    + spi_unregister_driver(&max3100_driver);
    +}
    +module_exit(max3100_exit);
    +
    +MODULE_DESCRIPTION("MAX3100 driver");
    +MODULE_LICENSE("GPL");
    diff --git a/include/linux/serial_max3100.h b/include/linux/serial_max3100.h
    new file mode 100644
    index 0000000..eefac54
    --- /dev/null
    +++ b/include/linux/serial_max3100.h
    @@ -0,0 +1,25 @@
    +
    +/*
    + *
    + * Copyright (C) 2007 Christian Pellegrin
    + *
    + * 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.
    + */
    +
    +
    +#ifndef _LINUX_SERIAL_MAX3100_H
    +#define _LINUX_SERIAL_MAX3100_H 1
    +
    +struct plat_max3100 {
    + int loopback;
    +/* force MAX3100 in loopback */
    + int crystal;
    +/* 0 for 3.6864 Mhz, 1 for 1.8432 */
    + int only_edge_irq;
    +/* for archs like PXA with only edge irqs */
    +};
    +
    +#endif
    --
    1.4.4.4

    \
     
     \ /
      Last update: 2007-12-05 11:29    [W:0.074 / U:209.880 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site