linux/drivers/spi/spi-dw-mmio.c
Wan Ahmad Zainie e539f435cb
spi: dw: Add support for DesignWare DWC_ssi
This patch adds initial support for DesignWare DWC_ssi soft IP. DWC_ssi is
the enhanced version of DW_apb_ssi, which is currently supported by this
driver. Their registers are same, but the bit fields of register CTRLR0
are different.

DWC_ssi has additional features compared to DW_apb_ssi. Major enhancements
in DWC_ssi are hyper bus protocol, boot mode support and advanced XIP
support. DWC_ssi is an AHB slave device, whilst DW_apb_ssi is an APB slave
device.

Register offset
                DW_ssi          DW_apb_ssi
CTRLR0          0x00            0x00
CTRLR1          0x04            0x04
SSIENR          0x08            0x08
MWCR            0x0c            0x0c
SER             0x10            0x10
BAUDR           0x14            0x14
TXFTLR          0x18            0x18
RXFTLR          0x1c            0x1c
TXFLR           0x20            0x20
RXFLR           0x24            0x24
SR              0x28            0x28
IMR             0x2c            0x2c
ISR             0x30            0x30
RISR            0x34            0x34
TXOICR          0x38            0x38
RXOICR          0x3c            0x3c
RXUICR          0x40            0x40
MSTICR          0x44            0x44
ICR             0x48            0x48
DMACR           0x4c            0x4c
DMATDLR         0x50            0x50
DMARDLR         0x54            0x54
IDR             0x58            0x58
SSI_VERSION_ID  0x5c            0x5c
DRx (0 to 35)   0x60+i*0x4      0x60+i*0x4
RX_SAMPLE_DLY   0xf0            0xf0
SPI_CTRLR0      0xf4            0xf4
TXD_DRIVE_EDGE  0xf8            0xf8
XIP_MODE_BITS   0xfc            RSVD

Register configuration - CTRLR0
                        DW_ssi          DW_apb_ssi
  SPI_HYPERBUS_EN       bit[24]         NONE
  SPI_FRF               bit[23:22]      bit[22:21]
  DFS_32                NONE            bit[20:16]
  CFS                   bit[19:16]      bit[15:12]
  SSTE                  bit[14]         bit[24]
  SRL                   bit[13]         bit[11]
  SLV_OE                bit[12]         bit[10]
  TMOD                  bit[11:10]      bit[9:8]
  SCPOL | SPHA          bit[9:8]        bit[7:6]
  FRF                   bit[7:6]        bit[5:4]
  DFS                   bit[4:0]        bit[3:0]

The documents used are
[1] DW_apb_ssi_databook.pdf version 4.01a (2016.10a).
[2] DWC_ssi_databook.pdf version 1.01a.

Signed-off-by: Wan Ahmad Zainie <wan.ahmad.zainie.wan.mohamad@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20200505130618.554-4-wan.ahmad.zainie.wan.mohamad@intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-05-05 15:07:59 +01:00

281 lines
7.3 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Memory-mapped interface driver for DW SPI Core
*
* Copyright (c) 2010, Octasic semiconductor.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/scatterlist.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/acpi.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include "spi-dw.h"
#define DRIVER_NAME "dw_spi_mmio"
struct dw_spi_mmio {
struct dw_spi dws;
struct clk *clk;
struct clk *pclk;
void *priv;
};
#define MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24
#define OCELOT_IF_SI_OWNER_OFFSET 4
#define JAGUAR2_IF_SI_OWNER_OFFSET 6
#define MSCC_IF_SI_OWNER_MASK GENMASK(1, 0)
#define MSCC_IF_SI_OWNER_SISL 0
#define MSCC_IF_SI_OWNER_SIBM 1
#define MSCC_IF_SI_OWNER_SIMC 2
#define MSCC_SPI_MST_SW_MODE 0x14
#define MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE BIT(13)
#define MSCC_SPI_MST_SW_MODE_SW_SPI_CS(x) (x << 5)
struct dw_spi_mscc {
struct regmap *syscon;
void __iomem *spi_mst;
};
/*
* The Designware SPI controller (referred to as master in the documentation)
* automatically deasserts chip select when the tx fifo is empty. The chip
* selects then needs to be either driven as GPIOs or, for the first 4 using the
* the SPI boot controller registers. the final chip select is an OR gate
* between the Designware SPI controller and the SPI boot controller.
*/
static void dw_spi_mscc_set_cs(struct spi_device *spi, bool enable)
{
struct dw_spi *dws = spi_master_get_devdata(spi->master);
struct dw_spi_mmio *dwsmmio = container_of(dws, struct dw_spi_mmio, dws);
struct dw_spi_mscc *dwsmscc = dwsmmio->priv;
u32 cs = spi->chip_select;
if (cs < 4) {
u32 sw_mode = MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE;
if (!enable)
sw_mode |= MSCC_SPI_MST_SW_MODE_SW_SPI_CS(BIT(cs));
writel(sw_mode, dwsmscc->spi_mst + MSCC_SPI_MST_SW_MODE);
}
dw_spi_set_cs(spi, enable);
}
static int dw_spi_mscc_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio,
const char *cpu_syscon, u32 if_si_owner_offset)
{
struct dw_spi_mscc *dwsmscc;
dwsmscc = devm_kzalloc(&pdev->dev, sizeof(*dwsmscc), GFP_KERNEL);
if (!dwsmscc)
return -ENOMEM;
dwsmscc->spi_mst = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(dwsmscc->spi_mst)) {
dev_err(&pdev->dev, "SPI_MST region map failed\n");
return PTR_ERR(dwsmscc->spi_mst);
}
dwsmscc->syscon = syscon_regmap_lookup_by_compatible(cpu_syscon);
if (IS_ERR(dwsmscc->syscon))
return PTR_ERR(dwsmscc->syscon);
/* Deassert all CS */
writel(0, dwsmscc->spi_mst + MSCC_SPI_MST_SW_MODE);
/* Select the owner of the SI interface */
regmap_update_bits(dwsmscc->syscon, MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL,
MSCC_IF_SI_OWNER_MASK << if_si_owner_offset,
MSCC_IF_SI_OWNER_SIMC << if_si_owner_offset);
dwsmmio->dws.set_cs = dw_spi_mscc_set_cs;
dwsmmio->priv = dwsmscc;
/* Register hook to configure CTRLR0 */
dwsmmio->dws.update_cr0 = dw_spi_update_cr0;
return 0;
}
static int dw_spi_mscc_ocelot_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio)
{
return dw_spi_mscc_init(pdev, dwsmmio, "mscc,ocelot-cpu-syscon",
OCELOT_IF_SI_OWNER_OFFSET);
}
static int dw_spi_mscc_jaguar2_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio)
{
return dw_spi_mscc_init(pdev, dwsmmio, "mscc,jaguar2-cpu-syscon",
JAGUAR2_IF_SI_OWNER_OFFSET);
}
static int dw_spi_alpine_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio)
{
dwsmmio->dws.cs_override = 1;
/* Register hook to configure CTRLR0 */
dwsmmio->dws.update_cr0 = dw_spi_update_cr0;
return 0;
}
static int dw_spi_dw_apb_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio)
{
/* Register hook to configure CTRLR0 */
dwsmmio->dws.update_cr0 = dw_spi_update_cr0;
return 0;
}
static int dw_spi_dwc_ssi_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio)
{
/* Register hook to configure CTRLR0 */
dwsmmio->dws.update_cr0 = dw_spi_update_cr0_v1_01a;
return 0;
}
static int dw_spi_mmio_probe(struct platform_device *pdev)
{
int (*init_func)(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio);
struct dw_spi_mmio *dwsmmio;
struct dw_spi *dws;
int ret;
int num_cs;
dwsmmio = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_mmio),
GFP_KERNEL);
if (!dwsmmio)
return -ENOMEM;
dws = &dwsmmio->dws;
/* Get basic io resource and map it */
dws->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dws->regs)) {
dev_err(&pdev->dev, "SPI region map failed\n");
return PTR_ERR(dws->regs);
}
dws->irq = platform_get_irq(pdev, 0);
if (dws->irq < 0)
return dws->irq; /* -ENXIO */
dwsmmio->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(dwsmmio->clk))
return PTR_ERR(dwsmmio->clk);
ret = clk_prepare_enable(dwsmmio->clk);
if (ret)
return ret;
/* Optional clock needed to access the registers */
dwsmmio->pclk = devm_clk_get_optional(&pdev->dev, "pclk");
if (IS_ERR(dwsmmio->pclk)) {
ret = PTR_ERR(dwsmmio->pclk);
goto out_clk;
}
ret = clk_prepare_enable(dwsmmio->pclk);
if (ret)
goto out_clk;
dws->bus_num = pdev->id;
dws->max_freq = clk_get_rate(dwsmmio->clk);
device_property_read_u32(&pdev->dev, "reg-io-width", &dws->reg_io_width);
num_cs = 4;
device_property_read_u32(&pdev->dev, "num-cs", &num_cs);
dws->num_cs = num_cs;
init_func = device_get_match_data(&pdev->dev);
if (init_func) {
ret = init_func(pdev, dwsmmio);
if (ret)
goto out;
}
pm_runtime_enable(&pdev->dev);
ret = dw_spi_add_host(&pdev->dev, dws);
if (ret)
goto out;
platform_set_drvdata(pdev, dwsmmio);
return 0;
out:
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(dwsmmio->pclk);
out_clk:
clk_disable_unprepare(dwsmmio->clk);
return ret;
}
static int dw_spi_mmio_remove(struct platform_device *pdev)
{
struct dw_spi_mmio *dwsmmio = platform_get_drvdata(pdev);
dw_spi_remove_host(&dwsmmio->dws);
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(dwsmmio->pclk);
clk_disable_unprepare(dwsmmio->clk);
return 0;
}
static const struct of_device_id dw_spi_mmio_of_match[] = {
{ .compatible = "snps,dw-apb-ssi", .data = dw_spi_dw_apb_init},
{ .compatible = "mscc,ocelot-spi", .data = dw_spi_mscc_ocelot_init},
{ .compatible = "mscc,jaguar2-spi", .data = dw_spi_mscc_jaguar2_init},
{ .compatible = "amazon,alpine-dw-apb-ssi", .data = dw_spi_alpine_init},
{ .compatible = "renesas,rzn1-spi", .data = dw_spi_dw_apb_init},
{ .compatible = "snps,dwc-ssi-1.01a", .data = dw_spi_dwc_ssi_init},
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, dw_spi_mmio_of_match);
static const struct acpi_device_id dw_spi_mmio_acpi_match[] = {
{"HISI0173", (kernel_ulong_t)dw_spi_dw_apb_init},
{},
};
MODULE_DEVICE_TABLE(acpi, dw_spi_mmio_acpi_match);
static struct platform_driver dw_spi_mmio_driver = {
.probe = dw_spi_mmio_probe,
.remove = dw_spi_mmio_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = dw_spi_mmio_of_match,
.acpi_match_table = ACPI_PTR(dw_spi_mmio_acpi_match),
},
};
module_platform_driver(dw_spi_mmio_driver);
MODULE_AUTHOR("Jean-Hugues Deschenes <jean-hugues.deschenes@octasic.com>");
MODULE_DESCRIPTION("Memory-mapped I/O interface driver for DW SPI Core");
MODULE_LICENSE("GPL v2");