|
@@ -0,0 +1,136 @@
|
|
|
|
+/*
|
|
|
|
+ * Ethernet driver for the WIZnet W5100 chip.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2016 Akinobu Mita <akinobu.mita@gmail.com>
|
|
|
|
+ *
|
|
|
|
+ * Licensed under the GPL-2 or later.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/delay.h>
|
|
|
|
+#include <linux/netdevice.h>
|
|
|
|
+#include <linux/spi/spi.h>
|
|
|
|
+
|
|
|
|
+#include "w5100.h"
|
|
|
|
+
|
|
|
|
+#define W5100_SPI_WRITE_OPCODE 0xf0
|
|
|
|
+#define W5100_SPI_READ_OPCODE 0x0f
|
|
|
|
+
|
|
|
|
+static int w5100_spi_read(struct net_device *ndev, u16 addr)
|
|
|
|
+{
|
|
|
|
+ struct spi_device *spi = to_spi_device(ndev->dev.parent);
|
|
|
|
+ u8 cmd[3] = { W5100_SPI_READ_OPCODE, addr >> 8, addr & 0xff };
|
|
|
|
+ u8 data;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = spi_write_then_read(spi, cmd, sizeof(cmd), &data, 1);
|
|
|
|
+
|
|
|
|
+ return ret ? ret : data;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int w5100_spi_write(struct net_device *ndev, u16 addr, u8 data)
|
|
|
|
+{
|
|
|
|
+ struct spi_device *spi = to_spi_device(ndev->dev.parent);
|
|
|
|
+ u8 cmd[4] = { W5100_SPI_WRITE_OPCODE, addr >> 8, addr & 0xff, data};
|
|
|
|
+
|
|
|
|
+ return spi_write_then_read(spi, cmd, sizeof(cmd), NULL, 0);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int w5100_spi_read16(struct net_device *ndev, u16 addr)
|
|
|
|
+{
|
|
|
|
+ u16 data;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = w5100_spi_read(ndev, addr);
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ return ret;
|
|
|
|
+ data = ret << 8;
|
|
|
|
+ ret = w5100_spi_read(ndev, addr + 1);
|
|
|
|
+
|
|
|
|
+ return ret < 0 ? ret : data | ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int w5100_spi_write16(struct net_device *ndev, u16 addr, u16 data)
|
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = w5100_spi_write(ndev, addr, data >> 8);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ return w5100_spi_write(ndev, addr + 1, data & 0xff);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int w5100_spi_readbulk(struct net_device *ndev, u16 addr, u8 *buf,
|
|
|
|
+ int len)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
|
+ int ret = w5100_spi_read(ndev, addr + i);
|
|
|
|
+
|
|
|
|
+ if (ret < 0)
|
|
|
|
+ return ret;
|
|
|
|
+ buf[i] = ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int w5100_spi_writebulk(struct net_device *ndev, u16 addr, const u8 *buf,
|
|
|
|
+ int len)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < len; i++) {
|
|
|
|
+ int ret = w5100_spi_write(ndev, addr + i, buf[i]);
|
|
|
|
+
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct w5100_ops w5100_spi_ops = {
|
|
|
|
+ .may_sleep = true,
|
|
|
|
+ .read = w5100_spi_read,
|
|
|
|
+ .write = w5100_spi_write,
|
|
|
|
+ .read16 = w5100_spi_read16,
|
|
|
|
+ .write16 = w5100_spi_write16,
|
|
|
|
+ .readbulk = w5100_spi_readbulk,
|
|
|
|
+ .writebulk = w5100_spi_writebulk,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int w5100_spi_probe(struct spi_device *spi)
|
|
|
|
+{
|
|
|
|
+ return w5100_probe(&spi->dev, &w5100_spi_ops, 0, NULL, spi->irq,
|
|
|
|
+ -EINVAL);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int w5100_spi_remove(struct spi_device *spi)
|
|
|
|
+{
|
|
|
|
+ return w5100_remove(&spi->dev);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct spi_device_id w5100_spi_ids[] = {
|
|
|
|
+ { "w5100", 0 },
|
|
|
|
+ {}
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(spi, w5100_spi_ids);
|
|
|
|
+
|
|
|
|
+static struct spi_driver w5100_spi_driver = {
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "w5100",
|
|
|
|
+ .pm = &w5100_pm_ops,
|
|
|
|
+ },
|
|
|
|
+ .probe = w5100_spi_probe,
|
|
|
|
+ .remove = w5100_spi_remove,
|
|
|
|
+ .id_table = w5100_spi_ids,
|
|
|
|
+};
|
|
|
|
+module_spi_driver(w5100_spi_driver);
|
|
|
|
+
|
|
|
|
+MODULE_DESCRIPTION("WIZnet W5100 Ethernet driver for SPI mode");
|
|
|
|
+MODULE_AUTHOR("Akinobu Mita <akinobu.mita@gmail.com>");
|
|
|
|
+MODULE_LICENSE("GPL");
|