* [PATCH 02/12] spi: fix spi_message init during __spi_validate
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 03/12] spi: add spi_{set,get}_ctldata accessors Marco Felsch
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
Set the spi_device pointer accordingly to allow drivers to use it.
While on it, set the actual_length to 0 before passing the message to
the .transfer() hook as well.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/spi/spi.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index c627d88954a7..36d0653a191c 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -321,6 +321,8 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
if (list_empty(&message->transfers))
return -EINVAL;
+ message->spi = spi;
+
list_for_each_entry(xfer, &message->transfers, transfer_list) {
if (!xfer->bits_per_word)
xfer->bits_per_word = spi->bits_per_word;
@@ -347,6 +349,7 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
return -EINVAL;
}
+ message->actual_length = 0;
message->status = -EINPROGRESS;
return 0;
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 03/12] spi: add spi_{set,get}_ctldata accessors
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
2024-11-15 19:57 ` [PATCH 02/12] spi: fix spi_message init during __spi_validate Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 04/12] gpiolib: add support for gpiod_get_index and gpiod_get_index_optional Marco Felsch
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
We alreary support the controller_state priv data pointer, add the Linux
APIs to set/get the data.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
include/spi/spi.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/include/spi/spi.h b/include/spi/spi.h
index 622de732fa6d..9261d508befd 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -116,6 +116,17 @@ static inline struct spi_device *to_spi_device(struct device *dev)
return dev ? container_of(dev, struct spi_device, dev) : NULL;
}
+/* ctldata is for the bus_controller driver's runtime state */
+static inline void *spi_get_ctldata(const struct spi_device *spi)
+{
+ return spi->controller_state;
+}
+
+static inline void spi_set_ctldata(struct spi_device *spi, void *state)
+{
+ spi->controller_state = state;
+}
+
/**
* struct spi_controller - interface to SPI master or slave controller
* @dev: device interface to this driver
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 04/12] gpiolib: add support for gpiod_get_index and gpiod_get_index_optional
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
2024-11-15 19:57 ` [PATCH 02/12] spi: fix spi_message init during __spi_validate Marco Felsch
2024-11-15 19:57 ` [PATCH 03/12] spi: add spi_{set,get}_ctldata accessors Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 05/12] gpiolib: add support for gpiod_set_consumer_name Marco Felsch
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
Add Linux compatible APIs to make it easier to port code from Linux to
barebox. This aligns the GPIO label naming scheme with Linux too.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/gpio/gpiolib.c | 62 +++++++++++++++++++++++++++++++++++
include/linux/gpio/consumer.h | 26 +++++++++++++++
2 files changed, 88 insertions(+)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 146eaf9af138..55beaba6acbc 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -22,6 +22,8 @@ struct gpio_desc {
u32 flags; /* OR-d enum of_gpio_flags */
};
+#define gpiod_not_found(desc) (IS_ERR(desc) && PTR_ERR(desc) == -ENOENT)
+
/*
* This descriptor validation needs to be inserted verbatim into each
* function taking a descriptor, so we need to use a preprocessor
@@ -1053,6 +1055,66 @@ struct gpio_desc *dev_gpiod_get_index(struct device *dev,
return ret ? ERR_PTR(ret): desc;
}
+/**
+ * gpiod_get_index - obtain a GPIO from a multi-index GPIO function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @idx: index of the GPIO to obtain in the consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * This variant of gpiod_get() allows to access GPIOs other than the first
+ * defined one for functions that define several GPIOs.
+ *
+ * Returns:
+ * A valid GPIO descriptor, -ENOENT if no GPIO has been assigned to the
+ * requested function and/or index, or another IS_ERR() code if an error
+ * occurred while trying to acquire the GPIO.
+ */
+struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags flags)
+{
+ struct device_node *np = dev_of_node(dev);
+ const char *devname = dev ? dev_name(dev) : "?";
+ const char *label = con_id ?: devname;
+
+ return dev_gpiod_get_index(dev, np, con_id, idx, flags, label);
+}
+EXPORT_SYMBOL_GPL(gpiod_get_index);
+
+/**
+ * gpiod_get_index_optional - obtain an optional GPIO from a multi-index GPIO
+ * function
+ * @dev: GPIO consumer, can be NULL for system-global GPIOs
+ * @con_id: function within the GPIO consumer
+ * @index: index of the GPIO to obtain in the consumer
+ * @flags: optional GPIO initialization flags
+ *
+ * This is equivalent to gpiod_get_index(), except that when no GPIO with the
+ * specified index was assigned to the requested function it will return NULL.
+ * This is convenient for drivers that need to handle optional GPIOs.
+ *
+ * Returns:
+ * A valid GPIO descriptor, NULL if no GPIO has been assigned to the
+ * requested function and/or index, or another IS_ERR() code if an error
+ * occurred while trying to acquire the GPIO.
+ */
+struct gpio_desc *__must_check gpiod_get_index_optional(struct device *dev,
+ const char *con_id,
+ unsigned int index,
+ enum gpiod_flags flags)
+{
+ struct gpio_desc *desc;
+
+ desc = gpiod_get_index(dev, con_id, index, flags);
+ if (gpiod_not_found(desc))
+ return NULL;
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(gpiod_get_index_optional);
+
/**
* gpiod_count - return the number of GPIOs associated with a device / function
* or -ENOENT if no GPIO has been assigned to the requested function
diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
index f0d5bf7b255b..34e5795cbc07 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -86,6 +86,16 @@ void gpiod_put(struct gpio_desc *desc);
int gpiod_count(struct device *dev, const char *con_id);
+struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags flags);
+
+struct gpio_desc *__must_check gpiod_get_index_optional(struct device *dev,
+ const char *con_id,
+ unsigned int index,
+ enum gpiod_flags flags);
+
struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
@@ -167,6 +177,22 @@ static inline int gpiod_count(struct device *dev, const char *con_id)
return 0;
}
+static inline struct gpio_desc *__must_check
+gpiod_get_index(struct device *dev,
+ const char *con_id,
+ unsigned int idx,
+ enum gpiod_flags flags)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline struct gpio_desc *__must_check
+gpiod_get_index_optional(struct device *dev, const char *con_id,
+ unsigned int index, enum gpiod_flags flags)
+{
+ return NULL;
+}
+
static inline struct gpio_descs *__must_check
gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags)
{
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 05/12] gpiolib: add support for gpiod_set_consumer_name
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (2 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 04/12] gpiolib: add support for gpiod_get_index and gpiod_get_index_optional Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 06/12] spi: add support to handle cs-gpios Marco Felsch
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
Add support for the Linux gpiod_set_consumer_name() API to make it
easier to port drivers. Compared to Linux the function itself is very
different.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/gpio/gpiolib.c | 19 +++++++++++++++++++
include/linux/gpio/consumer.h | 10 ++++++++++
2 files changed, 29 insertions(+)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 55beaba6acbc..ec4d8e889bb1 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -1223,6 +1223,25 @@ int gpiod_set_array_value(unsigned int array_size,
}
EXPORT_SYMBOL_GPL(gpiod_set_array_value);
+/**
+ * gpiod_set_consumer_name() - set the consumer name for the descriptor
+ * @desc: gpio to set the consumer name on
+ * @name: the new consumer name
+ *
+ * Returns:
+ * 0 on success, or negative errno on failure.
+ */
+int gpiod_set_consumer_name(struct gpio_desc *desc, const char *name)
+{
+ VALIDATE_DESC(desc);
+
+ free(desc->label);
+ desc->label = xstrdup(name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gpiod_set_consumer_name);
+
int gpiochip_add(struct gpio_chip *chip)
{
int i;
diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
index 34e5795cbc07..d411c3aa3608 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -84,6 +84,8 @@ int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_put(struct gpio_desc *desc);
+int gpiod_set_consumer_name(struct gpio_desc *desc, const char *name);
+
int gpiod_count(struct device *dev, const char *con_id);
struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
@@ -172,6 +174,14 @@ static inline void gpiod_put(struct gpio_desc *desc)
WARN_ON(desc);
}
+static inline int gpiod_set_consumer_name(struct gpio_desc *desc,
+ const char *name)
+{
+ /* GPIO can never have been requested */
+ WARN_ON(desc);
+ return -EINVAL;
+}
+
static inline int gpiod_count(struct device *dev, const char *con_id)
{
return 0;
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 06/12] spi: add support to handle cs-gpios
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (3 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 05/12] gpiolib: add support for gpiod_set_consumer_name Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 07/12] spi: add support to setup spi-cs-{setup,hold,inactive}-delay-ns Marco Felsch
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
At the moment all drivers have to parse the cs-gpios on their own and
have to implement the mapping. By this commit we add the support to
handle this within the core and if there is a valid CS GPIO for a device
we assign it accordingly.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/spi/spi.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
include/spi/spi.h | 9 +++++++
2 files changed, 75 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 36d0653a191c..c239de9d8549 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -8,6 +8,8 @@
*/
#include <common.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
#include <linux/spi/spi-mem.h>
#include <spi/spi.h>
#include <xfuncs.h>
@@ -64,6 +66,8 @@ struct spi_device *spi_new_device(struct spi_controller *ctrl,
proxy = xzalloc(sizeof *proxy);
proxy->master = ctrl;
proxy->chip_select = chip->chip_select;
+ if (ctrl->cs_gpiods)
+ proxy->cs_gpiod = ctrl->cs_gpiods[chip->chip_select];
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
proxy->bits_per_word = chip->bits_per_word ? chip->bits_per_word : 8;
@@ -215,6 +219,62 @@ static void scan_boardinfo(struct spi_controller *ctrl)
}
}
+/**
+ * spi_get_gpio_descs() - grab chip select GPIOs for the master
+ * @ctlr: The SPI master to grab GPIO descriptors for
+ */
+static int spi_get_gpio_descs(struct spi_controller *ctlr)
+{
+ int nb, i;
+ struct gpio_desc **cs;
+ struct device *dev = ctlr->dev;
+
+ nb = gpiod_count(dev, "cs");
+ if (nb < 0) {
+ /* No GPIOs at all is fine, else return the error */
+ if (nb == -ENOENT)
+ return 0;
+ return nb;
+ }
+
+ ctlr->num_chipselect = max_t(int, nb, ctlr->num_chipselect);
+
+ cs = devm_kcalloc(dev, ctlr->num_chipselect, sizeof(*cs),
+ GFP_KERNEL);
+ if (!cs)
+ return -ENOMEM;
+ ctlr->cs_gpiods = cs;
+
+ for (i = 0; i < nb; i++) {
+ /*
+ * Most chipselects are active low, the inverted
+ * semantics are handled by special quirks in gpiolib,
+ * so initializing them GPIOD_OUT_LOW here means
+ * "unasserted", in most cases this will drive the physical
+ * line high.
+ */
+ cs[i] = gpiod_get_index_optional(dev, "cs", i, GPIOD_OUT_LOW);
+ if (IS_ERR(cs[i]))
+ return PTR_ERR(cs[i]);
+
+ if (cs[i]) {
+ /*
+ * If we find a CS GPIO, name it after the device and
+ * chip select line.
+ */
+ char *gpioname;
+
+ gpioname = basprintf("%s CS%d", dev_name(dev), i);
+ if (!gpioname)
+ return -ENOMEM;
+ gpiod_set_consumer_name(cs[i], gpioname);
+ free(gpioname);
+ }
+ }
+
+ return 0;
+}
+
static int spi_controller_check_ops(struct spi_controller *ctlr)
{
/*
@@ -285,6 +345,12 @@ int spi_register_controller(struct spi_controller *ctrl)
if (ctrl->bus_num < 0)
ctrl->bus_num = dyn_bus_id--;
+ if (ctrl->use_gpio_descriptors) {
+ status = spi_get_gpio_descs(ctrl);
+ if (status)
+ return status;
+ }
+
list_add_tail(&ctrl->list, &spi_controller_list);
spi_of_register_slaves(ctrl);
diff --git a/include/spi/spi.h b/include/spi/spi.h
index 9261d508befd..53d6bd32e025 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -8,6 +8,7 @@
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
struct spi_controller_mem_ops;
struct spi_message;
@@ -99,6 +100,7 @@ struct spi_device {
void *controller_state;
void *controller_data;
const char *modalias;
+ struct gpio_desc *cs_gpiod; /* Chip select gpio desc */
/*
* likely need more hooks for more protocol options affecting how
@@ -156,6 +158,9 @@ static inline void spi_set_ctldata(struct spi_device *spi, void *state)
* the device whose settings are being modified.
* @transfer: adds a message to the controller's transfer queue.
* @cleanup: frees controller-specific state
+ * @cs_gpiods: Array of GPIO descriptors to use as chip select lines; one per CS
+ * number. Any individual value may be NULL for CS lines that
+ * are not GPIOs (driven by the SPI controller itself).
* @list: link with the global spi_controller list
*
* Each SPI controller can communicate with one or more @spi_device
@@ -233,6 +238,10 @@ struct spi_controller {
/* called on release() to free memory provided by spi_controller */
void (*cleanup)(struct spi_device *spi);
+ /* GPIO chip select */
+ struct gpio_desc **cs_gpiods;
+ bool use_gpio_descriptors;
+
struct list_head list;
};
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 07/12] spi: add support to setup spi-cs-{setup,hold,inactive}-delay-ns
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (4 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 06/12] spi: add support to handle cs-gpios Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 08/12] spi: allow reporting the effectivly used speed_hz for a transfer Marco Felsch
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
Add support to parse and setup the common OF CS delay properties. The
parsing is quite confusing since the kernel driver API decided to use a
u16 for the value. I kept it this way to be closer to the Linux code.
This prepares the core for the upcoming core based message handling.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/spi/spi.c | 25 +++++++++++++++++++++++++
include/spi/spi.h | 22 ++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index c239de9d8549..e8a0b1b84be5 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -70,6 +70,9 @@ struct spi_device *spi_new_device(struct spi_controller *ctrl,
proxy->cs_gpiod = ctrl->cs_gpiods[chip->chip_select];
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
+ proxy->cs_setup = chip->cs_setup;
+ proxy->cs_hold = chip->cs_hold;
+ proxy->cs_inactive = chip->cs_inactive;
proxy->bits_per_word = chip->bits_per_word ? chip->bits_per_word : 8;
proxy->dev.platform_data = chip->platform_data;
proxy->dev.bus = &spi_bus;
@@ -111,6 +114,22 @@ struct spi_device *spi_new_device(struct spi_controller *ctrl,
}
EXPORT_SYMBOL(spi_new_device);
+static void of_spi_parse_dt_cs_delay(struct device_node *nc,
+ struct spi_delay *delay, const char *prop)
+{
+ u32 value;
+
+ if (!of_property_read_u32(nc, prop, &value)) {
+ if (value > U16_MAX) {
+ delay->value = DIV_ROUND_UP(value, 1000);
+ delay->unit = SPI_DELAY_UNIT_USECS;
+ } else {
+ delay->value = value;
+ delay->unit = SPI_DELAY_UNIT_NSECS;
+ }
+ }
+}
+
static void spi_of_register_slaves(struct spi_controller *ctrl)
{
struct device_node *n;
@@ -145,6 +164,12 @@ static void spi_of_register_slaves(struct spi_controller *ctrl)
chip.mode |= SPI_3WIRE;
of_property_read_u32(n, "spi-max-frequency",
&chip.max_speed_hz);
+
+ /* Device CS delays */
+ of_spi_parse_dt_cs_delay(n, &chip.cs_setup, "spi-cs-setup-delay-ns");
+ of_spi_parse_dt_cs_delay(n, &chip.cs_hold, "spi-cs-hold-delay-ns");
+ of_spi_parse_dt_cs_delay(n, &chip.cs_inactive, "spi-cs-inactive-delay-ns");
+
reg = of_find_property(n, "reg", NULL);
if (!reg)
continue;
diff --git a/include/spi/spi.h b/include/spi/spi.h
index 53d6bd32e025..8363e73c6754 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -13,6 +13,19 @@
struct spi_controller_mem_ops;
struct spi_message;
+/**
+ * struct spi_delay - SPI delay information
+ * @value: Value for the delay
+ * @unit: Unit for the delay
+ */
+struct spi_delay {
+#define SPI_DELAY_UNIT_USECS 0
+#define SPI_DELAY_UNIT_NSECS 1
+#define SPI_DELAY_UNIT_SCK 2
+ u16 value;
+ u8 unit;
+};
+
struct spi_board_info {
char *name;
int max_speed_hz;
@@ -26,6 +39,11 @@ struct spi_board_info {
u8 bits_per_word;
void *platform_data;
struct device_node *device_node;
+
+ /* CS delays */
+ struct spi_delay cs_setup;
+ struct spi_delay cs_hold;
+ struct spi_delay cs_inactive;
};
/**
@@ -101,6 +119,10 @@ struct spi_device {
void *controller_data;
const char *modalias;
struct gpio_desc *cs_gpiod; /* Chip select gpio desc */
+ /* CS delays */
+ struct spi_delay cs_setup;
+ struct spi_delay cs_hold;
+ struct spi_delay cs_inactive;
/*
* likely need more hooks for more protocol options affecting how
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 08/12] spi: allow reporting the effectivly used speed_hz for a transfer
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (5 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 07/12] spi: add support to setup spi-cs-{setup,hold,inactive}-delay-ns Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 09/12] spi: import spi_controller::flags Marco Felsch
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
This is a copy of Linux commit:
8<-------------------------------------------------------------------------
commit 5d7e2b5ed5858fe739d4cb8ad22dcce7bd9dbe7b
Author: Martin Sperl <kernel@martin.sperl.org>
Date: Sat Feb 23 08:49:49 2019 +0000
spi: core: allow reporting the effectivly used speed_hz for a transfer
Provide a means for the spi bus driver to report the effectively used
spi clock frequency used for each spi_transfer.
Signed-off-by: Martin Sperl <kernel@martin.sperl.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
8<-------------------------------------------------------------------------
to sync the spi core more with Linux which is required for the later
core common message handling.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/spi/spi.c | 1 +
include/spi/spi.h | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index e8a0b1b84be5..78569301776f 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -415,6 +415,7 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
message->spi = spi;
list_for_each_entry(xfer, &message->transfers, transfer_list) {
+ xfer->effective_speed_hz = 0;
if (!xfer->bits_per_word)
xfer->bits_per_word = spi->bits_per_word;
diff --git a/include/spi/spi.h b/include/spi/spi.h
index 8363e73c6754..2b441e934d12 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -330,6 +330,9 @@ static inline size_t spi_max_transfer_size(struct spi_device *spi)
* @len: size of rx and tx buffers (in bytes)
* @speed_hz: Select a speed other then the device default for this
* transfer. If 0 the default (from @spi_device) is used.
+ * @effective_speed_hz: the effective SCK-speed that was used to
+ * transfer this transfer. Set to 0 if the SPI bus driver does
+ * not support it.
* @bits_per_word: select a bits_per_word other then the device default
* for this transfer. If 0 the default (from @spi_device) is used.
* @cs_change: affects chipselect after this transfer completes
@@ -399,6 +402,8 @@ struct spi_transfer {
u16 delay_usecs;
u32 speed_hz;
+ u32 effective_speed_hz;
+
struct list_head transfer_list;
};
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 09/12] spi: import spi_controller::flags
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (6 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 08/12] spi: allow reporting the effectivly used speed_hz for a transfer Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 10/12] spi: add support for spi_controller::set_cs_timing Marco Felsch
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
Import the flags supported by the Linux spi controller implementation to
make it easier to port driver from Linux.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
include/spi/spi.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/include/spi/spi.h b/include/spi/spi.h
index 2b441e934d12..8354b7d8baa6 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -173,6 +173,7 @@ static inline void spi_set_ctldata(struct spi_device *spi, void *state)
* unsupported bits_per_word. If not set, this value is simply ignored,
* and it's up to the individual driver to perform any validation.
* @max_speed_hz: Highest supported transfer speed
+ * @flags: other constraints relevant to this driver
* @setup: updates the device mode and clocking records used by a
* device's SPI controller; protocol code may call this. This
* must fail if an unrecognized or unsupported mode is requested.
@@ -232,6 +233,16 @@ struct spi_controller {
/* limits on transfer speed */
u32 max_speed_hz;
+ /* Other constraints relevant to this driver */
+ u16 flags;
+#define SPI_CONTROLLER_HALF_DUPLEX BIT(0) /* Can't do full duplex */
+#define SPI_CONTROLLER_NO_RX BIT(1) /* Can't do buffer read */
+#define SPI_CONTROLLER_NO_TX BIT(2) /* Can't do buffer write */
+#define SPI_CONTROLLER_MUST_RX BIT(3) /* Requires rx */
+#define SPI_CONTROLLER_MUST_TX BIT(4) /* Requires tx */
+#define SPI_CONTROLLER_GPIO_SS BIT(5) /* GPIO CS must select slave */
+#define SPI_CONTROLLER_SUSPENDED BIT(6) /* Currently suspended */
+
/* setup mode and clock, etc (spi driver may call many times) */
int (*setup)(struct spi_device *spi);
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 10/12] spi: add support for spi_controller::set_cs_timing
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (7 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 09/12] spi: import spi_controller::flags Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 11/12] spi: Provide common spi_message processing loop Marco Felsch
2024-11-15 19:57 ` [PATCH 12/12] spi: add support for BCM2835 SPI controller Marco Felsch
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
Import the spi_controller set_cs_timing() hook to make it easier to port
code from the Linux.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
include/spi/spi.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/include/spi/spi.h b/include/spi/spi.h
index 8354b7d8baa6..092eacd4a8e1 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -179,6 +179,9 @@ static inline void spi_set_ctldata(struct spi_device *spi, void *state)
* must fail if an unrecognized or unsupported mode is requested.
* It's always safe to call this unless transfers are pending on
* the device whose settings are being modified.
+ * @set_cs_timing: optional hook for SPI devices to request SPI master
+ * controller for configuring specific CS setup time, hold time and inactive
+ * delay interms of clock counts
* @transfer: adds a message to the controller's transfer queue.
* @cleanup: frees controller-specific state
* @cs_gpiods: Array of GPIO descriptors to use as chip select lines; one per CS
@@ -246,6 +249,16 @@ struct spi_controller {
/* setup mode and clock, etc (spi driver may call many times) */
int (*setup)(struct spi_device *spi);
+ /*
+ * set_cs_timing() method is for SPI controllers that supports
+ * configuring CS timing.
+ *
+ * This hook allows SPI client drivers to request SPI controllers
+ * to configure specific CS timing through spi_set_cs_timing() after
+ * spi_setup().
+ */
+ int (*set_cs_timing)(struct spi_device *spi);
+
/* bidirectional bulk transfers
*
* + The transfer() method may not sleep; its main role is
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 11/12] spi: Provide common spi_message processing loop
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (8 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 10/12] spi: add support for spi_controller::set_cs_timing Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
2024-11-15 19:57 ` [PATCH 12/12] spi: add support for BCM2835 SPI controller Marco Felsch
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
Add barebox spi core message handling support. This mimics the current
Linux spi core handling and the initial Linux commit:
8<--------------------------------------------------------------------------------
commit b158935f70b9c156903338053216dd0adf7ce31c
Author: Mark Brown <broonie@linaro.org>
Date: Sat Oct 5 11:50:40 2013 +0100
spi: Provide common spi_message processing loop
The loops which SPI controller drivers use to process the list of transfers
in a spi_message are typically very similar and have some error prone areas
such as the handling of /CS. Help simplify drivers by factoring this code
out into the core - if drivers provide a transfer_one() function instead
of a transfer_one_message() function the core will handle processing at the
message level.
/CS can be controlled by either setting cs_gpio or providing a set_cs
function. If this is not possible for hardware reasons then both can be
omitted and the driver should continue to implement manual /CS handling.
This is a first step in refactoring and it is expected that there will be
further enhancements, for example factoring out of the mapping of transfers
for DMA and the initiation and completion of interrupt driven transfers.
Signed-off-by: Mark Brown <broonie@linaro.org>
8<--------------------------------------------------------------------------------
This message handling implementation is much simpler compared to the
Linux but it should improve the current state of our spi framework by a
lot. After this commit it should be much simpler to port spi drivers from
Linux to barebox.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/spi/spi.c | 182 +++++++++++++++++++++++++++++++++++++++++++++-
include/spi/spi.h | 44 +++++++++++
2 files changed, 225 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 78569301776f..202527107625 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -300,6 +300,183 @@ static int spi_get_gpio_descs(struct spi_controller *ctlr)
return 0;
}
+static void _spi_transfer_delay_ns(u32 ns)
+{
+ if (!ns)
+ return;
+ if (ns <= NSEC_PER_USEC) {
+ ndelay(ns);
+ } else {
+ u32 us = DIV_ROUND_UP(ns, NSEC_PER_USEC);
+
+ udelay(us);
+ }
+}
+
+int spi_delay_to_ns(struct spi_delay *_delay, struct spi_transfer *xfer)
+{
+ u32 delay = _delay->value;
+ u32 unit = _delay->unit;
+ u32 hz;
+
+ if (!delay)
+ return 0;
+
+ switch (unit) {
+ case SPI_DELAY_UNIT_USECS:
+ delay *= NSEC_PER_USEC;
+ break;
+ case SPI_DELAY_UNIT_NSECS:
+ /* Nothing to do here */
+ break;
+ case SPI_DELAY_UNIT_SCK:
+ /* Clock cycles need to be obtained from spi_transfer */
+ if (!xfer)
+ return -EINVAL;
+ /*
+ * If there is unknown effective speed, approximate it
+ * by underestimating with half of the requested Hz.
+ */
+ hz = xfer->effective_speed_hz ?: xfer->speed_hz / 2;
+ if (!hz)
+ return -EINVAL;
+
+ /* Convert delay to nanoseconds */
+ delay *= DIV_ROUND_UP(NSEC_PER_SEC, hz);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return delay;
+}
+EXPORT_SYMBOL_GPL(spi_delay_to_ns);
+
+int spi_delay_exec(struct spi_delay *_delay, struct spi_transfer *xfer)
+{
+ int delay;
+
+ if (!_delay)
+ return -EINVAL;
+
+ delay = spi_delay_to_ns(_delay, xfer);
+ if (delay < 0)
+ return delay;
+
+ _spi_transfer_delay_ns(delay);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_delay_exec);
+
+static void spi_set_cs(struct spi_device *spi, bool enable)
+{
+ bool activate = enable;
+
+ if (spi->cs_gpiod && !activate)
+ spi_delay_exec(&spi->cs_hold, NULL);
+
+ if (spi->mode & SPI_CS_HIGH)
+ enable = !enable;
+
+ if (spi->cs_gpiod) {
+ if (!(spi->mode & SPI_NO_CS)) {
+ /* Polarity handled by GPIO library */
+ gpiod_set_value(spi->cs_gpiod, activate);
+ }
+ /* Some SPI masters need both GPIO CS & slave_select */
+ if ((spi->controller->flags & SPI_CONTROLLER_GPIO_SS) &&
+ spi->controller->set_cs)
+ spi->controller->set_cs(spi, !enable);
+ } else if (spi->controller->set_cs) {
+ spi->controller->set_cs(spi, !enable);
+ }
+
+ if (spi->cs_gpiod || !spi->controller->set_cs_timing) {
+ if (activate)
+ spi_delay_exec(&spi->cs_setup, NULL);
+ else
+ spi_delay_exec(&spi->cs_inactive, NULL);
+ }
+}
+
+/*
+ * spi_transfer_one_message - Default implementation of transfer()
+ *
+ * This is a standard implementation of transfer() for drivers which implement a
+ * transfer_one() operation. It provides standard handling of delays and chip
+ * select management.
+ *
+ */
+static int spi_transfer_one_message(struct spi_device *spi, struct spi_message *msg)
+{
+ struct spi_controller *ctlr = spi->controller;
+ struct spi_transfer *xfer;
+ bool keep_cs = false;
+ int ret = 0;
+
+ if (ctlr->prepare_message) {
+ ret = ctlr->prepare_message(ctlr, msg);
+ if (ret) {
+ dev_err(ctlr->dev, "failed to prepare message: %d\n",
+ ret);
+ msg->status = ret;
+ return ret;
+ }
+ }
+
+ spi_set_cs(msg->spi, true);
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ if ((xfer->tx_buf || xfer->rx_buf) && xfer->len) {
+ ret = ctlr->transfer_one(ctlr, msg->spi, xfer);
+ if (ret < 0) {
+ dev_err(&msg->spi->dev,
+ "SPI transfer failed: %d\n", ret);
+ goto out;
+ }
+ } else {
+ if (xfer->len)
+ dev_err(&msg->spi->dev,
+ "Bufferless transfer has length %u\n",
+ xfer->len);
+ }
+
+ if (msg->status != -EINPROGRESS)
+ goto out;
+
+ /* TODO: Convert to new spi_delay API */
+ if (xfer->delay_usecs)
+ udelay(xfer->delay_usecs);
+
+ if (xfer->cs_change) {
+ if (list_is_last(&xfer->transfer_list,
+ &msg->transfers)) {
+ keep_cs = true;
+ } else {
+ spi_set_cs(msg->spi, false);
+ /* TODO: Convert to new spi_delay API */
+ udelay(10);
+ spi_set_cs(msg->spi, true);
+ }
+ }
+
+ msg->actual_length += xfer->len;
+ }
+
+out:
+ if (ret != 0 || !keep_cs)
+ spi_set_cs(msg->spi, false);
+
+ if (msg->status == -EINPROGRESS)
+ msg->status = ret;
+
+ if (msg->status && ctlr->handle_err)
+ ctlr->handle_err(ctlr, msg);
+
+ return ret;
+}
+
static int spi_controller_check_ops(struct spi_controller *ctlr)
{
/*
@@ -312,7 +489,7 @@ static int spi_controller_check_ops(struct spi_controller *ctlr)
if (ctlr->mem_ops) {
if (!ctlr->mem_ops->exec_op)
return -EINVAL;
- } else if (!ctlr->transfer) {
+ } else if (!ctlr->transfer && !ctlr->transfer_one) {
return -EINVAL;
}
@@ -355,6 +532,9 @@ int spi_register_controller(struct spi_controller *ctrl)
if (status)
return status;
+ if (ctrl->transfer_one)
+ ctrl->transfer = spi_transfer_one_message;
+
slice_init(&ctrl->slice, dev_name(ctrl->dev));
/* even if it's just one always-selected device, there must
diff --git a/include/spi/spi.h b/include/spi/spi.h
index 092eacd4a8e1..d0b338b2822b 100644
--- a/include/spi/spi.h
+++ b/include/spi/spi.h
@@ -10,6 +10,8 @@
#include <linux/bitops.h>
#include <linux/gpio/consumer.h>
+struct spi_controller;
+struct spi_transfer;
struct spi_controller_mem_ops;
struct spi_message;
@@ -26,6 +28,9 @@ struct spi_delay {
u8 unit;
};
+extern int spi_delay_to_ns(struct spi_delay *_delay, struct spi_transfer *xfer);
+extern int spi_delay_exec(struct spi_delay *_delay, struct spi_transfer *xfer);
+
struct spi_board_info {
char *name;
int max_speed_hz;
@@ -184,6 +189,26 @@ static inline void spi_set_ctldata(struct spi_device *spi, void *state)
* delay interms of clock counts
* @transfer: adds a message to the controller's transfer queue.
* @cleanup: frees controller-specific state
+ * @set_cs: set the logic level of the chip select line. May be called
+ * from interrupt context.
+ * @prepare_message: set up the controller to transfer a single message,
+ * for example doing DMA mapping. Called from threaded
+ * context.
+ * @transfer_one: transfer a single spi_transfer.
+ *
+ * - return 0 if the transfer is finished,
+ * - return 1 if the transfer is still in progress. When
+ * the driver is finished with this transfer it must
+ * call spi_finalize_current_transfer() so the subsystem
+ * can issue the next transfer. If the transfer fails, the
+ * driver must set the flag SPI_TRANS_FAIL_IO to
+ * spi_transfer->error first, before calling
+ * spi_finalize_current_transfer().
+ * Note: transfer_one and transfer_one_message are mutually
+ * exclusive; when both are set, the generic subsystem does
+ * not call your transfer_one callback.
+ * @handle_err: the subsystem calls the driver to handle an error that occurs
+ * in the generic implementation of transfer_one_message().
* @cs_gpiods: Array of GPIO descriptors to use as chip select lines; one per CS
* number. Any individual value may be NULL for CS lines that
* are not GPIOs (driven by the SPI controller itself).
@@ -284,6 +309,25 @@ struct spi_controller {
/* called on release() to free memory provided by spi_controller */
void (*cleanup)(struct spi_device *spi);
+ /*
+ * These hooks are for drivers that want to use the generic
+ * controller transfer mechanism. If these are used, the
+ * transfer() function above must NOT be specified by the driver.
+ * Over time we expect SPI drivers to be phased over to this API.
+ */
+ int (*prepare_message)(struct spi_controller *ctlr,
+ struct spi_message *message);
+
+ /*
+ * These hooks are for drivers that use a generic implementation
+ * of transfer_one_message() provided by the core.
+ */
+ void (*set_cs)(struct spi_device *spi, bool enable);
+ int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,
+ struct spi_transfer *transfer);
+ void (*handle_err)(struct spi_controller *ctlr,
+ struct spi_message *message);
+
/* GPIO chip select */
struct gpio_desc **cs_gpiods;
bool use_gpio_descriptors;
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 12/12] spi: add support for BCM2835 SPI controller
2024-11-15 19:57 [PATCH 01/12] spi: cosmetic style fixes Marco Felsch
` (9 preceding siblings ...)
2024-11-15 19:57 ` [PATCH 11/12] spi: Provide common spi_message processing loop Marco Felsch
@ 2024-11-15 19:57 ` Marco Felsch
10 siblings, 0 replies; 12+ messages in thread
From: Marco Felsch @ 2024-11-15 19:57 UTC (permalink / raw)
To: barebox
This ports the Linux spi-bcm2835 driver to barebox.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/spi/Kconfig | 11 ++
drivers/spi/Makefile | 1 +
drivers/spi/spi-bcm2835.c | 400 ++++++++++++++++++++++++++++++++++++++
3 files changed, 412 insertions(+)
create mode 100644 drivers/spi/spi-bcm2835.c
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 445c756a38a2..8357f7806e3c 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -95,6 +95,17 @@ config DRIVER_SPI_DSPI
This enables support for the Freescale DSPI controller in master
mode. VF610 platform uses the controller.
+config SPI_BCM2835
+ tristate "BCM2835 SPI controller"
+ depends on ARCH_BCM283X || COMPILE_TEST
+ help
+ This selects a driver for the Broadcom BCM2835 SPI master.
+
+ The BCM2835 contains two types of SPI master controller; the
+ "universal SPI master", and the regular SPI controller. This driver
+ is for the regular SPI controller. Slave mode operation is not also
+ not supported.
+
config SPI_ZYNQ_QSPI
tristate "Xilinx Zynq QSPI controller"
depends on ARCH_ZYNQ
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 68a8c4e675a5..e0f1124090a4 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_FSL_DSPI) += spi-fsl-dspi.o
obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
obj-$(CONFIG_DRIVER_SPI_OMAP3) += omap3_spi.o
obj-$(CONFIG_DRIVER_SPI_DSPI) += dspi_spi.o
+obj-$(CONFIG_SPI_BCM2835) += spi-bcm2835.o
obj-$(CONFIG_SPI_ZYNQ_QSPI) += zynq_qspi.o
obj-$(CONFIG_SPI_NXP_FLEXSPI) += spi-nxp-fspi.o
obj-$(CONFIG_DRIVER_SPI_STM32) += stm32_spi.o
diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c
new file mode 100644
index 000000000000..f0dc76a956f7
--- /dev/null
+++ b/drivers/spi/spi-bcm2835.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Broadcom BCM2835 SPI Controllers
+ *
+ * Copyright (C) 2012 Chris Boot
+ * Copyright (C) 2013 Stephen Warren
+ * Copyright (C) 2015 Martin Sperl
+ *
+ * This driver is inspired by:
+ * spi-ath79.c, Copyright (C) 2009-2011 Gabor Juhos <juhosg@openwrt.org>
+ * spi-atmel.c, Copyright (C) 2006 Atmel Corporation
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <spi/spi.h>
+
+/* SPI register offsets */
+#define BCM2835_SPI_CS 0x00
+#define BCM2835_SPI_FIFO 0x04
+#define BCM2835_SPI_CLK 0x08
+#define BCM2835_SPI_DLEN 0x0c
+
+/* Bitfields in CS */
+#define BCM2835_SPI_CS_TXD 0x00040000
+#define BCM2835_SPI_CS_RXD 0x00020000
+#define BCM2835_SPI_CS_DONE 0x00010000
+#define BCM2835_SPI_CS_REN 0x00001000
+#define BCM2835_SPI_CS_ADCS 0x00000800
+#define BCM2835_SPI_CS_INTR 0x00000400
+#define BCM2835_SPI_CS_INTD 0x00000200
+#define BCM2835_SPI_CS_DMAEN 0x00000100
+#define BCM2835_SPI_CS_TA 0x00000080
+#define BCM2835_SPI_CS_CLEAR_RX 0x00000020
+#define BCM2835_SPI_CS_CLEAR_TX 0x00000010
+#define BCM2835_SPI_CS_CPOL 0x00000008
+#define BCM2835_SPI_CS_CPHA 0x00000004
+#define BCM2835_SPI_CS_CS_10 0x00000002
+#define BCM2835_SPI_CS_CS_01 0x00000001
+
+#define BCM2835_SPI_FIFO_SIZE 64
+#define BCM2835_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \
+ | SPI_NO_CS | SPI_3WIRE)
+
+#define DRV_NAME "spi-bcm2835"
+
+/**
+ * struct bcm2835_spi - BCM2835 SPI controller
+ * @regs: base address of register map
+ * @clk: core clock, divided to calculate serial clock
+ * @clk_hz: core clock cached speed
+ * @tfr: SPI transfer currently processed
+ * @ctlr: SPI controller reverse lookup
+ * @tx_buf: pointer whence next transmitted byte is read
+ * @rx_buf: pointer where next received byte is written
+ * @tx_len: remaining bytes to transmit
+ * @rx_len: remaining bytes to receive
+ */
+struct bcm2835_spi {
+ void __iomem *regs;
+ struct clk *clk;
+ unsigned long clk_hz;
+ struct spi_transfer *tfr;
+ struct spi_controller ctlr;
+ const u8 *tx_buf;
+ u8 *rx_buf;
+ int tx_len;
+ int rx_len;
+};
+
+/**
+ * struct bcm2835_spidev - BCM2835 SPI target
+ * @prepare_cs: precalculated CS register value for ->prepare_message()
+ * (uses target-specific clock polarity and phase settings)
+ */
+struct bcm2835_spidev {
+ u32 prepare_cs;
+};
+
+static inline u32 bcm2835_rd(struct bcm2835_spi *bs, unsigned int reg)
+{
+ return readl(bs->regs + reg);
+}
+
+static inline void bcm2835_wr(struct bcm2835_spi *bs, unsigned int reg, u32 val)
+{
+ writel(val, bs->regs + reg);
+}
+
+static inline void bcm2835_rd_fifo(struct bcm2835_spi *bs)
+{
+ u8 byte;
+
+ while ((bs->rx_len) &&
+ (bcm2835_rd(bs, BCM2835_SPI_CS) & BCM2835_SPI_CS_RXD)) {
+ byte = bcm2835_rd(bs, BCM2835_SPI_FIFO);
+ if (bs->rx_buf)
+ *bs->rx_buf++ = byte;
+ bs->rx_len--;
+ }
+}
+
+static inline void bcm2835_wr_fifo(struct bcm2835_spi *bs)
+{
+ u8 byte;
+
+ while ((bs->tx_len) &&
+ (bcm2835_rd(bs, BCM2835_SPI_CS) & BCM2835_SPI_CS_TXD)) {
+ byte = bs->tx_buf ? *bs->tx_buf++ : 0;
+ bcm2835_wr(bs, BCM2835_SPI_FIFO, byte);
+ bs->tx_len--;
+ }
+}
+
+/**
+ * bcm2835_rd_fifo_count() - blindly read exactly @count bytes from RX FIFO
+ * @bs: BCM2835 SPI controller
+ * @count: bytes to read from RX FIFO
+ *
+ * The caller must ensure that @bs->rx_len is greater than or equal to @count,
+ * that the RX FIFO contains at least @count bytes and that the DMA Enable flag
+ * in the CS register is set (such that a read from the FIFO register receives
+ * 32-bit instead of just 8-bit). Moreover @bs->rx_buf must not be %NULL.
+ */
+static inline void bcm2835_rd_fifo_count(struct bcm2835_spi *bs, int count)
+{
+ u32 val;
+ int len;
+
+ bs->rx_len -= count;
+
+ do {
+ val = bcm2835_rd(bs, BCM2835_SPI_FIFO);
+ len = min(count, 4);
+ memcpy(bs->rx_buf, &val, len);
+ bs->rx_buf += len;
+ count -= 4;
+ } while (count > 0);
+}
+
+/**
+ * bcm2835_wr_fifo_blind() - blindly write up to @count bytes to TX FIFO
+ * @bs: BCM2835 SPI controller
+ * @count: bytes available for writing in TX FIFO
+ */
+static inline void bcm2835_wr_fifo_blind(struct bcm2835_spi *bs, int count)
+{
+ u8 val;
+
+ count = min(count, bs->tx_len);
+ bs->tx_len -= count;
+
+ do {
+ val = bs->tx_buf ? *bs->tx_buf++ : 0;
+ bcm2835_wr(bs, BCM2835_SPI_FIFO, val);
+ } while (--count);
+}
+
+static void bcm2835_spi_reset_hw(struct bcm2835_spi *bs)
+{
+ u32 cs = bcm2835_rd(bs, BCM2835_SPI_CS);
+
+ /* Disable SPI interrupts and transfer */
+ cs &= ~(BCM2835_SPI_CS_INTR |
+ BCM2835_SPI_CS_INTD |
+ BCM2835_SPI_CS_DMAEN |
+ BCM2835_SPI_CS_TA);
+ /*
+ * Transmission sometimes breaks unless the DONE bit is written at the
+ * end of every transfer. The spec says it's a RO bit. Either the
+ * spec is wrong and the bit is actually of type RW1C, or it's a
+ * hardware erratum.
+ */
+ cs |= BCM2835_SPI_CS_DONE;
+ /* and reset RX/TX FIFOS */
+ cs |= BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX;
+
+ /* and reset the SPI_HW */
+ bcm2835_wr(bs, BCM2835_SPI_CS, cs);
+ /* as well as DLEN */
+ bcm2835_wr(bs, BCM2835_SPI_DLEN, 0);
+}
+
+static int bcm2835_spi_transfer_one_poll(struct spi_controller *ctlr,
+ struct spi_device *spi,
+ struct spi_transfer *tfr,
+ u32 cs)
+{
+ struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+
+ /* enable HW block without interrupts */
+ bcm2835_wr(bs, BCM2835_SPI_CS, cs | BCM2835_SPI_CS_TA);
+
+ bcm2835_wr_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE);
+
+ /* loop until finished the transfer */
+ while (bs->rx_len) {
+ /* fill in tx fifo with remaining data */
+ bcm2835_wr_fifo(bs);
+
+ /* read from fifo as much as possible */
+ bcm2835_rd_fifo(bs);
+ }
+
+ /* Transfer complete - reset SPI HW */
+ bcm2835_spi_reset_hw(bs);
+ /* and return without waiting for completion */
+ return 0;
+}
+
+static int bcm2835_spi_transfer_one(struct spi_controller *ctlr,
+ struct spi_device *spi,
+ struct spi_transfer *tfr)
+{
+ struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+ struct bcm2835_spidev *target = spi_get_ctldata(spi);
+ unsigned long spi_hz, cdiv;
+ u32 cs = target->prepare_cs;
+
+ /* set clock */
+ spi_hz = tfr->speed_hz;
+
+ if (spi_hz >= bs->clk_hz / 2) {
+ cdiv = 2; /* clk_hz/2 is the fastest we can go */
+ } else if (spi_hz) {
+ /* CDIV must be a multiple of two */
+ cdiv = DIV_ROUND_UP(bs->clk_hz, spi_hz);
+ cdiv += (cdiv % 2);
+
+ if (cdiv >= 65536)
+ cdiv = 0; /* 0 is the slowest we can go */
+ } else {
+ cdiv = 0; /* 0 is the slowest we can go */
+ }
+ tfr->effective_speed_hz = cdiv ? (bs->clk_hz / cdiv) : (bs->clk_hz / 65536);
+ bcm2835_wr(bs, BCM2835_SPI_CLK, cdiv);
+
+ /* handle all the 3-wire mode */
+ if (spi->mode & SPI_3WIRE && tfr->rx_buf)
+ cs |= BCM2835_SPI_CS_REN;
+
+ /* set transmit buffers and length */
+ bs->tx_buf = tfr->tx_buf;
+ bs->rx_buf = tfr->rx_buf;
+ bs->tx_len = tfr->len;
+ bs->rx_len = tfr->len;
+
+ return bcm2835_spi_transfer_one_poll(ctlr, spi, tfr, cs);
+}
+
+static int bcm2835_spi_prepare_message(struct spi_controller *ctlr,
+ struct spi_message *msg)
+{
+ struct spi_device *spi = msg->spi;
+ struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+ struct bcm2835_spidev *target = spi_get_ctldata(spi);
+
+ /*
+ * Set up clock polarity before spi_transfer_one_message() asserts
+ * chip select to avoid a gratuitous clock signal edge.
+ */
+ bcm2835_wr(bs, BCM2835_SPI_CS, target->prepare_cs);
+
+ return 0;
+}
+
+static void bcm2835_spi_handle_err(struct spi_controller *ctlr,
+ struct spi_message *msg)
+{
+ struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+
+ /* and reset */
+ bcm2835_spi_reset_hw(bs);
+}
+
+
+static void bcm2835_spi_cleanup(struct spi_device *spi)
+{
+ struct bcm2835_spidev *target = spi_get_ctldata(spi);
+
+ kfree(target);
+}
+
+static size_t bcm2835_spi_max_transfer_size(struct spi_device *spi)
+{
+ return SIZE_MAX;
+}
+
+static int bcm2835_spi_setup(struct spi_device *spi)
+{
+ struct bcm2835_spidev *target = spi_get_ctldata(spi);
+ u32 cs;
+
+ if (!target) {
+ target = kzalloc(sizeof(*target), GFP_KERNEL);
+ if (!target)
+ return -ENOMEM;
+
+ spi_set_ctldata(spi, target);
+ }
+
+ /*
+ * Precalculate SPI target's CS register value for ->prepare_message():
+ * The driver always uses software-controlled GPIO chip select, hence
+ * set the hardware-controlled native chip select to an invalid value
+ * to prevent it from interfering.
+ */
+ cs = BCM2835_SPI_CS_CS_10 | BCM2835_SPI_CS_CS_01;
+ if (spi->mode & SPI_CPOL)
+ cs |= BCM2835_SPI_CS_CPOL;
+ if (spi->mode & SPI_CPHA)
+ cs |= BCM2835_SPI_CS_CPHA;
+ target->prepare_cs = cs;
+
+ /*
+ * sanity checking the native-chipselects
+ */
+ if (spi->mode & SPI_NO_CS)
+ return 0;
+
+ if (!spi->cs_gpiod) {
+ dev_err(&spi->dev, "Driver supports only cs-gpios at the moment\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bcm2835_spi_probe(struct device *dev)
+{
+ struct spi_controller *ctlr;
+ struct resource *iores;
+ struct bcm2835_spi *bs;
+ int err;
+
+ bs = xzalloc(sizeof(*bs));
+
+ ctlr = &bs->ctlr;
+
+ /* ctlr->mode_bits = BCM2835_SPI_MODE_BITS; */
+ ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
+ ctlr->num_chipselect = 3;
+ ctlr->use_gpio_descriptors = true;
+ ctlr->max_transfer_size = bcm2835_spi_max_transfer_size;
+ ctlr->setup = bcm2835_spi_setup;
+ ctlr->cleanup = bcm2835_spi_cleanup;
+ ctlr->transfer_one = bcm2835_spi_transfer_one;
+ ctlr->handle_err = bcm2835_spi_handle_err;
+ ctlr->prepare_message = bcm2835_spi_prepare_message;
+ ctlr->dev = dev;
+
+ spi_controller_set_devdata(ctlr, bs);
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return dev_err_probe(dev, PTR_ERR(iores),
+ "failed to get io-resource\n");
+ bs->regs = IOMEM(iores->start);
+
+ bs->clk = clk_get_enabled(dev, NULL);
+ if (IS_ERR(bs->clk))
+ return dev_err_probe(dev, PTR_ERR(bs->clk),
+ "could not get clk\n");
+
+ ctlr->max_speed_hz = clk_get_rate(bs->clk) / 2;
+
+ bs->clk_hz = clk_get_rate(bs->clk);
+
+ /* initialise the hardware with the default polarities */
+ bcm2835_wr(bs, BCM2835_SPI_CS,
+ BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX);
+
+ err = spi_register_controller(ctlr);
+ if (err)
+ return dev_err_probe(dev, err,
+ "could not register SPI controller\n");
+
+ return 0;
+}
+
+static const struct of_device_id bcm2835_spi_match[] = {
+ { .compatible = "brcm,bcm2835-spi", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, bcm2835_spi_match);
+
+static struct driver bcm2835_spi_driver = {
+ .name = DRV_NAME,
+ .of_compatible = DRV_OF_COMPAT(bcm2835_spi_match),
+ .probe = bcm2835_spi_probe,
+};
+coredevice_platform_driver(bcm2835_spi_driver);
+
+MODULE_DESCRIPTION("SPI controller driver for Broadcom BCM2835");
+MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply [flat|nested] 12+ messages in thread