mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-09 23:39:18 +00:00
Merge remote-tracking branches 'spi/topic/sunxi', 'spi/topic/tegra114', 'spi/topic/ti-qspi', 'spi/topic/ti-ssp', 'spi/topic/topcliff-pch', 'spi/topic/txx9', 'spi/topic/xcomm' and 'spi/topic/xfer' into spi-next
This commit is contained in:
parent
81235b4ea3
743a46b89a
971e9084be
9304f51e5f
7661ba5a84
342451df07
f8c717a567
af348519e4
16a0ce4e10
commit
b1ad487c42
24
Documentation/devicetree/bindings/spi/spi-sun4i.txt
Normal file
24
Documentation/devicetree/bindings/spi/spi-sun4i.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Allwinner A10 SPI controller
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Should be "allwinner,sun4-a10-spi".
|
||||||
|
- reg: Should contain register location and length.
|
||||||
|
- interrupts: Should contain interrupt.
|
||||||
|
- clocks: phandle to the clocks feeding the SPI controller. Two are
|
||||||
|
needed:
|
||||||
|
- "ahb": the gated AHB parent clock
|
||||||
|
- "mod": the parent module clock
|
||||||
|
- clock-names: Must contain the clock names described just above
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
spi1: spi@01c06000 {
|
||||||
|
compatible = "allwinner,sun4i-a10-spi";
|
||||||
|
reg = <0x01c06000 0x1000>;
|
||||||
|
interrupts = <11>;
|
||||||
|
clocks = <&ahb_gates 21>, <&spi1_clk>;
|
||||||
|
clock-names = "ahb", "mod";
|
||||||
|
status = "disabled";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
};
|
24
Documentation/devicetree/bindings/spi/spi-sun6i.txt
Normal file
24
Documentation/devicetree/bindings/spi/spi-sun6i.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Allwinner A31 SPI controller
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: Should be "allwinner,sun6i-a31-spi".
|
||||||
|
- reg: Should contain register location and length.
|
||||||
|
- interrupts: Should contain interrupt.
|
||||||
|
- clocks: phandle to the clocks feeding the SPI controller. Two are
|
||||||
|
needed:
|
||||||
|
- "ahb": the gated AHB parent clock
|
||||||
|
- "mod": the parent module clock
|
||||||
|
- clock-names: Must contain the clock names described just above
|
||||||
|
- resets: phandle to the reset controller asserting this device in
|
||||||
|
reset
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
spi1: spi@01c69000 {
|
||||||
|
compatible = "allwinner,sun6i-a31-spi";
|
||||||
|
reg = <0x01c69000 0x1000>;
|
||||||
|
interrupts = <0 66 4>;
|
||||||
|
clocks = <&ahb1_gates 21>, <&spi1_clk>;
|
||||||
|
clock-names = "ahb", "mod";
|
||||||
|
resets = <&ahb1_rst 21>;
|
||||||
|
};
|
@ -458,6 +458,19 @@ config SPI_SIRF
|
|||||||
help
|
help
|
||||||
SPI driver for CSR SiRFprimaII SoCs
|
SPI driver for CSR SiRFprimaII SoCs
|
||||||
|
|
||||||
|
config SPI_SUN4I
|
||||||
|
tristate "Allwinner A10 SoCs SPI controller"
|
||||||
|
depends on ARCH_SUNXI || COMPILE_TEST
|
||||||
|
help
|
||||||
|
SPI driver for Allwinner sun4i, sun5i and sun7i SoCs
|
||||||
|
|
||||||
|
config SPI_SUN6I
|
||||||
|
tristate "Allwinner A31 SPI controller"
|
||||||
|
depends on ARCH_SUNXI || COMPILE_TEST
|
||||||
|
depends on RESET_CONTROLLER
|
||||||
|
help
|
||||||
|
This enables using the SPI controller on the Allwinner A31 SoCs.
|
||||||
|
|
||||||
config SPI_MXS
|
config SPI_MXS
|
||||||
tristate "Freescale MXS SPI controller"
|
tristate "Freescale MXS SPI controller"
|
||||||
depends on ARCH_MXS
|
depends on ARCH_MXS
|
||||||
@ -490,13 +503,6 @@ config SPI_TEGRA20_SLINK
|
|||||||
help
|
help
|
||||||
SPI driver for Nvidia Tegra20/Tegra30 SLINK Controller interface.
|
SPI driver for Nvidia Tegra20/Tegra30 SLINK Controller interface.
|
||||||
|
|
||||||
config SPI_TI_SSP
|
|
||||||
tristate "TI Sequencer Serial Port - SPI Support"
|
|
||||||
depends on MFD_TI_SSP
|
|
||||||
help
|
|
||||||
This selects an SPI master implementation using a TI sequencer
|
|
||||||
serial port.
|
|
||||||
|
|
||||||
config SPI_TOPCLIFF_PCH
|
config SPI_TOPCLIFF_PCH
|
||||||
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI"
|
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI"
|
||||||
depends on PCI
|
depends on PCI
|
||||||
|
@ -71,10 +71,11 @@ obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o
|
|||||||
obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o
|
obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o
|
||||||
obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o
|
obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o
|
||||||
obj-$(CONFIG_SPI_SIRF) += spi-sirf.o
|
obj-$(CONFIG_SPI_SIRF) += spi-sirf.o
|
||||||
|
obj-$(CONFIG_SPI_SUN4I) += spi-sun4i.o
|
||||||
|
obj-$(CONFIG_SPI_SUN6I) += spi-sun6i.o
|
||||||
obj-$(CONFIG_SPI_TEGRA114) += spi-tegra114.o
|
obj-$(CONFIG_SPI_TEGRA114) += spi-tegra114.o
|
||||||
obj-$(CONFIG_SPI_TEGRA20_SFLASH) += spi-tegra20-sflash.o
|
obj-$(CONFIG_SPI_TEGRA20_SFLASH) += spi-tegra20-sflash.o
|
||||||
obj-$(CONFIG_SPI_TEGRA20_SLINK) += spi-tegra20-slink.o
|
obj-$(CONFIG_SPI_TEGRA20_SLINK) += spi-tegra20-slink.o
|
||||||
obj-$(CONFIG_SPI_TI_SSP) += spi-ti-ssp.o
|
|
||||||
obj-$(CONFIG_SPI_TLE62X0) += spi-tle62x0.o
|
obj-$(CONFIG_SPI_TLE62X0) += spi-tle62x0.o
|
||||||
obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o
|
obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o
|
||||||
obj-$(CONFIG_SPI_TXX9) += spi-txx9.o
|
obj-$(CONFIG_SPI_TXX9) += spi-txx9.o
|
||||||
|
478
drivers/spi/spi-sun4i.c
Normal file
478
drivers/spi/spi-sun4i.c
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 - 2014 Allwinner Tech
|
||||||
|
* Pan Nan <pannan@allwinnertech.com>
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Maxime Ripard
|
||||||
|
* Maxime Ripard <maxime.ripard@free-electrons.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
|
#include <linux/spi/spi.h>
|
||||||
|
|
||||||
|
#define SUN4I_FIFO_DEPTH 64
|
||||||
|
|
||||||
|
#define SUN4I_RXDATA_REG 0x00
|
||||||
|
|
||||||
|
#define SUN4I_TXDATA_REG 0x04
|
||||||
|
|
||||||
|
#define SUN4I_CTL_REG 0x08
|
||||||
|
#define SUN4I_CTL_ENABLE BIT(0)
|
||||||
|
#define SUN4I_CTL_MASTER BIT(1)
|
||||||
|
#define SUN4I_CTL_CPHA BIT(2)
|
||||||
|
#define SUN4I_CTL_CPOL BIT(3)
|
||||||
|
#define SUN4I_CTL_CS_ACTIVE_LOW BIT(4)
|
||||||
|
#define SUN4I_CTL_LMTF BIT(6)
|
||||||
|
#define SUN4I_CTL_TF_RST BIT(8)
|
||||||
|
#define SUN4I_CTL_RF_RST BIT(9)
|
||||||
|
#define SUN4I_CTL_XCH BIT(10)
|
||||||
|
#define SUN4I_CTL_CS_MASK 0x3000
|
||||||
|
#define SUN4I_CTL_CS(cs) (((cs) << 12) & SUN4I_CTL_CS_MASK)
|
||||||
|
#define SUN4I_CTL_DHB BIT(15)
|
||||||
|
#define SUN4I_CTL_CS_MANUAL BIT(16)
|
||||||
|
#define SUN4I_CTL_CS_LEVEL BIT(17)
|
||||||
|
#define SUN4I_CTL_TP BIT(18)
|
||||||
|
|
||||||
|
#define SUN4I_INT_CTL_REG 0x0c
|
||||||
|
#define SUN4I_INT_CTL_TC BIT(16)
|
||||||
|
|
||||||
|
#define SUN4I_INT_STA_REG 0x10
|
||||||
|
|
||||||
|
#define SUN4I_DMA_CTL_REG 0x14
|
||||||
|
|
||||||
|
#define SUN4I_WAIT_REG 0x18
|
||||||
|
|
||||||
|
#define SUN4I_CLK_CTL_REG 0x1c
|
||||||
|
#define SUN4I_CLK_CTL_CDR2_MASK 0xff
|
||||||
|
#define SUN4I_CLK_CTL_CDR2(div) ((div) & SUN4I_CLK_CTL_CDR2_MASK)
|
||||||
|
#define SUN4I_CLK_CTL_CDR1_MASK 0xf
|
||||||
|
#define SUN4I_CLK_CTL_CDR1(div) (((div) & SUN4I_CLK_CTL_CDR1_MASK) << 8)
|
||||||
|
#define SUN4I_CLK_CTL_DRS BIT(12)
|
||||||
|
|
||||||
|
#define SUN4I_BURST_CNT_REG 0x20
|
||||||
|
#define SUN4I_BURST_CNT(cnt) ((cnt) & 0xffffff)
|
||||||
|
|
||||||
|
#define SUN4I_XMIT_CNT_REG 0x24
|
||||||
|
#define SUN4I_XMIT_CNT(cnt) ((cnt) & 0xffffff)
|
||||||
|
|
||||||
|
#define SUN4I_FIFO_STA_REG 0x28
|
||||||
|
#define SUN4I_FIFO_STA_RF_CNT_MASK 0x7f
|
||||||
|
#define SUN4I_FIFO_STA_RF_CNT_BITS 0
|
||||||
|
#define SUN4I_FIFO_STA_TF_CNT_MASK 0x7f
|
||||||
|
#define SUN4I_FIFO_STA_TF_CNT_BITS 16
|
||||||
|
|
||||||
|
struct sun4i_spi {
|
||||||
|
struct spi_master *master;
|
||||||
|
void __iomem *base_addr;
|
||||||
|
struct clk *hclk;
|
||||||
|
struct clk *mclk;
|
||||||
|
|
||||||
|
struct completion done;
|
||||||
|
|
||||||
|
const u8 *tx_buf;
|
||||||
|
u8 *rx_buf;
|
||||||
|
int len;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline u32 sun4i_spi_read(struct sun4i_spi *sspi, u32 reg)
|
||||||
|
{
|
||||||
|
return readl(sspi->base_addr + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sun4i_spi_write(struct sun4i_spi *sspi, u32 reg, u32 value)
|
||||||
|
{
|
||||||
|
writel(value, sspi->base_addr + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sun4i_spi_drain_fifo(struct sun4i_spi *sspi, int len)
|
||||||
|
{
|
||||||
|
u32 reg, cnt;
|
||||||
|
u8 byte;
|
||||||
|
|
||||||
|
/* See how much data is available */
|
||||||
|
reg = sun4i_spi_read(sspi, SUN4I_FIFO_STA_REG);
|
||||||
|
reg &= SUN4I_FIFO_STA_RF_CNT_MASK;
|
||||||
|
cnt = reg >> SUN4I_FIFO_STA_RF_CNT_BITS;
|
||||||
|
|
||||||
|
if (len > cnt)
|
||||||
|
len = cnt;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
byte = readb(sspi->base_addr + SUN4I_RXDATA_REG);
|
||||||
|
if (sspi->rx_buf)
|
||||||
|
*sspi->rx_buf++ = byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sun4i_spi_fill_fifo(struct sun4i_spi *sspi, int len)
|
||||||
|
{
|
||||||
|
u8 byte;
|
||||||
|
|
||||||
|
if (len > sspi->len)
|
||||||
|
len = sspi->len;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
byte = sspi->tx_buf ? *sspi->tx_buf++ : 0;
|
||||||
|
writeb(byte, sspi->base_addr + SUN4I_TXDATA_REG);
|
||||||
|
sspi->len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sun4i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||||
|
{
|
||||||
|
struct sun4i_spi *sspi = spi_master_get_devdata(spi->master);
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = sun4i_spi_read(sspi, SUN4I_CTL_REG);
|
||||||
|
|
||||||
|
reg &= ~SUN4I_CTL_CS_MASK;
|
||||||
|
reg |= SUN4I_CTL_CS(spi->chip_select);
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
reg |= SUN4I_CTL_CS_LEVEL;
|
||||||
|
else
|
||||||
|
reg &= ~SUN4I_CTL_CS_LEVEL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Even though this looks irrelevant since we are supposed to
|
||||||
|
* be controlling the chip select manually, this bit also
|
||||||
|
* controls the levels of the chip select for inactive
|
||||||
|
* devices.
|
||||||
|
*
|
||||||
|
* If we don't set it, the chip select level will go low by
|
||||||
|
* default when the device is idle, which is not really
|
||||||
|
* expected in the common case where the chip select is active
|
||||||
|
* low.
|
||||||
|
*/
|
||||||
|
if (spi->mode & SPI_CS_HIGH)
|
||||||
|
reg &= ~SUN4I_CTL_CS_ACTIVE_LOW;
|
||||||
|
else
|
||||||
|
reg |= SUN4I_CTL_CS_ACTIVE_LOW;
|
||||||
|
|
||||||
|
sun4i_spi_write(sspi, SUN4I_CTL_REG, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun4i_spi_transfer_one(struct spi_master *master,
|
||||||
|
struct spi_device *spi,
|
||||||
|
struct spi_transfer *tfr)
|
||||||
|
{
|
||||||
|
struct sun4i_spi *sspi = spi_master_get_devdata(master);
|
||||||
|
unsigned int mclk_rate, div, timeout;
|
||||||
|
unsigned int tx_len = 0;
|
||||||
|
int ret = 0;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
/* We don't support transfer larger than the FIFO */
|
||||||
|
if (tfr->len > SUN4I_FIFO_DEPTH)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
reinit_completion(&sspi->done);
|
||||||
|
sspi->tx_buf = tfr->tx_buf;
|
||||||
|
sspi->rx_buf = tfr->rx_buf;
|
||||||
|
sspi->len = tfr->len;
|
||||||
|
|
||||||
|
/* Clear pending interrupts */
|
||||||
|
sun4i_spi_write(sspi, SUN4I_INT_STA_REG, ~0);
|
||||||
|
|
||||||
|
|
||||||
|
reg = sun4i_spi_read(sspi, SUN4I_CTL_REG);
|
||||||
|
|
||||||
|
/* Reset FIFOs */
|
||||||
|
sun4i_spi_write(sspi, SUN4I_CTL_REG,
|
||||||
|
reg | SUN4I_CTL_RF_RST | SUN4I_CTL_TF_RST);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup the transfer control register: Chip Select,
|
||||||
|
* polarities, etc.
|
||||||
|
*/
|
||||||
|
if (spi->mode & SPI_CPOL)
|
||||||
|
reg |= SUN4I_CTL_CPOL;
|
||||||
|
else
|
||||||
|
reg &= ~SUN4I_CTL_CPOL;
|
||||||
|
|
||||||
|
if (spi->mode & SPI_CPHA)
|
||||||
|
reg |= SUN4I_CTL_CPHA;
|
||||||
|
else
|
||||||
|
reg &= ~SUN4I_CTL_CPHA;
|
||||||
|
|
||||||
|
if (spi->mode & SPI_LSB_FIRST)
|
||||||
|
reg |= SUN4I_CTL_LMTF;
|
||||||
|
else
|
||||||
|
reg &= ~SUN4I_CTL_LMTF;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If it's a TX only transfer, we don't want to fill the RX
|
||||||
|
* FIFO with bogus data
|
||||||
|
*/
|
||||||
|
if (sspi->rx_buf)
|
||||||
|
reg &= ~SUN4I_CTL_DHB;
|
||||||
|
else
|
||||||
|
reg |= SUN4I_CTL_DHB;
|
||||||
|
|
||||||
|
/* We want to control the chip select manually */
|
||||||
|
reg |= SUN4I_CTL_CS_MANUAL;
|
||||||
|
|
||||||
|
sun4i_spi_write(sspi, SUN4I_CTL_REG, reg);
|
||||||
|
|
||||||
|
/* Ensure that we have a parent clock fast enough */
|
||||||
|
mclk_rate = clk_get_rate(sspi->mclk);
|
||||||
|
if (mclk_rate < (2 * spi->max_speed_hz)) {
|
||||||
|
clk_set_rate(sspi->mclk, 2 * spi->max_speed_hz);
|
||||||
|
mclk_rate = clk_get_rate(sspi->mclk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup clock divider.
|
||||||
|
*
|
||||||
|
* We have two choices there. Either we can use the clock
|
||||||
|
* divide rate 1, which is calculated thanks to this formula:
|
||||||
|
* SPI_CLK = MOD_CLK / (2 ^ (cdr + 1))
|
||||||
|
* Or we can use CDR2, which is calculated with the formula:
|
||||||
|
* SPI_CLK = MOD_CLK / (2 * (cdr + 1))
|
||||||
|
* Wether we use the former or the latter is set through the
|
||||||
|
* DRS bit.
|
||||||
|
*
|
||||||
|
* First try CDR2, and if we can't reach the expected
|
||||||
|
* frequency, fall back to CDR1.
|
||||||
|
*/
|
||||||
|
div = mclk_rate / (2 * spi->max_speed_hz);
|
||||||
|
if (div <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) {
|
||||||
|
if (div > 0)
|
||||||
|
div--;
|
||||||
|
|
||||||
|
reg = SUN4I_CLK_CTL_CDR2(div) | SUN4I_CLK_CTL_DRS;
|
||||||
|
} else {
|
||||||
|
div = ilog2(mclk_rate) - ilog2(spi->max_speed_hz);
|
||||||
|
reg = SUN4I_CLK_CTL_CDR1(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
sun4i_spi_write(sspi, SUN4I_CLK_CTL_REG, reg);
|
||||||
|
|
||||||
|
/* Setup the transfer now... */
|
||||||
|
if (sspi->tx_buf)
|
||||||
|
tx_len = tfr->len;
|
||||||
|
|
||||||
|
/* Setup the counters */
|
||||||
|
sun4i_spi_write(sspi, SUN4I_BURST_CNT_REG, SUN4I_BURST_CNT(tfr->len));
|
||||||
|
sun4i_spi_write(sspi, SUN4I_XMIT_CNT_REG, SUN4I_XMIT_CNT(tx_len));
|
||||||
|
|
||||||
|
/* Fill the TX FIFO */
|
||||||
|
sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH);
|
||||||
|
|
||||||
|
/* Enable the interrupts */
|
||||||
|
sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, SUN4I_INT_CTL_TC);
|
||||||
|
|
||||||
|
/* Start the transfer */
|
||||||
|
reg = sun4i_spi_read(sspi, SUN4I_CTL_REG);
|
||||||
|
sun4i_spi_write(sspi, SUN4I_CTL_REG, reg | SUN4I_CTL_XCH);
|
||||||
|
|
||||||
|
timeout = wait_for_completion_timeout(&sspi->done,
|
||||||
|
msecs_to_jiffies(1000));
|
||||||
|
if (!timeout) {
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH);
|
||||||
|
|
||||||
|
out:
|
||||||
|
sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t sun4i_spi_handler(int irq, void *dev_id)
|
||||||
|
{
|
||||||
|
struct sun4i_spi *sspi = dev_id;
|
||||||
|
u32 status = sun4i_spi_read(sspi, SUN4I_INT_STA_REG);
|
||||||
|
|
||||||
|
/* Transfer complete */
|
||||||
|
if (status & SUN4I_INT_CTL_TC) {
|
||||||
|
sun4i_spi_write(sspi, SUN4I_INT_STA_REG, SUN4I_INT_CTL_TC);
|
||||||
|
complete(&sspi->done);
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun4i_spi_runtime_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct spi_master *master = dev_get_drvdata(dev);
|
||||||
|
struct sun4i_spi *sspi = spi_master_get_devdata(master);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(sspi->hclk);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Couldn't enable AHB clock\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(sspi->mclk);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Couldn't enable module clock\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
sun4i_spi_write(sspi, SUN4I_CTL_REG,
|
||||||
|
SUN4I_CTL_ENABLE | SUN4I_CTL_MASTER | SUN4I_CTL_TP);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
clk_disable_unprepare(sspi->hclk);
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun4i_spi_runtime_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct spi_master *master = dev_get_drvdata(dev);
|
||||||
|
struct sun4i_spi *sspi = spi_master_get_devdata(master);
|
||||||
|
|
||||||
|
clk_disable_unprepare(sspi->mclk);
|
||||||
|
clk_disable_unprepare(sspi->hclk);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun4i_spi_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct spi_master *master;
|
||||||
|
struct sun4i_spi *sspi;
|
||||||
|
struct resource *res;
|
||||||
|
int ret = 0, irq;
|
||||||
|
|
||||||
|
master = spi_alloc_master(&pdev->dev, sizeof(struct sun4i_spi));
|
||||||
|
if (!master) {
|
||||||
|
dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, master);
|
||||||
|
sspi = spi_master_get_devdata(master);
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
sspi->base_addr = devm_ioremap_resource(&pdev->dev, res);
|
||||||
|
if (IS_ERR(sspi->base_addr)) {
|
||||||
|
ret = PTR_ERR(sspi->base_addr);
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq = platform_get_irq(pdev, 0);
|
||||||
|
if (irq < 0) {
|
||||||
|
dev_err(&pdev->dev, "No spi IRQ specified\n");
|
||||||
|
ret = -ENXIO;
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = devm_request_irq(&pdev->dev, irq, sun4i_spi_handler,
|
||||||
|
0, "sun4i-spi", sspi);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "Cannot request IRQ\n");
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
sspi->master = master;
|
||||||
|
master->set_cs = sun4i_spi_set_cs;
|
||||||
|
master->transfer_one = sun4i_spi_transfer_one;
|
||||||
|
master->num_chipselect = 4;
|
||||||
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST;
|
||||||
|
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||||
|
master->dev.of_node = pdev->dev.of_node;
|
||||||
|
master->auto_runtime_pm = true;
|
||||||
|
|
||||||
|
sspi->hclk = devm_clk_get(&pdev->dev, "ahb");
|
||||||
|
if (IS_ERR(sspi->hclk)) {
|
||||||
|
dev_err(&pdev->dev, "Unable to acquire AHB clock\n");
|
||||||
|
ret = PTR_ERR(sspi->hclk);
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
sspi->mclk = devm_clk_get(&pdev->dev, "mod");
|
||||||
|
if (IS_ERR(sspi->mclk)) {
|
||||||
|
dev_err(&pdev->dev, "Unable to acquire module clock\n");
|
||||||
|
ret = PTR_ERR(sspi->mclk);
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_completion(&sspi->done);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This wake-up/shutdown pattern is to be able to have the
|
||||||
|
* device woken up, even if runtime_pm is disabled
|
||||||
|
*/
|
||||||
|
ret = sun4i_spi_runtime_resume(&pdev->dev);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "Couldn't resume the device\n");
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
pm_runtime_set_active(&pdev->dev);
|
||||||
|
pm_runtime_enable(&pdev->dev);
|
||||||
|
pm_runtime_idle(&pdev->dev);
|
||||||
|
|
||||||
|
ret = devm_spi_register_master(&pdev->dev, master);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "cannot register SPI master\n");
|
||||||
|
goto err_pm_disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_pm_disable:
|
||||||
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
sun4i_spi_runtime_suspend(&pdev->dev);
|
||||||
|
err_free_master:
|
||||||
|
spi_master_put(master);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun4i_spi_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id sun4i_spi_match[] = {
|
||||||
|
{ .compatible = "allwinner,sun4i-a10-spi", },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, sun4i_spi_match);
|
||||||
|
|
||||||
|
static const struct dev_pm_ops sun4i_spi_pm_ops = {
|
||||||
|
.runtime_resume = sun4i_spi_runtime_resume,
|
||||||
|
.runtime_suspend = sun4i_spi_runtime_suspend,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_driver sun4i_spi_driver = {
|
||||||
|
.probe = sun4i_spi_probe,
|
||||||
|
.remove = sun4i_spi_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "sun4i-spi",
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = sun4i_spi_match,
|
||||||
|
.pm = &sun4i_spi_pm_ops,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module_platform_driver(sun4i_spi_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Pan Nan <pannan@allwinnertech.com>");
|
||||||
|
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
|
||||||
|
MODULE_DESCRIPTION("Allwinner A1X/A20 SPI controller driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
484
drivers/spi/spi-sun6i.c
Normal file
484
drivers/spi/spi-sun6i.c
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 - 2014 Allwinner Tech
|
||||||
|
* Pan Nan <pannan@allwinnertech.com>
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Maxime Ripard
|
||||||
|
* Maxime Ripard <maxime.ripard@free-electrons.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/reset.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
|
#include <linux/spi/spi.h>
|
||||||
|
|
||||||
|
#define SUN6I_FIFO_DEPTH 128
|
||||||
|
|
||||||
|
#define SUN6I_GBL_CTL_REG 0x04
|
||||||
|
#define SUN6I_GBL_CTL_BUS_ENABLE BIT(0)
|
||||||
|
#define SUN6I_GBL_CTL_MASTER BIT(1)
|
||||||
|
#define SUN6I_GBL_CTL_TP BIT(7)
|
||||||
|
#define SUN6I_GBL_CTL_RST BIT(31)
|
||||||
|
|
||||||
|
#define SUN6I_TFR_CTL_REG 0x08
|
||||||
|
#define SUN6I_TFR_CTL_CPHA BIT(0)
|
||||||
|
#define SUN6I_TFR_CTL_CPOL BIT(1)
|
||||||
|
#define SUN6I_TFR_CTL_SPOL BIT(2)
|
||||||
|
#define SUN6I_TFR_CTL_CS_MASK 0x30
|
||||||
|
#define SUN6I_TFR_CTL_CS(cs) (((cs) << 4) & SUN6I_TFR_CTL_CS_MASK)
|
||||||
|
#define SUN6I_TFR_CTL_CS_MANUAL BIT(6)
|
||||||
|
#define SUN6I_TFR_CTL_CS_LEVEL BIT(7)
|
||||||
|
#define SUN6I_TFR_CTL_DHB BIT(8)
|
||||||
|
#define SUN6I_TFR_CTL_FBS BIT(12)
|
||||||
|
#define SUN6I_TFR_CTL_XCH BIT(31)
|
||||||
|
|
||||||
|
#define SUN6I_INT_CTL_REG 0x10
|
||||||
|
#define SUN6I_INT_CTL_RF_OVF BIT(8)
|
||||||
|
#define SUN6I_INT_CTL_TC BIT(12)
|
||||||
|
|
||||||
|
#define SUN6I_INT_STA_REG 0x14
|
||||||
|
|
||||||
|
#define SUN6I_FIFO_CTL_REG 0x18
|
||||||
|
#define SUN6I_FIFO_CTL_RF_RST BIT(15)
|
||||||
|
#define SUN6I_FIFO_CTL_TF_RST BIT(31)
|
||||||
|
|
||||||
|
#define SUN6I_FIFO_STA_REG 0x1c
|
||||||
|
#define SUN6I_FIFO_STA_RF_CNT_MASK 0x7f
|
||||||
|
#define SUN6I_FIFO_STA_RF_CNT_BITS 0
|
||||||
|
#define SUN6I_FIFO_STA_TF_CNT_MASK 0x7f
|
||||||
|
#define SUN6I_FIFO_STA_TF_CNT_BITS 16
|
||||||
|
|
||||||
|
#define SUN6I_CLK_CTL_REG 0x24
|
||||||
|
#define SUN6I_CLK_CTL_CDR2_MASK 0xff
|
||||||
|
#define SUN6I_CLK_CTL_CDR2(div) (((div) & SUN6I_CLK_CTL_CDR2_MASK) << 0)
|
||||||
|
#define SUN6I_CLK_CTL_CDR1_MASK 0xf
|
||||||
|
#define SUN6I_CLK_CTL_CDR1(div) (((div) & SUN6I_CLK_CTL_CDR1_MASK) << 8)
|
||||||
|
#define SUN6I_CLK_CTL_DRS BIT(12)
|
||||||
|
|
||||||
|
#define SUN6I_BURST_CNT_REG 0x30
|
||||||
|
#define SUN6I_BURST_CNT(cnt) ((cnt) & 0xffffff)
|
||||||
|
|
||||||
|
#define SUN6I_XMIT_CNT_REG 0x34
|
||||||
|
#define SUN6I_XMIT_CNT(cnt) ((cnt) & 0xffffff)
|
||||||
|
|
||||||
|
#define SUN6I_BURST_CTL_CNT_REG 0x38
|
||||||
|
#define SUN6I_BURST_CTL_CNT_STC(cnt) ((cnt) & 0xffffff)
|
||||||
|
|
||||||
|
#define SUN6I_TXDATA_REG 0x200
|
||||||
|
#define SUN6I_RXDATA_REG 0x300
|
||||||
|
|
||||||
|
struct sun6i_spi {
|
||||||
|
struct spi_master *master;
|
||||||
|
void __iomem *base_addr;
|
||||||
|
struct clk *hclk;
|
||||||
|
struct clk *mclk;
|
||||||
|
struct reset_control *rstc;
|
||||||
|
|
||||||
|
struct completion done;
|
||||||
|
|
||||||
|
const u8 *tx_buf;
|
||||||
|
u8 *rx_buf;
|
||||||
|
int len;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline u32 sun6i_spi_read(struct sun6i_spi *sspi, u32 reg)
|
||||||
|
{
|
||||||
|
return readl(sspi->base_addr + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sun6i_spi_write(struct sun6i_spi *sspi, u32 reg, u32 value)
|
||||||
|
{
|
||||||
|
writel(value, sspi->base_addr + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi, int len)
|
||||||
|
{
|
||||||
|
u32 reg, cnt;
|
||||||
|
u8 byte;
|
||||||
|
|
||||||
|
/* See how much data is available */
|
||||||
|
reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG);
|
||||||
|
reg &= SUN6I_FIFO_STA_RF_CNT_MASK;
|
||||||
|
cnt = reg >> SUN6I_FIFO_STA_RF_CNT_BITS;
|
||||||
|
|
||||||
|
if (len > cnt)
|
||||||
|
len = cnt;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
byte = readb(sspi->base_addr + SUN6I_RXDATA_REG);
|
||||||
|
if (sspi->rx_buf)
|
||||||
|
*sspi->rx_buf++ = byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sun6i_spi_fill_fifo(struct sun6i_spi *sspi, int len)
|
||||||
|
{
|
||||||
|
u8 byte;
|
||||||
|
|
||||||
|
if (len > sspi->len)
|
||||||
|
len = sspi->len;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
byte = sspi->tx_buf ? *sspi->tx_buf++ : 0;
|
||||||
|
writeb(byte, sspi->base_addr + SUN6I_TXDATA_REG);
|
||||||
|
sspi->len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sun6i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||||
|
{
|
||||||
|
struct sun6i_spi *sspi = spi_master_get_devdata(spi->master);
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||||
|
reg &= ~SUN6I_TFR_CTL_CS_MASK;
|
||||||
|
reg |= SUN6I_TFR_CTL_CS(spi->chip_select);
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
reg |= SUN6I_TFR_CTL_CS_LEVEL;
|
||||||
|
else
|
||||||
|
reg &= ~SUN6I_TFR_CTL_CS_LEVEL;
|
||||||
|
|
||||||
|
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||||
|
struct spi_device *spi,
|
||||||
|
struct spi_transfer *tfr)
|
||||||
|
{
|
||||||
|
struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||||
|
unsigned int mclk_rate, div, timeout;
|
||||||
|
unsigned int tx_len = 0;
|
||||||
|
int ret = 0;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
/* We don't support transfer larger than the FIFO */
|
||||||
|
if (tfr->len > SUN6I_FIFO_DEPTH)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
reinit_completion(&sspi->done);
|
||||||
|
sspi->tx_buf = tfr->tx_buf;
|
||||||
|
sspi->rx_buf = tfr->rx_buf;
|
||||||
|
sspi->len = tfr->len;
|
||||||
|
|
||||||
|
/* Clear pending interrupts */
|
||||||
|
sun6i_spi_write(sspi, SUN6I_INT_STA_REG, ~0);
|
||||||
|
|
||||||
|
/* Reset FIFO */
|
||||||
|
sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG,
|
||||||
|
SUN6I_FIFO_CTL_RF_RST | SUN6I_FIFO_CTL_TF_RST);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup the transfer control register: Chip Select,
|
||||||
|
* polarities, etc.
|
||||||
|
*/
|
||||||
|
reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||||
|
|
||||||
|
if (spi->mode & SPI_CPOL)
|
||||||
|
reg |= SUN6I_TFR_CTL_CPOL;
|
||||||
|
else
|
||||||
|
reg &= ~SUN6I_TFR_CTL_CPOL;
|
||||||
|
|
||||||
|
if (spi->mode & SPI_CPHA)
|
||||||
|
reg |= SUN6I_TFR_CTL_CPHA;
|
||||||
|
else
|
||||||
|
reg &= ~SUN6I_TFR_CTL_CPHA;
|
||||||
|
|
||||||
|
if (spi->mode & SPI_LSB_FIRST)
|
||||||
|
reg |= SUN6I_TFR_CTL_FBS;
|
||||||
|
else
|
||||||
|
reg &= ~SUN6I_TFR_CTL_FBS;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If it's a TX only transfer, we don't want to fill the RX
|
||||||
|
* FIFO with bogus data
|
||||||
|
*/
|
||||||
|
if (sspi->rx_buf)
|
||||||
|
reg &= ~SUN6I_TFR_CTL_DHB;
|
||||||
|
else
|
||||||
|
reg |= SUN6I_TFR_CTL_DHB;
|
||||||
|
|
||||||
|
/* We want to control the chip select manually */
|
||||||
|
reg |= SUN6I_TFR_CTL_CS_MANUAL;
|
||||||
|
|
||||||
|
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
||||||
|
|
||||||
|
/* Ensure that we have a parent clock fast enough */
|
||||||
|
mclk_rate = clk_get_rate(sspi->mclk);
|
||||||
|
if (mclk_rate < (2 * spi->max_speed_hz)) {
|
||||||
|
clk_set_rate(sspi->mclk, 2 * spi->max_speed_hz);
|
||||||
|
mclk_rate = clk_get_rate(sspi->mclk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup clock divider.
|
||||||
|
*
|
||||||
|
* We have two choices there. Either we can use the clock
|
||||||
|
* divide rate 1, which is calculated thanks to this formula:
|
||||||
|
* SPI_CLK = MOD_CLK / (2 ^ cdr)
|
||||||
|
* Or we can use CDR2, which is calculated with the formula:
|
||||||
|
* SPI_CLK = MOD_CLK / (2 * (cdr + 1))
|
||||||
|
* Wether we use the former or the latter is set through the
|
||||||
|
* DRS bit.
|
||||||
|
*
|
||||||
|
* First try CDR2, and if we can't reach the expected
|
||||||
|
* frequency, fall back to CDR1.
|
||||||
|
*/
|
||||||
|
div = mclk_rate / (2 * spi->max_speed_hz);
|
||||||
|
if (div <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
|
||||||
|
if (div > 0)
|
||||||
|
div--;
|
||||||
|
|
||||||
|
reg = SUN6I_CLK_CTL_CDR2(div) | SUN6I_CLK_CTL_DRS;
|
||||||
|
} else {
|
||||||
|
div = ilog2(mclk_rate) - ilog2(spi->max_speed_hz);
|
||||||
|
reg = SUN6I_CLK_CTL_CDR1(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg);
|
||||||
|
|
||||||
|
/* Setup the transfer now... */
|
||||||
|
if (sspi->tx_buf)
|
||||||
|
tx_len = tfr->len;
|
||||||
|
|
||||||
|
/* Setup the counters */
|
||||||
|
sun6i_spi_write(sspi, SUN6I_BURST_CNT_REG, SUN6I_BURST_CNT(tfr->len));
|
||||||
|
sun6i_spi_write(sspi, SUN6I_XMIT_CNT_REG, SUN6I_XMIT_CNT(tx_len));
|
||||||
|
sun6i_spi_write(sspi, SUN6I_BURST_CTL_CNT_REG,
|
||||||
|
SUN6I_BURST_CTL_CNT_STC(tx_len));
|
||||||
|
|
||||||
|
/* Fill the TX FIFO */
|
||||||
|
sun6i_spi_fill_fifo(sspi, SUN6I_FIFO_DEPTH);
|
||||||
|
|
||||||
|
/* Enable the interrupts */
|
||||||
|
sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, SUN6I_INT_CTL_TC);
|
||||||
|
|
||||||
|
/* Start the transfer */
|
||||||
|
reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||||
|
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg | SUN6I_TFR_CTL_XCH);
|
||||||
|
|
||||||
|
timeout = wait_for_completion_timeout(&sspi->done,
|
||||||
|
msecs_to_jiffies(1000));
|
||||||
|
if (!timeout) {
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sun6i_spi_drain_fifo(sspi, SUN6I_FIFO_DEPTH);
|
||||||
|
|
||||||
|
out:
|
||||||
|
sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
|
||||||
|
{
|
||||||
|
struct sun6i_spi *sspi = dev_id;
|
||||||
|
u32 status = sun6i_spi_read(sspi, SUN6I_INT_STA_REG);
|
||||||
|
|
||||||
|
/* Transfer complete */
|
||||||
|
if (status & SUN6I_INT_CTL_TC) {
|
||||||
|
sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TC);
|
||||||
|
complete(&sspi->done);
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun6i_spi_runtime_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct spi_master *master = dev_get_drvdata(dev);
|
||||||
|
struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(sspi->hclk);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Couldn't enable AHB clock\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(sspi->mclk);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Couldn't enable module clock\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = reset_control_deassert(sspi->rstc);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Couldn't deassert the device from reset\n");
|
||||||
|
goto err2;
|
||||||
|
}
|
||||||
|
|
||||||
|
sun6i_spi_write(sspi, SUN6I_GBL_CTL_REG,
|
||||||
|
SUN6I_GBL_CTL_BUS_ENABLE | SUN6I_GBL_CTL_MASTER | SUN6I_GBL_CTL_TP);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err2:
|
||||||
|
clk_disable_unprepare(sspi->mclk);
|
||||||
|
err:
|
||||||
|
clk_disable_unprepare(sspi->hclk);
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun6i_spi_runtime_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct spi_master *master = dev_get_drvdata(dev);
|
||||||
|
struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||||
|
|
||||||
|
reset_control_assert(sspi->rstc);
|
||||||
|
clk_disable_unprepare(sspi->mclk);
|
||||||
|
clk_disable_unprepare(sspi->hclk);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun6i_spi_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct spi_master *master;
|
||||||
|
struct sun6i_spi *sspi;
|
||||||
|
struct resource *res;
|
||||||
|
int ret = 0, irq;
|
||||||
|
|
||||||
|
master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));
|
||||||
|
if (!master) {
|
||||||
|
dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, master);
|
||||||
|
sspi = spi_master_get_devdata(master);
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
sspi->base_addr = devm_ioremap_resource(&pdev->dev, res);
|
||||||
|
if (IS_ERR(sspi->base_addr)) {
|
||||||
|
ret = PTR_ERR(sspi->base_addr);
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq = platform_get_irq(pdev, 0);
|
||||||
|
if (irq < 0) {
|
||||||
|
dev_err(&pdev->dev, "No spi IRQ specified\n");
|
||||||
|
ret = -ENXIO;
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = devm_request_irq(&pdev->dev, irq, sun6i_spi_handler,
|
||||||
|
0, "sun6i-spi", sspi);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "Cannot request IRQ\n");
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
sspi->master = master;
|
||||||
|
master->set_cs = sun6i_spi_set_cs;
|
||||||
|
master->transfer_one = sun6i_spi_transfer_one;
|
||||||
|
master->num_chipselect = 4;
|
||||||
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST;
|
||||||
|
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||||
|
master->dev.of_node = pdev->dev.of_node;
|
||||||
|
master->auto_runtime_pm = true;
|
||||||
|
|
||||||
|
sspi->hclk = devm_clk_get(&pdev->dev, "ahb");
|
||||||
|
if (IS_ERR(sspi->hclk)) {
|
||||||
|
dev_err(&pdev->dev, "Unable to acquire AHB clock\n");
|
||||||
|
ret = PTR_ERR(sspi->hclk);
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
sspi->mclk = devm_clk_get(&pdev->dev, "mod");
|
||||||
|
if (IS_ERR(sspi->mclk)) {
|
||||||
|
dev_err(&pdev->dev, "Unable to acquire module clock\n");
|
||||||
|
ret = PTR_ERR(sspi->mclk);
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_completion(&sspi->done);
|
||||||
|
|
||||||
|
sspi->rstc = devm_reset_control_get(&pdev->dev, NULL);
|
||||||
|
if (IS_ERR(sspi->rstc)) {
|
||||||
|
dev_err(&pdev->dev, "Couldn't get reset controller\n");
|
||||||
|
ret = PTR_ERR(sspi->rstc);
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This wake-up/shutdown pattern is to be able to have the
|
||||||
|
* device woken up, even if runtime_pm is disabled
|
||||||
|
*/
|
||||||
|
ret = sun6i_spi_runtime_resume(&pdev->dev);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "Couldn't resume the device\n");
|
||||||
|
goto err_free_master;
|
||||||
|
}
|
||||||
|
|
||||||
|
pm_runtime_set_active(&pdev->dev);
|
||||||
|
pm_runtime_enable(&pdev->dev);
|
||||||
|
pm_runtime_idle(&pdev->dev);
|
||||||
|
|
||||||
|
ret = devm_spi_register_master(&pdev->dev, master);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "cannot register SPI master\n");
|
||||||
|
goto err_pm_disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_pm_disable:
|
||||||
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
sun6i_spi_runtime_suspend(&pdev->dev);
|
||||||
|
err_free_master:
|
||||||
|
spi_master_put(master);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sun6i_spi_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id sun6i_spi_match[] = {
|
||||||
|
{ .compatible = "allwinner,sun6i-a31-spi", },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, sun6i_spi_match);
|
||||||
|
|
||||||
|
static const struct dev_pm_ops sun6i_spi_pm_ops = {
|
||||||
|
.runtime_resume = sun6i_spi_runtime_resume,
|
||||||
|
.runtime_suspend = sun6i_spi_runtime_suspend,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_driver sun6i_spi_driver = {
|
||||||
|
.probe = sun6i_spi_probe,
|
||||||
|
.remove = sun6i_spi_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "sun6i-spi",
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = sun6i_spi_match,
|
||||||
|
.pm = &sun6i_spi_pm_ops,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module_platform_driver(sun6i_spi_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Pan Nan <pannan@allwinnertech.com>");
|
||||||
|
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
|
||||||
|
MODULE_DESCRIPTION("Allwinner A31 SPI controller driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
@ -846,8 +846,8 @@ complete_xfer:
|
|||||||
SPI_COMMAND1);
|
SPI_COMMAND1);
|
||||||
tegra_spi_transfer_delay(xfer->delay_usecs);
|
tegra_spi_transfer_delay(xfer->delay_usecs);
|
||||||
goto exit;
|
goto exit;
|
||||||
} else if (msg->transfers.prev == &xfer->transfer_list) {
|
} else if (list_is_last(&xfer->transfer_list,
|
||||||
/* This is the last transfer in message */
|
&msg->transfers)) {
|
||||||
if (xfer->cs_change)
|
if (xfer->cs_change)
|
||||||
tspi->cs_control = spi;
|
tspi->cs_control = spi;
|
||||||
else {
|
else {
|
||||||
|
@ -461,7 +461,6 @@ static int ti_qspi_probe(struct platform_device *pdev)
|
|||||||
if (res_mmap == NULL) {
|
if (res_mmap == NULL) {
|
||||||
dev_err(&pdev->dev,
|
dev_err(&pdev->dev,
|
||||||
"memory mapped resource not required\n");
|
"memory mapped resource not required\n");
|
||||||
return -ENODEV;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,378 +0,0 @@
|
|||||||
/*
|
|
||||||
* Sequencer Serial Port (SSP) based SPI master driver
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010 Texas Instruments Inc
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/err.h>
|
|
||||||
#include <linux/completion.h>
|
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/spi/spi.h>
|
|
||||||
#include <linux/mfd/ti_ssp.h>
|
|
||||||
|
|
||||||
#define MODE_BITS (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH)
|
|
||||||
|
|
||||||
struct ti_ssp_spi {
|
|
||||||
struct spi_master *master;
|
|
||||||
struct device *dev;
|
|
||||||
spinlock_t lock;
|
|
||||||
struct list_head msg_queue;
|
|
||||||
struct completion complete;
|
|
||||||
bool shutdown;
|
|
||||||
struct workqueue_struct *workqueue;
|
|
||||||
struct work_struct work;
|
|
||||||
u8 mode, bpw;
|
|
||||||
int cs_active;
|
|
||||||
u32 pc_en, pc_dis, pc_wr, pc_rd;
|
|
||||||
void (*select)(int cs);
|
|
||||||
};
|
|
||||||
|
|
||||||
static u32 ti_ssp_spi_rx(struct ti_ssp_spi *hw)
|
|
||||||
{
|
|
||||||
u32 ret;
|
|
||||||
|
|
||||||
ti_ssp_run(hw->dev, hw->pc_rd, 0, &ret);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ti_ssp_spi_tx(struct ti_ssp_spi *hw, u32 data)
|
|
||||||
{
|
|
||||||
ti_ssp_run(hw->dev, hw->pc_wr, data << (32 - hw->bpw), NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ti_ssp_spi_txrx(struct ti_ssp_spi *hw, struct spi_message *msg,
|
|
||||||
struct spi_transfer *t)
|
|
||||||
{
|
|
||||||
int count;
|
|
||||||
|
|
||||||
if (hw->bpw <= 8) {
|
|
||||||
u8 *rx = t->rx_buf;
|
|
||||||
const u8 *tx = t->tx_buf;
|
|
||||||
|
|
||||||
for (count = 0; count < t->len; count += 1) {
|
|
||||||
if (t->tx_buf)
|
|
||||||
ti_ssp_spi_tx(hw, *tx++);
|
|
||||||
if (t->rx_buf)
|
|
||||||
*rx++ = ti_ssp_spi_rx(hw);
|
|
||||||
}
|
|
||||||
} else if (hw->bpw <= 16) {
|
|
||||||
u16 *rx = t->rx_buf;
|
|
||||||
const u16 *tx = t->tx_buf;
|
|
||||||
|
|
||||||
for (count = 0; count < t->len; count += 2) {
|
|
||||||
if (t->tx_buf)
|
|
||||||
ti_ssp_spi_tx(hw, *tx++);
|
|
||||||
if (t->rx_buf)
|
|
||||||
*rx++ = ti_ssp_spi_rx(hw);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
u32 *rx = t->rx_buf;
|
|
||||||
const u32 *tx = t->tx_buf;
|
|
||||||
|
|
||||||
for (count = 0; count < t->len; count += 4) {
|
|
||||||
if (t->tx_buf)
|
|
||||||
ti_ssp_spi_tx(hw, *tx++);
|
|
||||||
if (t->rx_buf)
|
|
||||||
*rx++ = ti_ssp_spi_rx(hw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msg->actual_length += count; /* bytes transferred */
|
|
||||||
|
|
||||||
dev_dbg(&msg->spi->dev, "xfer %s%s, %d bytes, %d bpw, count %d%s\n",
|
|
||||||
t->tx_buf ? "tx" : "", t->rx_buf ? "rx" : "", t->len,
|
|
||||||
hw->bpw, count, (count < t->len) ? " (under)" : "");
|
|
||||||
|
|
||||||
return (count < t->len) ? -EIO : 0; /* left over data */
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ti_ssp_spi_chip_select(struct ti_ssp_spi *hw, int cs_active)
|
|
||||||
{
|
|
||||||
cs_active = !!cs_active;
|
|
||||||
if (cs_active == hw->cs_active)
|
|
||||||
return;
|
|
||||||
ti_ssp_run(hw->dev, cs_active ? hw->pc_en : hw->pc_dis, 0, NULL);
|
|
||||||
hw->cs_active = cs_active;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define __SHIFT_OUT(bits) (SSP_OPCODE_SHIFT | SSP_OUT_MODE | \
|
|
||||||
cs_en | clk | SSP_COUNT((bits) * 2 - 1))
|
|
||||||
#define __SHIFT_IN(bits) (SSP_OPCODE_SHIFT | SSP_IN_MODE | \
|
|
||||||
cs_en | clk | SSP_COUNT((bits) * 2 - 1))
|
|
||||||
|
|
||||||
static int ti_ssp_spi_setup_transfer(struct ti_ssp_spi *hw, u8 bpw, u8 mode)
|
|
||||||
{
|
|
||||||
int error, idx = 0;
|
|
||||||
u32 seqram[16];
|
|
||||||
u32 cs_en, cs_dis, clk;
|
|
||||||
u32 topbits, botbits;
|
|
||||||
|
|
||||||
mode &= MODE_BITS;
|
|
||||||
if (mode == hw->mode && bpw == hw->bpw)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
cs_en = (mode & SPI_CS_HIGH) ? SSP_CS_HIGH : SSP_CS_LOW;
|
|
||||||
cs_dis = (mode & SPI_CS_HIGH) ? SSP_CS_LOW : SSP_CS_HIGH;
|
|
||||||
clk = (mode & SPI_CPOL) ? SSP_CLK_HIGH : SSP_CLK_LOW;
|
|
||||||
|
|
||||||
/* Construct instructions */
|
|
||||||
|
|
||||||
/* Disable Chip Select */
|
|
||||||
hw->pc_dis = idx;
|
|
||||||
seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_dis | clk;
|
|
||||||
seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_dis | clk;
|
|
||||||
|
|
||||||
/* Enable Chip Select */
|
|
||||||
hw->pc_en = idx;
|
|
||||||
seqram[idx++] = SSP_OPCODE_DIRECT | SSP_OUT_MODE | cs_en | clk;
|
|
||||||
seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk;
|
|
||||||
|
|
||||||
/* Reads and writes need to be split for bpw > 16 */
|
|
||||||
topbits = (bpw > 16) ? 16 : bpw;
|
|
||||||
botbits = bpw - topbits;
|
|
||||||
|
|
||||||
/* Write */
|
|
||||||
hw->pc_wr = idx;
|
|
||||||
seqram[idx++] = __SHIFT_OUT(topbits) | SSP_ADDR_REG;
|
|
||||||
if (botbits)
|
|
||||||
seqram[idx++] = __SHIFT_OUT(botbits) | SSP_DATA_REG;
|
|
||||||
seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk;
|
|
||||||
|
|
||||||
/* Read */
|
|
||||||
hw->pc_rd = idx;
|
|
||||||
if (botbits)
|
|
||||||
seqram[idx++] = __SHIFT_IN(botbits) | SSP_ADDR_REG;
|
|
||||||
seqram[idx++] = __SHIFT_IN(topbits) | SSP_DATA_REG;
|
|
||||||
seqram[idx++] = SSP_OPCODE_STOP | SSP_OUT_MODE | cs_en | clk;
|
|
||||||
|
|
||||||
error = ti_ssp_load(hw->dev, 0, seqram, idx);
|
|
||||||
if (error < 0)
|
|
||||||
return error;
|
|
||||||
|
|
||||||
error = ti_ssp_set_mode(hw->dev, ((mode & SPI_CPHA) ?
|
|
||||||
0 : SSP_EARLY_DIN));
|
|
||||||
if (error < 0)
|
|
||||||
return error;
|
|
||||||
|
|
||||||
hw->bpw = bpw;
|
|
||||||
hw->mode = mode;
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ti_ssp_spi_work(struct work_struct *work)
|
|
||||||
{
|
|
||||||
struct ti_ssp_spi *hw = container_of(work, struct ti_ssp_spi, work);
|
|
||||||
|
|
||||||
spin_lock(&hw->lock);
|
|
||||||
|
|
||||||
while (!list_empty(&hw->msg_queue)) {
|
|
||||||
struct spi_message *m;
|
|
||||||
struct spi_device *spi;
|
|
||||||
struct spi_transfer *t = NULL;
|
|
||||||
int status = 0;
|
|
||||||
|
|
||||||
m = container_of(hw->msg_queue.next, struct spi_message,
|
|
||||||
queue);
|
|
||||||
|
|
||||||
list_del_init(&m->queue);
|
|
||||||
|
|
||||||
spin_unlock(&hw->lock);
|
|
||||||
|
|
||||||
spi = m->spi;
|
|
||||||
|
|
||||||
if (hw->select)
|
|
||||||
hw->select(spi->chip_select);
|
|
||||||
|
|
||||||
list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
||||||
int bpw = spi->bits_per_word;
|
|
||||||
int xfer_status;
|
|
||||||
|
|
||||||
if (t->bits_per_word)
|
|
||||||
bpw = t->bits_per_word;
|
|
||||||
|
|
||||||
if (ti_ssp_spi_setup_transfer(hw, bpw, spi->mode) < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
ti_ssp_spi_chip_select(hw, 1);
|
|
||||||
|
|
||||||
xfer_status = ti_ssp_spi_txrx(hw, m, t);
|
|
||||||
if (xfer_status < 0)
|
|
||||||
status = xfer_status;
|
|
||||||
|
|
||||||
if (t->delay_usecs)
|
|
||||||
udelay(t->delay_usecs);
|
|
||||||
|
|
||||||
if (t->cs_change)
|
|
||||||
ti_ssp_spi_chip_select(hw, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ti_ssp_spi_chip_select(hw, 0);
|
|
||||||
m->status = status;
|
|
||||||
m->complete(m->context);
|
|
||||||
|
|
||||||
spin_lock(&hw->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hw->shutdown)
|
|
||||||
complete(&hw->complete);
|
|
||||||
|
|
||||||
spin_unlock(&hw->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ti_ssp_spi_transfer(struct spi_device *spi, struct spi_message *m)
|
|
||||||
{
|
|
||||||
struct ti_ssp_spi *hw;
|
|
||||||
struct spi_transfer *t;
|
|
||||||
int error = 0;
|
|
||||||
|
|
||||||
m->actual_length = 0;
|
|
||||||
m->status = -EINPROGRESS;
|
|
||||||
|
|
||||||
hw = spi_master_get_devdata(spi->master);
|
|
||||||
|
|
||||||
if (list_empty(&m->transfers) || !m->complete)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
||||||
if (t->len && !(t->rx_buf || t->tx_buf)) {
|
|
||||||
dev_err(&spi->dev, "invalid xfer, no buffer\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t->len && t->rx_buf && t->tx_buf) {
|
|
||||||
dev_err(&spi->dev, "invalid xfer, full duplex\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock(&hw->lock);
|
|
||||||
if (hw->shutdown) {
|
|
||||||
error = -ESHUTDOWN;
|
|
||||||
goto error_unlock;
|
|
||||||
}
|
|
||||||
list_add_tail(&m->queue, &hw->msg_queue);
|
|
||||||
queue_work(hw->workqueue, &hw->work);
|
|
||||||
error_unlock:
|
|
||||||
spin_unlock(&hw->lock);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ti_ssp_spi_probe(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
const struct ti_ssp_spi_data *pdata;
|
|
||||||
struct ti_ssp_spi *hw;
|
|
||||||
struct spi_master *master;
|
|
||||||
struct device *dev = &pdev->dev;
|
|
||||||
int error = 0;
|
|
||||||
|
|
||||||
pdata = dev_get_platdata(dev);
|
|
||||||
if (!pdata) {
|
|
||||||
dev_err(dev, "platform data not found\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
master = spi_alloc_master(dev, sizeof(struct ti_ssp_spi));
|
|
||||||
if (!master) {
|
|
||||||
dev_err(dev, "cannot allocate SPI master\n");
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
hw = spi_master_get_devdata(master);
|
|
||||||
platform_set_drvdata(pdev, hw);
|
|
||||||
|
|
||||||
hw->master = master;
|
|
||||||
hw->dev = dev;
|
|
||||||
hw->select = pdata->select;
|
|
||||||
|
|
||||||
spin_lock_init(&hw->lock);
|
|
||||||
init_completion(&hw->complete);
|
|
||||||
INIT_LIST_HEAD(&hw->msg_queue);
|
|
||||||
INIT_WORK(&hw->work, ti_ssp_spi_work);
|
|
||||||
|
|
||||||
hw->workqueue = create_singlethread_workqueue(dev_name(dev));
|
|
||||||
if (!hw->workqueue) {
|
|
||||||
error = -ENOMEM;
|
|
||||||
dev_err(dev, "work queue creation failed\n");
|
|
||||||
goto error_wq;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = ti_ssp_set_iosel(hw->dev, pdata->iosel);
|
|
||||||
if (error < 0) {
|
|
||||||
dev_err(dev, "io setup failed\n");
|
|
||||||
goto error_iosel;
|
|
||||||
}
|
|
||||||
|
|
||||||
master->bus_num = pdev->id;
|
|
||||||
master->num_chipselect = pdata->num_cs;
|
|
||||||
master->mode_bits = MODE_BITS;
|
|
||||||
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
|
|
||||||
master->flags = SPI_MASTER_HALF_DUPLEX;
|
|
||||||
master->transfer = ti_ssp_spi_transfer;
|
|
||||||
|
|
||||||
error = spi_register_master(master);
|
|
||||||
if (error) {
|
|
||||||
dev_err(dev, "master registration failed\n");
|
|
||||||
goto error_reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error_reg:
|
|
||||||
error_iosel:
|
|
||||||
destroy_workqueue(hw->workqueue);
|
|
||||||
error_wq:
|
|
||||||
spi_master_put(master);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ti_ssp_spi_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct ti_ssp_spi *hw = platform_get_drvdata(pdev);
|
|
||||||
int error;
|
|
||||||
|
|
||||||
hw->shutdown = 1;
|
|
||||||
while (!list_empty(&hw->msg_queue)) {
|
|
||||||
error = wait_for_completion_interruptible(&hw->complete);
|
|
||||||
if (error < 0) {
|
|
||||||
hw->shutdown = 0;
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destroy_workqueue(hw->workqueue);
|
|
||||||
spi_unregister_master(hw->master);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct platform_driver ti_ssp_spi_driver = {
|
|
||||||
.probe = ti_ssp_spi_probe,
|
|
||||||
.remove = ti_ssp_spi_remove,
|
|
||||||
.driver = {
|
|
||||||
.name = "ti-ssp-spi",
|
|
||||||
.owner = THIS_MODULE,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
module_platform_driver(ti_ssp_spi_driver);
|
|
||||||
|
|
||||||
MODULE_DESCRIPTION("SSP SPI Master");
|
|
||||||
MODULE_AUTHOR("Cyril Chemparathy");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
MODULE_ALIAS("platform:ti-ssp-spi");
|
|
@ -332,7 +332,7 @@ static void pch_spi_handler_sub(struct pch_spi_data *data, u32 reg_spsr_val,
|
|||||||
data->transfer_active = false;
|
data->transfer_active = false;
|
||||||
wake_up(&data->wait);
|
wake_up(&data->wait);
|
||||||
} else {
|
} else {
|
||||||
dev_err(&data->master->dev,
|
dev_vdbg(&data->master->dev,
|
||||||
"%s : Transfer is not completed",
|
"%s : Transfer is not completed",
|
||||||
__func__);
|
__func__);
|
||||||
}
|
}
|
||||||
@ -464,20 +464,6 @@ static void pch_spi_reset(struct spi_master *master)
|
|||||||
pch_spi_writereg(master, PCH_SRST, 0x0);
|
pch_spi_writereg(master, PCH_SRST, 0x0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pch_spi_setup(struct spi_device *pspi)
|
|
||||||
{
|
|
||||||
/* Check baud rate setting */
|
|
||||||
/* if baud rate of chip is greater than
|
|
||||||
max we can support,return error */
|
|
||||||
if ((pspi->max_speed_hz) > PCH_MAX_BAUDRATE)
|
|
||||||
pspi->max_speed_hz = PCH_MAX_BAUDRATE;
|
|
||||||
|
|
||||||
dev_dbg(&pspi->dev, "%s MODE = %x\n", __func__,
|
|
||||||
(pspi->mode) & (SPI_CPOL | SPI_CPHA));
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
|
static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -486,23 +472,6 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
|
|||||||
int retval;
|
int retval;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
/* validate spi message and baud rate */
|
|
||||||
if (unlikely(list_empty(&pmsg->transfers) == 1)) {
|
|
||||||
dev_err(&pspi->dev, "%s list empty\n", __func__);
|
|
||||||
retval = -EINVAL;
|
|
||||||
goto err_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unlikely(pspi->max_speed_hz == 0)) {
|
|
||||||
dev_err(&pspi->dev, "%s pch_spi_transfer maxspeed=%d\n",
|
|
||||||
__func__, pspi->max_speed_hz);
|
|
||||||
retval = -EINVAL;
|
|
||||||
goto err_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
dev_dbg(&pspi->dev,
|
|
||||||
"%s Transfer List not empty. Transfer Speed is set.\n", __func__);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&data->lock, flags);
|
spin_lock_irqsave(&data->lock, flags);
|
||||||
/* validate Tx/Rx buffers and Transfer length */
|
/* validate Tx/Rx buffers and Transfer length */
|
||||||
list_for_each_entry(transfer, &pmsg->transfers, transfer_list) {
|
list_for_each_entry(transfer, &pmsg->transfers, transfer_list) {
|
||||||
@ -523,10 +492,6 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
|
|||||||
dev_dbg(&pspi->dev,
|
dev_dbg(&pspi->dev,
|
||||||
"%s Tx/Rx buffer valid. Transfer length valid\n",
|
"%s Tx/Rx buffer valid. Transfer length valid\n",
|
||||||
__func__);
|
__func__);
|
||||||
|
|
||||||
/* if baud rate has been specified validate the same */
|
|
||||||
if (transfer->speed_hz > PCH_MAX_BAUDRATE)
|
|
||||||
transfer->speed_hz = PCH_MAX_BAUDRATE;
|
|
||||||
}
|
}
|
||||||
spin_unlock_irqrestore(&data->lock, flags);
|
spin_unlock_irqrestore(&data->lock, flags);
|
||||||
|
|
||||||
@ -1151,8 +1116,7 @@ static void pch_spi_handle_dma(struct pch_spi_data *data, int *bpw)
|
|||||||
dma->nent = num;
|
dma->nent = num;
|
||||||
dma->desc_tx = desc_tx;
|
dma->desc_tx = desc_tx;
|
||||||
|
|
||||||
dev_dbg(&data->master->dev, "\n%s:Pulling down SSN low - writing "
|
dev_dbg(&data->master->dev, "%s:Pulling down SSN low - writing 0x2 to SSNXCR\n", __func__);
|
||||||
"0x2 to SSNXCR\n", __func__);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&data->lock, flags);
|
spin_lock_irqsave(&data->lock, flags);
|
||||||
pch_spi_writereg(data->master, PCH_SSNXCR, SSN_LOW);
|
pch_spi_writereg(data->master, PCH_SSNXCR, SSN_LOW);
|
||||||
@ -1418,10 +1382,10 @@ static int pch_spi_pd_probe(struct platform_device *plat_dev)
|
|||||||
|
|
||||||
/* initialize members of SPI master */
|
/* initialize members of SPI master */
|
||||||
master->num_chipselect = PCH_MAX_CS;
|
master->num_chipselect = PCH_MAX_CS;
|
||||||
master->setup = pch_spi_setup;
|
|
||||||
master->transfer = pch_spi_transfer;
|
master->transfer = pch_spi_transfer;
|
||||||
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;
|
||||||
master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
|
master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
|
||||||
|
master->max_speed_hz = PCH_MAX_BAUDRATE;
|
||||||
|
|
||||||
data->board_dat = board_dat;
|
data->board_dat = board_dat;
|
||||||
data->plat_dev = plat_dev;
|
data->plat_dev = plat_dev;
|
||||||
@ -1605,8 +1569,7 @@ static struct platform_driver pch_spi_pd_driver = {
|
|||||||
.resume = pch_spi_pd_resume
|
.resume = pch_spi_pd_resume
|
||||||
};
|
};
|
||||||
|
|
||||||
static int pch_spi_probe(struct pci_dev *pdev,
|
static int pch_spi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||||
const struct pci_device_id *id)
|
|
||||||
{
|
{
|
||||||
struct pch_spi_board_data *board_dat;
|
struct pch_spi_board_data *board_dat;
|
||||||
struct platform_device *pd_dev = NULL;
|
struct platform_device *pd_dev = NULL;
|
||||||
@ -1676,6 +1639,8 @@ static int pch_spi_probe(struct pci_dev *pdev,
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_platform_device:
|
err_platform_device:
|
||||||
|
while (--i >= 0)
|
||||||
|
platform_device_unregister(pd_dev_save->pd_save[i]);
|
||||||
pci_disable_device(pdev);
|
pci_disable_device(pdev);
|
||||||
pci_enable_device:
|
pci_enable_device:
|
||||||
pci_release_regions(pdev);
|
pci_release_regions(pdev);
|
||||||
|
@ -80,7 +80,6 @@ struct txx9spi {
|
|||||||
void __iomem *membase;
|
void __iomem *membase;
|
||||||
int baseclk;
|
int baseclk;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
u32 max_speed_hz, min_speed_hz;
|
|
||||||
int last_chipselect;
|
int last_chipselect;
|
||||||
int last_chipselect_val;
|
int last_chipselect_val;
|
||||||
};
|
};
|
||||||
@ -117,9 +116,7 @@ static int txx9spi_setup(struct spi_device *spi)
|
|||||||
{
|
{
|
||||||
struct txx9spi *c = spi_master_get_devdata(spi->master);
|
struct txx9spi *c = spi_master_get_devdata(spi->master);
|
||||||
|
|
||||||
if (!spi->max_speed_hz
|
if (!spi->max_speed_hz)
|
||||||
|| spi->max_speed_hz > c->max_speed_hz
|
|
||||||
|| spi->max_speed_hz < c->min_speed_hz)
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (gpio_direction_output(spi->chip_select,
|
if (gpio_direction_output(spi->chip_select,
|
||||||
@ -309,15 +306,8 @@ static int txx9spi_transfer(struct spi_device *spi, struct spi_message *m)
|
|||||||
|
|
||||||
/* check each transfer's parameters */
|
/* check each transfer's parameters */
|
||||||
list_for_each_entry(t, &m->transfers, transfer_list) {
|
list_for_each_entry(t, &m->transfers, transfer_list) {
|
||||||
u32 speed_hz = t->speed_hz ? : spi->max_speed_hz;
|
|
||||||
u8 bits_per_word = t->bits_per_word;
|
|
||||||
|
|
||||||
if (!t->tx_buf && !t->rx_buf && t->len)
|
if (!t->tx_buf && !t->rx_buf && t->len)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
if (t->len & ((bits_per_word >> 3) - 1))
|
|
||||||
return -EINVAL;
|
|
||||||
if (speed_hz < c->min_speed_hz || speed_hz > c->max_speed_hz)
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spin_lock_irqsave(&c->lock, flags);
|
spin_lock_irqsave(&c->lock, flags);
|
||||||
@ -360,17 +350,12 @@ static int txx9spi_probe(struct platform_device *dev)
|
|||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
c->baseclk = clk_get_rate(c->clk);
|
c->baseclk = clk_get_rate(c->clk);
|
||||||
c->min_speed_hz = DIV_ROUND_UP(c->baseclk, SPI_MAX_DIVIDER + 1);
|
master->min_speed_hz = DIV_ROUND_UP(c->baseclk, SPI_MAX_DIVIDER + 1);
|
||||||
c->max_speed_hz = c->baseclk / (SPI_MIN_DIVIDER + 1);
|
master->max_speed_hz = c->baseclk / (SPI_MIN_DIVIDER + 1);
|
||||||
|
|
||||||
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
||||||
if (!res)
|
c->membase = devm_ioremap_resource(&dev->dev, res);
|
||||||
goto exit_busy;
|
if (IS_ERR(c->membase))
|
||||||
if (!devm_request_mem_region(&dev->dev, res->start, resource_size(res),
|
|
||||||
"spi_txx9"))
|
|
||||||
goto exit_busy;
|
|
||||||
c->membase = devm_ioremap(&dev->dev, res->start, resource_size(res));
|
|
||||||
if (!c->membase)
|
|
||||||
goto exit_busy;
|
goto exit_busy;
|
||||||
|
|
||||||
/* enter config mode */
|
/* enter config mode */
|
||||||
|
@ -73,15 +73,13 @@ static void spi_xcomm_chipselect(struct spi_xcomm *spi_xcomm,
|
|||||||
static int spi_xcomm_setup_transfer(struct spi_xcomm *spi_xcomm,
|
static int spi_xcomm_setup_transfer(struct spi_xcomm *spi_xcomm,
|
||||||
struct spi_device *spi, struct spi_transfer *t, unsigned int *settings)
|
struct spi_device *spi, struct spi_transfer *t, unsigned int *settings)
|
||||||
{
|
{
|
||||||
unsigned int speed;
|
|
||||||
|
|
||||||
if (t->len > 62)
|
if (t->len > 62)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
speed = t->speed_hz ? t->speed_hz : spi->max_speed_hz;
|
if (t->speed_hz != spi_xcomm->current_speed) {
|
||||||
|
unsigned int divider;
|
||||||
|
|
||||||
if (speed != spi_xcomm->current_speed) {
|
divider = DIV_ROUND_UP(SPI_XCOMM_CLOCK, t->speed_hz);
|
||||||
unsigned int divider = DIV_ROUND_UP(SPI_XCOMM_CLOCK, speed);
|
|
||||||
if (divider >= 64)
|
if (divider >= 64)
|
||||||
*settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_64;
|
*settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_64;
|
||||||
else if (divider >= 16)
|
else if (divider >= 16)
|
||||||
@ -89,7 +87,7 @@ static int spi_xcomm_setup_transfer(struct spi_xcomm *spi_xcomm,
|
|||||||
else
|
else
|
||||||
*settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_4;
|
*settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_4;
|
||||||
|
|
||||||
spi_xcomm->current_speed = speed;
|
spi_xcomm->current_speed = t->speed_hz;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spi->mode & SPI_CPOL)
|
if (spi->mode & SPI_CPOL)
|
||||||
@ -147,8 +145,6 @@ static int spi_xcomm_transfer_one(struct spi_master *master,
|
|||||||
int status = 0;
|
int status = 0;
|
||||||
bool is_last;
|
bool is_last;
|
||||||
|
|
||||||
is_first = true;
|
|
||||||
|
|
||||||
spi_xcomm_chipselect(spi_xcomm, spi, true);
|
spi_xcomm_chipselect(spi_xcomm, spi, true);
|
||||||
|
|
||||||
list_for_each_entry(t, &msg->transfers, transfer_list) {
|
list_for_each_entry(t, &msg->transfers, transfer_list) {
|
||||||
|
@ -756,6 +756,7 @@ static int spi_transfer_one_message(struct spi_master *master,
|
|||||||
struct spi_transfer *xfer;
|
struct spi_transfer *xfer;
|
||||||
bool keep_cs = false;
|
bool keep_cs = false;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
int ms = 1;
|
||||||
|
|
||||||
spi_set_cs(msg->spi, true);
|
spi_set_cs(msg->spi, true);
|
||||||
|
|
||||||
@ -773,7 +774,16 @@ static int spi_transfer_one_message(struct spi_master *master,
|
|||||||
|
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
ret = 0;
|
ret = 0;
|
||||||
wait_for_completion(&master->xfer_completion);
|
ms = xfer->len * 8 * 1000 / xfer->speed_hz;
|
||||||
|
ms += 10; /* some tolerance */
|
||||||
|
|
||||||
|
ms = wait_for_completion_timeout(&master->xfer_completion,
|
||||||
|
msecs_to_jiffies(ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ms == 0) {
|
||||||
|
dev_err(&msg->spi->dev, "SPI transfer timed out\n");
|
||||||
|
msg->status = -ETIMEDOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace_spi_transfer_stop(msg, xfer);
|
trace_spi_transfer_stop(msg, xfer);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user