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 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 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 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 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 16295711cd5SPawel Dembicki static int vsc73xx_spi_remove(struct spi_device *spi) 16395711cd5SPawel Dembicki { 16495711cd5SPawel Dembicki struct vsc73xx_spi *vsc_spi = spi_get_drvdata(spi); 16595711cd5SPawel Dembicki 166*0650bf52SVladimir Oltean if (!vsc_spi) 167*0650bf52SVladimir Oltean return 0; 168*0650bf52SVladimir Oltean 169*0650bf52SVladimir Oltean vsc73xx_remove(&vsc_spi->vsc); 170*0650bf52SVladimir Oltean 171*0650bf52SVladimir Oltean spi_set_drvdata(spi, NULL); 172*0650bf52SVladimir Oltean 173*0650bf52SVladimir Oltean return 0; 174*0650bf52SVladimir Oltean } 175*0650bf52SVladimir Oltean 176*0650bf52SVladimir Oltean static void vsc73xx_spi_shutdown(struct spi_device *spi) 177*0650bf52SVladimir Oltean { 178*0650bf52SVladimir Oltean struct vsc73xx_spi *vsc_spi = spi_get_drvdata(spi); 179*0650bf52SVladimir Oltean 180*0650bf52SVladimir Oltean if (!vsc_spi) 181*0650bf52SVladimir Oltean return; 182*0650bf52SVladimir Oltean 183*0650bf52SVladimir Oltean vsc73xx_shutdown(&vsc_spi->vsc); 184*0650bf52SVladimir Oltean 185*0650bf52SVladimir Oltean spi_set_drvdata(spi, NULL); 18695711cd5SPawel Dembicki } 18795711cd5SPawel Dembicki 18895711cd5SPawel Dembicki static const struct vsc73xx_ops vsc73xx_spi_ops = { 18995711cd5SPawel Dembicki .read = vsc73xx_spi_read, 19095711cd5SPawel Dembicki .write = vsc73xx_spi_write, 19195711cd5SPawel Dembicki }; 19295711cd5SPawel Dembicki 19395711cd5SPawel Dembicki static const struct of_device_id vsc73xx_of_match[] = { 19495711cd5SPawel Dembicki { 19595711cd5SPawel Dembicki .compatible = "vitesse,vsc7385", 19695711cd5SPawel Dembicki }, 19795711cd5SPawel Dembicki { 19895711cd5SPawel Dembicki .compatible = "vitesse,vsc7388", 19995711cd5SPawel Dembicki }, 20095711cd5SPawel Dembicki { 20195711cd5SPawel Dembicki .compatible = "vitesse,vsc7395", 20295711cd5SPawel Dembicki }, 20395711cd5SPawel Dembicki { 20495711cd5SPawel Dembicki .compatible = "vitesse,vsc7398", 20595711cd5SPawel Dembicki }, 20695711cd5SPawel Dembicki { }, 20795711cd5SPawel Dembicki }; 20895711cd5SPawel Dembicki MODULE_DEVICE_TABLE(of, vsc73xx_of_match); 20995711cd5SPawel Dembicki 21095711cd5SPawel Dembicki static struct spi_driver vsc73xx_spi_driver = { 21195711cd5SPawel Dembicki .probe = vsc73xx_spi_probe, 21295711cd5SPawel Dembicki .remove = vsc73xx_spi_remove, 213*0650bf52SVladimir Oltean .shutdown = vsc73xx_spi_shutdown, 21495711cd5SPawel Dembicki .driver = { 21595711cd5SPawel Dembicki .name = "vsc73xx-spi", 21695711cd5SPawel Dembicki .of_match_table = vsc73xx_of_match, 21795711cd5SPawel Dembicki }, 21895711cd5SPawel Dembicki }; 21995711cd5SPawel Dembicki module_spi_driver(vsc73xx_spi_driver); 22095711cd5SPawel Dembicki 22195711cd5SPawel Dembicki MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); 22295711cd5SPawel Dembicki MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 SPI driver"); 22395711cd5SPawel Dembicki MODULE_LICENSE("GPL v2"); 224