mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 16:41:39 +00:00
fd811b6293
Merge series from Li Zetao <lizetao1@huawei.com>:
Commit 7ef9651e97
("clk: Provide new devm_clk helpers for prepared
and enabled clocks") provides a new helper function for prepared and
enabled clocks when a driver keeps a clock prepared (or enabled) during
the whole lifetime of the driver. So where drivers get clocks and enable
them immediately, it can be combined into a single function
devm_clk_get_*(). Moreover, the unprepare and disable function
has been registered to devm_clk_state, and before devm_clk_state is
released, the clocks will be unprepareed and disable, so it is unnecessary
to unprepare and disable clocks explicitly when remove drivers or in the
error handling path.
598 lines
15 KiB
C
598 lines
15 KiB
C
// SPDX-License-Identifier: (GPL-2.0)
|
|
/*
|
|
* Microchip CoreSPI SPI controller driver
|
|
*
|
|
* Copyright (c) 2018-2022 Microchip Technology Inc. and its subsidiaries
|
|
*
|
|
* Author: Daire McNamara <daire.mcnamara@microchip.com>
|
|
* Author: Conor Dooley <conor.dooley@microchip.com>
|
|
*
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#define MAX_LEN (0xffff)
|
|
#define MAX_CS (8)
|
|
#define DEFAULT_FRAMESIZE (8)
|
|
#define FIFO_DEPTH (32)
|
|
#define CLK_GEN_MODE1_MAX (255)
|
|
#define CLK_GEN_MODE0_MAX (15)
|
|
#define CLK_GEN_MIN (0)
|
|
#define MODE_X_MASK_SHIFT (24)
|
|
|
|
#define CONTROL_ENABLE BIT(0)
|
|
#define CONTROL_MASTER BIT(1)
|
|
#define CONTROL_RX_DATA_INT BIT(4)
|
|
#define CONTROL_TX_DATA_INT BIT(5)
|
|
#define CONTROL_RX_OVER_INT BIT(6)
|
|
#define CONTROL_TX_UNDER_INT BIT(7)
|
|
#define CONTROL_SPO BIT(24)
|
|
#define CONTROL_SPH BIT(25)
|
|
#define CONTROL_SPS BIT(26)
|
|
#define CONTROL_FRAMEURUN BIT(27)
|
|
#define CONTROL_CLKMODE BIT(28)
|
|
#define CONTROL_BIGFIFO BIT(29)
|
|
#define CONTROL_OENOFF BIT(30)
|
|
#define CONTROL_RESET BIT(31)
|
|
|
|
#define CONTROL_MODE_MASK GENMASK(3, 2)
|
|
#define MOTOROLA_MODE (0)
|
|
#define CONTROL_FRAMECNT_MASK GENMASK(23, 8)
|
|
#define CONTROL_FRAMECNT_SHIFT (8)
|
|
|
|
#define STATUS_ACTIVE BIT(14)
|
|
#define STATUS_SSEL BIT(13)
|
|
#define STATUS_FRAMESTART BIT(12)
|
|
#define STATUS_TXFIFO_EMPTY_NEXT_READ BIT(11)
|
|
#define STATUS_TXFIFO_EMPTY BIT(10)
|
|
#define STATUS_TXFIFO_FULL_NEXT_WRITE BIT(9)
|
|
#define STATUS_TXFIFO_FULL BIT(8)
|
|
#define STATUS_RXFIFO_EMPTY_NEXT_READ BIT(7)
|
|
#define STATUS_RXFIFO_EMPTY BIT(6)
|
|
#define STATUS_RXFIFO_FULL_NEXT_WRITE BIT(5)
|
|
#define STATUS_RXFIFO_FULL BIT(4)
|
|
#define STATUS_TX_UNDERRUN BIT(3)
|
|
#define STATUS_RX_OVERFLOW BIT(2)
|
|
#define STATUS_RXDAT_RXED BIT(1)
|
|
#define STATUS_TXDAT_SENT BIT(0)
|
|
|
|
#define INT_TXDONE BIT(0)
|
|
#define INT_RXRDY BIT(1)
|
|
#define INT_RX_CHANNEL_OVERFLOW BIT(2)
|
|
#define INT_TX_CHANNEL_UNDERRUN BIT(3)
|
|
|
|
#define INT_ENABLE_MASK (CONTROL_RX_DATA_INT | CONTROL_TX_DATA_INT | \
|
|
CONTROL_RX_OVER_INT | CONTROL_TX_UNDER_INT)
|
|
|
|
#define REG_CONTROL (0x00)
|
|
#define REG_FRAME_SIZE (0x04)
|
|
#define REG_STATUS (0x08)
|
|
#define REG_INT_CLEAR (0x0c)
|
|
#define REG_RX_DATA (0x10)
|
|
#define REG_TX_DATA (0x14)
|
|
#define REG_CLK_GEN (0x18)
|
|
#define REG_SLAVE_SELECT (0x1c)
|
|
#define SSEL_MASK GENMASK(7, 0)
|
|
#define SSEL_DIRECT BIT(8)
|
|
#define SSELOUT_SHIFT 9
|
|
#define SSELOUT BIT(SSELOUT_SHIFT)
|
|
#define REG_MIS (0x20)
|
|
#define REG_RIS (0x24)
|
|
#define REG_CONTROL2 (0x28)
|
|
#define REG_COMMAND (0x2c)
|
|
#define REG_PKTSIZE (0x30)
|
|
#define REG_CMD_SIZE (0x34)
|
|
#define REG_HWSTATUS (0x38)
|
|
#define REG_STAT8 (0x3c)
|
|
#define REG_CTRL2 (0x48)
|
|
#define REG_FRAMESUP (0x50)
|
|
|
|
struct mchp_corespi {
|
|
void __iomem *regs;
|
|
struct clk *clk;
|
|
const u8 *tx_buf;
|
|
u8 *rx_buf;
|
|
u32 clk_gen; /* divider for spi output clock generated by the controller */
|
|
u32 clk_mode;
|
|
int irq;
|
|
int tx_len;
|
|
int rx_len;
|
|
int pending;
|
|
};
|
|
|
|
static inline u32 mchp_corespi_read(struct mchp_corespi *spi, unsigned int reg)
|
|
{
|
|
return readl(spi->regs + reg);
|
|
}
|
|
|
|
static inline void mchp_corespi_write(struct mchp_corespi *spi, unsigned int reg, u32 val)
|
|
{
|
|
writel(val, spi->regs + reg);
|
|
}
|
|
|
|
static inline void mchp_corespi_disable(struct mchp_corespi *spi)
|
|
{
|
|
u32 control = mchp_corespi_read(spi, REG_CONTROL);
|
|
|
|
control &= ~CONTROL_ENABLE;
|
|
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
}
|
|
|
|
static inline void mchp_corespi_read_fifo(struct mchp_corespi *spi)
|
|
{
|
|
u8 data;
|
|
int fifo_max, i = 0;
|
|
|
|
fifo_max = min(spi->rx_len, FIFO_DEPTH);
|
|
|
|
while ((i < fifo_max) && !(mchp_corespi_read(spi, REG_STATUS) & STATUS_RXFIFO_EMPTY)) {
|
|
data = mchp_corespi_read(spi, REG_RX_DATA);
|
|
|
|
if (spi->rx_buf)
|
|
*spi->rx_buf++ = data;
|
|
i++;
|
|
}
|
|
spi->rx_len -= i;
|
|
spi->pending -= i;
|
|
}
|
|
|
|
static void mchp_corespi_enable_ints(struct mchp_corespi *spi)
|
|
{
|
|
u32 control, mask = INT_ENABLE_MASK;
|
|
|
|
mchp_corespi_disable(spi);
|
|
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
|
|
control |= mask;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
|
|
control |= CONTROL_ENABLE;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
}
|
|
|
|
static void mchp_corespi_disable_ints(struct mchp_corespi *spi)
|
|
{
|
|
u32 control, mask = INT_ENABLE_MASK;
|
|
|
|
mchp_corespi_disable(spi);
|
|
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
control &= ~mask;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
|
|
control |= CONTROL_ENABLE;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
}
|
|
|
|
static inline void mchp_corespi_set_xfer_size(struct mchp_corespi *spi, int len)
|
|
{
|
|
u32 control;
|
|
u16 lenpart;
|
|
|
|
/*
|
|
* Disable the SPI controller. Writes to transfer length have
|
|
* no effect when the controller is enabled.
|
|
*/
|
|
mchp_corespi_disable(spi);
|
|
|
|
/*
|
|
* The lower 16 bits of the frame count are stored in the control reg
|
|
* for legacy reasons, but the upper 16 written to a different register:
|
|
* FRAMESUP. While both the upper and lower bits can be *READ* from the
|
|
* FRAMESUP register, writing to the lower 16 bits is a NOP
|
|
*/
|
|
lenpart = len & 0xffff;
|
|
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
control &= ~CONTROL_FRAMECNT_MASK;
|
|
control |= lenpart << CONTROL_FRAMECNT_SHIFT;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
|
|
lenpart = len & 0xffff0000;
|
|
mchp_corespi_write(spi, REG_FRAMESUP, lenpart);
|
|
|
|
control |= CONTROL_ENABLE;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
}
|
|
|
|
static inline void mchp_corespi_write_fifo(struct mchp_corespi *spi)
|
|
{
|
|
u8 byte;
|
|
int fifo_max, i = 0;
|
|
|
|
fifo_max = min(spi->tx_len, FIFO_DEPTH);
|
|
mchp_corespi_set_xfer_size(spi, fifo_max);
|
|
|
|
while ((i < fifo_max) && !(mchp_corespi_read(spi, REG_STATUS) & STATUS_TXFIFO_FULL)) {
|
|
byte = spi->tx_buf ? *spi->tx_buf++ : 0xaa;
|
|
mchp_corespi_write(spi, REG_TX_DATA, byte);
|
|
i++;
|
|
}
|
|
|
|
spi->tx_len -= i;
|
|
spi->pending += i;
|
|
}
|
|
|
|
static inline void mchp_corespi_set_framesize(struct mchp_corespi *spi, int bt)
|
|
{
|
|
u32 control;
|
|
|
|
/*
|
|
* Disable the SPI controller. Writes to the frame size have
|
|
* no effect when the controller is enabled.
|
|
*/
|
|
mchp_corespi_disable(spi);
|
|
|
|
mchp_corespi_write(spi, REG_FRAME_SIZE, bt);
|
|
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
control |= CONTROL_ENABLE;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
}
|
|
|
|
static void mchp_corespi_set_cs(struct spi_device *spi, bool disable)
|
|
{
|
|
u32 reg;
|
|
struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller);
|
|
|
|
reg = mchp_corespi_read(corespi, REG_SLAVE_SELECT);
|
|
reg &= ~BIT(spi_get_chipselect(spi, 0));
|
|
reg |= !disable << spi_get_chipselect(spi, 0);
|
|
|
|
mchp_corespi_write(corespi, REG_SLAVE_SELECT, reg);
|
|
}
|
|
|
|
static int mchp_corespi_setup(struct spi_device *spi)
|
|
{
|
|
struct mchp_corespi *corespi = spi_controller_get_devdata(spi->controller);
|
|
u32 reg;
|
|
|
|
/*
|
|
* Active high targets need to be specifically set to their inactive
|
|
* states during probe by adding them to the "control group" & thus
|
|
* driving their select line low.
|
|
*/
|
|
if (spi->mode & SPI_CS_HIGH) {
|
|
reg = mchp_corespi_read(corespi, REG_SLAVE_SELECT);
|
|
reg |= BIT(spi_get_chipselect(spi, 0));
|
|
mchp_corespi_write(corespi, REG_SLAVE_SELECT, reg);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mchp_corespi_init(struct spi_controller *host, struct mchp_corespi *spi)
|
|
{
|
|
unsigned long clk_hz;
|
|
u32 control = mchp_corespi_read(spi, REG_CONTROL);
|
|
|
|
control |= CONTROL_MASTER;
|
|
|
|
control &= ~CONTROL_MODE_MASK;
|
|
control |= MOTOROLA_MODE;
|
|
|
|
mchp_corespi_set_framesize(spi, DEFAULT_FRAMESIZE);
|
|
|
|
/* max. possible spi clock rate is the apb clock rate */
|
|
clk_hz = clk_get_rate(spi->clk);
|
|
host->max_speed_hz = clk_hz;
|
|
|
|
/*
|
|
* The controller must be configured so that it doesn't remove Chip
|
|
* Select until the entire message has been transferred, even if at
|
|
* some points TX FIFO becomes empty.
|
|
*
|
|
* BIGFIFO mode is also enabled, which sets the fifo depth to 32 frames
|
|
* for the 8 bit transfers that this driver uses.
|
|
*/
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
control |= CONTROL_SPS | CONTROL_BIGFIFO;
|
|
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
|
|
mchp_corespi_enable_ints(spi);
|
|
|
|
/*
|
|
* It is required to enable direct mode, otherwise control over the chip
|
|
* select is relinquished to the hardware. SSELOUT is enabled too so we
|
|
* can deal with active high targets.
|
|
*/
|
|
mchp_corespi_write(spi, REG_SLAVE_SELECT, SSELOUT | SSEL_DIRECT);
|
|
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
|
|
control &= ~CONTROL_RESET;
|
|
control |= CONTROL_ENABLE;
|
|
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
}
|
|
|
|
static inline void mchp_corespi_set_clk_gen(struct mchp_corespi *spi)
|
|
{
|
|
u32 control;
|
|
|
|
mchp_corespi_disable(spi);
|
|
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
if (spi->clk_mode)
|
|
control |= CONTROL_CLKMODE;
|
|
else
|
|
control &= ~CONTROL_CLKMODE;
|
|
|
|
mchp_corespi_write(spi, REG_CLK_GEN, spi->clk_gen);
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
mchp_corespi_write(spi, REG_CONTROL, control | CONTROL_ENABLE);
|
|
}
|
|
|
|
static inline void mchp_corespi_set_mode(struct mchp_corespi *spi, unsigned int mode)
|
|
{
|
|
u32 control, mode_val;
|
|
|
|
switch (mode & SPI_MODE_X_MASK) {
|
|
case SPI_MODE_0:
|
|
mode_val = 0;
|
|
break;
|
|
case SPI_MODE_1:
|
|
mode_val = CONTROL_SPH;
|
|
break;
|
|
case SPI_MODE_2:
|
|
mode_val = CONTROL_SPO;
|
|
break;
|
|
case SPI_MODE_3:
|
|
mode_val = CONTROL_SPH | CONTROL_SPO;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Disable the SPI controller. Writes to the frame size have
|
|
* no effect when the controller is enabled.
|
|
*/
|
|
mchp_corespi_disable(spi);
|
|
|
|
control = mchp_corespi_read(spi, REG_CONTROL);
|
|
control &= ~(SPI_MODE_X_MASK << MODE_X_MASK_SHIFT);
|
|
control |= mode_val;
|
|
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
|
|
control |= CONTROL_ENABLE;
|
|
mchp_corespi_write(spi, REG_CONTROL, control);
|
|
}
|
|
|
|
static irqreturn_t mchp_corespi_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct spi_controller *host = dev_id;
|
|
struct mchp_corespi *spi = spi_controller_get_devdata(host);
|
|
u32 intfield = mchp_corespi_read(spi, REG_MIS) & 0xf;
|
|
bool finalise = false;
|
|
|
|
/* Interrupt line may be shared and not for us at all */
|
|
if (intfield == 0)
|
|
return IRQ_NONE;
|
|
|
|
if (intfield & INT_TXDONE) {
|
|
mchp_corespi_write(spi, REG_INT_CLEAR, INT_TXDONE);
|
|
|
|
if (spi->rx_len)
|
|
mchp_corespi_read_fifo(spi);
|
|
|
|
if (spi->tx_len)
|
|
mchp_corespi_write_fifo(spi);
|
|
|
|
if (!spi->rx_len)
|
|
finalise = true;
|
|
}
|
|
|
|
if (intfield & INT_RXRDY)
|
|
mchp_corespi_write(spi, REG_INT_CLEAR, INT_RXRDY);
|
|
|
|
if (intfield & INT_RX_CHANNEL_OVERFLOW) {
|
|
mchp_corespi_write(spi, REG_INT_CLEAR, INT_RX_CHANNEL_OVERFLOW);
|
|
finalise = true;
|
|
dev_err(&host->dev,
|
|
"%s: RX OVERFLOW: rxlen: %d, txlen: %d\n", __func__,
|
|
spi->rx_len, spi->tx_len);
|
|
}
|
|
|
|
if (intfield & INT_TX_CHANNEL_UNDERRUN) {
|
|
mchp_corespi_write(spi, REG_INT_CLEAR, INT_TX_CHANNEL_UNDERRUN);
|
|
finalise = true;
|
|
dev_err(&host->dev,
|
|
"%s: TX UNDERFLOW: rxlen: %d, txlen: %d\n", __func__,
|
|
spi->rx_len, spi->tx_len);
|
|
}
|
|
|
|
if (finalise)
|
|
spi_finalize_current_transfer(host);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int mchp_corespi_calculate_clkgen(struct mchp_corespi *spi,
|
|
unsigned long target_hz)
|
|
{
|
|
unsigned long clk_hz, spi_hz, clk_gen;
|
|
|
|
clk_hz = clk_get_rate(spi->clk);
|
|
if (!clk_hz)
|
|
return -EINVAL;
|
|
spi_hz = min(target_hz, clk_hz);
|
|
|
|
/*
|
|
* There are two possible clock modes for the controller generated
|
|
* clock's division ratio:
|
|
* CLK_MODE = 0: 1 / (2^(CLK_GEN + 1)) where CLK_GEN = 0 to 15.
|
|
* CLK_MODE = 1: 1 / (2 * CLK_GEN + 1) where CLK_GEN = 0 to 255.
|
|
* First try mode 1, fall back to 0 and if we have tried both modes and
|
|
* we /still/ can't get a good setting, we then throw the toys out of
|
|
* the pram and give up
|
|
* clk_gen is the register name for the clock divider on MPFS.
|
|
*/
|
|
clk_gen = DIV_ROUND_UP(clk_hz, 2 * spi_hz) - 1;
|
|
if (clk_gen > CLK_GEN_MODE1_MAX || clk_gen <= CLK_GEN_MIN) {
|
|
clk_gen = DIV_ROUND_UP(clk_hz, spi_hz);
|
|
clk_gen = fls(clk_gen) - 1;
|
|
|
|
if (clk_gen > CLK_GEN_MODE0_MAX)
|
|
return -EINVAL;
|
|
|
|
spi->clk_mode = 0;
|
|
} else {
|
|
spi->clk_mode = 1;
|
|
}
|
|
|
|
spi->clk_gen = clk_gen;
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_corespi_transfer_one(struct spi_controller *host,
|
|
struct spi_device *spi_dev,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
struct mchp_corespi *spi = spi_controller_get_devdata(host);
|
|
int ret;
|
|
|
|
ret = mchp_corespi_calculate_clkgen(spi, (unsigned long)xfer->speed_hz);
|
|
if (ret) {
|
|
dev_err(&host->dev, "failed to set clk_gen for target %u Hz\n", xfer->speed_hz);
|
|
return ret;
|
|
}
|
|
|
|
mchp_corespi_set_clk_gen(spi);
|
|
|
|
spi->tx_buf = xfer->tx_buf;
|
|
spi->rx_buf = xfer->rx_buf;
|
|
spi->tx_len = xfer->len;
|
|
spi->rx_len = xfer->len;
|
|
spi->pending = 0;
|
|
|
|
mchp_corespi_set_xfer_size(spi, (spi->tx_len > FIFO_DEPTH)
|
|
? FIFO_DEPTH : spi->tx_len);
|
|
|
|
if (spi->tx_len)
|
|
mchp_corespi_write_fifo(spi);
|
|
return 1;
|
|
}
|
|
|
|
static int mchp_corespi_prepare_message(struct spi_controller *host,
|
|
struct spi_message *msg)
|
|
{
|
|
struct spi_device *spi_dev = msg->spi;
|
|
struct mchp_corespi *spi = spi_controller_get_devdata(host);
|
|
|
|
mchp_corespi_set_framesize(spi, DEFAULT_FRAMESIZE);
|
|
mchp_corespi_set_mode(spi, spi_dev->mode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mchp_corespi_probe(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *host;
|
|
struct mchp_corespi *spi;
|
|
struct resource *res;
|
|
u32 num_cs;
|
|
int ret = 0;
|
|
|
|
host = devm_spi_alloc_host(&pdev->dev, sizeof(*spi));
|
|
if (!host)
|
|
return dev_err_probe(&pdev->dev, -ENOMEM,
|
|
"unable to allocate host for SPI controller\n");
|
|
|
|
platform_set_drvdata(pdev, host);
|
|
|
|
if (of_property_read_u32(pdev->dev.of_node, "num-cs", &num_cs))
|
|
num_cs = MAX_CS;
|
|
|
|
host->num_chipselect = num_cs;
|
|
host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
|
|
host->setup = mchp_corespi_setup;
|
|
host->bits_per_word_mask = SPI_BPW_MASK(8);
|
|
host->transfer_one = mchp_corespi_transfer_one;
|
|
host->prepare_message = mchp_corespi_prepare_message;
|
|
host->set_cs = mchp_corespi_set_cs;
|
|
host->dev.of_node = pdev->dev.of_node;
|
|
|
|
spi = spi_controller_get_devdata(host);
|
|
|
|
spi->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
|
if (IS_ERR(spi->regs))
|
|
return PTR_ERR(spi->regs);
|
|
|
|
spi->irq = platform_get_irq(pdev, 0);
|
|
if (spi->irq < 0)
|
|
return spi->irq;
|
|
|
|
ret = devm_request_irq(&pdev->dev, spi->irq, mchp_corespi_interrupt,
|
|
IRQF_SHARED, dev_name(&pdev->dev), host);
|
|
if (ret)
|
|
return dev_err_probe(&pdev->dev, ret,
|
|
"could not request irq\n");
|
|
|
|
spi->clk = devm_clk_get_enabled(&pdev->dev, NULL);
|
|
if (IS_ERR(spi->clk))
|
|
return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk),
|
|
"could not get clk\n");
|
|
|
|
mchp_corespi_init(host, spi);
|
|
|
|
ret = devm_spi_register_controller(&pdev->dev, host);
|
|
if (ret) {
|
|
mchp_corespi_disable(spi);
|
|
return dev_err_probe(&pdev->dev, ret,
|
|
"unable to register host for SPI controller\n");
|
|
}
|
|
|
|
dev_info(&pdev->dev, "Registered SPI controller %d\n", host->bus_num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mchp_corespi_remove(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *host = platform_get_drvdata(pdev);
|
|
struct mchp_corespi *spi = spi_controller_get_devdata(host);
|
|
|
|
mchp_corespi_disable_ints(spi);
|
|
mchp_corespi_disable(spi);
|
|
}
|
|
|
|
#define MICROCHIP_SPI_PM_OPS (NULL)
|
|
|
|
/*
|
|
* Platform driver data structure
|
|
*/
|
|
|
|
#if defined(CONFIG_OF)
|
|
static const struct of_device_id mchp_corespi_dt_ids[] = {
|
|
{ .compatible = "microchip,mpfs-spi" },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mchp_corespi_dt_ids);
|
|
#endif
|
|
|
|
static struct platform_driver mchp_corespi_driver = {
|
|
.probe = mchp_corespi_probe,
|
|
.driver = {
|
|
.name = "microchip-corespi",
|
|
.pm = MICROCHIP_SPI_PM_OPS,
|
|
.of_match_table = of_match_ptr(mchp_corespi_dt_ids),
|
|
},
|
|
.remove_new = mchp_corespi_remove,
|
|
};
|
|
module_platform_driver(mchp_corespi_driver);
|
|
MODULE_DESCRIPTION("Microchip coreSPI SPI controller driver");
|
|
MODULE_AUTHOR("Daire McNamara <daire.mcnamara@microchip.com>");
|
|
MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>");
|
|
MODULE_LICENSE("GPL");
|