summaryrefslogtreecommitdiff
path: root/drivers/spi/spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/spi.c')
-rw-r--r--drivers/spi/spi.c235
1 files changed, 217 insertions, 18 deletions
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index cf8b91b23a76..3abb3903f2ad 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -67,11 +67,141 @@ modalias_show(struct device *dev, struct device_attribute *a, char *buf)
}
static DEVICE_ATTR_RO(modalias);
+#define SPI_STATISTICS_ATTRS(field, file) \
+static ssize_t spi_master_##field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct spi_master *master = container_of(dev, \
+ struct spi_master, dev); \
+ return spi_statistics_##field##_show(&master->statistics, buf); \
+} \
+static struct device_attribute dev_attr_spi_master_##field = { \
+ .attr = { .name = file, .mode = S_IRUGO }, \
+ .show = spi_master_##field##_show, \
+}; \
+static ssize_t spi_device_##field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct spi_device *spi = container_of(dev, \
+ struct spi_device, dev); \
+ return spi_statistics_##field##_show(&spi->statistics, buf); \
+} \
+static struct device_attribute dev_attr_spi_device_##field = { \
+ .attr = { .name = file, .mode = S_IRUGO }, \
+ .show = spi_device_##field##_show, \
+}
+
+#define SPI_STATISTICS_SHOW_NAME(name, file, field, format_string) \
+static ssize_t spi_statistics_##name##_show(struct spi_statistics *stat, \
+ char *buf) \
+{ \
+ unsigned long flags; \
+ ssize_t len; \
+ spin_lock_irqsave(&stat->lock, flags); \
+ len = sprintf(buf, format_string, stat->field); \
+ spin_unlock_irqrestore(&stat->lock, flags); \
+ return len; \
+} \
+SPI_STATISTICS_ATTRS(name, file)
+
+#define SPI_STATISTICS_SHOW(field, format_string) \
+ SPI_STATISTICS_SHOW_NAME(field, __stringify(field), \
+ field, format_string)
+
+SPI_STATISTICS_SHOW(messages, "%lu");
+SPI_STATISTICS_SHOW(transfers, "%lu");
+SPI_STATISTICS_SHOW(errors, "%lu");
+SPI_STATISTICS_SHOW(timedout, "%lu");
+
+SPI_STATISTICS_SHOW(spi_sync, "%lu");
+SPI_STATISTICS_SHOW(spi_sync_immediate, "%lu");
+SPI_STATISTICS_SHOW(spi_async, "%lu");
+
+SPI_STATISTICS_SHOW(bytes, "%llu");
+SPI_STATISTICS_SHOW(bytes_rx, "%llu");
+SPI_STATISTICS_SHOW(bytes_tx, "%llu");
+
static struct attribute *spi_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
-ATTRIBUTE_GROUPS(spi_dev);
+
+static const struct attribute_group spi_dev_group = {
+ .attrs = spi_dev_attrs,
+};
+
+static struct attribute *spi_device_statistics_attrs[] = {
+ &dev_attr_spi_device_messages.attr,
+ &dev_attr_spi_device_transfers.attr,
+ &dev_attr_spi_device_errors.attr,
+ &dev_attr_spi_device_timedout.attr,
+ &dev_attr_spi_device_spi_sync.attr,
+ &dev_attr_spi_device_spi_sync_immediate.attr,
+ &dev_attr_spi_device_spi_async.attr,
+ &dev_attr_spi_device_bytes.attr,
+ &dev_attr_spi_device_bytes_rx.attr,
+ &dev_attr_spi_device_bytes_tx.attr,
+ NULL,
+};
+
+static const struct attribute_group spi_device_statistics_group = {
+ .name = "statistics",
+ .attrs = spi_device_statistics_attrs,
+};
+
+static const struct attribute_group *spi_dev_groups[] = {
+ &spi_dev_group,
+ &spi_device_statistics_group,
+ NULL,
+};
+
+static struct attribute *spi_master_statistics_attrs[] = {
+ &dev_attr_spi_master_messages.attr,
+ &dev_attr_spi_master_transfers.attr,
+ &dev_attr_spi_master_errors.attr,
+ &dev_attr_spi_master_timedout.attr,
+ &dev_attr_spi_master_spi_sync.attr,
+ &dev_attr_spi_master_spi_sync_immediate.attr,
+ &dev_attr_spi_master_spi_async.attr,
+ &dev_attr_spi_master_bytes.attr,
+ &dev_attr_spi_master_bytes_rx.attr,
+ &dev_attr_spi_master_bytes_tx.attr,
+ NULL,
+};
+
+static const struct attribute_group spi_master_statistics_group = {
+ .name = "statistics",
+ .attrs = spi_master_statistics_attrs,
+};
+
+static const struct attribute_group *spi_master_groups[] = {
+ &spi_master_statistics_group,
+ NULL,
+};
+
+void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
+ struct spi_transfer *xfer,
+ struct spi_master *master)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&stats->lock, flags);
+
+ stats->transfers++;
+
+ stats->bytes += xfer->len;
+ if ((xfer->tx_buf) &&
+ (xfer->tx_buf != master->dummy_tx))
+ stats->bytes_tx += xfer->len;
+ if ((xfer->rx_buf) &&
+ (xfer->rx_buf != master->dummy_rx))
+ stats->bytes_rx += xfer->len;
+
+ spin_unlock_irqrestore(&stats->lock, flags);
+}
+EXPORT_SYMBOL_GPL(spi_statistics_add_transfer_stats);
/* modalias support makes "modprobe $MODALIAS" new-style hotplug work,
* and the sysfs version makes coldplug work too.
@@ -249,6 +379,9 @@ struct spi_device *spi_alloc_device(struct spi_master *master)
spi->dev.bus = &spi_bus_type;
spi->dev.release = spidev_release;
spi->cs_gpio = -ENOENT;
+
+ spin_lock_init(&spi->statistics.lock);
+
device_initialize(&spi->dev);
return spi;
}
@@ -476,21 +609,30 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
enum dma_data_direction dir)
{
const bool vmalloced_buf = is_vmalloc_addr(buf);
- const int desc_len = vmalloced_buf ? PAGE_SIZE : master->max_dma_len;
- const int sgs = DIV_ROUND_UP(len, desc_len);
+ int desc_len;
+ int sgs;
struct page *vm_page;
void *sg_buf;
size_t min;
int i, ret;
+ if (vmalloced_buf) {
+ desc_len = PAGE_SIZE;
+ sgs = DIV_ROUND_UP(len + offset_in_page(buf), desc_len);
+ } else {
+ desc_len = master->max_dma_len;
+ sgs = DIV_ROUND_UP(len, desc_len);
+ }
+
ret = sg_alloc_table(sgt, sgs, GFP_KERNEL);
if (ret != 0)
return ret;
for (i = 0; i < sgs; i++) {
- min = min_t(size_t, len, desc_len);
if (vmalloced_buf) {
+ min = min_t(size_t,
+ len, desc_len - offset_in_page(buf));
vm_page = vmalloc_to_page(buf);
if (!vm_page) {
sg_free_table(sgt);
@@ -499,6 +641,7 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
sg_set_page(&sgt->sgl[i], vm_page,
min, offset_in_page(buf));
} else {
+ min = min_t(size_t, len, desc_len);
sg_buf = buf;
sg_set_buf(&sgt->sgl[i], sg_buf, min);
}
@@ -539,8 +682,15 @@ static int __spi_map_msg(struct spi_master *master, struct spi_message *msg)
if (!master->can_dma)
return 0;
- tx_dev = master->dma_tx->device->dev;
- rx_dev = master->dma_rx->device->dev;
+ if (master->dma_tx)
+ tx_dev = master->dma_tx->device->dev;
+ else
+ tx_dev = &master->dev;
+
+ if (master->dma_rx)
+ rx_dev = master->dma_rx->device->dev;
+ else
+ rx_dev = &master->dev;
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
if (!master->can_dma(master, msg->spi, xfer))
@@ -579,8 +729,15 @@ static int __spi_unmap_msg(struct spi_master *master, struct spi_message *msg)
if (!master->cur_msg_mapped || !master->can_dma)
return 0;
- tx_dev = master->dma_tx->device->dev;
- rx_dev = master->dma_rx->device->dev;
+ if (master->dma_tx)
+ tx_dev = master->dma_tx->device->dev;
+ else
+ tx_dev = &master->dev;
+
+ if (master->dma_rx)
+ rx_dev = master->dma_rx->device->dev;
+ else
+ rx_dev = &master->dev;
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
if (!master->can_dma(master, msg->spi, xfer))
@@ -689,17 +846,29 @@ static int spi_transfer_one_message(struct spi_master *master,
bool keep_cs = false;
int ret = 0;
unsigned long ms = 1;
+ struct spi_statistics *statm = &master->statistics;
+ struct spi_statistics *stats = &msg->spi->statistics;
spi_set_cs(msg->spi, true);
+ SPI_STATISTICS_INCREMENT_FIELD(statm, messages);
+ SPI_STATISTICS_INCREMENT_FIELD(stats, messages);
+
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
trace_spi_transfer_start(msg, xfer);
+ spi_statistics_add_transfer_stats(statm, xfer, master);
+ spi_statistics_add_transfer_stats(stats, xfer, master);
+
if (xfer->tx_buf || xfer->rx_buf) {
reinit_completion(&master->xfer_completion);
ret = master->transfer_one(master, msg->spi, xfer);
if (ret < 0) {
+ SPI_STATISTICS_INCREMENT_FIELD(statm,
+ errors);
+ SPI_STATISTICS_INCREMENT_FIELD(stats,
+ errors);
dev_err(&msg->spi->dev,
"SPI transfer failed: %d\n", ret);
goto out;
@@ -715,6 +884,10 @@ static int spi_transfer_one_message(struct spi_master *master,
}
if (ms == 0) {
+ SPI_STATISTICS_INCREMENT_FIELD(statm,
+ timedout);
+ SPI_STATISTICS_INCREMENT_FIELD(stats,
+ timedout);
dev_err(&msg->spi->dev,
"SPI transfer timed out\n");
msg->status = -ETIMEDOUT;
@@ -1416,10 +1589,10 @@ static struct class spi_master_class = {
.name = "spi_master",
.owner = THIS_MODULE,
.dev_release = spi_master_release,
+ .dev_groups = spi_master_groups,
};
-
/**
* spi_alloc_master - allocate SPI master controller
* @dev: the controller, possibly using the platform_bus
@@ -1585,6 +1758,8 @@ int spi_register_master(struct spi_master *master)
goto done;
}
}
+ /* add statistics */
+ spin_lock_init(&master->statistics.lock);
mutex_lock(&board_lock);
list_add_tail(&master->list, &spi_master_list);
@@ -1740,6 +1915,20 @@ EXPORT_SYMBOL_GPL(spi_busnum_to_master);
* other core methods are currently defined as inline functions.
*/
+static int __spi_validate_bits_per_word(struct spi_master *master, u8 bits_per_word)
+{
+ if (master->bits_per_word_mask) {
+ /* Only 32 bits fit in the mask */
+ if (bits_per_word > 32)
+ return -EINVAL;
+ if (!(master->bits_per_word_mask &
+ SPI_BPW_MASK(bits_per_word)))
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/**
* spi_setup - setup SPI mode and clock rate
* @spi: the device whose settings are being modified
@@ -1798,6 +1987,9 @@ int spi_setup(struct spi_device *spi)
if (!spi->bits_per_word)
spi->bits_per_word = 8;
+ if (__spi_validate_bits_per_word(spi->master, spi->bits_per_word))
+ return -EINVAL;
+
if (!spi->max_speed_hz)
spi->max_speed_hz = spi->master->max_speed_hz;
@@ -1860,19 +2052,15 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
if (!xfer->speed_hz)
xfer->speed_hz = spi->max_speed_hz;
+ if (!xfer->speed_hz)
+ xfer->speed_hz = master->max_speed_hz;
if (master->max_speed_hz &&
xfer->speed_hz > master->max_speed_hz)
xfer->speed_hz = master->max_speed_hz;
- if (master->bits_per_word_mask) {
- /* Only 32 bits fit in the mask */
- if (xfer->bits_per_word > 32)
- return -EINVAL;
- if (!(master->bits_per_word_mask &
- BIT(xfer->bits_per_word - 1)))
- return -EINVAL;
- }
+ if (__spi_validate_bits_per_word(master, xfer->bits_per_word))
+ return -EINVAL;
/*
* SPI transfer length should be multiple of SPI word size
@@ -1939,6 +2127,9 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message)
message->spi = spi;
+ SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_async);
+ SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async);
+
trace_spi_message_submit(message);
return master->transfer(spi, message);
@@ -2075,6 +2266,9 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
message->context = &done;
message->spi = spi;
+ SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);
+ SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
+
if (!bus_locked)
mutex_lock(&master->bus_lock_mutex);
@@ -2102,8 +2296,13 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
/* Push out the messages in the calling context if we
* can.
*/
- if (master->transfer == spi_queued_transfer)
+ if (master->transfer == spi_queued_transfer) {
+ SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+ spi_sync_immediate);
+ SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
+ spi_sync_immediate);
__spi_pump_messages(master, false);
+ }
wait_for_completion(&done);
status = message->status;