Merge remote-tracking branches 'spi/topic/orion', 'spi/topic/pl022', 'spi/topic/qup', 'spi/topic/rockchip' and 'spi/topic/rspi' into spi-next
This commit is contained in:
Коммит
0c18b7638a
|
@ -7,7 +7,11 @@ SPI in master mode supports up to 50MHz, up to four chip selects, programmable
|
|||
data path from 4 bits to 32 bits and numerous protocol variants.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain "qcom,spi-qup-v2.1.1" or "qcom,spi-qup-v2.2.1"
|
||||
- compatible: Should contain:
|
||||
"qcom,spi-qup-v1.1.1" for 8660, 8960 and 8064.
|
||||
"qcom,spi-qup-v2.1.1" for 8974 and later
|
||||
"qcom,spi-qup-v2.2.1" for 8974 v2 and later.
|
||||
|
||||
- reg: Should contain base register location and length
|
||||
- interrupts: Interrupt number used by this controller
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
* Rockchip SPI Controller
|
||||
|
||||
The Rockchip SPI controller is used to interface with various devices such as flash
|
||||
and display controllers using the SPI communication interface.
|
||||
|
||||
Required Properties:
|
||||
|
||||
- compatible: should be one of the following.
|
||||
"rockchip,rk3066-spi" for rk3066.
|
||||
"rockchip,rk3188-spi", "rockchip,rk3066-spi" for rk3188.
|
||||
"rockchip,rk3288-spi", "rockchip,rk3066-spi" for rk3288.
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- interrupts: The interrupt number to the cpu. The interrupt specifier format
|
||||
depends on the interrupt controller.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
- clock-names: Shall be "spiclk" for the transfer-clock, and "apb_pclk" for
|
||||
the peripheral clock.
|
||||
- dmas: DMA specifiers for tx and rx dma. See the DMA client binding,
|
||||
Documentation/devicetree/bindings/dma/dma.txt
|
||||
- dma-names: DMA request names should include "tx" and "rx" if present.
|
||||
- #address-cells: should be 1.
|
||||
- #size-cells: should be 0.
|
||||
|
||||
Example:
|
||||
|
||||
spi0: spi@ff110000 {
|
||||
compatible = "rockchip,rk3066-spi";
|
||||
reg = <0xff110000 0x1000>;
|
||||
dmas = <&pdma1 11>, <&pdma1 12>;
|
||||
dma-names = "tx", "rx";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
|
||||
clock-names = "spiclk", "apb_pclk";
|
||||
};
|
|
@ -382,9 +382,21 @@ config SPI_PXA2XX
|
|||
config SPI_PXA2XX_PCI
|
||||
def_tristate SPI_PXA2XX && PCI
|
||||
|
||||
config SPI_ROCKCHIP
|
||||
tristate "Rockchip SPI controller driver"
|
||||
depends on ARM || ARM64 || AVR32 || HEXAGON || MIPS || SUPERH
|
||||
help
|
||||
This selects a driver for Rockchip SPI controller.
|
||||
|
||||
If you say yes to this option, support will be included for
|
||||
RK3066, RK3188 and RK3288 families of SPI controller.
|
||||
Rockchip SPI controller support DMA transport and PIO mode.
|
||||
The main usecase of this controller is to use spi flash as boot
|
||||
device.
|
||||
|
||||
config SPI_RSPI
|
||||
tristate "Renesas RSPI/QSPI controller"
|
||||
depends on (SUPERH && SH_DMAE_BASE) || ARCH_SHMOBILE
|
||||
depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST
|
||||
help
|
||||
SPI driver for Renesas RSPI and QSPI blocks.
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA) += spi-pxa2xx-dma.o
|
|||
obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o
|
||||
obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
|
||||
obj-$(CONFIG_SPI_QUP) += spi-qup.o
|
||||
obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o
|
||||
obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
|
||||
obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o
|
||||
spi-s3c24xx-hw-y := spi-s3c24xx.o
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/sizes.h>
|
||||
|
@ -23,6 +24,9 @@
|
|||
|
||||
#define DRIVER_NAME "orion_spi"
|
||||
|
||||
/* Runtime PM autosuspend timeout: PM is fairly light on this driver */
|
||||
#define SPI_AUTOSUSPEND_TIMEOUT 200
|
||||
|
||||
#define ORION_NUM_CHIPSELECTS 1 /* only one slave is supported*/
|
||||
#define ORION_SPI_WAIT_RDY_MAX_LOOP 2000 /* in usec */
|
||||
|
||||
|
@ -277,7 +281,6 @@ out:
|
|||
return xfer->len - count;
|
||||
}
|
||||
|
||||
|
||||
static int orion_spi_transfer_one_message(struct spi_master *master,
|
||||
struct spi_message *m)
|
||||
{
|
||||
|
@ -368,6 +371,7 @@ static int orion_spi_probe(struct platform_device *pdev)
|
|||
master->transfer_one_message = orion_spi_transfer_one_message;
|
||||
master->num_chipselect = ORION_NUM_CHIPSELECTS;
|
||||
master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
|
||||
master->auto_runtime_pm = true;
|
||||
|
||||
platform_set_drvdata(pdev, master);
|
||||
|
||||
|
@ -380,8 +384,10 @@ static int orion_spi_probe(struct platform_device *pdev)
|
|||
goto out;
|
||||
}
|
||||
|
||||
clk_prepare(spi->clk);
|
||||
clk_enable(spi->clk);
|
||||
status = clk_prepare_enable(spi->clk);
|
||||
if (status)
|
||||
goto out;
|
||||
|
||||
tclk_hz = clk_get_rate(spi->clk);
|
||||
master->max_speed_hz = DIV_ROUND_UP(tclk_hz, 4);
|
||||
master->min_speed_hz = DIV_ROUND_UP(tclk_hz, 30);
|
||||
|
@ -393,16 +399,27 @@ static int orion_spi_probe(struct platform_device *pdev)
|
|||
goto out_rel_clk;
|
||||
}
|
||||
|
||||
if (orion_spi_reset(spi) < 0)
|
||||
goto out_rel_clk;
|
||||
pm_runtime_set_active(&pdev->dev);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
status = orion_spi_reset(spi);
|
||||
if (status < 0)
|
||||
goto out_rel_pm;
|
||||
|
||||
pm_runtime_mark_last_busy(&pdev->dev);
|
||||
pm_runtime_put_autosuspend(&pdev->dev);
|
||||
|
||||
master->dev.of_node = pdev->dev.of_node;
|
||||
status = devm_spi_register_master(&pdev->dev, master);
|
||||
status = spi_register_master(master);
|
||||
if (status < 0)
|
||||
goto out_rel_clk;
|
||||
goto out_rel_pm;
|
||||
|
||||
return status;
|
||||
|
||||
out_rel_pm:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
out_rel_clk:
|
||||
clk_disable_unprepare(spi->clk);
|
||||
out:
|
||||
|
@ -413,19 +430,45 @@ out:
|
|||
|
||||
static int orion_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_master *master;
|
||||
struct orion_spi *spi;
|
||||
|
||||
master = platform_get_drvdata(pdev);
|
||||
spi = spi_master_get_devdata(master);
|
||||
struct spi_master *master = platform_get_drvdata(pdev);
|
||||
struct orion_spi *spi = spi_master_get_devdata(master);
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
clk_disable_unprepare(spi->clk);
|
||||
|
||||
spi_unregister_master(master);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MODULE_ALIAS("platform:" DRIVER_NAME);
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int orion_spi_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
struct orion_spi *spi = spi_master_get_devdata(master);
|
||||
|
||||
clk_disable_unprepare(spi->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int orion_spi_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
struct orion_spi *spi = spi_master_get_devdata(master);
|
||||
|
||||
return clk_prepare_enable(spi->clk);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops orion_spi_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(orion_spi_runtime_suspend,
|
||||
orion_spi_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id orion_spi_of_match_table[] = {
|
||||
{ .compatible = "marvell,orion-spi", },
|
||||
{}
|
||||
|
@ -436,6 +479,7 @@ static struct platform_driver orion_spi_driver = {
|
|||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &orion_spi_pm_ops,
|
||||
.of_match_table = of_match_ptr(orion_spi_of_match_table),
|
||||
},
|
||||
.probe = orion_spi_probe,
|
||||
|
|
|
@ -1417,7 +1417,7 @@ static void do_interrupt_dma_transfer(struct pl022 *pl022)
|
|||
* Default is to enable all interrupts except RX -
|
||||
* this will be enabled once TX is complete
|
||||
*/
|
||||
u32 irqflags = ENABLE_ALL_INTERRUPTS & ~SSP_IMSC_MASK_RXIM;
|
||||
u32 irqflags = (u32)(ENABLE_ALL_INTERRUPTS & ~SSP_IMSC_MASK_RXIM);
|
||||
|
||||
/* Enable target chip, if not already active */
|
||||
if (!pl022->next_msg_cs_active)
|
||||
|
|
|
@ -142,6 +142,7 @@ struct spi_qup {
|
|||
int w_size; /* bytes per SPI word */
|
||||
int tx_bytes;
|
||||
int rx_bytes;
|
||||
int qup_v1;
|
||||
};
|
||||
|
||||
|
||||
|
@ -420,7 +421,9 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
|
|||
config |= QUP_CONFIG_SPI_MODE;
|
||||
writel_relaxed(config, controller->base + QUP_CONFIG);
|
||||
|
||||
writel_relaxed(0, controller->base + QUP_OPERATIONAL_MASK);
|
||||
/* only write to OPERATIONAL_MASK when register is present */
|
||||
if (!controller->qup_v1)
|
||||
writel_relaxed(0, controller->base + QUP_OPERATIONAL_MASK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -486,7 +489,7 @@ static int spi_qup_probe(struct platform_device *pdev)
|
|||
struct resource *res;
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
u32 data, max_freq, iomode;
|
||||
u32 max_freq, iomode;
|
||||
int ret, irq, size;
|
||||
|
||||
dev = &pdev->dev;
|
||||
|
@ -529,15 +532,6 @@ static int spi_qup_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
data = readl_relaxed(base + QUP_HW_VERSION);
|
||||
|
||||
if (data < QUP_HW_VERSION_2_1_1) {
|
||||
clk_disable_unprepare(cclk);
|
||||
clk_disable_unprepare(iclk);
|
||||
dev_err(dev, "v.%08x is not supported\n", data);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
master = spi_alloc_master(dev, sizeof(struct spi_qup));
|
||||
if (!master) {
|
||||
clk_disable_unprepare(cclk);
|
||||
|
@ -570,6 +564,10 @@ static int spi_qup_probe(struct platform_device *pdev)
|
|||
controller->cclk = cclk;
|
||||
controller->irq = irq;
|
||||
|
||||
/* set v1 flag if device is version 1 */
|
||||
if (of_device_is_compatible(dev->of_node, "qcom,spi-qup-v1.1.1"))
|
||||
controller->qup_v1 = 1;
|
||||
|
||||
spin_lock_init(&controller->lock);
|
||||
init_completion(&controller->done);
|
||||
|
||||
|
@ -593,8 +591,8 @@ static int spi_qup_probe(struct platform_device *pdev)
|
|||
size = QUP_IO_M_INPUT_FIFO_SIZE(iomode);
|
||||
controller->in_fifo_sz = controller->in_blk_sz * (2 << size);
|
||||
|
||||
dev_info(dev, "v.%08x IN:block:%d, fifo:%d, OUT:block:%d, fifo:%d\n",
|
||||
data, controller->in_blk_sz, controller->in_fifo_sz,
|
||||
dev_info(dev, "IN:block:%d, fifo:%d, OUT:block:%d, fifo:%d\n",
|
||||
controller->in_blk_sz, controller->in_fifo_sz,
|
||||
controller->out_blk_sz, controller->out_fifo_sz);
|
||||
|
||||
writel_relaxed(1, base + QUP_SW_RESET);
|
||||
|
@ -607,10 +605,19 @@ static int spi_qup_probe(struct platform_device *pdev)
|
|||
|
||||
writel_relaxed(0, base + QUP_OPERATIONAL);
|
||||
writel_relaxed(0, base + QUP_IO_M_MODES);
|
||||
writel_relaxed(0, base + QUP_OPERATIONAL_MASK);
|
||||
|
||||
if (!controller->qup_v1)
|
||||
writel_relaxed(0, base + QUP_OPERATIONAL_MASK);
|
||||
|
||||
writel_relaxed(SPI_ERROR_CLK_UNDER_RUN | SPI_ERROR_CLK_OVER_RUN,
|
||||
base + SPI_ERROR_FLAGS_EN);
|
||||
|
||||
/* if earlier version of the QUP, disable INPUT_OVERRUN */
|
||||
if (controller->qup_v1)
|
||||
writel_relaxed(QUP_ERROR_OUTPUT_OVER_RUN |
|
||||
QUP_ERROR_INPUT_UNDER_RUN | QUP_ERROR_OUTPUT_UNDER_RUN,
|
||||
base + QUP_ERROR_FLAGS_EN);
|
||||
|
||||
writel_relaxed(0, base + SPI_CONFIG);
|
||||
writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL);
|
||||
|
||||
|
@ -732,6 +739,7 @@ static int spi_qup_remove(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
static const struct of_device_id spi_qup_dt_match[] = {
|
||||
{ .compatible = "qcom,spi-qup-v1.1.1", },
|
||||
{ .compatible = "qcom,spi-qup-v2.1.1", },
|
||||
{ .compatible = "qcom,spi-qup-v2.2.1", },
|
||||
{ }
|
||||
|
|
|
@ -0,0 +1,837 @@
|
|||
/*
|
||||
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
|
||||
* Author: Addy Ke <addy.ke@rock-chips.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/dmaengine.h>
|
||||
|
||||
#define DRIVER_NAME "rockchip-spi"
|
||||
|
||||
/* SPI register offsets */
|
||||
#define ROCKCHIP_SPI_CTRLR0 0x0000
|
||||
#define ROCKCHIP_SPI_CTRLR1 0x0004
|
||||
#define ROCKCHIP_SPI_SSIENR 0x0008
|
||||
#define ROCKCHIP_SPI_SER 0x000c
|
||||
#define ROCKCHIP_SPI_BAUDR 0x0010
|
||||
#define ROCKCHIP_SPI_TXFTLR 0x0014
|
||||
#define ROCKCHIP_SPI_RXFTLR 0x0018
|
||||
#define ROCKCHIP_SPI_TXFLR 0x001c
|
||||
#define ROCKCHIP_SPI_RXFLR 0x0020
|
||||
#define ROCKCHIP_SPI_SR 0x0024
|
||||
#define ROCKCHIP_SPI_IPR 0x0028
|
||||
#define ROCKCHIP_SPI_IMR 0x002c
|
||||
#define ROCKCHIP_SPI_ISR 0x0030
|
||||
#define ROCKCHIP_SPI_RISR 0x0034
|
||||
#define ROCKCHIP_SPI_ICR 0x0038
|
||||
#define ROCKCHIP_SPI_DMACR 0x003c
|
||||
#define ROCKCHIP_SPI_DMATDLR 0x0040
|
||||
#define ROCKCHIP_SPI_DMARDLR 0x0044
|
||||
#define ROCKCHIP_SPI_TXDR 0x0400
|
||||
#define ROCKCHIP_SPI_RXDR 0x0800
|
||||
|
||||
/* Bit fields in CTRLR0 */
|
||||
#define CR0_DFS_OFFSET 0
|
||||
|
||||
#define CR0_CFS_OFFSET 2
|
||||
|
||||
#define CR0_SCPH_OFFSET 6
|
||||
|
||||
#define CR0_SCPOL_OFFSET 7
|
||||
|
||||
#define CR0_CSM_OFFSET 8
|
||||
#define CR0_CSM_KEEP 0x0
|
||||
/* ss_n be high for half sclk_out cycles */
|
||||
#define CR0_CSM_HALF 0X1
|
||||
/* ss_n be high for one sclk_out cycle */
|
||||
#define CR0_CSM_ONE 0x2
|
||||
|
||||
/* ss_n to sclk_out delay */
|
||||
#define CR0_SSD_OFFSET 10
|
||||
/*
|
||||
* The period between ss_n active and
|
||||
* sclk_out active is half sclk_out cycles
|
||||
*/
|
||||
#define CR0_SSD_HALF 0x0
|
||||
/*
|
||||
* The period between ss_n active and
|
||||
* sclk_out active is one sclk_out cycle
|
||||
*/
|
||||
#define CR0_SSD_ONE 0x1
|
||||
|
||||
#define CR0_EM_OFFSET 11
|
||||
#define CR0_EM_LITTLE 0x0
|
||||
#define CR0_EM_BIG 0x1
|
||||
|
||||
#define CR0_FBM_OFFSET 12
|
||||
#define CR0_FBM_MSB 0x0
|
||||
#define CR0_FBM_LSB 0x1
|
||||
|
||||
#define CR0_BHT_OFFSET 13
|
||||
#define CR0_BHT_16BIT 0x0
|
||||
#define CR0_BHT_8BIT 0x1
|
||||
|
||||
#define CR0_RSD_OFFSET 14
|
||||
|
||||
#define CR0_FRF_OFFSET 16
|
||||
#define CR0_FRF_SPI 0x0
|
||||
#define CR0_FRF_SSP 0x1
|
||||
#define CR0_FRF_MICROWIRE 0x2
|
||||
|
||||
#define CR0_XFM_OFFSET 18
|
||||
#define CR0_XFM_MASK (0x03 << SPI_XFM_OFFSET)
|
||||
#define CR0_XFM_TR 0x0
|
||||
#define CR0_XFM_TO 0x1
|
||||
#define CR0_XFM_RO 0x2
|
||||
|
||||
#define CR0_OPM_OFFSET 20
|
||||
#define CR0_OPM_MASTER 0x0
|
||||
#define CR0_OPM_SLAVE 0x1
|
||||
|
||||
#define CR0_MTM_OFFSET 0x21
|
||||
|
||||
/* Bit fields in SER, 2bit */
|
||||
#define SER_MASK 0x3
|
||||
|
||||
/* Bit fields in SR, 5bit */
|
||||
#define SR_MASK 0x1f
|
||||
#define SR_BUSY (1 << 0)
|
||||
#define SR_TF_FULL (1 << 1)
|
||||
#define SR_TF_EMPTY (1 << 2)
|
||||
#define SR_RF_EMPTY (1 << 3)
|
||||
#define SR_RF_FULL (1 << 4)
|
||||
|
||||
/* Bit fields in ISR, IMR, ISR, RISR, 5bit */
|
||||
#define INT_MASK 0x1f
|
||||
#define INT_TF_EMPTY (1 << 0)
|
||||
#define INT_TF_OVERFLOW (1 << 1)
|
||||
#define INT_RF_UNDERFLOW (1 << 2)
|
||||
#define INT_RF_OVERFLOW (1 << 3)
|
||||
#define INT_RF_FULL (1 << 4)
|
||||
|
||||
/* Bit fields in ICR, 4bit */
|
||||
#define ICR_MASK 0x0f
|
||||
#define ICR_ALL (1 << 0)
|
||||
#define ICR_RF_UNDERFLOW (1 << 1)
|
||||
#define ICR_RF_OVERFLOW (1 << 2)
|
||||
#define ICR_TF_OVERFLOW (1 << 3)
|
||||
|
||||
/* Bit fields in DMACR */
|
||||
#define RF_DMA_EN (1 << 0)
|
||||
#define TF_DMA_EN (1 << 1)
|
||||
|
||||
#define RXBUSY (1 << 0)
|
||||
#define TXBUSY (1 << 1)
|
||||
|
||||
enum rockchip_ssi_type {
|
||||
SSI_MOTO_SPI = 0,
|
||||
SSI_TI_SSP,
|
||||
SSI_NS_MICROWIRE,
|
||||
};
|
||||
|
||||
struct rockchip_spi_dma_data {
|
||||
struct dma_chan *ch;
|
||||
enum dma_transfer_direction direction;
|
||||
dma_addr_t addr;
|
||||
};
|
||||
|
||||
struct rockchip_spi {
|
||||
struct device *dev;
|
||||
struct spi_master *master;
|
||||
|
||||
struct clk *spiclk;
|
||||
struct clk *apb_pclk;
|
||||
|
||||
void __iomem *regs;
|
||||
/*depth of the FIFO buffer */
|
||||
u32 fifo_len;
|
||||
/* max bus freq supported */
|
||||
u32 max_freq;
|
||||
/* supported slave numbers */
|
||||
enum rockchip_ssi_type type;
|
||||
|
||||
u16 mode;
|
||||
u8 tmode;
|
||||
u8 bpw;
|
||||
u8 n_bytes;
|
||||
unsigned len;
|
||||
u32 speed;
|
||||
|
||||
const void *tx;
|
||||
const void *tx_end;
|
||||
void *rx;
|
||||
void *rx_end;
|
||||
|
||||
u32 state;
|
||||
/* protect state */
|
||||
spinlock_t lock;
|
||||
|
||||
struct completion xfer_completion;
|
||||
|
||||
u32 use_dma;
|
||||
struct sg_table tx_sg;
|
||||
struct sg_table rx_sg;
|
||||
struct rockchip_spi_dma_data dma_rx;
|
||||
struct rockchip_spi_dma_data dma_tx;
|
||||
};
|
||||
|
||||
static inline void spi_enable_chip(struct rockchip_spi *rs, int enable)
|
||||
{
|
||||
writel_relaxed((enable ? 1 : 0), rs->regs + ROCKCHIP_SPI_SSIENR);
|
||||
}
|
||||
|
||||
static inline void spi_set_clk(struct rockchip_spi *rs, u16 div)
|
||||
{
|
||||
writel_relaxed(div, rs->regs + ROCKCHIP_SPI_BAUDR);
|
||||
}
|
||||
|
||||
static inline void flush_fifo(struct rockchip_spi *rs)
|
||||
{
|
||||
while (readl_relaxed(rs->regs + ROCKCHIP_SPI_RXFLR))
|
||||
readl_relaxed(rs->regs + ROCKCHIP_SPI_RXDR);
|
||||
}
|
||||
|
||||
static inline void wait_for_idle(struct rockchip_spi *rs)
|
||||
{
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(5);
|
||||
|
||||
do {
|
||||
if (!(readl_relaxed(rs->regs + ROCKCHIP_SPI_SR) & SR_BUSY))
|
||||
return;
|
||||
} while (time_before(jiffies, timeout));
|
||||
|
||||
dev_warn(rs->dev, "spi controller is in busy state!\n");
|
||||
}
|
||||
|
||||
static u32 get_fifo_len(struct rockchip_spi *rs)
|
||||
{
|
||||
u32 fifo;
|
||||
|
||||
for (fifo = 2; fifo < 32; fifo++) {
|
||||
writel_relaxed(fifo, rs->regs + ROCKCHIP_SPI_TXFTLR);
|
||||
if (fifo != readl_relaxed(rs->regs + ROCKCHIP_SPI_TXFTLR))
|
||||
break;
|
||||
}
|
||||
|
||||
writel_relaxed(0, rs->regs + ROCKCHIP_SPI_TXFTLR);
|
||||
|
||||
return (fifo == 31) ? 0 : fifo;
|
||||
}
|
||||
|
||||
static inline u32 tx_max(struct rockchip_spi *rs)
|
||||
{
|
||||
u32 tx_left, tx_room;
|
||||
|
||||
tx_left = (rs->tx_end - rs->tx) / rs->n_bytes;
|
||||
tx_room = rs->fifo_len - readl_relaxed(rs->regs + ROCKCHIP_SPI_TXFLR);
|
||||
|
||||
return min(tx_left, tx_room);
|
||||
}
|
||||
|
||||
static inline u32 rx_max(struct rockchip_spi *rs)
|
||||
{
|
||||
u32 rx_left = (rs->rx_end - rs->rx) / rs->n_bytes;
|
||||
u32 rx_room = (u32)readl_relaxed(rs->regs + ROCKCHIP_SPI_RXFLR);
|
||||
|
||||
return min(rx_left, rx_room);
|
||||
}
|
||||
|
||||
static void rockchip_spi_set_cs(struct spi_device *spi, bool enable)
|
||||
{
|
||||
u32 ser;
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(spi->master);
|
||||
|
||||
ser = readl_relaxed(rs->regs + ROCKCHIP_SPI_SER) & SER_MASK;
|
||||
|
||||
/*
|
||||
* drivers/spi/spi.c:
|
||||
* static void spi_set_cs(struct spi_device *spi, bool enable)
|
||||
* {
|
||||
* if (spi->mode & SPI_CS_HIGH)
|
||||
* enable = !enable;
|
||||
*
|
||||
* if (spi->cs_gpio >= 0)
|
||||
* gpio_set_value(spi->cs_gpio, !enable);
|
||||
* else if (spi->master->set_cs)
|
||||
* spi->master->set_cs(spi, !enable);
|
||||
* }
|
||||
*
|
||||
* Note: enable(rockchip_spi_set_cs) = !enable(spi_set_cs)
|
||||
*/
|
||||
if (!enable)
|
||||
ser |= 1 << spi->chip_select;
|
||||
else
|
||||
ser &= ~(1 << spi->chip_select);
|
||||
|
||||
writel_relaxed(ser, rs->regs + ROCKCHIP_SPI_SER);
|
||||
}
|
||||
|
||||
static int rockchip_spi_prepare_message(struct spi_master *master,
|
||||
struct spi_message *msg)
|
||||
{
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
struct spi_device *spi = msg->spi;
|
||||
|
||||
rs->mode = spi->mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rockchip_spi_unprepare_message(struct spi_master *master,
|
||||
struct spi_message *msg)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
spin_lock_irqsave(&rs->lock, flags);
|
||||
|
||||
/*
|
||||
* For DMA mode, we need terminate DMA channel and flush
|
||||
* fifo for the next transfer if DMA thansfer timeout.
|
||||
* unprepare_message() was called by core if transfer complete
|
||||
* or timeout. Maybe it is reasonable for error handling here.
|
||||
*/
|
||||
if (rs->use_dma) {
|
||||
if (rs->state & RXBUSY) {
|
||||
dmaengine_terminate_all(rs->dma_rx.ch);
|
||||
flush_fifo(rs);
|
||||
}
|
||||
|
||||
if (rs->state & TXBUSY)
|
||||
dmaengine_terminate_all(rs->dma_tx.ch);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rockchip_spi_pio_writer(struct rockchip_spi *rs)
|
||||
{
|
||||
u32 max = tx_max(rs);
|
||||
u32 txw = 0;
|
||||
|
||||
while (max--) {
|
||||
if (rs->n_bytes == 1)
|
||||
txw = *(u8 *)(rs->tx);
|
||||
else
|
||||
txw = *(u16 *)(rs->tx);
|
||||
|
||||
writel_relaxed(txw, rs->regs + ROCKCHIP_SPI_TXDR);
|
||||
rs->tx += rs->n_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
static void rockchip_spi_pio_reader(struct rockchip_spi *rs)
|
||||
{
|
||||
u32 max = rx_max(rs);
|
||||
u32 rxw;
|
||||
|
||||
while (max--) {
|
||||
rxw = readl_relaxed(rs->regs + ROCKCHIP_SPI_RXDR);
|
||||
if (rs->n_bytes == 1)
|
||||
*(u8 *)(rs->rx) = (u8)rxw;
|
||||
else
|
||||
*(u16 *)(rs->rx) = (u16)rxw;
|
||||
rs->rx += rs->n_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
static int rockchip_spi_pio_transfer(struct rockchip_spi *rs)
|
||||
{
|
||||
int remain = 0;
|
||||
|
||||
do {
|
||||
if (rs->tx) {
|
||||
remain = rs->tx_end - rs->tx;
|
||||
rockchip_spi_pio_writer(rs);
|
||||
}
|
||||
|
||||
if (rs->rx) {
|
||||
remain = rs->rx_end - rs->rx;
|
||||
rockchip_spi_pio_reader(rs);
|
||||
}
|
||||
|
||||
cpu_relax();
|
||||
} while (remain);
|
||||
|
||||
/* If tx, wait until the FIFO data completely. */
|
||||
if (rs->tx)
|
||||
wait_for_idle(rs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rockchip_spi_dma_rxcb(void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct rockchip_spi *rs = data;
|
||||
|
||||
spin_lock_irqsave(&rs->lock, flags);
|
||||
|
||||
rs->state &= ~RXBUSY;
|
||||
if (!(rs->state & TXBUSY))
|
||||
spi_finalize_current_transfer(rs->master);
|
||||
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
}
|
||||
|
||||
static void rockchip_spi_dma_txcb(void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct rockchip_spi *rs = data;
|
||||
|
||||
/* Wait until the FIFO data completely. */
|
||||
wait_for_idle(rs);
|
||||
|
||||
spin_lock_irqsave(&rs->lock, flags);
|
||||
|
||||
rs->state &= ~TXBUSY;
|
||||
if (!(rs->state & RXBUSY))
|
||||
spi_finalize_current_transfer(rs->master);
|
||||
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
}
|
||||
|
||||
static int rockchip_spi_dma_transfer(struct rockchip_spi *rs)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct dma_slave_config rxconf, txconf;
|
||||
struct dma_async_tx_descriptor *rxdesc, *txdesc;
|
||||
|
||||
spin_lock_irqsave(&rs->lock, flags);
|
||||
rs->state &= ~RXBUSY;
|
||||
rs->state &= ~TXBUSY;
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
|
||||
if (rs->rx) {
|
||||
rxconf.direction = rs->dma_rx.direction;
|
||||
rxconf.src_addr = rs->dma_rx.addr;
|
||||
rxconf.src_addr_width = rs->n_bytes;
|
||||
rxconf.src_maxburst = rs->n_bytes;
|
||||
dmaengine_slave_config(rs->dma_rx.ch, &rxconf);
|
||||
|
||||
rxdesc = dmaengine_prep_slave_sg(
|
||||
rs->dma_rx.ch,
|
||||
rs->rx_sg.sgl, rs->rx_sg.nents,
|
||||
rs->dma_rx.direction, DMA_PREP_INTERRUPT);
|
||||
|
||||
rxdesc->callback = rockchip_spi_dma_rxcb;
|
||||
rxdesc->callback_param = rs;
|
||||
}
|
||||
|
||||
if (rs->tx) {
|
||||
txconf.direction = rs->dma_tx.direction;
|
||||
txconf.dst_addr = rs->dma_tx.addr;
|
||||
txconf.dst_addr_width = rs->n_bytes;
|
||||
txconf.dst_maxburst = rs->n_bytes;
|
||||
dmaengine_slave_config(rs->dma_tx.ch, &txconf);
|
||||
|
||||
txdesc = dmaengine_prep_slave_sg(
|
||||
rs->dma_tx.ch,
|
||||
rs->tx_sg.sgl, rs->tx_sg.nents,
|
||||
rs->dma_tx.direction, DMA_PREP_INTERRUPT);
|
||||
|
||||
txdesc->callback = rockchip_spi_dma_txcb;
|
||||
txdesc->callback_param = rs;
|
||||
}
|
||||
|
||||
/* rx must be started before tx due to spi instinct */
|
||||
if (rs->rx) {
|
||||
spin_lock_irqsave(&rs->lock, flags);
|
||||
rs->state |= RXBUSY;
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
dmaengine_submit(rxdesc);
|
||||
dma_async_issue_pending(rs->dma_rx.ch);
|
||||
}
|
||||
|
||||
if (rs->tx) {
|
||||
spin_lock_irqsave(&rs->lock, flags);
|
||||
rs->state |= TXBUSY;
|
||||
spin_unlock_irqrestore(&rs->lock, flags);
|
||||
dmaengine_submit(txdesc);
|
||||
dma_async_issue_pending(rs->dma_tx.ch);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void rockchip_spi_config(struct rockchip_spi *rs)
|
||||
{
|
||||
u32 div = 0;
|
||||
u32 dmacr = 0;
|
||||
|
||||
u32 cr0 = (CR0_BHT_8BIT << CR0_BHT_OFFSET)
|
||||
| (CR0_SSD_ONE << CR0_SSD_OFFSET);
|
||||
|
||||
cr0 |= (rs->n_bytes << CR0_DFS_OFFSET);
|
||||
cr0 |= ((rs->mode & 0x3) << CR0_SCPH_OFFSET);
|
||||
cr0 |= (rs->tmode << CR0_XFM_OFFSET);
|
||||
cr0 |= (rs->type << CR0_FRF_OFFSET);
|
||||
|
||||
if (rs->use_dma) {
|
||||
if (rs->tx)
|
||||
dmacr |= TF_DMA_EN;
|
||||
if (rs->rx)
|
||||
dmacr |= RF_DMA_EN;
|
||||
}
|
||||
|
||||
/* div doesn't support odd number */
|
||||
div = rs->max_freq / rs->speed;
|
||||
div = (div + 1) & 0xfffe;
|
||||
|
||||
spi_enable_chip(rs, 0);
|
||||
|
||||
writel_relaxed(cr0, rs->regs + ROCKCHIP_SPI_CTRLR0);
|
||||
|
||||
writel_relaxed(rs->len - 1, rs->regs + ROCKCHIP_SPI_CTRLR1);
|
||||
writel_relaxed(rs->fifo_len / 2 - 1, rs->regs + ROCKCHIP_SPI_TXFTLR);
|
||||
writel_relaxed(rs->fifo_len / 2 - 1, rs->regs + ROCKCHIP_SPI_RXFTLR);
|
||||
|
||||
writel_relaxed(0, rs->regs + ROCKCHIP_SPI_DMATDLR);
|
||||
writel_relaxed(0, rs->regs + ROCKCHIP_SPI_DMARDLR);
|
||||
writel_relaxed(dmacr, rs->regs + ROCKCHIP_SPI_DMACR);
|
||||
|
||||
spi_set_clk(rs, div);
|
||||
|
||||
dev_dbg(rs->dev, "cr0 0x%x, div %d\n", cr0, div);
|
||||
|
||||
spi_enable_chip(rs, 1);
|
||||
}
|
||||
|
||||
static int rockchip_spi_transfer_one(
|
||||
struct spi_master *master,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
int ret = 0;
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
WARN_ON((readl_relaxed(rs->regs + ROCKCHIP_SPI_SR) & SR_BUSY));
|
||||
|
||||
if (!xfer->tx_buf && !xfer->rx_buf) {
|
||||
dev_err(rs->dev, "No buffer for transfer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rs->speed = xfer->speed_hz;
|
||||
rs->bpw = xfer->bits_per_word;
|
||||
rs->n_bytes = rs->bpw >> 3;
|
||||
|
||||
rs->tx = xfer->tx_buf;
|
||||
rs->tx_end = rs->tx + xfer->len;
|
||||
rs->rx = xfer->rx_buf;
|
||||
rs->rx_end = rs->rx + xfer->len;
|
||||
rs->len = xfer->len;
|
||||
|
||||
rs->tx_sg = xfer->tx_sg;
|
||||
rs->rx_sg = xfer->rx_sg;
|
||||
|
||||
if (rs->tx && rs->rx)
|
||||
rs->tmode = CR0_XFM_TR;
|
||||
else if (rs->tx)
|
||||
rs->tmode = CR0_XFM_TO;
|
||||
else if (rs->rx)
|
||||
rs->tmode = CR0_XFM_RO;
|
||||
|
||||
if (master->can_dma && master->can_dma(master, spi, xfer))
|
||||
rs->use_dma = 1;
|
||||
else
|
||||
rs->use_dma = 0;
|
||||
|
||||
rockchip_spi_config(rs);
|
||||
|
||||
if (rs->use_dma)
|
||||
ret = rockchip_spi_dma_transfer(rs);
|
||||
else
|
||||
ret = rockchip_spi_pio_transfer(rs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool rockchip_spi_can_dma(struct spi_master *master,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *xfer)
|
||||
{
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
return (xfer->len > rs->fifo_len);
|
||||
}
|
||||
|
||||
static int rockchip_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct rockchip_spi *rs;
|
||||
struct spi_master *master;
|
||||
struct resource *mem;
|
||||
|
||||
master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi));
|
||||
if (!master)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, master);
|
||||
|
||||
rs = spi_master_get_devdata(master);
|
||||
memset(rs, 0, sizeof(struct rockchip_spi));
|
||||
|
||||
/* Get basic io resource and map it */
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
rs->regs = devm_ioremap_resource(&pdev->dev, mem);
|
||||
if (IS_ERR(rs->regs)) {
|
||||
ret = PTR_ERR(rs->regs);
|
||||
goto err_ioremap_resource;
|
||||
}
|
||||
|
||||
rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk");
|
||||
if (IS_ERR(rs->apb_pclk)) {
|
||||
dev_err(&pdev->dev, "Failed to get apb_pclk\n");
|
||||
ret = PTR_ERR(rs->apb_pclk);
|
||||
goto err_ioremap_resource;
|
||||
}
|
||||
|
||||
rs->spiclk = devm_clk_get(&pdev->dev, "spiclk");
|
||||
if (IS_ERR(rs->spiclk)) {
|
||||
dev_err(&pdev->dev, "Failed to get spi_pclk\n");
|
||||
ret = PTR_ERR(rs->spiclk);
|
||||
goto err_ioremap_resource;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(rs->apb_pclk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to enable apb_pclk\n");
|
||||
goto err_ioremap_resource;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(rs->spiclk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to enable spi_clk\n");
|
||||
goto err_spiclk_enable;
|
||||
}
|
||||
|
||||
spi_enable_chip(rs, 0);
|
||||
|
||||
rs->type = SSI_MOTO_SPI;
|
||||
rs->master = master;
|
||||
rs->dev = &pdev->dev;
|
||||
rs->max_freq = clk_get_rate(rs->spiclk);
|
||||
|
||||
rs->fifo_len = get_fifo_len(rs);
|
||||
if (!rs->fifo_len) {
|
||||
dev_err(&pdev->dev, "Failed to get fifo length\n");
|
||||
ret = -EINVAL;
|
||||
goto err_get_fifo_len;
|
||||
}
|
||||
|
||||
spin_lock_init(&rs->lock);
|
||||
|
||||
pm_runtime_set_active(&pdev->dev);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
master->auto_runtime_pm = true;
|
||||
master->bus_num = pdev->id;
|
||||
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;
|
||||
master->num_chipselect = 2;
|
||||
master->dev.of_node = pdev->dev.of_node;
|
||||
master->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8);
|
||||
|
||||
master->set_cs = rockchip_spi_set_cs;
|
||||
master->prepare_message = rockchip_spi_prepare_message;
|
||||
master->unprepare_message = rockchip_spi_unprepare_message;
|
||||
master->transfer_one = rockchip_spi_transfer_one;
|
||||
|
||||
rs->dma_tx.ch = dma_request_slave_channel(rs->dev, "tx");
|
||||
if (!rs->dma_tx.ch)
|
||||
dev_warn(rs->dev, "Failed to request TX DMA channel\n");
|
||||
|
||||
rs->dma_rx.ch = dma_request_slave_channel(rs->dev, "rx");
|
||||
if (!rs->dma_rx.ch) {
|
||||
if (rs->dma_tx.ch) {
|
||||
dma_release_channel(rs->dma_tx.ch);
|
||||
rs->dma_tx.ch = NULL;
|
||||
}
|
||||
dev_warn(rs->dev, "Failed to request RX DMA channel\n");
|
||||
}
|
||||
|
||||
if (rs->dma_tx.ch && rs->dma_rx.ch) {
|
||||
rs->dma_tx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_TXDR);
|
||||
rs->dma_rx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_RXDR);
|
||||
rs->dma_tx.direction = DMA_MEM_TO_DEV;
|
||||
rs->dma_tx.direction = DMA_DEV_TO_MEM;
|
||||
|
||||
master->can_dma = rockchip_spi_can_dma;
|
||||
master->dma_tx = rs->dma_tx.ch;
|
||||
master->dma_rx = rs->dma_rx.ch;
|
||||
}
|
||||
|
||||
ret = devm_spi_register_master(&pdev->dev, master);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to register master\n");
|
||||
goto err_register_master;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_register_master:
|
||||
if (rs->dma_tx.ch)
|
||||
dma_release_channel(rs->dma_tx.ch);
|
||||
if (rs->dma_rx.ch)
|
||||
dma_release_channel(rs->dma_rx.ch);
|
||||
err_get_fifo_len:
|
||||
clk_disable_unprepare(rs->spiclk);
|
||||
err_spiclk_enable:
|
||||
clk_disable_unprepare(rs->apb_pclk);
|
||||
err_ioremap_resource:
|
||||
spi_master_put(master);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rockchip_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_master *master = spi_master_get(platform_get_drvdata(pdev));
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
clk_disable_unprepare(rs->spiclk);
|
||||
clk_disable_unprepare(rs->apb_pclk);
|
||||
|
||||
if (rs->dma_tx.ch)
|
||||
dma_release_channel(rs->dma_tx.ch);
|
||||
if (rs->dma_rx.ch)
|
||||
dma_release_channel(rs->dma_rx.ch);
|
||||
|
||||
spi_master_put(master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int rockchip_spi_suspend(struct device *dev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
ret = spi_master_suspend(rs->master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!pm_runtime_suspended(dev)) {
|
||||
clk_disable_unprepare(rs->spiclk);
|
||||
clk_disable_unprepare(rs->apb_pclk);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rockchip_spi_resume(struct device *dev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
if (!pm_runtime_suspended(dev)) {
|
||||
ret = clk_prepare_enable(rs->apb_pclk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(rs->spiclk);
|
||||
if (ret < 0) {
|
||||
clk_disable_unprepare(rs->apb_pclk);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = spi_master_resume(rs->master);
|
||||
if (ret < 0) {
|
||||
clk_disable_unprepare(rs->spiclk);
|
||||
clk_disable_unprepare(rs->apb_pclk);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int rockchip_spi_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
clk_disable_unprepare(rs->spiclk);
|
||||
clk_disable_unprepare(rs->apb_pclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rockchip_spi_runtime_resume(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
struct rockchip_spi *rs = spi_master_get_devdata(master);
|
||||
|
||||
ret = clk_prepare_enable(rs->apb_pclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(rs->spiclk);
|
||||
if (ret)
|
||||
clk_disable_unprepare(rs->apb_pclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_PM_RUNTIME */
|
||||
|
||||
static const struct dev_pm_ops rockchip_spi_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(rockchip_spi_suspend, rockchip_spi_resume)
|
||||
SET_RUNTIME_PM_OPS(rockchip_spi_runtime_suspend,
|
||||
rockchip_spi_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id rockchip_spi_dt_match[] = {
|
||||
{ .compatible = "rockchip,rk3066-spi", },
|
||||
{ .compatible = "rockchip,rk3188-spi", },
|
||||
{ .compatible = "rockchip,rk3288-spi", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match);
|
||||
|
||||
static struct platform_driver rockchip_spi_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &rockchip_spi_pm,
|
||||
.of_match_table = of_match_ptr(rockchip_spi_dt_match),
|
||||
},
|
||||
.probe = rockchip_spi_probe,
|
||||
.remove = rockchip_spi_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(rockchip_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>");
|
||||
MODULE_DESCRIPTION("ROCKCHIP SPI Controller Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -477,7 +477,7 @@ static int rspi_dma_transfer(struct rspi_data *rspi, struct sg_table *tx,
|
|||
tx->sgl, tx->nents, DMA_TO_DEVICE,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc_tx)
|
||||
return -EIO;
|
||||
goto no_dma;
|
||||
|
||||
irq_mask |= SPCR_SPTIE;
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ static int rspi_dma_transfer(struct rspi_data *rspi, struct sg_table *tx,
|
|||
rx->sgl, rx->nents, DMA_FROM_DEVICE,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc_rx)
|
||||
return -EIO;
|
||||
goto no_dma;
|
||||
|
||||
irq_mask |= SPCR_SPRIE;
|
||||
}
|
||||
|
@ -540,6 +540,12 @@ static int rspi_dma_transfer(struct rspi_data *rspi, struct sg_table *tx,
|
|||
enable_irq(rspi->rx_irq);
|
||||
|
||||
return ret;
|
||||
|
||||
no_dma:
|
||||
pr_warn_once("%s %s: DMA not available, falling back to PIO\n",
|
||||
dev_driver_string(&rspi->master->dev),
|
||||
dev_name(&rspi->master->dev));
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
static void rspi_receive_init(const struct rspi_data *rspi)
|
||||
|
@ -593,8 +599,10 @@ static int rspi_common_transfer(struct rspi_data *rspi,
|
|||
|
||||
if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) {
|
||||
/* rx_buf can be NULL on RSPI on SH in TX-only Mode */
|
||||
return rspi_dma_transfer(rspi, &xfer->tx_sg,
|
||||
xfer->rx_buf ? &xfer->rx_sg : NULL);
|
||||
ret = rspi_dma_transfer(rspi, &xfer->tx_sg,
|
||||
xfer->rx_buf ? &xfer->rx_sg : NULL);
|
||||
if (ret != -EAGAIN)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = rspi_pio_transfer(rspi, xfer->tx_buf, xfer->rx_buf, xfer->len);
|
||||
|
@ -630,7 +638,6 @@ static int rspi_rz_transfer_one(struct spi_master *master,
|
|||
struct spi_transfer *xfer)
|
||||
{
|
||||
struct rspi_data *rspi = spi_master_get_devdata(master);
|
||||
int ret;
|
||||
|
||||
rspi_rz_receive_init(rspi);
|
||||
|
||||
|
@ -649,8 +656,11 @@ static int qspi_transfer_out(struct rspi_data *rspi, struct spi_transfer *xfer)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer))
|
||||
return rspi_dma_transfer(rspi, &xfer->tx_sg, NULL);
|
||||
if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) {
|
||||
ret = rspi_dma_transfer(rspi, &xfer->tx_sg, NULL);
|
||||
if (ret != -EAGAIN)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = rspi_pio_transfer(rspi, xfer->tx_buf, NULL, xfer->len);
|
||||
if (ret < 0)
|
||||
|
@ -664,8 +674,11 @@ static int qspi_transfer_out(struct rspi_data *rspi, struct spi_transfer *xfer)
|
|||
|
||||
static int qspi_transfer_in(struct rspi_data *rspi, struct spi_transfer *xfer)
|
||||
{
|
||||
if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer))
|
||||
return rspi_dma_transfer(rspi, NULL, &xfer->rx_sg);
|
||||
if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) {
|
||||
int ret = rspi_dma_transfer(rspi, NULL, &xfer->rx_sg);
|
||||
if (ret != -EAGAIN)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return rspi_pio_transfer(rspi, NULL, xfer->rx_buf, xfer->len);
|
||||
}
|
||||
|
@ -927,19 +940,19 @@ static int rspi_request_dma(struct device *dev, struct spi_master *master,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void rspi_release_dma(struct rspi_data *rspi)
|
||||
static void rspi_release_dma(struct spi_master *master)
|
||||
{
|
||||
if (rspi->master->dma_tx)
|
||||
dma_release_channel(rspi->master->dma_tx);
|
||||
if (rspi->master->dma_rx)
|
||||
dma_release_channel(rspi->master->dma_rx);
|
||||
if (master->dma_tx)
|
||||
dma_release_channel(master->dma_tx);
|
||||
if (master->dma_rx)
|
||||
dma_release_channel(master->dma_rx);
|
||||
}
|
||||
|
||||
static int rspi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rspi_data *rspi = platform_get_drvdata(pdev);
|
||||
|
||||
rspi_release_dma(rspi);
|
||||
rspi_release_dma(rspi->master);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
@ -1141,7 +1154,7 @@ static int rspi_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
|
||||
error3:
|
||||
rspi_release_dma(rspi);
|
||||
rspi_release_dma(master);
|
||||
error2:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
error1:
|
||||
|
|
Загрузка…
Ссылка в новой задаче