Commit ad7b134f authored by David S. Miller's avatar David S. Miller

Merge branch 'net-dsa-Add-Vitesse-VSC73xx-parallel-mode'

Pawel Dembicki says:

====================
net: dsa: Add Vitesse VSC73xx parallel mode

Main goal of this patch series is to add support for CPU attached parallel
bus in Vitesse VSC73xx switches. Existing driver supports only SPI mode.

Second change is needed for devices in unmanaged state.

V3:
- fix commit messages and descriptions about memory-mapped I/O mode

V2:
- drop changes in compatible strings
- make changes less invasive
- drop mutex in platform part and move mutex from core to spi part
- fix indentation
- fix devm_ioremap_resource result check
- add cover letter
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 30a4616c 1da39ff0
......@@ -2,8 +2,8 @@ Vitesse VSC73xx Switches
========================
This defines device tree bindings for the Vitesse VSC73xx switch chips.
The Vitesse company has been acquired by Microsemi and Microsemi in turn
acquired by Microchip but retains this vendor branding.
The Vitesse company has been acquired by Microsemi and Microsemi has
been acquired Microchip but retains this vendor branding.
The currently supported switch chips are:
Vitesse VSC7385 SparX-G5 5+1-port Integrated Gigabit Ethernet Switch
......@@ -11,8 +11,14 @@ 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
The device tree node is an SPI device so it must reside inside a SPI bus
device tree node, see spi/spi-bus.txt
This switch could have two different management interface.
If SPI interface is used, the device tree node is an SPI device so it must
reside inside a SPI bus device tree node, see spi/spi-bus.txt
When the chip is connected to a parallel memory bus and work in memory-mapped
I/O mode, a platform device is used to represent the vsc73xx. In this case it
must reside inside a platform bus device tree node.
Required properties:
......@@ -38,6 +44,7 @@ and subnodes of DSA switches.
Examples:
SPI:
switch@0 {
compatible = "vitesse,vsc7395";
reg = <0>;
......@@ -79,3 +86,46 @@ switch@0 {
};
};
};
Platform:
switch@2,0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "vitesse,vsc7385";
reg = <0x2 0x0 0x20000>;
reset-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
label = "lan1";
};
port@1 {
reg = <1>;
label = "lan2";
};
port@2 {
reg = <2>;
label = "lan3";
};
port@3 {
reg = <3>;
label = "lan4";
};
vsc: port@6 {
reg = <6>;
label = "cpu";
ethernet = <&enet0>;
phy-mode = "rgmii";
fixed-link {
speed = <1000>;
full-duplex;
pause;
};
};
};
};
......@@ -99,8 +99,8 @@ config NET_DSA_SMSC_LAN9303_MDIO
for MDIO managed mode.
config NET_DSA_VITESSE_VSC73XX
tristate "Vitesse VSC7385/7388/7395/7398 support"
depends on OF && SPI
tristate
depends on OF
depends on NET_DSA
select FIXED_PHY
select VITESSE_PHY
......@@ -109,4 +109,20 @@ config NET_DSA_VITESSE_VSC73XX
This enables support for the Vitesse VSC7385, VSC7388,
VSC7395 and VSC7398 SparX integrated ethernet switches.
config NET_DSA_VITESSE_VSC73XX_SPI
tristate "Vitesse VSC7385/7388/7395/7398 SPI mode support"
depends on SPI
select NET_DSA_VITESSE_VSC73XX
---help---
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches in SPI managed mode.
config NET_DSA_VITESSE_VSC73XX_PLATFORM
tristate "Vitesse VSC7385/7388/7395/7398 Platform mode support"
depends on HAS_IOMEM
select NET_DSA_VITESSE_VSC73XX
---help---
This enables support for the Vitesse VSC7385, VSC7388, VSC7395
and VSC7398 SparX integrated ethernet switches, connected over
a CPU-attached address bus and work in memory-mapped I/O mode.
endmenu
......@@ -14,7 +14,9 @@ realtek-smi-objs := realtek-smi-core.o rtl8366.o rtl8366rb.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
obj-y += b53/
obj-y += microchip/
obj-y += mv88e6xxx/
......
......@@ -10,10 +10,6 @@
* handling the switch in a memory-mapped manner by connecting to that external
* CPU's memory bus.
*
* This driver (currently) only takes control of the switch chip over SPI and
* configures it to route packages around when connected to a CPU port. The
* chip has embedded PHYs and VLAN support so we model it using DSA.
*
* Copyright (C) 2018 Linus Wallej <linus.walleij@linaro.org>
* Includes portions of code from the firmware uploader by:
* Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
......@@ -24,8 +20,6 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/bitops.h>
#include <linux/if_bridge.h>
#include <linux/etherdevice.h>
......@@ -34,6 +28,8 @@
#include <linux/random.h>
#include <net/dsa.h>
#include "vitesse-vsc73xx.h"
#define VSC73XX_BLOCK_MAC 0x1 /* Subblocks 0-4, 6 (CPU port) */
#define VSC73XX_BLOCK_ANALYZER 0x2 /* Only subblock 0 */
#define VSC73XX_BLOCK_MII 0x3 /* Subblocks 0 and 1 */
......@@ -255,13 +251,6 @@
#define VSC73XX_GLORESET_PHY_RESET BIT(1)
#define VSC73XX_GLORESET_MASTER_RESET BIT(0)
#define VSC73XX_CMD_MODE_READ 0
#define VSC73XX_CMD_MODE_WRITE 1
#define VSC73XX_CMD_MODE_SHIFT 4
#define VSC73XX_CMD_BLOCK_SHIFT 5
#define VSC73XX_CMD_BLOCK_MASK 0x7
#define VSC73XX_CMD_SUBBLOCK_MASK 0xf
#define VSC7385_CLOCK_DELAY ((3 << 4) | 3)
#define VSC7385_CLOCK_DELAY_MASK ((3 << 4) | 3)
......@@ -274,20 +263,6 @@
VSC73XX_ICPU_CTRL_CLK_EN | \
VSC73XX_ICPU_CTRL_SRST)
/**
* struct vsc73xx - VSC73xx state container
*/
struct vsc73xx {
struct device *dev;
struct gpio_desc *reset;
struct spi_device *spi;
struct dsa_switch *ds;
struct gpio_chip gc;
u16 chipid;
u8 addr[ETH_ALEN];
struct mutex lock; /* Protects SPI traffic */
};
#define IS_7385(a) ((a)->chipid == VSC73XX_CHIPID_ID_7385)
#define IS_7388(a) ((a)->chipid == VSC73XX_CHIPID_ID_7388)
#define IS_7395(a) ((a)->chipid == VSC73XX_CHIPID_ID_7395)
......@@ -365,7 +340,7 @@ static const struct vsc73xx_counter vsc73xx_tx_counters[] = {
{ 29, "TxQoSClass3" }, /* non-standard counter */
};
static int vsc73xx_is_addr_valid(u8 block, u8 subblock)
int vsc73xx_is_addr_valid(u8 block, u8 subblock)
{
switch (block) {
case VSC73XX_BLOCK_MAC:
......@@ -396,96 +371,18 @@ static int vsc73xx_is_addr_valid(u8 block, u8 subblock)
return 0;
}
static u8 vsc73xx_make_addr(u8 mode, u8 block, u8 subblock)
{
u8 ret;
ret = (block & VSC73XX_CMD_BLOCK_MASK) << VSC73XX_CMD_BLOCK_SHIFT;
ret |= (mode & 1) << VSC73XX_CMD_MODE_SHIFT;
ret |= subblock & VSC73XX_CMD_SUBBLOCK_MASK;
return ret;
}
EXPORT_SYMBOL(vsc73xx_is_addr_valid);
static int vsc73xx_read(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg,
u32 *val)
{
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_MODE_READ, block, subblock);
cmd[1] = reg;
cmd[2] = 0;
cmd[3] = 0;
mutex_lock(&vsc->lock);
ret = spi_sync(vsc->spi, &m);
mutex_unlock(&vsc->lock);
if (ret)
return ret;
*val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
return 0;
return vsc->ops->read(vsc, block, subblock, reg, val);
}
static int vsc73xx_write(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg,
u32 val)
{
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_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->lock);
ret = spi_sync(vsc->spi, &m);
mutex_unlock(&vsc->lock);
return ret;
return vsc->ops->write(vsc, block, subblock, reg, val);
}
static int vsc73xx_update_bits(struct vsc73xx *vsc, u8 block, u8 subblock,
......@@ -520,22 +417,8 @@ static int vsc73xx_detect(struct vsc73xx *vsc)
}
if (val == 0xffffffff) {
dev_info(vsc->dev, "chip seems dead, assert reset\n");
gpiod_set_value_cansleep(vsc->reset, 1);
/* Reset pulse should be 20ns minimum, according to datasheet
* table 245, so 10us should be fine
*/
usleep_range(10, 100);
gpiod_set_value_cansleep(vsc->reset, 0);
/* Wait 20ms according to datasheet table 245 */
msleep(20);
ret = vsc73xx_read(vsc, VSC73XX_BLOCK_SYSTEM, 0,
VSC73XX_ICPU_MBOX_VAL, &val);
if (val == 0xffffffff) {
dev_err(vsc->dev, "seems not to help, giving up\n");
return -ENODEV;
}
dev_info(vsc->dev, "chip seems dead.\n");
return -EAGAIN;
}
ret = vsc73xx_read(vsc, VSC73XX_BLOCK_SYSTEM, 0,
......@@ -586,9 +469,8 @@ static int vsc73xx_detect(struct vsc73xx *vsc)
}
if (icpu_si_boot_en && !icpu_pi_en) {
dev_err(vsc->dev,
"iCPU enabled boots from SI, no external memory\n");
dev_err(vsc->dev, "no idea how to deal with this\n");
return -ENODEV;
"iCPU enabled boots from PI/SI, no external memory\n");
return -EAGAIN;
}
if (!icpu_si_boot_en && icpu_pi_en) {
dev_err(vsc->dev,
......@@ -1245,21 +1127,11 @@ static int vsc73xx_gpio_probe(struct vsc73xx *vsc)
return 0;
}
static int vsc73xx_probe(struct spi_device *spi)
int vsc73xx_probe(struct vsc73xx *vsc)
{
struct device *dev = &spi->dev;
struct vsc73xx *vsc;
struct device *dev = vsc->dev;
int ret;
vsc = devm_kzalloc(dev, sizeof(*vsc), GFP_KERNEL);
if (!vsc)
return -ENOMEM;
spi_set_drvdata(spi, vsc);
vsc->spi = spi_dev_get(spi);
vsc->dev = dev;
mutex_init(&vsc->lock);
/* Release reset, if any */
vsc->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(vsc->reset)) {
......@@ -1270,15 +1142,20 @@ static int vsc73xx_probe(struct spi_device *spi)
/* Wait 20ms according to datasheet table 245 */
msleep(20);
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;
}
ret = vsc73xx_detect(vsc);
if (ret == -EAGAIN) {
dev_err(vsc->dev,
"Chip seems to be out of control. Assert reset and try again.\n");
gpiod_set_value_cansleep(vsc->reset, 1);
/* Reset pulse should be 20ns minimum, according to datasheet
* table 245, so 10us should be fine
*/
usleep_range(10, 100);
gpiod_set_value_cansleep(vsc->reset, 0);
/* Wait 20ms according to datasheet table 245 */
msleep(20);
ret = vsc73xx_detect(vsc);
}
if (ret) {
dev_err(dev, "no chip found (%d)\n", ret);
return -ENODEV;
......@@ -1321,43 +1198,16 @@ static int vsc73xx_probe(struct spi_device *spi)
return 0;
}
EXPORT_SYMBOL(vsc73xx_probe);
static int vsc73xx_remove(struct spi_device *spi)
int vsc73xx_remove(struct vsc73xx *vsc)
{
struct vsc73xx *vsc = spi_get_drvdata(spi);
dsa_unregister_switch(vsc->ds);
gpiod_set_value(vsc->reset, 1);
return 0;
}
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_driver = {
.probe = vsc73xx_probe,
.remove = vsc73xx_remove,
.driver = {
.name = "vsc73xx",
.of_match_table = vsc73xx_of_match,
},
};
module_spi_driver(vsc73xx_driver);
EXPORT_SYMBOL(vsc73xx_remove);
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 driver");
......
// 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 connected over CPU-attached
* address bus and configures it to route packages around when connected to
* a CPU port.
*
* Copyright (C) 2019 Pawel Dembicki <paweldembicki@gmail.com>
* Based on vitesse-vsc-spi.c by:
* Copyright (C) 2018 Linus Wallej <linus.walleij@linaro.org>
* Includes portions of code from the firmware uploader by:
* Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include "vitesse-vsc73xx.h"
#define VSC73XX_CMD_PLATFORM_BLOCK_SHIFT 14
#define VSC73XX_CMD_PLATFORM_BLOCK_MASK 0x7
#define VSC73XX_CMD_PLATFORM_SUBBLOCK_SHIFT 10
#define VSC73XX_CMD_PLATFORM_SUBBLOCK_MASK 0xf
#define VSC73XX_CMD_PLATFORM_REGISTER_SHIFT 2
/**
* struct vsc73xx_platform - VSC73xx Platform state container
*/
struct vsc73xx_platform {
struct platform_device *pdev;
void __iomem *base_addr;
struct vsc73xx vsc;
};
static const struct vsc73xx_ops vsc73xx_platform_ops;
static u32 vsc73xx_make_addr(u8 block, u8 subblock, u8 reg)
{
u32 ret;
ret = (block & VSC73XX_CMD_PLATFORM_BLOCK_MASK)
<< VSC73XX_CMD_PLATFORM_BLOCK_SHIFT;
ret |= (subblock & VSC73XX_CMD_PLATFORM_SUBBLOCK_MASK)
<< VSC73XX_CMD_PLATFORM_SUBBLOCK_SHIFT;
ret |= reg << VSC73XX_CMD_PLATFORM_REGISTER_SHIFT;
return ret;
}
static int vsc73xx_platform_read(struct vsc73xx *vsc, u8 block, u8 subblock,
u8 reg, u32 *val)
{
struct vsc73xx_platform *vsc_platform = vsc->priv;
u32 offset;
if (!vsc73xx_is_addr_valid(block, subblock))
return -EINVAL;
offset = vsc73xx_make_addr(block, subblock, reg);
/* By default vsc73xx running in big-endian mode.
* (See "Register Addressing" section 5.5.3 in the VSC7385 manual.)
*/
*val = ioread32be(vsc_platform->base_addr + offset);
return 0;
}
static int vsc73xx_platform_write(struct vsc73xx *vsc, u8 block, u8 subblock,
u8 reg, u32 val)
{
struct vsc73xx_platform *vsc_platform = vsc->priv;
u32 offset;
if (!vsc73xx_is_addr_valid(block, subblock))
return -EINVAL;
offset = vsc73xx_make_addr(block, subblock, reg);
iowrite32be(val, vsc_platform->base_addr + offset);
return 0;
}
static int vsc73xx_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct vsc73xx_platform *vsc_platform;
struct resource *res = NULL;
int ret;
vsc_platform = devm_kzalloc(dev, sizeof(*vsc_platform), GFP_KERNEL);
if (!vsc_platform)
return -ENOMEM;
platform_set_drvdata(pdev, vsc_platform);
vsc_platform->pdev = pdev;
vsc_platform->vsc.dev = dev;
vsc_platform->vsc.priv = vsc_platform;
vsc_platform->vsc.ops = &vsc73xx_platform_ops;
/* obtain I/O memory space */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "cannot obtain I/O memory space\n");
ret = -ENXIO;
return ret;
}
vsc_platform->base_addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(vsc_platform->base_addr)) {
dev_err(&pdev->dev, "cannot request I/O memory space\n");
ret = -ENXIO;
return ret;
}
return vsc73xx_probe(&vsc_platform->vsc);
}
static int vsc73xx_platform_remove(struct platform_device *pdev)
{
struct vsc73xx_platform *vsc_platform = platform_get_drvdata(pdev);
return vsc73xx_remove(&vsc_platform->vsc);
}
static const struct vsc73xx_ops vsc73xx_platform_ops = {
.read = vsc73xx_platform_read,
.write = vsc73xx_platform_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 platform_driver vsc73xx_platform_driver = {
.probe = vsc73xx_platform_probe,
.remove = vsc73xx_platform_remove,
.driver = {
.name = "vsc73xx-platform",
.of_match_table = vsc73xx_of_match,
},
};
module_platform_driver(vsc73xx_platform_driver);
MODULE_AUTHOR("Pawel Dembicki <paweldembicki@gmail.com>");
MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 Platform driver");
MODULE_LICENSE("GPL v2");
// 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 <linus.walleij@linaro.org>
* Includes portions of code from the firmware uploader by:
* Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
#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 <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("Vitesse VSC7385/7388/7395/7398 SPI driver");
MODULE_LICENSE("GPL v2");
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/device.h>
#include <linux/etherdevice.h>
#include <linux/gpio/driver.h>
/**
* struct vsc73xx - VSC73xx state container
*/
struct vsc73xx {
struct device *dev;
struct gpio_desc *reset;
struct dsa_switch *ds;
struct gpio_chip gc;
u16 chipid;
u8 addr[ETH_ALEN];
const struct vsc73xx_ops *ops;
void *priv;
};
struct vsc73xx_ops {
int (*read)(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg,
u32 *val);
int (*write)(struct vsc73xx *vsc, u8 block, u8 subblock, u8 reg,
u32 val);
};
int vsc73xx_is_addr_valid(u8 block, u8 subblock);
int vsc73xx_probe(struct vsc73xx *vsc);
int vsc73xx_remove(struct vsc73xx *vsc);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment