xref: /linux/drivers/net/dsa/vitesse-vsc73xx-spi.c (revision 1774559f07993e1cac33c2406e99049d4bdea6c8)
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 
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 	spi_set_drvdata(spi, NULL);
1720650bf52SVladimir Oltean }
1730650bf52SVladimir Oltean 
1740650bf52SVladimir Oltean static void vsc73xx_spi_shutdown(struct spi_device *spi)
1750650bf52SVladimir Oltean {
1760650bf52SVladimir Oltean 	struct vsc73xx_spi *vsc_spi = spi_get_drvdata(spi);
1770650bf52SVladimir Oltean 
1780650bf52SVladimir Oltean 	if (!vsc_spi)
1790650bf52SVladimir Oltean 		return;
1800650bf52SVladimir Oltean 
1810650bf52SVladimir Oltean 	vsc73xx_shutdown(&vsc_spi->vsc);
1820650bf52SVladimir Oltean 
1830650bf52SVladimir Oltean 	spi_set_drvdata(spi, NULL);
18495711cd5SPawel Dembicki }
18595711cd5SPawel Dembicki 
18695711cd5SPawel Dembicki static const struct vsc73xx_ops vsc73xx_spi_ops = {
18795711cd5SPawel Dembicki 	.read = vsc73xx_spi_read,
18895711cd5SPawel Dembicki 	.write = vsc73xx_spi_write,
18995711cd5SPawel Dembicki };
19095711cd5SPawel Dembicki 
19195711cd5SPawel Dembicki static const struct of_device_id vsc73xx_of_match[] = {
19295711cd5SPawel Dembicki 	{
19395711cd5SPawel Dembicki 		.compatible = "vitesse,vsc7385",
19495711cd5SPawel Dembicki 	},
19595711cd5SPawel Dembicki 	{
19695711cd5SPawel Dembicki 		.compatible = "vitesse,vsc7388",
19795711cd5SPawel Dembicki 	},
19895711cd5SPawel Dembicki 	{
19995711cd5SPawel Dembicki 		.compatible = "vitesse,vsc7395",
20095711cd5SPawel Dembicki 	},
20195711cd5SPawel Dembicki 	{
20295711cd5SPawel Dembicki 		.compatible = "vitesse,vsc7398",
20395711cd5SPawel Dembicki 	},
20495711cd5SPawel Dembicki 	{ },
20595711cd5SPawel Dembicki };
20695711cd5SPawel Dembicki MODULE_DEVICE_TABLE(of, vsc73xx_of_match);
20795711cd5SPawel Dembicki 
208*1774559fSOleksij Rempel static const struct spi_device_id vsc73xx_spi_ids[] = {
209*1774559fSOleksij Rempel 	{ "vsc7385" },
210*1774559fSOleksij Rempel 	{ "vsc7388" },
211*1774559fSOleksij Rempel 	{ "vsc7395" },
212*1774559fSOleksij Rempel 	{ "vsc7398" },
213*1774559fSOleksij Rempel 	{ },
214*1774559fSOleksij Rempel };
215*1774559fSOleksij Rempel MODULE_DEVICE_TABLE(spi, vsc73xx_spi_ids);
216*1774559fSOleksij Rempel 
21795711cd5SPawel Dembicki static struct spi_driver vsc73xx_spi_driver = {
21895711cd5SPawel Dembicki 	.probe = vsc73xx_spi_probe,
21995711cd5SPawel Dembicki 	.remove = vsc73xx_spi_remove,
2200650bf52SVladimir Oltean 	.shutdown = vsc73xx_spi_shutdown,
221*1774559fSOleksij Rempel 	.id_table = vsc73xx_spi_ids,
22295711cd5SPawel Dembicki 	.driver = {
22395711cd5SPawel Dembicki 		.name = "vsc73xx-spi",
22495711cd5SPawel Dembicki 		.of_match_table = vsc73xx_of_match,
22595711cd5SPawel Dembicki 	},
22695711cd5SPawel Dembicki };
22795711cd5SPawel Dembicki module_spi_driver(vsc73xx_spi_driver);
22895711cd5SPawel Dembicki 
22995711cd5SPawel Dembicki MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
23095711cd5SPawel Dembicki MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 SPI driver");
23195711cd5SPawel Dembicki MODULE_LICENSE("GPL v2");
232