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:
Родитель
81235b4ea3
743a46b89a
971e9084be
9304f51e5f
7661ba5a84
342451df07
f8c717a567
af348519e4
16a0ce4e10
Коммит
b1ad487c42
|
@ -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>;
|
||||
};
|
|
@ -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
|
||||
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
|
||||
tristate "Freescale MXS SPI controller"
|
||||
depends on ARCH_MXS
|
||||
|
@ -490,13 +503,6 @@ config SPI_TEGRA20_SLINK
|
|||
help
|
||||
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
|
||||
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) SPI"
|
||||
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_SCI) += spi-sh-sci.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_TEGRA20_SFLASH) += spi-tegra20-sflash.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_TOPCLIFF_PCH) += spi-topcliff-pch.o
|
||||
obj-$(CONFIG_SPI_TXX9) += spi-txx9.o
|
||||
|
|
|
@ -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");
|
|
@ -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);
|
||||
tegra_spi_transfer_delay(xfer->delay_usecs);
|
||||
goto exit;
|
||||
} else if (msg->transfers.prev == &xfer->transfer_list) {
|
||||
/* This is the last transfer in message */
|
||||
} else if (list_is_last(&xfer->transfer_list,
|
||||
&msg->transfers)) {
|
||||
if (xfer->cs_change)
|
||||
tspi->cs_control = spi;
|
||||
else {
|
||||
|
|
|
@ -461,7 +461,6 @@ static int ti_qspi_probe(struct platform_device *pdev)
|
|||
if (res_mmap == NULL) {
|
||||
dev_err(&pdev->dev,
|
||||
"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;
|
||||
wake_up(&data->wait);
|
||||
} else {
|
||||
dev_err(&data->master->dev,
|
||||
dev_vdbg(&data->master->dev,
|
||||
"%s : Transfer is not completed",
|
||||
__func__);
|
||||
}
|
||||
|
@ -464,20 +464,6 @@ static void pch_spi_reset(struct spi_master *master)
|
|||
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)
|
||||
{
|
||||
|
||||
|
@ -486,23 +472,6 @@ static int pch_spi_transfer(struct spi_device *pspi, struct spi_message *pmsg)
|
|||
int retval;
|
||||
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);
|
||||
/* validate Tx/Rx buffers and Transfer length */
|
||||
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,
|
||||
"%s Tx/Rx buffer valid. Transfer length valid\n",
|
||||
__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);
|
||||
|
||||
|
@ -1151,8 +1116,7 @@ static void pch_spi_handle_dma(struct pch_spi_data *data, int *bpw)
|
|||
dma->nent = num;
|
||||
dma->desc_tx = desc_tx;
|
||||
|
||||
dev_dbg(&data->master->dev, "\n%s:Pulling down SSN low - writing "
|
||||
"0x2 to SSNXCR\n", __func__);
|
||||
dev_dbg(&data->master->dev, "%s:Pulling down SSN low - writing 0x2 to SSNXCR\n", __func__);
|
||||
|
||||
spin_lock_irqsave(&data->lock, flags);
|
||||
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 */
|
||||
master->num_chipselect = PCH_MAX_CS;
|
||||
master->setup = pch_spi_setup;
|
||||
master->transfer = pch_spi_transfer;
|
||||
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;
|
||||
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->plat_dev = plat_dev;
|
||||
|
@ -1605,8 +1569,7 @@ static struct platform_driver pch_spi_pd_driver = {
|
|||
.resume = pch_spi_pd_resume
|
||||
};
|
||||
|
||||
static int pch_spi_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
static int pch_spi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
{
|
||||
struct pch_spi_board_data *board_dat;
|
||||
struct platform_device *pd_dev = NULL;
|
||||
|
@ -1676,6 +1639,8 @@ static int pch_spi_probe(struct pci_dev *pdev,
|
|||
return 0;
|
||||
|
||||
err_platform_device:
|
||||
while (--i >= 0)
|
||||
platform_device_unregister(pd_dev_save->pd_save[i]);
|
||||
pci_disable_device(pdev);
|
||||
pci_enable_device:
|
||||
pci_release_regions(pdev);
|
||||
|
|
|
@ -80,7 +80,6 @@ struct txx9spi {
|
|||
void __iomem *membase;
|
||||
int baseclk;
|
||||
struct clk *clk;
|
||||
u32 max_speed_hz, min_speed_hz;
|
||||
int last_chipselect;
|
||||
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);
|
||||
|
||||
if (!spi->max_speed_hz
|
||||
|| spi->max_speed_hz > c->max_speed_hz
|
||||
|| spi->max_speed_hz < c->min_speed_hz)
|
||||
if (!spi->max_speed_hz)
|
||||
return -EINVAL;
|
||||
|
||||
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 */
|
||||
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)
|
||||
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);
|
||||
|
@ -360,17 +350,12 @@ static int txx9spi_probe(struct platform_device *dev)
|
|||
goto exit;
|
||||
}
|
||||
c->baseclk = clk_get_rate(c->clk);
|
||||
c->min_speed_hz = DIV_ROUND_UP(c->baseclk, SPI_MAX_DIVIDER + 1);
|
||||
c->max_speed_hz = c->baseclk / (SPI_MIN_DIVIDER + 1);
|
||||
master->min_speed_hz = DIV_ROUND_UP(c->baseclk, SPI_MAX_DIVIDER + 1);
|
||||
master->max_speed_hz = c->baseclk / (SPI_MIN_DIVIDER + 1);
|
||||
|
||||
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
goto exit_busy;
|
||||
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)
|
||||
c->membase = devm_ioremap_resource(&dev->dev, res);
|
||||
if (IS_ERR(c->membase))
|
||||
goto exit_busy;
|
||||
|
||||
/* 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,
|
||||
struct spi_device *spi, struct spi_transfer *t, unsigned int *settings)
|
||||
{
|
||||
unsigned int speed;
|
||||
|
||||
if (t->len > 62)
|
||||
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) {
|
||||
unsigned int divider = DIV_ROUND_UP(SPI_XCOMM_CLOCK, speed);
|
||||
divider = DIV_ROUND_UP(SPI_XCOMM_CLOCK, t->speed_hz);
|
||||
if (divider >= 64)
|
||||
*settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_64;
|
||||
else if (divider >= 16)
|
||||
|
@ -89,7 +87,7 @@ static int spi_xcomm_setup_transfer(struct spi_xcomm *spi_xcomm,
|
|||
else
|
||||
*settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_4;
|
||||
|
||||
spi_xcomm->current_speed = speed;
|
||||
spi_xcomm->current_speed = t->speed_hz;
|
||||
}
|
||||
|
||||
if (spi->mode & SPI_CPOL)
|
||||
|
@ -147,8 +145,6 @@ static int spi_xcomm_transfer_one(struct spi_master *master,
|
|||
int status = 0;
|
||||
bool is_last;
|
||||
|
||||
is_first = true;
|
||||
|
||||
spi_xcomm_chipselect(spi_xcomm, spi, true);
|
||||
|
||||
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;
|
||||
bool keep_cs = false;
|
||||
int ret = 0;
|
||||
int ms = 1;
|
||||
|
||||
spi_set_cs(msg->spi, true);
|
||||
|
||||
|
@ -773,7 +774,16 @@ static int spi_transfer_one_message(struct spi_master *master,
|
|||
|
||||
if (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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче