Skip to content

Commit 53bb372

Browse files
ubiedafabiobaltieri
authored andcommitted
sensor: PA3905: Add basic functionality
- Add bus support for SPI (based on RTIO). - Support read/decode API for one-shot reads on the following channels: - SENSOR_CHAN_POS_DX. - SENSOR_CHAN_POS_DY. - SENSOR_CHAN_POS_DXYZ. Signed-off-by: Luis Ubieda <[email protected]>
1 parent 21f839b commit 53bb372

File tree

13 files changed

+814
-0
lines changed

13 files changed

+814
-0
lines changed

drivers/sensor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_subdirectory(microchip)
1919
add_subdirectory(nordic)
2020
add_subdirectory(nuvoton)
2121
add_subdirectory(nxp)
22+
add_subdirectory(pixart)
2223
add_subdirectory(renesas)
2324
add_subdirectory(rohm)
2425
add_subdirectory(seeed)

drivers/sensor/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ source "drivers/sensor/microchip/Kconfig"
105105
source "drivers/sensor/nordic/Kconfig"
106106
source "drivers/sensor/nuvoton/Kconfig"
107107
source "drivers/sensor/nxp/Kconfig"
108+
source "drivers/sensor/pixart/Kconfig"
108109
source "drivers/sensor/renesas/Kconfig"
109110
source "drivers/sensor/rohm/Kconfig"
110111
source "drivers/sensor/seeed/Kconfig"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright (c) 2025 Croxel Inc.
2+
# Copyright (c) 2025 CogniPilot Foundation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
add_subdirectory_ifdef(CONFIG_PAA3905 paa3905)

drivers/sensor/pixart/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) 2025 Croxel Inc.
2+
# Copyright (c) 2025 CogniPilot Foundation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# zephyr-keep-sorted-start
6+
source "drivers/sensor/pixart/paa3905/Kconfig"
7+
# zephyr-keep-sorted-stop
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2025 Croxel Inc.
2+
# Copyright (c) 2025 CogniPilot Foundation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
zephyr_library()
6+
zephyr_library_sources(
7+
paa3905.c
8+
paa3905_decoder.c
9+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) 2025 Croxel Inc.
2+
# Copyright (c) 2025 CogniPilot Foundation
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
config PAA3905
6+
bool "Optical Flow Sensor PAA3905"
7+
default y
8+
depends on DT_HAS_PIXART_PAA3905_ENABLED
9+
select SPI
10+
select SPI_RTIO
11+
select SENSOR_ASYNC_API
12+
help
13+
Enable driver for PAA3905 Optical Flow sensor.
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/*
2+
* Copyright (c) 2025 Croxel Inc.
3+
* Copyright (c) 2025 CogniPilot Foundation
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#define DT_DRV_COMPAT pixart_paa3905
9+
10+
#include <zephyr/drivers/sensor.h>
11+
#include <zephyr/drivers/spi.h>
12+
#include <zephyr/rtio/rtio.h>
13+
14+
#include "paa3905.h"
15+
#include "paa3905_bus.h"
16+
#include "paa3905_decoder.h"
17+
18+
#include <zephyr/logging/log.h>
19+
LOG_MODULE_REGISTER(PAA3905, CONFIG_SENSOR_LOG_LEVEL);
20+
21+
/* Helper struct used to set reg-val sequences for op-modes */
22+
struct reg_val_pair {
23+
uint8_t reg;
24+
uint8_t val;
25+
};
26+
27+
static void paa3905_complete_result(struct rtio *ctx,
28+
const struct rtio_sqe *sqe,
29+
void *arg)
30+
{
31+
struct rtio_iodev_sqe *iodev_sqe = (struct rtio_iodev_sqe *)sqe->userdata;
32+
struct rtio_cqe *cqe;
33+
int err = 0;
34+
35+
do {
36+
cqe = rtio_cqe_consume(ctx);
37+
if (cqe != NULL) {
38+
err = cqe->result;
39+
rtio_cqe_release(ctx, cqe);
40+
}
41+
} while (cqe != NULL);
42+
43+
if (err) {
44+
rtio_iodev_sqe_err(iodev_sqe, err);
45+
} else {
46+
rtio_iodev_sqe_ok(iodev_sqe, 0);
47+
}
48+
49+
LOG_DBG("One-shot fetch completed");
50+
}
51+
52+
static void paa3905_submit_one_shot(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
53+
{
54+
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
55+
const struct sensor_chan_spec *const channels = cfg->channels;
56+
struct paa3905_data *data = dev->data;
57+
const size_t num_channels = cfg->count;
58+
uint32_t min_buf_len = sizeof(struct paa3905_encoded_data);
59+
int err;
60+
uint8_t *buf;
61+
uint32_t buf_len;
62+
struct paa3905_encoded_data *edata;
63+
64+
err = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len);
65+
if (err) {
66+
LOG_ERR("Failed to get a read buffer of size %u bytes", min_buf_len);
67+
rtio_iodev_sqe_err(iodev_sqe, err);
68+
return;
69+
}
70+
71+
edata = (struct paa3905_encoded_data *)buf;
72+
73+
err = paa3905_encode(dev, channels, num_channels, buf);
74+
if (err != 0) {
75+
LOG_ERR("Failed to encode sensor data");
76+
rtio_iodev_sqe_err(iodev_sqe, err);
77+
return;
78+
}
79+
80+
struct rtio_sqe *write_sqe = rtio_sqe_acquire(data->rtio.ctx);
81+
struct rtio_sqe *read_sqe = rtio_sqe_acquire(data->rtio.ctx);
82+
struct rtio_sqe *complete_sqe = rtio_sqe_acquire(data->rtio.ctx);
83+
84+
if (!write_sqe || !read_sqe | !complete_sqe) {
85+
LOG_ERR("Failed to acquire RTIO SQEs");
86+
rtio_iodev_sqe_err(iodev_sqe, -ENOMEM);
87+
return;
88+
}
89+
90+
uint8_t val = REG_BURST_READ | REG_SPI_READ_BIT;
91+
92+
rtio_sqe_prep_tiny_write(write_sqe,
93+
data->rtio.iodev,
94+
RTIO_PRIO_HIGH,
95+
&val,
96+
1,
97+
NULL);
98+
write_sqe->flags |= RTIO_SQE_TRANSACTION;
99+
100+
rtio_sqe_prep_read(read_sqe,
101+
data->rtio.iodev,
102+
RTIO_PRIO_HIGH,
103+
edata->buf,
104+
sizeof(edata->buf),
105+
NULL);
106+
read_sqe->flags |= RTIO_SQE_CHAINED;
107+
108+
rtio_sqe_prep_callback_no_cqe(complete_sqe,
109+
paa3905_complete_result,
110+
(void *)dev,
111+
iodev_sqe);
112+
113+
rtio_submit(data->rtio.ctx, 0);
114+
}
115+
116+
static void paa3905_submit(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
117+
{
118+
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
119+
120+
if (!cfg->is_streaming) {
121+
paa3905_submit_one_shot(dev, iodev_sqe);
122+
} else {
123+
LOG_ERR("Streaming not supported");
124+
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
125+
}
126+
}
127+
128+
static DEVICE_API(sensor, paa3905_driver_api) = {
129+
.submit = paa3905_submit,
130+
.get_decoder = paa3905_get_decoder,
131+
};
132+
133+
static int detection_mode_standard(const struct device *dev)
134+
{
135+
int err;
136+
/** As per datasheet, set of values configure the sensor in Standard mode. */
137+
const static struct reg_val_pair paa3905_detection_mode_std[] = {
138+
{.reg = 0x7F, .val = 0x00}, {.reg = 0x51, .val = 0xFF}, {.reg = 0x4E, .val = 0x2A},
139+
{.reg = 0x66, .val = 0x3E}, {.reg = 0x7F, .val = 0x14}, {.reg = 0x7E, .val = 0x71},
140+
{.reg = 0x55, .val = 0x00}, {.reg = 0x59, .val = 0x00}, {.reg = 0x6F, .val = 0x2C},
141+
{.reg = 0x7F, .val = 0x05}, {.reg = 0x4D, .val = 0xAC}, {.reg = 0x4E, .val = 0x32},
142+
{.reg = 0x7F, .val = 0x09}, {.reg = 0x5C, .val = 0xAF}, {.reg = 0x5F, .val = 0xAF},
143+
{.reg = 0x70, .val = 0x08}, {.reg = 0x71, .val = 0x04}, {.reg = 0x72, .val = 0x06},
144+
{.reg = 0x74, .val = 0x3C}, {.reg = 0x75, .val = 0x28}, {.reg = 0x76, .val = 0x20},
145+
{.reg = 0x4E, .val = 0xBF}, {.reg = 0x7F, .val = 0x03}, {.reg = 0x64, .val = 0x14},
146+
{.reg = 0x65, .val = 0x0A}, {.reg = 0x66, .val = 0x10}, {.reg = 0x55, .val = 0x3C},
147+
{.reg = 0x56, .val = 0x28}, {.reg = 0x57, .val = 0x20}, {.reg = 0x4A, .val = 0x2D},
148+
{.reg = 0x4B, .val = 0x2D}, {.reg = 0x4E, .val = 0x4B}, {.reg = 0x69, .val = 0xFA},
149+
{.reg = 0x7F, .val = 0x05}, {.reg = 0x69, .val = 0x1F}, {.reg = 0x47, .val = 0x1F},
150+
{.reg = 0x48, .val = 0x0C}, {.reg = 0x5A, .val = 0x20}, {.reg = 0x75, .val = 0x0F},
151+
{.reg = 0x4A, .val = 0x0F}, {.reg = 0x42, .val = 0x02}, {.reg = 0x45, .val = 0x03},
152+
{.reg = 0x65, .val = 0x00}, {.reg = 0x67, .val = 0x76}, {.reg = 0x68, .val = 0x76},
153+
{.reg = 0x6A, .val = 0xC5}, {.reg = 0x43, .val = 0x00}, {.reg = 0x7F, .val = 0x06},
154+
{.reg = 0x4A, .val = 0x18}, {.reg = 0x4B, .val = 0x0C}, {.reg = 0x4C, .val = 0x0C},
155+
{.reg = 0x4D, .val = 0x0C}, {.reg = 0x46, .val = 0x0A}, {.reg = 0x59, .val = 0xCD},
156+
{.reg = 0x7F, .val = 0x0A}, {.reg = 0x4A, .val = 0x2A}, {.reg = 0x48, .val = 0x96},
157+
{.reg = 0x52, .val = 0xB4}, {.reg = 0x7F, .val = 0x00}, {.reg = 0x5B, .val = 0xA0},
158+
};
159+
160+
for (size_t i = 0 ; i < ARRAY_SIZE(paa3905_detection_mode_std) ; i++) {
161+
err = paa3905_bus_write(dev,
162+
paa3905_detection_mode_std[i].reg,
163+
&paa3905_detection_mode_std[i].val,
164+
1);
165+
if (err) {
166+
LOG_ERR("Failed to write detection mode standard");
167+
return err;
168+
}
169+
}
170+
171+
return 0;
172+
}
173+
174+
static int paa3905_configure(const struct device *dev)
175+
{
176+
const struct paa3905_config *cfg = dev->config;
177+
uint8_t val;
178+
int err;
179+
180+
/* Configure registers for Standard detection mode */
181+
err = detection_mode_standard(dev);
182+
if (err) {
183+
LOG_ERR("Failed to configure detection mode");
184+
return err;
185+
}
186+
187+
val = cfg->resolution;
188+
err = paa3905_bus_write(dev, REG_RESOLUTION, &val, 1);
189+
if (err) {
190+
LOG_ERR("Failed to configure resolution");
191+
return err;
192+
}
193+
194+
if (cfg->led_control) {
195+
struct reg_val_pair led_control_regs[] = {
196+
{.reg = 0x7F, .val = 0x14},
197+
{.reg = 0x6F, .val = 0x0C},
198+
{.reg = 0x7F, .val = 0x00},
199+
};
200+
201+
for (size_t i = 0 ; i < ARRAY_SIZE(led_control_regs) ; i++) {
202+
err = paa3905_bus_write(dev,
203+
led_control_regs[i].reg,
204+
&led_control_regs[i].val,
205+
1);
206+
if (err) {
207+
LOG_ERR("Failed to write LED control reg");
208+
return err;
209+
}
210+
}
211+
}
212+
213+
return 0;
214+
}
215+
216+
static int paa3905_init(const struct device *dev)
217+
{
218+
int err;
219+
uint8_t val;
220+
uint8_t motion_data[6];
221+
222+
/* Power-up sequence delay */
223+
k_sleep(K_MSEC(140));
224+
225+
/* Read Product ID */
226+
err = paa3905_bus_read(dev, REG_PRODUCT_ID, &val, 1);
227+
if (err) {
228+
LOG_ERR("Failed to read Product ID");
229+
return err;
230+
} else if (val != PRODUCT_ID) {
231+
LOG_ERR("Invalid Product ID: 0x%02X", val);
232+
return -EIO;
233+
}
234+
235+
/* Write 0x5A to Power up reset reg */
236+
val = POWER_UP_RESET_VAL;
237+
err = paa3905_bus_write(dev, REG_POWER_UP_RESET, &val, 1);
238+
if (err) {
239+
LOG_ERR("Failed to write Power up reset reg");
240+
return err;
241+
}
242+
/* As per datasheet, writing power-up reset requires 1-ms afterwards. */
243+
k_sleep(K_MSEC(1));
244+
245+
/* Read reg's 0x02-0x06 to clear motion data. */
246+
err = paa3905_bus_read(dev, REG_MOTION, motion_data, sizeof(motion_data));
247+
if (err) {
248+
LOG_ERR("Failed to read motion data");
249+
return err;
250+
}
251+
252+
err = paa3905_configure(dev);
253+
if (err) {
254+
LOG_ERR("Failed to configure");
255+
return err;
256+
}
257+
258+
return 0;
259+
}
260+
261+
#define PAA3905_INIT(inst) \
262+
\
263+
BUILD_ASSERT(DT_PROP(DT_DRV_INST(inst), resolution) > 0 && \
264+
DT_PROP(DT_DRV_INST(inst), resolution) <= 0xFF, \
265+
"Resolution must be in range 1-255"); \
266+
\
267+
RTIO_DEFINE(paa3905_rtio_ctx_##inst, 8, 8); \
268+
SPI_DT_IODEV_DEFINE(paa3905_bus_##inst, \
269+
DT_DRV_INST(inst), \
270+
SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB, \
271+
0U); \
272+
\
273+
static const struct paa3905_config paa3905_cfg_##inst = { \
274+
.resolution = DT_PROP(DT_DRV_INST(inst), resolution), \
275+
.led_control = DT_PROP_OR(DT_DRV_INST(inst), led_control, false), \
276+
}; \
277+
\
278+
static struct paa3905_data paa3905_data_##inst = { \
279+
.rtio = { \
280+
.iodev = &paa3905_bus_##inst, \
281+
.ctx = &paa3905_rtio_ctx_##inst, \
282+
}, \
283+
}; \
284+
\
285+
SENSOR_DEVICE_DT_INST_DEFINE(inst, \
286+
paa3905_init, \
287+
NULL, \
288+
&paa3905_data_##inst, \
289+
&paa3905_cfg_##inst, \
290+
POST_KERNEL, \
291+
CONFIG_SENSOR_INIT_PRIORITY, \
292+
&paa3905_driver_api);
293+
294+
DT_INST_FOREACH_STATUS_OKAY(PAA3905_INIT)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2025 Croxel Inc.
3+
* Copyright (c) 2025 CogniPilot Foundation
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#ifndef ZEPHYR_DRIVERS_SENSOR_PAA3905_H_
9+
#define ZEPHYR_DRIVERS_SENSOR_PAA3905_H_
10+
11+
#include <stdint.h>
12+
#include <zephyr/rtio/rtio.h>
13+
14+
struct paa3905_encoded_data {
15+
struct {
16+
uint64_t timestamp;
17+
uint8_t channels : 3;
18+
} header;
19+
union {
20+
uint8_t buf[14];
21+
struct {
22+
uint8_t motion;
23+
uint8_t observation;
24+
struct {
25+
int16_t x;
26+
int16_t y;
27+
} delta;
28+
uint8_t challenging_conditions;
29+
uint8_t squal;
30+
uint8_t raw_sum;
31+
uint8_t raw_max;
32+
uint8_t raw_min;
33+
uint32_t shutter : 24;
34+
} __attribute__((__packed__));
35+
};
36+
};
37+
38+
struct paa3905_data {
39+
struct {
40+
struct rtio_iodev *iodev;
41+
struct rtio *ctx;
42+
} rtio;
43+
};
44+
45+
struct paa3905_config {
46+
int resolution;
47+
bool led_control;
48+
};
49+
50+
#endif /* ZEPHYR_DRIVERS_SENSOR_PAA3905_H_ */

0 commit comments

Comments
 (0)