Last active
August 26, 2017 20:34
-
-
Save snegovick/a81575130bd5f789027e963a7050798a to your computer and use it in GitHub Desktop.
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
#include <linux/platform_device.h> | |
#include <linux/of_device.h> | |
#include <linux/of_gpio.h> | |
#include <linux/spi/spi.h> | |
#include <linux/module.h> | |
#include <linux/delay.h> | |
#include <linux/clk.h> | |
#include <linux/io.h> | |
#include <linux/of.h> | |
#define LPC2468_SPI_MAX_CLOCK_HZ (9000000) | |
#define SPCR_OFFT 0x00 | |
#define SPSR_OFFT 0x04 | |
#define SPDR_OFFT 0x08 | |
#define CCR_OFFT 0x0c | |
#define SPCR_CPHA (1<<3) | |
#define SPCR_CPOL (1<<4) | |
#define SPCR_MSTR (1<<5) | |
#define SPCR_LSBF (1<<6) | |
#define SPSR_ABRT (1<<3) | |
#define SPSR_MODF (1<<4) | |
#define SPSR_POVR (1<<5) | |
#define SPSR_WCOL (1<<6) | |
#define SPSR_SPIF (1<<7) | |
#define SPSR_ERRORS (SPSR_ABRT | SPSR_MODF | SPSR_POVR | SPSR_WCOL) | |
struct lpc2468_spi { | |
void __iomem *register_base; | |
struct clk *clk; | |
}; | |
static int lpc2468_spi_wait_ready(struct lpc2468_spi *p) | |
{ | |
unsigned int loops = 0; | |
u16 spsr = 0; | |
do { | |
if (loops++) | |
__delay(500); | |
spsr = readw(p->register_base + SPSR_OFFT); | |
if (spsr & (~SPSR_ERRORS)) { | |
return -EIO; | |
} | |
} while (!(spsr & SPSR_SPIF)); | |
return 0; | |
} | |
static int lpc2468_spi_transfer_one(struct spi_master *master, | |
struct spi_device *spi, | |
struct spi_transfer *xfer) | |
{ | |
struct lpc2468_spi *p = spi_master_get_devdata(master); | |
int mode = spi->mode; | |
unsigned int clkdiv = DIV_ROUND_UP(clk_get_rate(p->clk), xfer->speed_hz); | |
u16 cpha = ((mode & SPI_CPHA) ? SPCR_CPHA : 0); | |
u16 cpol = ((mode & SPI_CPOL) ? SPCR_CPOL : 0); | |
u16 lsbf = ((mode & SPI_LSB_FIRST) ? SPCR_LSBF : 0); | |
u16 spcr = cpha | cpol | lsbf | SPCR_MSTR; | |
u16 ccr = clkdiv; | |
const u8 *tx_buf; | |
u8 *rx_buf; | |
int len; | |
u16 tx; | |
u16 rx; | |
int ret; | |
int status = 0; | |
writew(ccr, p->register_base + CCR_OFFT); | |
writew(spcr, p->register_base + SPCR_OFFT); | |
tx_buf = xfer->tx_buf; | |
rx_buf = xfer->rx_buf; | |
len = xfer->len; | |
for (; len>0; len--) | |
{ | |
tx = (*tx_buf++)&0xff; | |
writew(tx, p->register_base + SPDR_OFFT); | |
ret = lpc2468_spi_wait_ready(p); | |
if (ret < 0) { | |
status = -EIO; | |
goto err; | |
} | |
rx = readw(p->register_base + SPDR_OFFT); | |
if (rx_buf) { | |
*rx_buf++ = rx; | |
} | |
} | |
ret = lpc2468_spi_wait_ready(p); | |
return 0; | |
err: | |
return status; | |
} | |
static int lpc2468_spi_probe(struct platform_device *pdev) | |
{ | |
struct resource *mem; | |
void __iomem *reg_base; | |
struct spi_master *master; | |
struct lpc2468_spi *p; | |
int ret = 0; | |
int err = -ENOENT; | |
int num_cs = 4; | |
master = spi_alloc_master(&pdev->dev, sizeof(struct lpc2468_spi)); | |
if (!master) | |
return -ENOMEM; | |
p = spi_master_get_devdata(master); | |
platform_set_drvdata(pdev, master); | |
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
reg_base = devm_ioremap_resource(&pdev->dev, mem); | |
if (IS_ERR(reg_base)) { | |
err = PTR_ERR(reg_base); | |
goto fail; | |
} | |
p->register_base = reg_base; | |
p->clk = devm_clk_get(&pdev->dev, NULL); | |
if (IS_ERR(p->clk)) | |
goto fail; | |
ret = clk_prepare_enable(p->clk); | |
if (ret) | |
goto fail; | |
device_property_read_u32(&pdev->dev, "num-cs", &num_cs); | |
master->num_chipselect = num_cs; | |
if (pdev->dev.of_node) { | |
int i; | |
for (i = 0; i < master->num_chipselect; i++) { | |
int cs_gpio = of_get_named_gpio(pdev->dev.of_node, | |
"cs-gpios", i); | |
if (cs_gpio == -EPROBE_DEFER) { | |
ret = cs_gpio; | |
goto fail_w_clk; | |
} | |
if (gpio_is_valid(cs_gpio)) { | |
ret = devm_gpio_request(&pdev->dev, cs_gpio, | |
dev_name(&pdev->dev)); | |
if (ret) | |
goto fail_w_clk; | |
} | |
} | |
} | |
master->mode_bits = SPI_CPHA | | |
SPI_CPOL | | |
SPI_CS_HIGH | | |
SPI_LSB_FIRST | | |
SPI_3WIRE; | |
master->transfer_one = lpc2468_spi_transfer_one; | |
master->bits_per_word_mask = SPI_BPW_MASK(8); | |
master->max_speed_hz = LPC2468_SPI_MAX_CLOCK_HZ; | |
master->dev.of_node = pdev->dev.of_node; | |
err = devm_spi_register_master(&pdev->dev, master); | |
if (err) { | |
dev_err(&pdev->dev, "register master failed: %d\n", err); | |
goto fail_w_clk; | |
} | |
dev_info(&pdev->dev, "LPC2468 SPI bus driver\n"); | |
return 0; | |
fail_w_clk: | |
clk_disable_unprepare(p->clk); | |
fail: | |
spi_master_put(master); | |
return err; | |
} | |
static int lpc2468_spi_remove(struct platform_device *pdev) | |
{ | |
struct spi_master *master; | |
struct lpc2468_spi *p; | |
master = platform_get_drvdata(pdev); | |
p = spi_master_get_devdata(master); | |
clk_disable_unprepare(p->clk); | |
spi_master_put(master); | |
return 0; | |
} | |
static const struct of_device_id lpc2468_spi_match[] = { | |
{ .compatible = "unknown,lpc2468-spi", }, | |
{}, | |
}; | |
MODULE_DEVICE_TABLE(of, lpc2468_spi_match); | |
static struct platform_driver lpc2468_spi_driver = { | |
.driver = { | |
.name = "spi-lpc2468", | |
.of_match_table = lpc2468_spi_match, | |
}, | |
.probe = lpc2468_spi_probe, | |
.remove = lpc2468_spi_remove, | |
}; | |
module_platform_driver(lpc2468_spi_driver); | |
MODULE_DESCRIPTION("LPC2468 SPI bus driver"); | |
MODULE_AUTHOR("Konstantin Kirik"); | |
MODULE_LICENSE("GPL"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment