📄 vr_spi.c
字号:
/* * drivers/spi/vr_spi.c * * SPI master controller driver for the Vermilion Range SPI controller. * * Copyright (C) 2007 MontaVista Software, Inc. * * Derived from drivers/spi/pxa2xx_spi.c * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs * * 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. */#include <linux/init.h>#include <linux/module.h>#include <linux/device.h>#include <linux/ioport.h>#include <linux/errno.h>#include <linux/interrupt.h>#include <linux/dma-mapping.h>#include <linux/spi/spi.h>#include <linux/workqueue.h>#include <linux/errno.h>#include <linux/delay.h>#include <linux/pci.h>#include "vr_spi.h"/* for testing SSCR1 changes that require SSP restart, basically * everything except the interrupt enables */#define SSCR1_CHANGE_MASK (SSCR1_STRF | SSCR1_EFWR |SSCR1_RFT \ | SSCR1_TFT | SSCR1_SPH | SSCR1_SPO | SSCR1_LBM)#define START_STATE ((void*)0)#define RUNNING_STATE ((void*)1)#define DONE_STATE ((void*)2)#define ERROR_STATE ((void*)-1)#define QUEUE_RUNNING 0#define QUEUE_STOPPED 1struct driver_data { /* Driver model hookup */ struct pci_dev *dev; /* SPI framework hookup */ struct spi_master *master; /* SSP register addresses */ void __iomem *ioaddr; /* SSP masks */ u32 int_cr1; u32 clear_sr; u32 mask_sr; /* Driver message queue */ struct workqueue_struct *workqueue; struct work_struct pump_messages; spinlock_t lock; struct list_head queue; int busy; int run; /* Message Transfer pump */ struct tasklet_struct pump_transfers; /* Current message transfer state info */ struct spi_message *cur_msg; struct spi_transfer *cur_transfer; struct chip_data *cur_chip; size_t len; void *tx; void *tx_end; void *rx; void *rx_end; u8 n_bytes; int cs_change; int (*write) (struct driver_data * drv_data); int (*read) (struct driver_data * drv_data); irqreturn_t(*transfer_handler) (struct driver_data * drv_data);};struct chip_data { u32 cr0; u32 cr1; u32 psp; u32 timeout; u8 n_bytes; u32 threshold; u8 bits_per_word; u8 chip_select; u32 speed_hz; int (*write) (struct driver_data * drv_data); int (*read) (struct driver_data * drv_data);};static void vr_spi_pump_messages(void *data);static int flush(struct driver_data *drv_data){ unsigned long limit = loops_per_jiffy << 1; void __iomem *reg = drv_data->ioaddr; do { while (readl(reg + SSSR) & SSSR_RNE) { readl(reg + SSDR); } } while ((readl(reg + SSSR) & SSSR_BSY) && limit--); writel(readl(reg + SSSR) | SSSR_ROR, reg + SSSR); return limit;}static int null_writer(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; u8 n_bytes = drv_data->n_bytes; if (((readl(reg + SSSR) & 0x00000f00) == 0x00000f00) || (drv_data->tx == drv_data->tx_end)) return 0; writel(0, reg + SSDR); drv_data->tx += n_bytes; return 1;}static int null_reader(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; u8 n_bytes = drv_data->n_bytes; while ((readl(reg + SSSR) & SSSR_RNE) && (drv_data->rx < drv_data->rx_end)) { readl(reg + SSDR); drv_data->rx += n_bytes; } return drv_data->rx == drv_data->rx_end;}static int u8_writer(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; if (((readl(reg + SSSR) & 0x00000f00) == 0x00000f00) || (drv_data->tx == drv_data->tx_end)) return 0; writel(*(u8 *) (drv_data->tx), reg + SSDR); ++drv_data->tx; return 1;}static int u8_reader(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; while ((readl(reg + SSSR) & SSSR_RNE) && (drv_data->rx < drv_data->rx_end)) { *(u8 *) (drv_data->rx) = readl(reg + SSDR); ++drv_data->rx; } return drv_data->rx == drv_data->rx_end;}static int u16_writer(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; if (((readl(reg + SSSR) & 0x00000f00) == 0x00000f00) || (drv_data->tx == drv_data->tx_end)) return 0; writel(*(u16 *) (drv_data->tx), reg + SSDR); drv_data->tx += 2; return 1;}static int u16_reader(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; while ((readl(reg + SSSR) & SSSR_RNE) && (drv_data->rx < drv_data->rx_end)) { *(u16 *) (drv_data->rx) = readl(reg + SSDR); drv_data->rx += 2; } return drv_data->rx == drv_data->rx_end;}static void *next_transfer(struct driver_data *drv_data){ struct spi_message *msg = drv_data->cur_msg; struct spi_transfer *trans = drv_data->cur_transfer; /* Move to next transfer */ if (trans->transfer_list.next != &msg->transfers) { drv_data->cur_transfer = list_entry(trans->transfer_list.next, struct spi_transfer, transfer_list); return RUNNING_STATE; } else return DONE_STATE;}/* caller already set message->status; dma and pio irqs are blocked */static void giveback(struct driver_data *drv_data){ struct spi_transfer *last_transfer; unsigned long flags; struct spi_message *msg; spin_lock_irqsave(&drv_data->lock, flags); msg = drv_data->cur_msg; drv_data->cur_msg = NULL; drv_data->cur_transfer = NULL; drv_data->cur_chip = NULL; queue_work(drv_data->workqueue, &drv_data->pump_messages); spin_unlock_irqrestore(&drv_data->lock, flags); last_transfer = list_entry(msg->transfers.prev, struct spi_transfer, transfer_list); msg->state = NULL; if (msg->complete) msg->complete(msg->context);}static int wait_ssp_rx_stall(void __iomem * ioaddr){ unsigned long limit = loops_per_jiffy << 1; while ((readl(ioaddr + SSSR) & SSSR_BSY) && limit--) cpu_relax(); return limit;}static void int_error_stop(struct driver_data *drv_data, const char *msg){ void __iomem *reg = drv_data->ioaddr; /* Stop and reset SSP */ writel(readl(reg + SSSR) | drv_data->clear_sr, reg + SSSR); writel(readl(reg + SSCR1) & ~drv_data->int_cr1, reg + SSCR1); flush(drv_data); writel(readl(reg + SSCR0) & ~SSCR0_SSE, reg + SSCR0); dev_err(&drv_data->dev->dev, "%s\n", msg); drv_data->cur_msg->state = ERROR_STATE; tasklet_schedule(&drv_data->pump_transfers);}static void int_transfer_complete(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; /* Stop SSP */ writel(readl(reg + SSSR) | drv_data->clear_sr, reg + SSSR); writel(readl(reg + SSCR1) & ~drv_data->int_cr1, reg + SSCR1); /* Update total byte transfered return count actual bytes read */ drv_data->cur_msg->actual_length += drv_data->len - (drv_data->rx_end - drv_data->rx); /* Move to next transfer */ drv_data->cur_msg->state = next_transfer(drv_data); /* Schedule transfer tasklet */ tasklet_schedule(&drv_data->pump_transfers);}static irqreturn_t interrupt_transfer(struct driver_data *drv_data){ void __iomem *reg = drv_data->ioaddr; u32 irq_mask = (readl(reg + SSCR1) & SSCR1_TIE) ? drv_data->mask_sr : drv_data->mask_sr & ~SSSR_TFS; u32 irq_status = readl(reg + SSSR) & irq_mask; if (irq_status & SSSR_ROR) { int_error_stop(drv_data, "interrupt_transfer: fifo overrun"); return IRQ_HANDLED; } /* Drain rx fifo, Fill tx fifo and prevent overruns */ do { if (drv_data->read(drv_data)) { int_transfer_complete(drv_data); return IRQ_HANDLED; } } while (drv_data->write(drv_data)); if (drv_data->read(drv_data)) { int_transfer_complete(drv_data); return IRQ_HANDLED; } if (drv_data->tx == drv_data->tx_end) { writel(readl(reg + SSCR1) & ~SSCR1_TIE, reg + SSCR1); /* read trailing bytes */ if (!wait_ssp_rx_stall(reg)) { int_error_stop(drv_data, "interrupt_transfer: " "rx stall failed"); return IRQ_HANDLED; } if (!drv_data->read(drv_data)) { int_error_stop(drv_data, "interrupt_transfer: " "trailing byte read failed"); return IRQ_HANDLED; } int_transfer_complete(drv_data); } /* We did something */ return IRQ_HANDLED;}static irqreturn_t vr_spi_int(int irq, void *dev_id, struct pt_regs *pt_regs){ struct driver_data *drv_data = dev_id; void __iomem *reg = drv_data->ioaddr; if (!(readl(reg + SSSR) & drv_data->mask_sr)) { /* * This isn't our interrupt. It must be for another device * sharing this IRQ. */ return IRQ_NONE; } if (!drv_data->cur_msg) { writel(readl(reg + SSCR0) & ~SSCR0_SSE, reg + SSCR0); writel(readl(reg + SSCR1) & ~drv_data->int_cr1, reg + SSCR1); writel(readl(reg + SSSR) | drv_data->clear_sr, reg + SSSR); dev_err(&drv_data->dev->dev, "bad message state " "in interrupt handler\n"); /* Never fail */ return IRQ_HANDLED; } return drv_data->transfer_handler(drv_data);}static void vr_spi_pump_transfers(unsigned long data){ struct driver_data *drv_data = (struct driver_data *)data; struct spi_message *message = NULL; struct spi_transfer *transfer = NULL; struct spi_transfer *previous = NULL; struct chip_data *chip = NULL; void __iomem *reg = drv_data->ioaddr; u32 cr0; u32 cr1; /* Get current state information */ message = drv_data->cur_msg; transfer = drv_data->cur_transfer; chip = drv_data->cur_chip; /* Handle for abort */ if (message->state == ERROR_STATE) { message->status = -EIO; giveback(drv_data); return; } /* Handle end of message */ if (message->state == DONE_STATE) { message->status = 0; giveback(drv_data); return; } /* Delay if requested at end of transfer */ if (message->state == RUNNING_STATE) { previous = list_entry(transfer->transfer_list.prev, struct spi_transfer, transfer_list); if (previous->delay_usecs) udelay(previous->delay_usecs); } /* Check transfer length */ if (transfer->len > 8191) { dev_warn(&drv_data->dev->dev, "pump_transfers: transfer " "length greater than 8191\n"); message->status = -EINVAL; giveback(drv_data); return; } /* Setup the transfer state based on the type of transfer */ if (flush(drv_data) == 0) { dev_err(&drv_data->dev->dev, "pump_transfers: flush failed\n"); message->status = -EIO; giveback(drv_data); return; } drv_data->n_bytes = chip->n_bytes; drv_data->tx = (void *)transfer->tx_buf; drv_data->tx_end = drv_data->tx + transfer->len; drv_data->rx = transfer->rx_buf; drv_data->rx_end = drv_data->rx + transfer->len; drv_data->len = transfer->len; drv_data->write = drv_data->tx ? chip->write : null_writer; drv_data->read = drv_data->rx ? chip->read : null_reader; drv_data->cs_change = transfer->cs_change; cr0 = chip->cr0; message->state = RUNNING_STATE; /* Ensure we have the correct interrupt handler */ drv_data->transfer_handler = interrupt_transfer; /* Clear status */ cr1 = chip->cr1 | chip->threshold | drv_data->int_cr1; writel(drv_data->clear_sr | (chip->chip_select ? SSSR_ALT_FRM : 0), reg + SSSR); /* see if we need to reload the config registers */ if ((readl(reg + SSCR0) != cr0) || (readl(reg + SSCR1) & SSCR1_CHANGE_MASK) != (cr1 & SSCR1_CHANGE_MASK)) { writel(cr0 & ~SSCR0_SSE, reg + SSCR0); writel(cr1, reg + SSCR1); writel(cr0, reg + SSCR0); } else { writel(cr1, reg + SSCR1); }}static void vr_spi_pump_messages(void *data)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -