blob: 6a2d8cc10e2f959f610baa6b759c475dcc6e7c08 [file] [edit]
/*
* Copyright (c) 2026 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT gpio_buzzer
#include <zephyr/drivers/buzzer.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(buzzer_gpio, CONFIG_BUZZER_LOG_LEVEL);
struct buzzer_gpio_config {
struct gpio_dt_spec gpio;
};
struct buzzer_gpio_data {
const struct device *dev;
struct k_work_delayable stop_work;
struct k_mutex lock;
uint8_t volume_percent;
};
static void buzzer_gpio_stop_work(struct k_work *work)
{
struct k_work_delayable *dw = k_work_delayable_from_work(work);
struct buzzer_gpio_data *data =
CONTAINER_OF(dw, struct buzzer_gpio_data, stop_work);
const struct buzzer_gpio_config *cfg = data->dev->config;
int ret;
ret = gpio_pin_set_dt(&cfg->gpio, 0);
if (ret < 0) {
LOG_ERR("Failed to silence buzzer (%d)", ret);
}
}
static void buzzer_gpio_cancel_stop(struct buzzer_gpio_data *data)
{
struct k_work_sync sync;
k_work_cancel_delayable_sync(&data->stop_work, &sync);
}
static int buzzer_gpio_tone(const struct device *dev, uint32_t freq_hz,
uint32_t duration_ms)
{
const struct buzzer_gpio_config *cfg = dev->config;
struct buzzer_gpio_data *data = dev->data;
int ret;
bool active;
k_mutex_lock(&data->lock, K_FOREVER);
buzzer_gpio_cancel_stop(data);
active = (freq_hz != BUZZER_FREQ_REST) && (data->volume_percent != 0U);
/*
* Active buzzers have no programmatic frequency control. Any
* non-zero request turns them on at their built-in resonance;
* zero is silence.
*/
ret = gpio_pin_set_dt(&cfg->gpio, active ? 1 : 0);
if (ret < 0) {
k_mutex_unlock(&data->lock);
return ret;
}
if (active && duration_ms != BUZZER_DURATION_FOREVER) {
ret = k_work_schedule(&data->stop_work, K_MSEC(duration_ms));
if (ret < 0) {
int silence_ret = gpio_pin_set_dt(&cfg->gpio, 0);
if (silence_ret < 0) {
LOG_ERR("Failed to silence buzzer (%d)", silence_ret);
}
k_mutex_unlock(&data->lock);
return ret;
}
}
k_mutex_unlock(&data->lock);
return 0;
}
static int buzzer_gpio_set_volume(const struct device *dev, uint8_t percent)
{
const struct buzzer_gpio_config *cfg = dev->config;
struct buzzer_gpio_data *data = dev->data;
int ret;
if (percent > BUZZER_VOLUME_MAX) {
return -EINVAL;
}
k_mutex_lock(&data->lock, K_FOREVER);
if (percent == 0U) {
data->volume_percent = 0U;
buzzer_gpio_cancel_stop(data);
ret = gpio_pin_set_dt(&cfg->gpio, 0);
k_mutex_unlock(&data->lock);
return ret;
}
/*
* Active buzzers cannot do intermediate volumes; treat any
* non-zero request as the single audible level the hardware
* supports.
*/
data->volume_percent = BUZZER_VOLUME_MAX;
k_mutex_unlock(&data->lock);
return 0;
}
static int buzzer_gpio_beep(const struct device *dev, uint32_t duration_ms)
{
/*
* Active buzzers run at a hardware-fixed frequency, so "beep" is
* the same as "tone with any non-zero frequency": just turn the
* buzzer on for the requested duration.
*/
return buzzer_gpio_tone(dev, 1U, duration_ms);
}
static int buzzer_gpio_stop(const struct device *dev)
{
const struct buzzer_gpio_config *cfg = dev->config;
struct buzzer_gpio_data *data = dev->data;
int ret;
k_mutex_lock(&data->lock, K_FOREVER);
buzzer_gpio_cancel_stop(data);
ret = gpio_pin_set_dt(&cfg->gpio, 0);
k_mutex_unlock(&data->lock);
return ret;
}
static int buzzer_gpio_init(const struct device *dev)
{
const struct buzzer_gpio_config *cfg = dev->config;
struct buzzer_gpio_data *data = dev->data;
int ret;
if (!gpio_is_ready_dt(&cfg->gpio)) {
LOG_ERR_DEVICE_NOT_READY(cfg->gpio.port);
return -ENODEV;
}
ret = gpio_pin_configure_dt(&cfg->gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
return ret;
}
data->dev = dev;
data->volume_percent = BUZZER_VOLUME_MAX;
k_mutex_init(&data->lock);
k_work_init_delayable(&data->stop_work, buzzer_gpio_stop_work);
return 0;
}
static DEVICE_API(buzzer, buzzer_gpio_api) = {
.tone = buzzer_gpio_tone,
.set_volume = buzzer_gpio_set_volume,
.beep = buzzer_gpio_beep,
.stop = buzzer_gpio_stop,
};
#define BUZZER_GPIO_INIT(inst) \
static const struct buzzer_gpio_config buzzer_gpio_cfg_##inst = { \
.gpio = GPIO_DT_SPEC_INST_GET(inst, gpios), \
}; \
static struct buzzer_gpio_data buzzer_gpio_data_##inst; \
DEVICE_DT_INST_DEFINE(inst, buzzer_gpio_init, NULL, \
&buzzer_gpio_data_##inst, &buzzer_gpio_cfg_##inst, \
POST_KERNEL, CONFIG_BUZZER_INIT_PRIORITY, \
&buzzer_gpio_api);
DT_INST_FOREACH_STATUS_OKAY(BUZZER_GPIO_INIT)