Commit 799eac91 authored by Joey Gouly's avatar Joey Gouly
Browse files

WIP: spi: apple: add SPI driver for Apple



TODO: needs a binding
TODO: add runtime-pm
Signed-off-by: Joey Gouly's avatarJoey Gouly <joey.gouly@arm.com>
parent 45a05664
......@@ -79,6 +79,13 @@ config SPI_ALTERA_DFL
Altera SPI master controller. The SPI master is connected
to a SPI slave to Avalon bridge in a Intel MAX BMC.
config SPI_APPLE_MC
tristate "Apple ARM SoC SPI-MC controller"
depends on GPIOLIB
help
This is the driver for SPI controller found in new Apple ARM SoCs,
including the M1.
config SPI_AR934X
tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver"
depends on ATH79 || COMPILE_TEST
......
......@@ -17,6 +17,7 @@ obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
obj-$(CONFIG_SPI_ALTERA) += spi-altera-platform.o
obj-$(CONFIG_SPI_ALTERA_CORE) += spi-altera-core.o
obj-$(CONFIG_SPI_ALTERA_DFL) += spi-altera-dfl.o
obj-$(CONFIG_SPI_APPLE_MC) += spi-apple-mc2.o
obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o
obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o
obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* SPIMC controller driver for Apple M1 SoC
*
* Copyright (C) The Asahi Linux Contributors
* Copyright (C) 2020-21 Corellium LLC
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/spi/spi.h>
#define REG_CLKCFG 0x00
#define REG_CLKCFG_ENABLE 0xD
#define REG_CONFIG 0x04
#define REG_CONFIG_PIOEN BIT(5)
#define REG_CONFIG_IE_RXRDY BIT(7)
#define REG_CONFIG_IE_TXEMPTY BIT(8)
#define REG_CONFIG_SET BIT(18)
#define REG_CONFIG_IE_COMPL BIT(21)
#define REG_STATUS 0x08
#define REG_STATUS_RXRDY BIT(0)
#define REG_STATUS_TXEMPTY BIT(1)
#define REG_STATUS_COMPL BIT(22)
#define REG_TXDATA 0x10
#define REG_RXDATA 0x20
#define REG_CLKDIV 0x30
#define REG_CLKDIV_MIN 2
#define REG_CLKDIV_MAX 2047
#define REG_RXCNT 0x34
#define REG_TXCNT 0x4C
#define REG_FIFO_LEVEL 0x10C
#define REG_FIFO_LEVEL_TX GENMASK(15, 8)
#define REG_FIFO_LEVEL_RX GENMASK(31, 24)
#define FIFO_SIZE (u32)16
struct apple_spimc {
struct spi_controller *master;
struct device *dev;
void __iomem *base;
unsigned int clkfreq;
struct clk *clk;
const unsigned char *tx_buf;
unsigned char *rx_buf;
unsigned int tx_len;
unsigned int rx_len;
};
static void apple_spimc_drain_rx(struct apple_spimc *spi, unsigned int rx_avail) {
int len, data;
len = min(spi->rx_len, rx_avail);
while (len--) {
data = readl_relaxed(spi->base + REG_RXDATA);
if (spi->rx_buf)
*spi->rx_buf++ = data;
spi->rx_len--;
}
}
static void apple_spimc_fill_tx(struct apple_spimc *spi, unsigned int tx_avail) {
int len, data;
len = min(spi->tx_len, FIFO_SIZE - (u32)tx_avail);
while (len--) {
data = spi->tx_buf ? *spi->tx_buf++ : 0;
writel_relaxed(data, spi->base + REG_TXDATA);
spi->tx_len--;
}
}
static irqreturn_t apple_spimc_irq(int irq, void *dev_id)
{
struct apple_spimc *spi = dev_id;
unsigned status, avail, rx_avail, tx_avail;
status = readl_relaxed(spi->base + REG_STATUS);
avail = readl_relaxed(spi->base + REG_FIFO_LEVEL);
writel_relaxed(status, spi->base + REG_STATUS);
rx_avail = FIELD_GET(REG_FIFO_LEVEL_RX, avail);
tx_avail = FIELD_GET(REG_FIFO_LEVEL_TX, avail);
apple_spimc_drain_rx(spi, rx_avail);
apple_spimc_fill_tx(spi, tx_avail);
if (!spi->tx_len && !spi->rx_len) {
writel_relaxed(REG_CONFIG_SET | REG_CONFIG_PIOEN, spi->base + REG_CONFIG);
spi_finalize_current_transfer(spi->master);
}
return IRQ_HANDLED;
}
static int apple_spimc_clock(struct apple_spimc *spi, u32 speed)
{
u32 clkdiv = clamp(DIV_ROUND_UP(spi->clkfreq, speed), (u32)REG_CLKDIV_MIN, (u32)REG_CLKDIV_MAX);
writel_relaxed(0, spi->base + REG_CLKCFG);
writel_relaxed(clkdiv, spi->base + REG_CLKDIV);
writel_relaxed(REG_CLKCFG_ENABLE, spi->base + REG_CLKCFG);
return 0;
}
static int apple_spimc_transfer_one(struct spi_controller *ctlr, struct spi_device *d, struct spi_transfer *t)
{
struct apple_spimc *spi = spi_controller_get_devdata(ctlr);
int status, config;
int avail, rx_avail, tx_avail;
spi->rx_buf = t->rx_buf;
spi->tx_buf = t->tx_buf;
spi->tx_len = t->len;
spi->rx_len = t->len;
apple_spimc_clock(spi, d->max_speed_hz);
writel_relaxed(t->len, spi->base + REG_TXCNT);
writel_relaxed(t->len, spi->base + REG_RXCNT);
writel_relaxed(REG_CONFIG_SET | REG_CONFIG_PIOEN, spi->base + REG_CONFIG);
avail = readl_relaxed(spi->base + REG_FIFO_LEVEL);
rx_avail = FIELD_GET(REG_FIFO_LEVEL_RX, avail);
tx_avail = FIELD_GET(REG_FIFO_LEVEL_TX, avail);
apple_spimc_drain_rx(spi, rx_avail);
apple_spimc_fill_tx(spi, tx_avail);
if (spi->tx_len || spi->rx_len) {
// start IRQ
config = readl_relaxed(spi->base + REG_CONFIG);
config |= REG_CONFIG_IE_TXEMPTY;
config |= REG_CONFIG_IE_RXRDY;
writel_relaxed(config, spi->base + REG_CONFIG);
return 1;
} else {
writel_relaxed(REG_CONFIG_SET, spi->base + REG_CONFIG);
status = readl_relaxed(spi->base + REG_STATUS);
writel_relaxed(status, spi->base + REG_STATUS);
spi_finalize_current_transfer(ctlr);
return 0;
}
}
static int apple_spimc_probe(struct platform_device *pdev)
{
struct spi_controller *master;
struct apple_spimc *spi;
void __iomem *base;
struct clk *clk;
int ret, irq;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get clock: %ld.\n", PTR_ERR(clk));
return PTR_ERR(clk);
}
ret = clk_prepare_enable(clk);
if (ret)
return ret;
master = spi_alloc_master(&pdev->dev, sizeof(*spi));
if (!master) {
dev_err(&pdev->dev, "master allocation failed.\n");
return -ENOMEM;
}
master->mode_bits = SPI_LSB_FIRST;
master->flags = 0;
master->transfer_one = apple_spimc_transfer_one;
master->bits_per_word_mask = SPI_BPW_MASK(8);
master->dev.of_node = pdev->dev.of_node;
master->use_gpio_descriptors = true;
dev_set_drvdata(&pdev->dev, master);
spi = spi_controller_get_devdata(master);
spi->dev = &pdev->dev;
spi->base = base;
spi->clk = clk;
spi->master = master;
spi->clkfreq = clk_get_rate(spi->clk);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, apple_spimc_irq, 0, dev_name(&pdev->dev), spi);
if (ret < 0)
return ret;
ret = devm_spi_register_controller(&pdev->dev, master);
return ret;
}
static int apple_spimc_remove(struct platform_device *pdev)
{
struct spi_controller *master = dev_get_drvdata(&pdev->dev);
struct apple_spimc *spi = spi_controller_get_devdata(master);
clk_disable_unprepare(spi->clk);
return 0;
}
static const struct of_device_id apple_spimc_match[] = {
{ .compatible = "apple,spi-mc-m1" },
{},
};
MODULE_DEVICE_TABLE(of, apple_spimc_match);
static struct platform_driver apple_spimc_driver = {
.driver = {
.name = "spi-apple-mc2",
.of_match_table = apple_spimc_match,
},
.probe = apple_spimc_probe,
.remove = apple_spimc_remove,
};
module_platform_driver(apple_spimc_driver);
MODULE_DESCRIPTION("Apple SoC SPI-MC driver");
MODULE_LICENSE("GPL");
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment