Difference between revisions of "Zephyr drivers"
m (→^ Zephyr interrupt set up code) |
m (→^ Zephyr interrupt set up code) |
||
Line 377: | Line 377: | ||
<pre> | <pre> | ||
+ | 5 / { | ||
+ | 6 aliases { | ||
+ | 9 sensorinterrupt1 = &iis2dhint1; | ||
+ | 11 }; | ||
+ | 12 }; | ||
+ | |||
65 / { | 65 / { | ||
66 sensor_interrupts { | 66 sensor_interrupts { |
Revision as of 15:49, 25 November 2022
Contents
- 1 ^ OVERVIEW
- 2 ^ Example Zephyr drivers
- 3 ^ Zephyr Kernel Level Device Support
- 4 ^ Understanding Zephyr SPI API
- 5 ^ Zephyr logging
- 6 ^ LPC55S69 flexcomm2 and hs_lspi pins conflict
- 7 ^ STMicro device context data structure
- 8 ^ Zephyr interrupt set up code
- 9 ^ C Preprocessor macros
- 10 ^ Sensors to seek for Zephyr demo exercising
^ OVERVIEW
2022 Q4 notes on Zephyr RTOS drivers, driver frameworks and driver design. Current projects include work to extend KX132-1211 Zephyr out-of-tree driver, plus work to craft an IIS2DH driver which supports in Zephyr RTOS context both I2C and SPI bus connections for this sensor.
^ Example Zephyr drivers
Example apps here offer varying amounts of insight into Zephyr RTOS' framework(s) for device driver development. Of particular interest 2022 Q4 are the ways in which developers use Zephyr device tree macros to obtain pointers to devices, in their mostly C based code, at compile time. Though not fully explained in Zephyr's extensive documentation it appears that device pointers which can be correctly passed to and used with API function device_is_ready() and macro DEVICE_DT_GET() involve pairings of macros, which must be expanded prior to these calls in order to compile correctly.
ted@localhost1:~/projects-sandbox/workspace-out-of-tree/zephyr$ grep -nr init_res ./* | grep -v reset | grep init_res ./include/zephyr/device.h:416: unsigned int init_res : 8; ./include/zephyr/net/dns_resolve.h:459:void dns_init_resolver(void); ./include/zephyr/net/dns_resolve.h:462:#define dns_init_resolver(...) ./kernel/device.c:84: dev->state->init_res = rc; ./kernel/device.c:161: return dev->state->initialized && (dev->state->init_res == 0U); ./subsys/net/ip/net_core.c:476: dns_init_resolver(); ./subsys/net/lib/dns/resolve.c:1428:void dns_init_resolver(void) What's going on in LM77 temperature sensor Kconfig file, on line 10? Interesting Kconfig stanza contains $(dt_compat_enabled,lm77):
Zephyr sensor API routine `device_is_ready()` is prototyped in zephyr/include/zephyr/device.h. A search for `device_is_ready()` implementation gives:
ted@localhost1:~/projects-sandbox/workspace-out-of-tree/zephyr$ grep -nr z_device_is_ready ./* ./include/zephyr/device.h:782:bool z_device_is_ready(const struct device *dev); ./include/zephyr/device.h:803: return z_device_is_ready(dev); ./kernel/device.c:108: if (z_device_is_ready(dev) && (dev->name == name)) { ./kernel/device.c:114: if (z_device_is_ready(dev) && (strcmp(name, dev->name) == 0)) { ./kernel/device.c:151:bool z_device_is_ready(const struct device *dev)
The body of this API routine is:
bool z_device_is_ready(const struct device *dev) { /* * if an invalid device pointer is passed as argument, this call * reports the `device` as not ready for usage. */ if (dev == NULL) { return false; } return dev->state->initialized && (dev->state->init_res == 0U); }
Having some trouble, however, finding places where `init_res` and `initialized` data members of device state structure get initialized. Looked recursively in both modules and zephyr source tree. Looks like there may be one place where this assignment takes place, in `zephyr/kernel/device.c`:
ted@localhost1:~/projects-sandbox/workspace-out-of-tree/zephyr$ grep -nr init_res ./* | grep -v reset | grep init_res ./include/zephyr/device.h:416: unsigned int init_res : 8; ./include/zephyr/net/dns_resolve.h:459:void dns_init_resolver(void); ./include/zephyr/net/dns_resolve.h:462:#define dns_init_resolver(...) ./kernel/device.c:84: dev->state->init_res = rc; ./kernel/device.c:161: return dev->state->initialized && (dev->state->init_res == 0U); ./subsys/net/ip/net_core.c:476: dns_init_resolver(); ./subsys/net/lib/dns/resolve.c:1428:void dns_init_resolver(void)
Routine which iterates over and initializes all devices needing initialization prior to app code starting:
54 void z_sys_init_run_level(int32_t level) 55 { 56 static const struct init_entry *levels[] = { 57 __init_PRE_KERNEL_1_start, 58 __init_PRE_KERNEL_2_start, 59 __init_POST_KERNEL_start, 60 __init_APPLICATION_start, 61 #ifdef CONFIG_SMP 62 __init_SMP_start, 63 #endif 64 /* End marker */ 65 __init_end, 66 }; 67 const struct init_entry *entry; 68 69 for (entry = levels[level]; entry < levels[level+1]; entry++) { 70 const struct device *dev = entry->dev; 71 int rc = entry->init(dev); 72 73 if (dev != NULL) { 74 /* Mark device initialized. If initialization 75 * failed, record the error condition. 76 */ 77 if (rc != 0) { 78 if (rc < 0) { 79 rc = -rc; 80 } 81 if (rc > UINT8_MAX) { 82 rc = UINT8_MAX; 83 } 84 dev->state->init_res = rc; 85 } 86 dev->state->initialized = true; 87 } 88 } 89 }
Sensor init priority:
ted@localhost1:~/projects-sandbox/workspace-for-nexus/zephyr$ grep -nr SENSOR_INIT_PRIORITY ./* | grep -v CONFIG_SENSOR | grep SENSOR_INIT_PRIORITY ./boards/arm/thingy52_nrf52832/board.c:33:#error BOARD_CCS_VDD_PWR_CTRL_INIT_PRIORITY must be lower than SENSOR_INIT_PRIORITY ./boards/arm/thingy52_nrf52832/Kconfig:15: BOARD_VDD_PWR_CTRL_INIT_PRIORITY, but smaller than SENSOR_INIT_PRIORITY. ./drivers/sensor/Kconfig:17:config SENSOR_INIT_PRIORITY
^ Zephyr Kernel Level Device Support
Interesting code excerpt from `zephy/kernel/device.c`, this following routine is the implementation behind a wrapper like function `device_is_read()`. But this routine, and sensibly so, doesn't say anything about how a given device is initialized. It only looks at two data members of a device instance data structure:
150 151 bool z_device_is_ready(const struct device *dev) 152 { 153 /* 154 * if an invalid device pointer is passed as argument, this call 155 * reports the `device` as not ready for usage. 156 */ 157 if (dev == NULL) { 158 return false; 159 } 160 161 return dev->state->initialized && (dev->state->init_res == 0U); 162 } 163
On a somewhat unrelated note, a couple of important Zephyr documents, including Zephyr device tree macros used to obtain node identifiers:
- https://docs.zephyrproject.org/latest/build/dts/dt-vs-kconfig.html Device Tree versus Kconfig, a fairly short read
^ Understanding Zephyr SPI API
To make use of Zephyr API for SPI bus, we must in part understand how each of the three parameters passed to spi_read() and spi_write() are defined. Further, when we have a sensor with which we communicate in our Zephyr based app we need to understand how parts of this sensor's data structures in the firmware relate to the SPI bus to which the sensor is connected. Our sensor driver's code which talks with the sensor via SPI must pass certain SPI bus related pointers to Zephyr's SPI Application Programmers' Interface.
Another important SPI defining resource in Zephyr RTOS is the header file spi.h#L379. This file defines the small data structure named `spi_dt_spec`. It gives us the data member names for a SPI bus peripheral, as Zephyr sees them.
2022-11-25
The best Zephyr SPI instructions we find so far are in the sample app `spi_bitbang`. The source file main.c there, sets up RX and TX buffers, and some Zephyr driver structures about these. There is an interesting design pattern in Zephyr's SPI API: receive and transmit buffer pairs can be made multiple, in two arrays of these same buffers. The SPI interface function `spi_tranceive()`, about which spi_write() and spi_read() wrap, steps through the count of write buffers and receive buffers it is passed from calling code. In this way, a careful choosing of sets of buffers permits app developers to express more complex SPI communications by defining the buffers with needed lengths, and then call the SPI API of Zephyr RTOS just once.
^ Zephyr logging
Note, in a Zephyr project or an out-of-tree driver, what we have learned here is that LOG_MODULE_REGISTER(project_name) must be called in one -- possibly the first -- of the project's source files. Any other dot c files from which developers need logging capability must include the function like macro LOG_MODULE_DECLARE().
- https://docs.zephyrproject.org/3.0.0/reference/logging/index.html#logging-in-a-module
- https://docs.zephyrproject.org/3.0.0/reference/logging/index.html#c.LOG_MODULE_DECLARE
^ LPC55S69 flexcomm2 and hs_lspi pins conflict
zephyr/boards/arm/lpcxpresso55s69/lpcxpresso55s69-pinctrl.dtsi lines 21 - 28
Note that in spite of the comment above in the dot dtsi file from NXP, SPI communications with an IIS2DH sensor work while UART2 is also in use and working - TMH
^ STMicro device context data structure
Symbol `stmdev_ctx_t` is defined in file modules/hal/st/sensor/stmemsc/iis2dh_STdC/driver/iis2dh_reg.c. On github this file as of 2022-11-18 located at https://github.com/zephyrproject-rtos/hal_st/tree/master/sensor/stmemsc/iis2dh_STdC/driver'
Data structure `stmdev_ctx_t` defined as follows:
104 /** @addtogroup Interfaces_Functions 105 * @brief This section provide a set of functions used to read and 106 * write a generic register of the device. 107 * MANDATORY: return 0 -> no Error. 108 * @{ 109 * 110 */ 111 112 typedef int32_t (*stmdev_write_ptr)(void *, uint8_t, const uint8_t *, uint16_t); 113 typedef int32_t (*stmdev_read_ptr)(void *, uint8_t, uint8_t *, uint16_t); 114 115 typedef struct 116 { 117 /** Component mandatory fields **/ 118 stmdev_write_ptr write_reg; 119 stmdev_read_ptr read_reg; 120 /** Customizable optional pointer **/ 121 void *handle; 122 } stmdev_ctx_t;
Parameter lists for the register write and register read functions are nearly the same, with exception of the third parameter. In the register write function this parameter is qualified with C `const`, as it is only expected to be read by the function using it.
With the above definition, an instance of stmdev_cts_t is created for each device with device tree compatible property of 'st_iis2dh', and is assigned in `zephyr/drivers/sensor/iis2dh/iis2dh-i2c.c` as follows:
38 stmdev_ctx_t iis2dh_i2c_ctx = { 39 .read_reg = (stmdev_read_ptr) iis2dh_i2c_read, 40 .write_reg = (stmdev_write_ptr) iis2dh_i2c_write, 41 }; 42 43 int iis2dh_i2c_init(const struct device *dev) 44 { 45 struct iis2dh_data *data = dev->data; 46 const struct iis2dh_device_config *config = dev->config; 47 48 if (!device_is_ready(config->i2c.bus)) { 49 LOG_ERR("Bus device is not ready"); 50 return -ENODEV; 51 } 52 53 data->ctx = &iis2dh_i2c_ctx; 54 data->ctx->handle = (void *)dev; 55 56 return 0; 57 } 58 #endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */
The above init function is called from iis2dh.c:
232 static int iis2dh_init_interface(const struct device *dev) 233 { 234 int res; 235 236 #if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) 237 res = iis2dh_spi_init(dev); 238 if (res) { 239 return res; 240 } 241 #elif DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) 242 res = iis2dh_i2c_init(dev); 243 if (res) { 244 return res; 245 } 246 #else 247 #error "BUS MACRO NOT DEFINED IN DTS" 248 #endif 249 250 return 0; 251 } 252 253 static int iis2dh_init(const struct device *dev) 254 { 255 struct iis2dh_data *iis2dh = dev->data; 256 const struct iis2dh_device_config *cfg = dev->config; 257 uint8_t wai; 258 259 if (iis2dh_init_interface(dev)) { 260 return -EINVAL; 261 }
Pointer *dev has a member named 'data', which in turn has a member named 'ctx', which in turn gets assigned the address of a not named instance of `iis2dh_i2c_ctx`. This means that we have `dev->data->ctx->read_reg()` and `dev->data->ctx->write_reg` on successful completion of routine iis2dh_i2c_init(). This is reflected in modules/hal/st/sensor/stmemsc/iis2dh_STdC/driver/iis2dh_reg.c, in the two generalized register read and write functions:
39 /** 40 * @brief Read generic device register 41 * 42 * @param ctx read / write interface definitions(ptr) 43 * @param reg register to read 44 * @param data pointer to buffer that store the data read(ptr) 45 * @param len number of consecutive register to read 46 * @retval interface status (MANDATORY: return 0 -> no Error) 47 * 48 */ 49 int32_t iis2dh_read_reg(stmdev_ctx_t *ctx, uint8_t reg, uint8_t *data, 50 uint16_t len) 51 { 52 int32_t ret; 53 54 ret = ctx->read_reg(ctx->handle, reg, data, len); 55 56 return ret; 57 } 58 59 /** 60 * @brief Write generic device register 61 * 62 * @param ctx read / write interface definitions(ptr) 63 * @param reg register to write 64 * @param data pointer to data to write in register reg(ptr) 65 * @param len number of consecutive register to write 66 * @retval interface status (MANDATORY: return 0 -> no Error) 67 * 68 */ 69 int32_t iis2dh_write_reg(stmdev_ctx_t *ctx, uint8_t reg, 70 uint8_t *data, 71 uint16_t len) 72 { 73 int32_t ret; 74 75 ret = ctx->write_reg(ctx->handle, reg, data, len); 76 77 return ret; 78 }
. . .
https://docs.zephyrproject.org/latest/hardware/peripherals/i2c.html#c.i2c_burst_read_dt
^ Zephyr interrupt set up code
In Zephyr RTOS context, triggers are interrupts which are generated by sensors. Zephyr constructs a notion of "trigger types" and "trigger channels". Some pages from Zephyr 3.2.0 documentation:
- https://docs.zephyrproject.org/latest/hardware/peripherals/sensor.html#triggers
- https://docs.zephyrproject.org/latest/hardware/peripherals/sensor.html#c.sensor_trigger_type
This section created to help address a challenge we have, setting up a sensor interrupt or Zephyr "trigger" in KX132 driver. While unsure, there is suspicion that the `int_gpio` member of the sensor's `config` data structure may not be getting assigned a GPIO port at all, in an early Zephyr init level at boot time. In a diagnostic, `cfg->int_gpio.port->name` shows as "=&", which does not look like a valid port name.
KX132's config data structure includes this snippet of code:
58 uint8_t pm; 59 #ifdef CONFIG_KX132_TRIGGER 60 #warning "KX132 1211 driver - compiling gpio_dt_spec instance in struct 'kx132_device_config'" 61 // # REF https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/gpio.h#L271 62 struct gpio_dt_spec int_gpio; 63 #endif /* CONFIG_KX132_TRIGGER */ 64 };
In a different sample app, iis2dh-driver-demo, we were able to set up a trigger per the code from Zephyr's `samples/basic/button` app. The early declarative elements to prepare to use a GPIO pin on the MCU, as an interrupt input line connected to a sensor, are in this sample:
127 #include <zephyr/drivers/gpio.h> // needed to provide GPIO_DT_SPEC_GET_OR Zephyr device tree macro 128 129 #define SENSOR_INT1_NODE DT_ALIAS(sensorinterrupt1) 130 #if !DT_NODE_HAS_STATUS(SENSOR_INT1_NODE, okay) 131 #error "- DEV 1108 - Could not find in device tree source any sensor interrupt type node!" 132 #endif 133 //static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0}); 134 static const struct gpio_dt_spec interrupt_line_1 = GPIO_DT_SPEC_GET_OR(SENSOR_INT1_NODE, gpios, {0}); 135 136 static struct gpio_callback sensor_interrupt_1_cb_data;
Device tree overlay expresses the above referenced DTS node alias:
5 / { 6 aliases { 9 sensorinterrupt1 = &iis2dhint1; 11 }; 12 }; 65 / { 66 sensor_interrupts { 67 compatible = "gpio-keys"; 68 iis2dhint1: iis2dh_int1 { 69 gpios = < &gpio1 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; 70 }; 71 /* ... other buttons ... */ 72 }; 73 };
In a driver use case, the interrupt line(s) or pins themselves likely won't require a DTS node alias. These GPIO pins will be expressed as values in the sensor's device tree node. The property will be one of `irq-gpios`, `drdy-gpios` and similar, depending on the given sensor's device tree bindings file. Of course, the port controlling the given pins must somewhere in device tree or DTS overlay files be enabled.
Zephyr interrupt set up code from button sample app . . .
MODULE_ID__THREAD_IIS2DH, sensor->state->init_res); // 'button' from samples/basic/button becomes 'interrupt_line_1': // STEP config step if (!device_is_ready(interrupt_line_1.port)) { printk("Error: sensor interrupt 1 port '%s' detected as not ready\n", interrupt_line_1.port->name); // return; } // STEP config step rstatus = gpio_pin_configure_dt(&interrupt_line_1, GPIO_INPUT); if (rstatus != 0) { printk("Error %d: failed to configure %s pin %d\n", rstatus, interrupt_line_1.port->name, interrupt_line_1.pin); // return; } // STEP config step rstatus = gpio_pin_interrupt_configure_dt(&interrupt_line_1, GPIO_INT_EDGE_TO_ACTIVE); if (rstatus != 0) { printk("Error %d: failed to configure interrupt on %s pin %d\n", rstatus, interrupt_line_1.port->name, interrupt_line_1.pin); // return; } // STEP config step // gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin)); // gpio_add_callback(button.port, &button_cb_data); // printk("Set up button at %s pin %d\n", button.port->name, button.pin); gpio_init_callback(&sensor_interrupt_1_cb_data, sensor_interrupt_1_cb, BIT(interrupt_line_1.pin)); gpio_add_callback(interrupt_line_1.port, &sensor_interrupt_1_cb_data); printk("Set up button at %s pin %d\n", interrupt_line_1.port->name, interrupt_line_1.pin);
^ C Preprocessor macros
^ Sensors to seek for Zephyr demo exercising
[ ] MCP9808 I2C based temperature sensor, datasheet, long lead times at Mouser