Commit 48c1699e authored by Stefan Wahren's avatar Stefan Wahren Committed by David S. Miller

net: qca_spi: Introduce write register verification

The SPI protocol for the QCA7000 doesn't have any fault detection.
In order to increase the drivers reliability in noisy environments,
we could implement a write verification inspired by the enc28j60.
This should avoid situations were the driver wrongly assumes the
receive interrupt is enabled and miss all incoming packets.

This function is disabled per default and can be controlled via module
parameter wr_verify.
Signed-off-by: default avatarMichael Heimpold <michael.heimpold@i2se.com>
Signed-off-by: default avatarStefan Wahren <stefan.wahren@i2se.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 4128c0cf
...@@ -81,8 +81,8 @@ qcaspi_read_register(struct qcaspi *qca, u16 reg, u16 *result) ...@@ -81,8 +81,8 @@ qcaspi_read_register(struct qcaspi *qca, u16 reg, u16 *result)
return ret; return ret;
} }
int static int
qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value) __qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value)
{ {
__be16 tx_data[2]; __be16 tx_data[2];
struct spi_transfer transfer[2]; struct spi_transfer transfer[2];
...@@ -117,3 +117,33 @@ qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value) ...@@ -117,3 +117,33 @@ qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value)
return ret; return ret;
} }
int
qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value, int retry)
{
int ret, i = 0;
u16 confirmed;
do {
ret = __qcaspi_write_register(qca, reg, value);
if (ret)
return ret;
if (!retry)
return 0;
ret = qcaspi_read_register(qca, reg, &confirmed);
if (ret)
return ret;
ret = confirmed != value;
if (!ret)
return 0;
i++;
qca->stats.write_verify_failed++;
} while (i <= retry);
return ret;
}
...@@ -66,6 +66,6 @@ ...@@ -66,6 +66,6 @@
void qcaspi_spi_error(struct qcaspi *qca); void qcaspi_spi_error(struct qcaspi *qca);
int qcaspi_read_register(struct qcaspi *qca, u16 reg, u16 *result); int qcaspi_read_register(struct qcaspi *qca, u16 reg, u16 *result);
int qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value); int qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value, int retry);
#endif /* _QCA_7K_H */ #endif /* _QCA_7K_H */
...@@ -60,6 +60,7 @@ static const char qcaspi_gstrings_stats[][ETH_GSTRING_LEN] = { ...@@ -60,6 +60,7 @@ static const char qcaspi_gstrings_stats[][ETH_GSTRING_LEN] = {
"Write buffer misses", "Write buffer misses",
"Transmit ring full", "Transmit ring full",
"SPI errors", "SPI errors",
"Write verify errors",
}; };
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
......
...@@ -69,6 +69,12 @@ static int qcaspi_pluggable = QCASPI_PLUGGABLE_MIN; ...@@ -69,6 +69,12 @@ static int qcaspi_pluggable = QCASPI_PLUGGABLE_MIN;
module_param(qcaspi_pluggable, int, 0); module_param(qcaspi_pluggable, int, 0);
MODULE_PARM_DESC(qcaspi_pluggable, "Pluggable SPI connection (yes/no)."); MODULE_PARM_DESC(qcaspi_pluggable, "Pluggable SPI connection (yes/no).");
#define QCASPI_WRITE_VERIFY_MIN 0
#define QCASPI_WRITE_VERIFY_MAX 3
static int wr_verify = QCASPI_WRITE_VERIFY_MIN;
module_param(wr_verify, int, 0);
MODULE_PARM_DESC(wr_verify, "SPI register write verify trails. Use 0-3.");
#define QCASPI_TX_TIMEOUT (1 * HZ) #define QCASPI_TX_TIMEOUT (1 * HZ)
#define QCASPI_QCA7K_REBOOT_TIME_MS 1000 #define QCASPI_QCA7K_REBOOT_TIME_MS 1000
...@@ -77,7 +83,7 @@ start_spi_intr_handling(struct qcaspi *qca, u16 *intr_cause) ...@@ -77,7 +83,7 @@ start_spi_intr_handling(struct qcaspi *qca, u16 *intr_cause)
{ {
*intr_cause = 0; *intr_cause = 0;
qcaspi_write_register(qca, SPI_REG_INTR_ENABLE, 0); qcaspi_write_register(qca, SPI_REG_INTR_ENABLE, 0, wr_verify);
qcaspi_read_register(qca, SPI_REG_INTR_CAUSE, intr_cause); qcaspi_read_register(qca, SPI_REG_INTR_CAUSE, intr_cause);
netdev_dbg(qca->net_dev, "interrupts: 0x%04x\n", *intr_cause); netdev_dbg(qca->net_dev, "interrupts: 0x%04x\n", *intr_cause);
} }
...@@ -90,8 +96,8 @@ end_spi_intr_handling(struct qcaspi *qca, u16 intr_cause) ...@@ -90,8 +96,8 @@ end_spi_intr_handling(struct qcaspi *qca, u16 intr_cause)
SPI_INT_RDBUF_ERR | SPI_INT_RDBUF_ERR |
SPI_INT_WRBUF_ERR); SPI_INT_WRBUF_ERR);
qcaspi_write_register(qca, SPI_REG_INTR_CAUSE, intr_cause); qcaspi_write_register(qca, SPI_REG_INTR_CAUSE, intr_cause, 0);
qcaspi_write_register(qca, SPI_REG_INTR_ENABLE, intr_enable); qcaspi_write_register(qca, SPI_REG_INTR_ENABLE, intr_enable, wr_verify);
netdev_dbg(qca->net_dev, "acking int: 0x%04x\n", intr_cause); netdev_dbg(qca->net_dev, "acking int: 0x%04x\n", intr_cause);
} }
...@@ -239,7 +245,7 @@ qcaspi_tx_frame(struct qcaspi *qca, struct sk_buff *skb) ...@@ -239,7 +245,7 @@ qcaspi_tx_frame(struct qcaspi *qca, struct sk_buff *skb)
len = skb->len; len = skb->len;
qcaspi_write_register(qca, SPI_REG_BFR_SIZE, len); qcaspi_write_register(qca, SPI_REG_BFR_SIZE, len, wr_verify);
if (qca->legacy_mode) if (qca->legacy_mode)
qcaspi_tx_cmd(qca, QCA7K_SPI_WRITE | QCA7K_SPI_EXTERNAL); qcaspi_tx_cmd(qca, QCA7K_SPI_WRITE | QCA7K_SPI_EXTERNAL);
...@@ -345,6 +351,7 @@ qcaspi_receive(struct qcaspi *qca) ...@@ -345,6 +351,7 @@ qcaspi_receive(struct qcaspi *qca)
/* Read the packet size. */ /* Read the packet size. */
qcaspi_read_register(qca, SPI_REG_RDBUF_BYTE_AVA, &available); qcaspi_read_register(qca, SPI_REG_RDBUF_BYTE_AVA, &available);
netdev_dbg(net_dev, "qcaspi_receive: SPI_REG_RDBUF_BYTE_AVA: Value: %08x\n", netdev_dbg(net_dev, "qcaspi_receive: SPI_REG_RDBUF_BYTE_AVA: Value: %08x\n",
available); available);
...@@ -353,7 +360,7 @@ qcaspi_receive(struct qcaspi *qca) ...@@ -353,7 +360,7 @@ qcaspi_receive(struct qcaspi *qca)
return -1; return -1;
} }
qcaspi_write_register(qca, SPI_REG_BFR_SIZE, available); qcaspi_write_register(qca, SPI_REG_BFR_SIZE, available, wr_verify);
if (qca->legacy_mode) if (qca->legacy_mode)
qcaspi_tx_cmd(qca, QCA7K_SPI_READ | QCA7K_SPI_EXTERNAL); qcaspi_tx_cmd(qca, QCA7K_SPI_READ | QCA7K_SPI_EXTERNAL);
...@@ -524,7 +531,7 @@ qcaspi_qca7k_sync(struct qcaspi *qca, int event) ...@@ -524,7 +531,7 @@ qcaspi_qca7k_sync(struct qcaspi *qca, int event)
netdev_dbg(qca->net_dev, "sync: resetting device.\n"); netdev_dbg(qca->net_dev, "sync: resetting device.\n");
qcaspi_read_register(qca, SPI_REG_SPI_CONFIG, &spi_config); qcaspi_read_register(qca, SPI_REG_SPI_CONFIG, &spi_config);
spi_config |= QCASPI_SLAVE_RESET_BIT; spi_config |= QCASPI_SLAVE_RESET_BIT;
qcaspi_write_register(qca, SPI_REG_SPI_CONFIG, spi_config); qcaspi_write_register(qca, SPI_REG_SPI_CONFIG, spi_config, 0);
qca->sync = QCASPI_SYNC_RESET; qca->sync = QCASPI_SYNC_RESET;
qca->stats.trig_reset++; qca->stats.trig_reset++;
...@@ -684,7 +691,7 @@ qcaspi_netdev_close(struct net_device *dev) ...@@ -684,7 +691,7 @@ qcaspi_netdev_close(struct net_device *dev)
netif_stop_queue(dev); netif_stop_queue(dev);
qcaspi_write_register(qca, SPI_REG_INTR_ENABLE, 0); qcaspi_write_register(qca, SPI_REG_INTR_ENABLE, 0, wr_verify);
free_irq(qca->spi_dev->irq, qca); free_irq(qca->spi_dev->irq, qca);
kthread_stop(qca->spi_thread); kthread_stop(qca->spi_thread);
...@@ -904,6 +911,13 @@ qca_spi_probe(struct spi_device *spi) ...@@ -904,6 +911,13 @@ qca_spi_probe(struct spi_device *spi)
return -EINVAL; return -EINVAL;
} }
if (wr_verify < QCASPI_WRITE_VERIFY_MIN ||
wr_verify > QCASPI_WRITE_VERIFY_MAX) {
dev_err(&spi->dev, "Invalid write verify: %d\n",
wr_verify);
return -EINVAL;
}
dev_info(&spi->dev, "ver=%s, clkspeed=%d, burst_len=%d, pluggable=%d\n", dev_info(&spi->dev, "ver=%s, clkspeed=%d, burst_len=%d, pluggable=%d\n",
QCASPI_DRV_VERSION, QCASPI_DRV_VERSION,
qcaspi_clkspeed, qcaspi_clkspeed,
......
...@@ -73,6 +73,7 @@ struct qcaspi_stats { ...@@ -73,6 +73,7 @@ struct qcaspi_stats {
u64 write_buf_miss; u64 write_buf_miss;
u64 ring_full; u64 ring_full;
u64 spi_err; u64 spi_err;
u64 write_verify_failed;
}; };
struct qcaspi { struct qcaspi {
......
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