drivers/spi/spi-apple-mc2.c
Raw
// 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_CLKIDLE 0x38
#define REG_TXCNT 0x4C
#define REG_AVAIL 0x10C
#define REG_AVAIL_TXFIFO GENMASK(15,8)
#define REG_AVAIL_RXFIFO GENMASK(31,24)
// TODO: why is AVAIL 255 if FIFO is 16
#define FIFO_SIZE (u32)16
struct apple_spimc2 {
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_spimc2 *spi) {
int avail, len, data;
avail = readl(spi->base + REG_AVAIL);
len = min(spi->rx_len, (u32)FIELD_GET(REG_AVAIL_RXFIFO, avail));
while (len--) {
data = readl(spi->base + REG_RXDATA);
if (spi->rx_buf)
*spi->rx_buf++ = data;
spi->rx_len--;
}
}
static void apple_spimc_fill_tx(struct apple_spimc2 *spi) {
int avail, len, data;
avail = readl(spi->base + REG_AVAIL);
len = min(spi->tx_len, FIFO_SIZE - (u32)FIELD_GET(REG_AVAIL_TXFIFO, avail));
while (len--) {
data = spi->tx_buf ? *spi->tx_buf++ : 0;
writel(data, spi->base + REG_TXDATA);
spi->tx_len--;
}
}
static irqreturn_t apple_spimc_irq(int irq, void *dev_id)
{
struct apple_spimc2 *spi = dev_id;
unsigned status, avail;
status = readl(spi->base + REG_STATUS);
avail = readl(spi->base + REG_AVAIL);
writel(status, spi->base + REG_STATUS);
if (FIELD_GET(REG_AVAIL_RXFIFO, avail) ||
FIELD_GET(REG_AVAIL_TXFIFO, avail)) {
apple_spimc_drain_rx(spi);
apple_spimc_fill_tx(spi);
if (!spi->tx_len && !spi->rx_len) {
writel(REG_CONFIG_SET + REG_CONFIG_PIOEN, spi->base + REG_CONFIG);
spi_finalize_current_transfer(spi->master);
}
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int apple_spimc_clock(struct apple_spimc2 *spi, u32 speed)
{
u32 clkdiv = clamp(DIV_ROUND_UP(spi->clkfreq, speed), (u32)REG_CLKDIV_MIN, (u32)REG_CLKDIV_MAX);
writel(0, spi->base + REG_CLKCFG);
writel(clkdiv, spi->base + REG_CLKDIV);
writel(0, spi->base + REG_CLKIDLE);
writel(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_spimc2 *spi = spi_controller_get_devdata(ctlr);
int status, config;
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(t->len, spi->base + REG_TXCNT);
writel(t->len, spi->base + REG_RXCNT);
writel(REG_CONFIG_SET | REG_CONFIG_PIOEN, spi->base + REG_CONFIG);
apple_spimc_drain_rx(spi);
apple_spimc_fill_tx(spi);
if (spi->tx_len || spi->rx_len) {
// start IRQ
config = readl(spi->base + REG_CONFIG);
config |= REG_CONFIG_IE_TXEMPTY;
config |= REG_CONFIG_IE_RXRDY;
writel(config, spi->base + REG_CONFIG);
return 1;
} else {
writel(REG_CONFIG_SET, spi->base + REG_CONFIG);
status = readl(spi->base + REG_STATUS);
writel(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_spimc2 *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_spimc2 *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");