/*
* Analog Devices SPI3 controller driver
*
* Copyright (c) 2013 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
#include <asm/bfin_spi3.h>
#include <asm/cacheflush.h>
#include <asm/dma.h>
#include <asm/portmux.h>
enum bfin_spi_state {
START_STATE,
RUNNING_STATE,
DONE_STATE,
ERROR_STATE
};
struct bfin_spi_master;
struct bfin_spi_transfer_ops {
void (*write) (struct bfin_spi_master *);
void (*read) (struct bfin_spi_master *);
void (*duplex) (struct bfin_spi_master *);
};
/* runtime info for spi master */
struct bfin_spi_master {
/* SPI framework hookup */
struct spi_master *master;
/* Regs base of SPI controller */
struct bfin_spi_regs __iomem *regs;
/* Pin request list */
u16 *pin_req;
/* Message Transfer pump */
struct tasklet_struct pump_transfers;
/* Current message transfer state info */
struct spi_message *cur_msg;
struct spi_transfer *cur_transfer;
struct bfin_spi_device *cur_chip;
unsigned transfer_len;
/* transfer buffer */
void *tx;
void *tx_end;
void *rx;
void *rx_end;
/* dma info */
unsigned int tx_dma;
unsigned int rx_dma;
dma_addr_t tx_dma_addr;
dma_addr_t rx_dma_addr;
unsigned long dummy_buffer; /* used in unidirectional transfer */
unsigned long tx_dma_size;
unsigned long rx_dma_size;
int tx_num;
int rx_num;
/* store register value for suspend/resume */
u32 control;
u32 ssel;
unsigned long sclk;
enum bfin_spi_state state;
const struct bfin_spi_transfer_ops *ops;
};
struct bfin_spi_device {
u32 control;
u32 clock;
u32 ssel;
u8 cs;
u16 cs_chg_udelay; /* Some devices require > 255usec delay */
u32 cs_gpio;
u32 tx_dummy_val; /* tx value for rx only transfer */
bool enable_dma;
const struct bfin_spi_transfer_ops *ops;
};
static void bfin_spi_enable(struct bfin_spi_master *drv_data)
{
bfin_write_or(&drv_data->regs->control, SPI_CTL_EN);
}
static void bfin_spi_disable(struct bfin_spi_master *drv_data)
{
bfin_write_and(&drv_data->regs->control, ~SPI_CTL_EN);
}
/* Caculate the SPI_CLOCK register value based on input HZ */
static u32 hz_to_spi_clock(u32 sclk, u32 speed_hz)
{
u32 spi_clock = sclk / speed_hz;
if (spi_clock)
spi_clock--;
return spi_clock;
}
static int bfin_spi_flush(struct bfin_spi_master *drv_data)
{
unsigned long limit = loops_per_jiffy << 1;
/* wait for stop and clear stat */
while (!(bfin_read(&drv_data->regs->status) & SPI_STAT_SPIF) && --limit)
cpu_relax();
bfin_write(&drv_data->regs->status, 0xFFFFFFFF);
return limit;
}
/* Chip select operation functions for cs_change flag */
static void bfin_spi_cs_active(struct bfin_spi_master *drv_data, struct bfin_spi_device *chip)
{
if (likely(chip->cs < MAX_CTRL_CS))
bfin_write_and(&drv_data->regs->ssel, ~chip->ssel);
else
gpio_set_value(chip->cs_gpio, 0);
}
static void bfin_spi_cs_deactive(struct bfin_spi_master *drv_data,
struct bfin_spi_device *chip)
{
if (likely(chip->cs < MAX_CTRL_CS))
bfin_write_or(&drv_data->regs->ssel, chip->ssel);
else
gpio_set_value(chip->cs_gpio, 1);
/* Move delay here for consistency */
if (chip->cs_chg_udelay)
udelay(chip->cs_chg_udelay);
}
/* enable or disable the pin muxed by GPIO and SPI CS to work as SPI CS */
static inline void bfin_spi_cs_enable(struct bfin_spi_master *drv_data,
struct bfin_spi_device *chip)
{
if (chip->cs < MAX_CTRL_CS)
bfin_write_or(&drv_data->regs->ssel, chip->ssel >> 8);
}
static inline void bfin_spi_cs_disable(struct bfin_spi_master *drv_data,
struct bfin_spi_device *chip)
{
if (chip->cs < MAX_CTRL_CS)
bfin_write_and(&drv_data->regs->ssel, ~(chip->ssel >> 8));
}
/* stop controller and re-config current chip*/
static void bfin_spi_restore_state(struct bfin_spi_master *drv_data