forked from torvalds/linux
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
spi: loongson: add bus driver for the loongson spi controller
This bus driver supports the Loongson spi hardware controller in the Loongson platforms and supports to use DTS and PCI framework to register spi device resources. Signed-off-by: Yinbo Zhu <zhuyinbo@loongson.cn>
- Loading branch information
1 parent
0cb7328
commit 3742622
Showing
7 changed files
with
536 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
// SPDX-License-Identifier: GPL-2.0+ | ||
// Loongson SPI Support | ||
// Copyright (C) 2023 Loongson Technology Corporation Limited | ||
|
||
#include <linux/init.h> | ||
#include <linux/module.h> | ||
#include <linux/kernel.h> | ||
#include <linux/interrupt.h> | ||
#include <linux/delay.h> | ||
#include <linux/err.h> | ||
#include <linux/spi/spi.h> | ||
#include <linux/clk.h> | ||
|
||
#include "spi-loongson.h" | ||
|
||
static inline void loongson_spi_write_reg(struct loongson_spi *spi, unsigned char reg, | ||
unsigned char data) | ||
{ | ||
writeb(data, spi->base + reg); | ||
} | ||
|
||
static inline char loongson_spi_read_reg(struct loongson_spi *spi, unsigned char reg) | ||
{ | ||
return readb(spi->base + reg); | ||
} | ||
|
||
static void loongson_spi_set_cs(struct spi_device *spi, bool val) | ||
{ | ||
int cs; | ||
struct loongson_spi *loongson_spi = spi_master_get_devdata(spi->master); | ||
|
||
if (loongson_spi->mode & SPI_NO_CS) | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, 0); | ||
else { | ||
cs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG) | ||
& ~(0x11 << spi->chip_select); | ||
loongson_spi_write_reg(loongson_spi, | ||
LOONGSON_SPI_SFCS_REG, | ||
(val ? (0x11 << spi->chip_select) : | ||
(0x1 << spi->chip_select)) | cs); | ||
} | ||
} | ||
|
||
static int loongson_spi_update_state(struct loongson_spi *loongson_spi, | ||
struct spi_device *spi, struct spi_transfer *t) | ||
{ | ||
unsigned int hz; | ||
unsigned int div, div_tmp; | ||
unsigned int bit; | ||
unsigned char val; | ||
const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11}; | ||
|
||
if (t) | ||
hz = t->speed_hz; | ||
|
||
if ((hz && loongson_spi->hz != hz) || | ||
((spi->mode ^ loongson_spi->mode) & (SPI_CPOL | SPI_CPHA))) { | ||
div = DIV_ROUND_UP_ULL(loongson_spi->clk_rate, hz); | ||
if (div < 2) | ||
div = 2; | ||
if (div > 4096) | ||
div = 4096; | ||
|
||
bit = fls(div) - 1; | ||
if ((1<<bit) == div) | ||
bit--; | ||
div_tmp = rdiv[bit]; | ||
dev_dbg(&spi->dev, "clk_rate = %llu hz = %d div_tmp = %d bit = %d\n", | ||
loongson_spi->clk_rate, hz, div_tmp, bit); | ||
|
||
loongson_spi->hz = hz; | ||
loongson_spi->spcr = div_tmp & 3; | ||
loongson_spi->sper = (div_tmp >> 2) & 3; | ||
val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG); | ||
val &= ~0xc; | ||
if (spi->mode & SPI_CPOL) | ||
val |= 8; | ||
if (spi->mode & SPI_CPHA) | ||
val |= 4; | ||
|
||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, (val & ~3) | | ||
loongson_spi->spcr); | ||
val = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG); | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, (val & ~3) | | ||
loongson_spi->sper); | ||
loongson_spi->mode &= SPI_NO_CS; | ||
loongson_spi->mode |= spi->mode; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int loongson_spi_setup(struct spi_device *spi) | ||
{ | ||
struct loongson_spi *loongson_spi; | ||
|
||
loongson_spi = spi_master_get_devdata(spi->master); | ||
if (spi->bits_per_word % 8) | ||
return -EINVAL; | ||
|
||
if (spi->chip_select >= spi->master->num_chipselect) | ||
return -EINVAL; | ||
|
||
loongson_spi->hz = 0; | ||
loongson_spi->mode &= SPI_NO_CS; | ||
loongson_spi_set_cs(spi, 1); | ||
|
||
return 0; | ||
} | ||
|
||
static int loongson_spi_write_read_8bit(struct spi_device *spi, const u8 **tx_buf, | ||
u8 **rx_buf, unsigned int num) | ||
{ | ||
struct loongson_spi *loongson_spi = spi_master_get_devdata(spi->master); | ||
unsigned long timeout = jiffies + SPI_COMPLETION_TIMEOUT; | ||
|
||
if (tx_buf && *tx_buf) { | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, *((*tx_buf)++)); | ||
while ((loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG) & 0x1) == 1 && | ||
time_after(timeout, jiffies)) | ||
cpu_relax(); | ||
} else { | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_FIFO_REG, 0); | ||
while ((loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG) & 0x1) == 1 && | ||
time_after(timeout, jiffies)) | ||
cpu_relax(); | ||
} | ||
|
||
if (rx_buf && *rx_buf) | ||
*(*rx_buf)++ = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG); | ||
else | ||
loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_FIFO_REG); | ||
|
||
return 0; | ||
} | ||
|
||
static unsigned int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer) | ||
{ | ||
unsigned int count; | ||
const u8 *tx = xfer->tx_buf; | ||
u8 *rx = xfer->rx_buf; | ||
|
||
count = xfer->len; | ||
|
||
do { | ||
if (loongson_spi_write_read_8bit(spi, &tx, &rx, count) < 0) | ||
goto out; | ||
count--; | ||
} while (count); | ||
|
||
out: | ||
return xfer->len - count; | ||
} | ||
|
||
static int loongson_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *m) | ||
{ | ||
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr); | ||
|
||
loongson_spi->para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG); | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para & ~1); | ||
|
||
return 0; | ||
} | ||
|
||
static int loongson_spi_transfer_one(struct spi_controller *ctrl, struct spi_device *spi, | ||
struct spi_transfer *xfer) | ||
{ | ||
struct loongson_spi *loongson_spi = spi_master_get_devdata(spi->master); | ||
|
||
loongson_spi_update_state(loongson_spi, spi, xfer); | ||
if (xfer->len) | ||
xfer->len = loongson_spi_write_read(spi, xfer); | ||
|
||
return 0; | ||
} | ||
|
||
static int loongson_spi_unprepare_message(struct spi_controller *ctrl, struct spi_message *m) | ||
{ | ||
struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctrl); | ||
|
||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para); | ||
|
||
return 0; | ||
} | ||
|
||
static void loongson_spi_reginit(struct loongson_spi *loongson_spi_dev) | ||
{ | ||
unsigned char val; | ||
|
||
val = loongson_spi_read_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG); | ||
val &= ~LOONGSON_SPI_SPCR_SPE; | ||
loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG, val); | ||
|
||
loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPSR_REG, | ||
(LOONGSON_SPI_SPSR_SPIF | LOONGSON_SPI_SPSR_WCOL)); | ||
|
||
val = loongson_spi_read_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG); | ||
val |= LOONGSON_SPI_SPCR_SPE; | ||
loongson_spi_write_reg(loongson_spi_dev, LOONGSON_SPI_SPCR_REG, val); | ||
} | ||
|
||
int loongson_spi_init_master(struct device *dev, struct resource *res) | ||
{ | ||
struct spi_master *master; | ||
struct loongson_spi *spi; | ||
struct clk *clk; | ||
int ret; | ||
|
||
master = spi_alloc_master(dev, sizeof(struct loongson_spi)); | ||
if (master == NULL) { | ||
dev_dbg(dev, "master allocation failed\n"); | ||
return -ENOMEM; | ||
} | ||
|
||
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; | ||
master->setup = loongson_spi_setup; | ||
master->prepare_message = loongson_spi_prepare_message; | ||
master->transfer_one = loongson_spi_transfer_one; | ||
master->unprepare_message = loongson_spi_unprepare_message; | ||
master->set_cs = loongson_spi_set_cs; | ||
master->num_chipselect = 4; | ||
master->dev.of_node = of_node_get(dev->of_node); | ||
dev_set_drvdata(dev, master); | ||
|
||
spi = spi_master_get_devdata(master); | ||
|
||
spi->master = master; | ||
|
||
spi->base = devm_ioremap(dev, res->start, resource_size(res)); | ||
if (spi->base == NULL) { | ||
dev_err(dev, "cannot map io\n"); | ||
ret = -ENXIO; | ||
goto free_master; | ||
} | ||
|
||
clk = devm_clk_get(dev, NULL); | ||
if (!IS_ERR(clk)) | ||
spi->clk_rate = clk_get_rate(clk); | ||
|
||
loongson_spi_reginit(spi); | ||
|
||
spi->mode = 0; | ||
if (of_get_property(dev->of_node, "spi-nocs", NULL)) | ||
spi->mode |= SPI_NO_CS; | ||
|
||
ret = spi_register_master(master); | ||
if (ret < 0) | ||
goto free_master; | ||
|
||
return ret; | ||
|
||
free_master: | ||
kfree(master); | ||
spi_master_put(master); | ||
|
||
return ret; | ||
} | ||
EXPORT_SYMBOL_GPL(loongson_spi_init_master); | ||
|
||
static int __maybe_unused loongson_spi_suspend(struct device *dev) | ||
{ | ||
struct loongson_spi *loongson_spi; | ||
struct spi_master *master; | ||
|
||
master = dev_get_drvdata(dev); | ||
loongson_spi = spi_master_get_devdata(master); | ||
|
||
loongson_spi->spcr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPCR_REG); | ||
loongson_spi->sper = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPER_REG); | ||
loongson_spi->spsr = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SPSR_REG); | ||
loongson_spi->para = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_PARA_REG); | ||
loongson_spi->sfcs = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_SFCS_REG); | ||
loongson_spi->timi = loongson_spi_read_reg(loongson_spi, LOONGSON_SPI_TIMI_REG); | ||
|
||
return 0; | ||
} | ||
|
||
static int __maybe_unused loongson_spi_resume(struct device *dev) | ||
{ | ||
struct loongson_spi *loongson_spi; | ||
struct spi_master *master; | ||
|
||
master = dev_get_drvdata(dev); | ||
loongson_spi = spi_master_get_devdata(master); | ||
|
||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPCR_REG, loongson_spi->spcr); | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPER_REG, loongson_spi->sper); | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SPSR_REG, loongson_spi->spsr); | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_PARA_REG, loongson_spi->para); | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_SFCS_REG, loongson_spi->sfcs); | ||
loongson_spi_write_reg(loongson_spi, LOONGSON_SPI_TIMI_REG, loongson_spi->timi); | ||
|
||
return 0; | ||
} | ||
|
||
const struct dev_pm_ops loongson_spi_dev_pm_ops = { | ||
.suspend = loongson_spi_suspend, | ||
.resume = loongson_spi_resume, | ||
}; | ||
|
||
MODULE_DESCRIPTION("Loongson spi core driver"); | ||
MODULE_LICENSE("GPL"); |
Oops, something went wrong.