// SPDX-License-Identifier: GPL-2.0 /* DSA driver for: * Vitesse VSC7385 SparX-G5 5+1-port Integrated Gigabit Ethernet Switch * Vitesse VSC7388 SparX-G8 8-port Integrated Gigabit Ethernet Switch * Vitesse VSC7395 SparX-G5e 5+1-port Integrated Gigabit Ethernet Switch * Vitesse VSC7398 SparX-G8e 8-port Integrated Gigabit Ethernet Switch * * This driver takes control of the switch chip over SPI and * configures it to route packages around when connected to a CPU port. * * Copyright (C) 2018 Linus Wallej * Includes portions of code from the firmware uploader by: * Copyright (C) 2009 Gabor Juhos */ #include #include #include #include #include "vitesse-vsc73xx.h" #define VSC73XX_CMD_SPI_MODE_READ 0 #define VSC73XX_CMD_SPI_MODE_WRITE 1 #define VSC73XX_CMD_SPI_MODE_SHIFT 4 #define VSC73XX_CMD_SPI_BLOCK_SHIFT 5 #define VSC73XX_CMD_SPI_BLOCK_MASK 0x7 #define VSC73XX_CMD_SPI_SUBBLOCK_MASK 0xf /** * struct vsc73xx_spi - VSC73xx SPI state container */ struct vsc73xx_spi { struct spi_device *spi; struct mutex lock; /* Protects SPI traffic */ struct vsc73xx vsc; }; static const struct vsc73xx_ops vsc73xx_spi_ops; static u8 vsc73xx_make_addr(u8 mode, u8 block, u8 subblock) { u8 ret; ret = (block & VSC73XX_CMD_SPI_BLOCK_MASK) << VSC73XX_CMD_SPI_BLOCK_SHIFT; ret |= (mode & 1) << VSC73XX_CMD_SPI_MODE_SHIFT; ret |= subblock & VSC73XX_CMD_SPI_SUBBLOCK_MASK; return ret; } static int vsc73xx_spi_read(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, u32 *val) { struct vsc73xx_spi *vsc_spi = vsc->priv; struct spi_transfer t[2]; struct spi_message m; u8 cmd[4]; u8 buf[4]; int ret; if (!vsc73xx_is_addr_valid(block, subblock)) return -EINVAL; spi_message_init(&m); memset(&t, 0, sizeof(t)); t[0].tx_buf = cmd; t[0].len = sizeof(cmd); spi_message_add_tail(&t[0], &m); t[1].rx_buf = buf; t[1].len = sizeof(buf); spi_message_add_tail(&t[1], &m); cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_SPI_MODE_READ, block, subblock); cmd[1] = reg; cmd[2] = 0; cmd[3] = 0; mutex_lock(&vsc_spi->lock); ret = spi_sync(vsc_spi->spi, &m); mutex_unlock(&vsc_spi->lock); if (ret) return ret; *val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return 0; } static int vsc73xx_spi_write(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg, u32 val) { struct vsc73xx_spi *vsc_spi = vsc->priv; struct spi_transfer t[2]; struct spi_message m; u8 cmd[2]; u8 buf[4]; int ret; if (!vsc73xx_is_addr_valid(block, subblock)) return -EINVAL; spi_message_init(&m); memset(&t, 0, sizeof(t)); t[0].tx_buf = cmd; t[0].len = sizeof(cmd); spi_message_add_tail(&t[0], &m); t[1].tx_buf = buf; t[1].len = sizeof(buf); spi_message_add_tail(&t[1], &m); cmd[0] = vsc73xx_make_addr(VSC73XX_CMD_SPI_MODE_WRITE, block, subblock); cmd[1] = reg; buf[0] = (val >> 24) & 0xff; buf[1] = (val >> 16) & 0xff; buf[2] = (val >> 8) & 0xff; buf[3] = val & 0xff; mutex_lock(&vsc_spi->lock); ret = spi_sync(vsc_spi->spi, &m); mutex_unlock(&vsc_spi->lock); return ret; } static int vsc73xx_spi_probe(struct spi_device *spi) { struct device *dev = &spi->dev; struct vsc73xx_spi *vsc_spi; int ret; vsc_spi = devm_kzalloc(dev, sizeof(*vsc_spi), GFP_KERNEL); if (!vsc_spi) return -ENOMEM; spi_set_drvdata(spi, vsc_spi); vsc_spi->spi = spi_dev_get(spi); vsc_spi->vsc.dev = dev; vsc_spi->vsc.priv = vsc_spi; vsc_spi->vsc.ops = &vsc73xx_spi_ops; mutex_init(&vsc_spi->lock); spi->mode = SPI_MODE_0; spi->bits_per_word = 8; ret = spi_setup(spi); if (ret < 0) { dev_err(dev, "spi setup failed.\n"); return ret; } return vsc73xx_probe(&vsc_spi->vsc); } static int vsc73xx_spi_remove(struct spi_device *spi) { struct vsc73xx_spi *vsc_spi = spi_get_drvdata(spi); return vsc73xx_remove(&vsc_spi->vsc); } static const struct vsc73xx_ops vsc73xx_spi_ops = { .read = vsc73xx_spi_read, .write = vsc73xx_spi_write, }; static const struct of_device_id vsc73xx_of_match[] = { { .compatible = "vitesse,vsc7385", }, { .compatible = "vitesse,vsc7388", }, { .compatible = "vitesse,vsc7395", }, { .compatible = "vitesse,vsc7398", }, { }, }; MODULE_DEVICE_TABLE(of, vsc73xx_of_match); static struct spi_driver vsc73xx_spi_driver = { .probe = vsc73xx_spi_probe, .remove = vsc73xx_spi_remove, .driver = { .name = "vsc73xx-spi", .of_match_table = vsc73xx_of_match, }, }; module_spi_driver(vsc73xx_spi_driver); MODULE_AUTHOR("Linus Walleij "); MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 SPI driver"); MODULE_LICENSE("GPL v2");