diff options
| author | Mark Brown <[email protected]> | 2015-10-12 18:09:27 +0100 | 
|---|---|---|
| committer | Mark Brown <[email protected]> | 2015-10-12 18:09:27 +0100 | 
| commit | 79828b4fa835f73cdaf4bffa48696abdcbea9d02 (patch) | |
| tree | 5e0fa7156acb75ba603022bc807df8f2fedb97a8 /drivers/spi/spi.c | |
| parent | 721b51fcf91898299d96f4b72cb9434cda29dce6 (diff) | |
| parent | 8c1a9d6323abf0fb1e5dad96cf3f1c783505ea5a (diff) | |
Merge remote-tracking branch 'asoc/fix/rt5645' into asoc-fix-rt5645
Diffstat (limited to 'drivers/spi/spi.c')
| -rw-r--r-- | drivers/spi/spi.c | 235 | 
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;  |