195711cd5SPawel Dembicki // SPDX-License-Identifier: GPL-2.0
295711cd5SPawel Dembicki /* DSA driver for:
395711cd5SPawel Dembicki * Vitesse VSC7385 SparX-G5 5+1-port Integrated Gigabit Ethernet Switch
495711cd5SPawel Dembicki * Vitesse VSC7388 SparX-G8 8-port Integrated Gigabit Ethernet Switch
595711cd5SPawel Dembicki * Vitesse VSC7395 SparX-G5e 5+1-port Integrated Gigabit Ethernet Switch
695711cd5SPawel Dembicki * Vitesse VSC7398 SparX-G8e 8-port Integrated Gigabit Ethernet Switch
795711cd5SPawel Dembicki *
895711cd5SPawel Dembicki * This driver takes control of the switch chip over SPI and
995711cd5SPawel Dembicki * configures it to route packages around when connected to a CPU port.
1095711cd5SPawel Dembicki *
1195711cd5SPawel Dembicki * Copyright (C) 2018 Linus Wallej <linus.walleij@linaro.org>
1295711cd5SPawel Dembicki * Includes portions of code from the firmware uploader by:
1395711cd5SPawel Dembicki * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
1495711cd5SPawel Dembicki */
1595711cd5SPawel Dembicki #include <linux/kernel.h>
1695711cd5SPawel Dembicki #include <linux/module.h>
1795711cd5SPawel Dembicki #include <linux/of.h>
1895711cd5SPawel Dembicki #include <linux/spi/spi.h>
1995711cd5SPawel Dembicki
2095711cd5SPawel Dembicki #include "vitesse-vsc73xx.h"
2195711cd5SPawel Dembicki
2295711cd5SPawel Dembicki #define VSC73XX_CMD_SPI_MODE_READ 0
2395711cd5SPawel Dembicki #define VSC73XX_CMD_SPI_MODE_WRITE 1
2495711cd5SPawel Dembicki #define VSC73XX_CMD_SPI_MODE_SHIFT 4
2595711cd5SPawel Dembicki #define VSC73XX_CMD_SPI_BLOCK_SHIFT 5
2695711cd5SPawel Dembicki #define VSC73XX_CMD_SPI_BLOCK_MASK 0x7
2795711cd5SPawel Dembicki #define VSC73XX_CMD_SPI_SUBBLOCK_MASK 0xf
2895711cd5SPawel Dembicki
295bd6ff0cSAndrew Lunn /*
3095711cd5SPawel Dembicki * struct vsc73xx_spi - VSC73xx SPI state container
3195711cd5SPawel Dembicki */
3295711cd5SPawel Dembicki struct vsc73xx_spi {
3395711cd5SPawel Dembicki struct spi_device *spi;
3495711cd5SPawel Dembicki struct mutex lock; /* Protects SPI traffic */
3595711cd5SPawel Dembicki struct vsc73xx vsc;
3695711cd5SPawel Dembicki };
3795711cd5SPawel Dembicki
3895711cd5SPawel Dembicki static const struct vsc73xx_ops vsc73xx_spi_ops;
3995711cd5SPawel Dembicki
vsc73xx_make_addr(u8 mode,u8 block,u8 subblock)4095711cd5SPawel Dembicki static u8 vsc73xx_make_addr(u8 mode, u8 block, u8 subblock)
4195711cd5SPawel Dembicki {
4295711cd5SPawel Dembicki u8 ret;
4395711cd5SPawel Dembicki
4495711cd5SPawel Dembicki ret =
4595711cd5SPawel Dembicki (block & VSC73XX_CMD_SPI_BLOCK_MASK) << VSC73XX_CMD_SPI_BLOCK_SHIFT;
4695711cd5SPawel Dembicki ret |= (mode & 1) << VSC73XX_CMD_SPI_MODE_SHIFT;
4795711cd5SPawel Dembicki ret |= subblock & VSC73XX_CMD_SPI_SUBBLOCK_MASK;
4895711cd5SPawel Dembicki
4995711cd5SPawel Dembicki return ret;
5095711cd5SPawel Dembicki }
5195711cd5SPawel Dembicki
vsc73xx_spi_read(struct vsc73xx * vsc,u8 block,u8 subblock,u8 reg,u32 * val)5295711cd5SPawel Dembicki static int vsc73xx_spi_read(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg,
5395711cd5SPawel Dembicki u32 *val)
5495711cd5SPawel Dembicki {
5595711cd5SPawel Dembicki struct vsc73xx_spi *vsc_spi = vsc->priv;
5695711cd5SPawel Dembicki struct spi_transfer t[2];
5795711cd5SPawel Dembicki struct spi_message m;
5895711cd5SPawel Dembicki u8 cmd[4];
5995711cd5SPawel Dembicki u8 buf[4];
6095711cd5SPawel Dembicki int ret;
6195711cd5SPawel Dembicki
6295711cd5SPawel Dembicki if (!vsc73xx_is_addr_valid(block, subblock))
6395711cd5SPawel Dembicki return -EINVAL;
6495711cd5SPawel Dembicki
6595711cd5SPawel Dembicki spi_message_init(&m);
6695711cd5SPawel Dembicki
6795711cd5SPawel Dembicki memset(&t, 0, sizeof(t));
6895711cd5SPawel Dembicki
6995711cd5SPawel Dembicki t[0].tx_buf = cmd;
7095711cd5SPawel Dembicki t[0].len = sizeof(cmd);
7195711cd5SPawel Dembicki spi_message_add_tail(&t[0], &m);
7295711cd5SPawel Dembicki
7395711cd5SPawel Dembicki t[1].rx_buf = buf;
7495711cd5SPawel Dembicki t[1].len = sizeof(buf);
7595711cd5SPawel Dembicki spi_message_add_tail(&t[1], &m);
7695711cd5SPawel Dembicki
7795711cd5SPawel Dembicki cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_SPI_MODE_READ, block, subblock);
7895711cd5SPawel Dembicki cmd[1] = reg;
7995711cd5SPawel Dembicki cmd[2] = 0;
8095711cd5SPawel Dembicki cmd[3] = 0;
8195711cd5SPawel Dembicki
8295711cd5SPawel Dembicki mutex_lock(&vsc_spi->lock);
8395711cd5SPawel Dembicki ret = spi_sync(vsc_spi->spi, &m);
8495711cd5SPawel Dembicki mutex_unlock(&vsc_spi->lock);
8595711cd5SPawel Dembicki
8695711cd5SPawel Dembicki if (ret)
8795711cd5SPawel Dembicki return ret;
8895711cd5SPawel Dembicki
8995711cd5SPawel Dembicki *val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
9095711cd5SPawel Dembicki
9195711cd5SPawel Dembicki return 0;
9295711cd5SPawel Dembicki }
9395711cd5SPawel Dembicki
vsc73xx_spi_write(struct vsc73xx * vsc,u8 block,u8 subblock,u8 reg,u32 val)9495711cd5SPawel Dembicki static int vsc73xx_spi_write(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg,
9595711cd5SPawel Dembicki u32 val)
9695711cd5SPawel Dembicki {
9795711cd5SPawel Dembicki struct vsc73xx_spi *vsc_spi = vsc->priv;
9895711cd5SPawel Dembicki struct spi_transfer t[2];
9995711cd5SPawel Dembicki struct spi_message m;
10095711cd5SPawel Dembicki u8 cmd[2];
10195711cd5SPawel Dembicki u8 buf[4];
10295711cd5SPawel Dembicki int ret;
10395711cd5SPawel Dembicki
10495711cd5SPawel Dembicki if (!vsc73xx_is_addr_valid(block, subblock))
10595711cd5SPawel Dembicki return -EINVAL;
10695711cd5SPawel Dembicki
10795711cd5SPawel Dembicki spi_message_init(&m);
10895711cd5SPawel Dembicki
10995711cd5SPawel Dembicki memset(&t, 0, sizeof(t));
11095711cd5SPawel Dembicki
11195711cd5SPawel Dembicki t[0].tx_buf = cmd;
11295711cd5SPawel Dembicki t[0].len = sizeof(cmd);
11395711cd5SPawel Dembicki spi_message_add_tail(&t[0], &m);
11495711cd5SPawel Dembicki
11595711cd5SPawel Dembicki t[1].tx_buf = buf;
11695711cd5SPawel Dembicki t[1].len = sizeof(buf);
11795711cd5SPawel Dembicki spi_message_add_tail(&t[1], &m);
11895711cd5SPawel Dembicki
11995711cd5SPawel Dembicki cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_SPI_MODE_WRITE, block, subblock);
12095711cd5SPawel Dembicki cmd[1] = reg;
12195711cd5SPawel Dembicki
12295711cd5SPawel Dembicki buf[0] = (val >> 24) & 0xff;
12395711cd5SPawel Dembicki buf[1] = (val >> 16) & 0xff;
12495711cd5SPawel Dembicki buf[2] = (val >> 8) & 0xff;
12595711cd5SPawel Dembicki buf[3] = val & 0xff;
12695711cd5SPawel Dembicki
12795711cd5SPawel Dembicki mutex_lock(&vsc_spi->lock);
12895711cd5SPawel Dembicki ret = spi_sync(vsc_spi->spi, &m);
12995711cd5SPawel Dembicki mutex_unlock(&vsc_spi->lock);
13095711cd5SPawel Dembicki
13195711cd5SPawel Dembicki return ret;
13295711cd5SPawel Dembicki }
13395711cd5SPawel Dembicki
vsc73xx_spi_probe(struct spi_device * spi)13495711cd5SPawel Dembicki static int vsc73xx_spi_probe(struct spi_device *spi)
13595711cd5SPawel Dembicki {
13695711cd5SPawel Dembicki struct device *dev = &spi->dev;
13795711cd5SPawel Dembicki struct vsc73xx_spi *vsc_spi;
13895711cd5SPawel Dembicki int ret;
13995711cd5SPawel Dembicki
14095711cd5SPawel Dembicki vsc_spi = devm_kzalloc(dev, sizeof(*vsc_spi), GFP_KERNEL);
14195711cd5SPawel Dembicki if (!vsc_spi)
14295711cd5SPawel Dembicki return -ENOMEM;
14395711cd5SPawel Dembicki
14495711cd5SPawel Dembicki spi_set_drvdata(spi, vsc_spi);
14595711cd5SPawel Dembicki vsc_spi->spi = spi_dev_get(spi);
14695711cd5SPawel Dembicki vsc_spi->vsc.dev = dev;
14795711cd5SPawel Dembicki vsc_spi->vsc.priv = vsc_spi;
14895711cd5SPawel Dembicki vsc_spi->vsc.ops = &vsc73xx_spi_ops;
14995711cd5SPawel Dembicki mutex_init(&vsc_spi->lock);
15095711cd5SPawel Dembicki
15195711cd5SPawel Dembicki spi->mode = SPI_MODE_0;
15295711cd5SPawel Dembicki spi->bits_per_word = 8;
15395711cd5SPawel Dembicki ret = spi_setup(spi);
15495711cd5SPawel Dembicki if (ret < 0) {
15595711cd5SPawel Dembicki dev_err(dev, "spi setup failed.\n");
15695711cd5SPawel Dembicki return ret;
15795711cd5SPawel Dembicki }
15895711cd5SPawel Dembicki
15995711cd5SPawel Dembicki return vsc73xx_probe(&vsc_spi->vsc);
16095711cd5SPawel Dembicki }
16195711cd5SPawel Dembicki
vsc73xx_spi_remove(struct spi_device * spi)162a0386bbaSUwe Kleine-König static void vsc73xx_spi_remove(struct spi_device *spi)
16395711cd5SPawel Dembicki {
16495711cd5SPawel Dembicki struct vsc73xx_spi *vsc_spi = spi_get_drvdata(spi);
16595711cd5SPawel Dembicki
1660650bf52SVladimir Oltean if (!vsc_spi)
167a0386bbaSUwe Kleine-König return;
1680650bf52SVladimir Oltean
1690650bf52SVladimir Oltean vsc73xx_remove(&vsc_spi->vsc);
1700650bf52SVladimir Oltean }
1710650bf52SVladimir Oltean
vsc73xx_spi_shutdown(struct spi_device * spi)1720650bf52SVladimir Oltean static void vsc73xx_spi_shutdown(struct spi_device *spi)
1730650bf52SVladimir Oltean {
1740650bf52SVladimir Oltean struct vsc73xx_spi *vsc_spi = spi_get_drvdata(spi);
1750650bf52SVladimir Oltean
1760650bf52SVladimir Oltean if (!vsc_spi)
1770650bf52SVladimir Oltean return;
1780650bf52SVladimir Oltean
1790650bf52SVladimir Oltean vsc73xx_shutdown(&vsc_spi->vsc);
1800650bf52SVladimir Oltean
1810650bf52SVladimir Oltean spi_set_drvdata(spi, NULL);
18295711cd5SPawel Dembicki }
18395711cd5SPawel Dembicki
18495711cd5SPawel Dembicki static const struct vsc73xx_ops vsc73xx_spi_ops = {
18595711cd5SPawel Dembicki .read = vsc73xx_spi_read,
18695711cd5SPawel Dembicki .write = vsc73xx_spi_write,
18795711cd5SPawel Dembicki };
18895711cd5SPawel Dembicki
18995711cd5SPawel Dembicki static const struct of_device_id vsc73xx_of_match[] = {
19095711cd5SPawel Dembicki {
19195711cd5SPawel Dembicki .compatible = "vitesse,vsc7385",
19295711cd5SPawel Dembicki },
19395711cd5SPawel Dembicki {
19495711cd5SPawel Dembicki .compatible = "vitesse,vsc7388",
19595711cd5SPawel Dembicki },
19695711cd5SPawel Dembicki {
19795711cd5SPawel Dembicki .compatible = "vitesse,vsc7395",
19895711cd5SPawel Dembicki },
19995711cd5SPawel Dembicki {
20095711cd5SPawel Dembicki .compatible = "vitesse,vsc7398",
20195711cd5SPawel Dembicki },
20295711cd5SPawel Dembicki { },
20395711cd5SPawel Dembicki };
20495711cd5SPawel Dembicki MODULE_DEVICE_TABLE(of, vsc73xx_of_match);
20595711cd5SPawel Dembicki
206*1774559fSOleksij Rempel static const struct spi_device_id vsc73xx_spi_ids[] = {
207*1774559fSOleksij Rempel { "vsc7385" },
208*1774559fSOleksij Rempel { "vsc7388" },
209*1774559fSOleksij Rempel { "vsc7395" },
210*1774559fSOleksij Rempel { "vsc7398" },
211*1774559fSOleksij Rempel { },
212*1774559fSOleksij Rempel };
213*1774559fSOleksij Rempel MODULE_DEVICE_TABLE(spi, vsc73xx_spi_ids);
214*1774559fSOleksij Rempel
21595711cd5SPawel Dembicki static struct spi_driver vsc73xx_spi_driver = {
21695711cd5SPawel Dembicki .probe = vsc73xx_spi_probe,
21795711cd5SPawel Dembicki .remove = vsc73xx_spi_remove,
2180650bf52SVladimir Oltean .shutdown = vsc73xx_spi_shutdown,
219*1774559fSOleksij Rempel .id_table = vsc73xx_spi_ids,
22095711cd5SPawel Dembicki .driver = {
22195711cd5SPawel Dembicki .name = "vsc73xx-spi",
22295711cd5SPawel Dembicki .of_match_table = vsc73xx_of_match,
22395711cd5SPawel Dembicki },
22495711cd5SPawel Dembicki };
22595711cd5SPawel Dembicki module_spi_driver(vsc73xx_spi_driver);
22695711cd5SPawel Dembicki
22795711cd5SPawel Dembicki MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
22895711cd5SPawel Dembicki MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 SPI driver");
22995711cd5SPawel Dembicki MODULE_LICENSE("GPL v2");
230