blob: 7000a839459235718db399907ea0fc5824fa42d4 [file]
/* SPDX-License-Identifier: LGPL-2.1-only */
/* SPDX-FileCopyrightText: 2023-2025 Uwe Kleine-König <u.kleine-koenig@baylibre.com> */
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <pwm.h>
#ifdef HAVE_USABLE_LINUX_PWM_H
#include <linux/pwm.h>
#else
#include "uapi-pwm.h"
#endif
#include "pwm-internal.h"
struct pwm_cdev {
struct pwm pwm;
unsigned int hwpwm;
};
struct pwm_chip_cdev {
struct pwm_chip chip;
int fd;
struct pwm_cdev *pwms[];
};
static void pwm_chip_cdev_close(struct pwm_chip *chip)
{
struct pwm_chip_cdev *chip_cdev = container_of(chip, struct pwm_chip_cdev, chip);
size_t i;
for (i = 0; i < chip->npwm; ++i)
if (chip_cdev->pwms[i]) {
ioctl(chip_cdev->fd, PWM_IOCTL_FREE, i);
free(chip_cdev->pwms[i]);
}
close(chip_cdev->fd);
free(chip_cdev);
}
static struct pwm *pwm_chip_cdev_get_pwm(struct pwm_chip *chip,
unsigned int offset)
{
struct pwm_chip_cdev *chip_cdev = container_of(chip, struct pwm_chip_cdev, chip);
struct pwm_cdev *pwm_cdev;
struct pwm *pwm;
int ret;
if (chip_cdev->pwms[offset])
return &chip_cdev->pwms[offset]->pwm;
pwm_cdev = calloc(1, sizeof(*pwm_cdev));
if (!pwm_cdev)
return NULL;
pwm = &pwm_cdev->pwm;
pwm->chip = chip;
pwm_cdev->hwpwm = offset;
ret = ioctl(chip_cdev->fd, PWM_IOCTL_REQUEST, offset);
if (ret) {
free(pwm_cdev);
return NULL;
}
chip_cdev->pwms[offset] = pwm_cdev;
return pwm;
}
static int pwm_chip_cdev_round_waveform(struct pwm *pwm,
struct pwm_waveform *wf)
{
struct pwm_cdev *pwm_cdev = container_of(pwm, struct pwm_cdev, pwm);
struct pwm_chip_cdev *chip_cdev = container_of(pwm->chip, struct pwm_chip_cdev, chip);
struct pwmchip_waveform cwf = {
.hwpwm = pwm_cdev->hwpwm,
.period_length_ns = wf->period_length_ns,
.duty_length_ns = wf->duty_length_ns,
.duty_offset_ns = wf->duty_offset_ns,
};
int ret;
ret = ioctl(chip_cdev->fd, PWM_IOCTL_ROUNDWF, &cwf);
if (ret)
return ret;
*wf = (struct pwm_waveform){
.period_length_ns = cwf.period_length_ns,
.duty_length_ns = cwf.duty_length_ns,
.duty_offset_ns = cwf.duty_offset_ns,
};
return 0;
}
static int pwm_chip_cdev_set_waveform(struct pwm *pwm,
const struct pwm_waveform *wf)
{
struct pwm_cdev *pwm_cdev = container_of(pwm, struct pwm_cdev, pwm);
struct pwm_chip_cdev *chip_cdev = container_of(pwm->chip, struct pwm_chip_cdev, chip);
const struct pwmchip_waveform cwf = {
.hwpwm = pwm_cdev->hwpwm,
.period_length_ns = wf->period_length_ns,
.duty_length_ns = wf->duty_length_ns,
.duty_offset_ns = wf->duty_offset_ns,
};
return ioctl(chip_cdev->fd, PWM_IOCTL_SETROUNDEDWF, &cwf);
}
static int pwm_chip_cdev_set_waveform_exact(struct pwm *pwm,
const struct pwm_waveform *wf)
{
struct pwm_cdev *pwm_cdev = container_of(pwm, struct pwm_cdev, pwm);
struct pwm_chip_cdev *chip_cdev = container_of(pwm->chip, struct pwm_chip_cdev, chip);
const struct pwmchip_waveform cwf = {
.hwpwm = pwm_cdev->hwpwm,
.period_length_ns = wf->period_length_ns,
.duty_length_ns = wf->duty_length_ns,
.duty_offset_ns = wf->duty_offset_ns,
};
return ioctl(chip_cdev->fd, PWM_IOCTL_SETEXACTWF, &cwf);
}
static int pwm_chip_cdev_get_waveform(struct pwm *pwm, struct pwm_waveform *wf)
{
struct pwm_cdev *pwm_cdev = container_of(pwm, struct pwm_cdev, pwm);
struct pwm_chip_cdev *chip_cdev = container_of(pwm->chip, struct pwm_chip_cdev, chip);
const struct pwmchip_waveform cwf = {
.hwpwm = pwm_cdev->hwpwm,
};
int ret;
ret = ioctl(chip_cdev->fd, PWM_IOCTL_GETWF, &cwf);
if (ret)
return ret;
wf->period_length_ns = cwf.period_length_ns;
wf->duty_length_ns = cwf.duty_length_ns;
wf->duty_offset_ns = cwf.duty_offset_ns;
return 0;
}
struct pwm_chip *pwm_chip_cdev_open_by_number(unsigned int num)
{
struct pwm_chip_cdev *chip_cdev;
struct pwm_chip *chip;
int fd, fdnpwm;
ssize_t ret;
unsigned int npwm;
char buf[128];
ret = snprintf(buf, sizeof(buf), "/dev/pwmchip%d", num);
if (ret < 0)
return NULL;
if (ret >= sizeof(buf)) {
/* huh */
errno = EINVAL;
return NULL;
}
fd = open(buf, O_RDWR | O_CLOEXEC);
if (fd < 0)
return NULL;
ret = snprintf(buf, sizeof(buf), "/sys/class/pwm/pwmchip%d/npwm", num);
if (ret < 0) {
close(fd);
return NULL;
}
if (ret >= sizeof(buf)) {
/* huh */
close(fd);
errno = EINVAL;
return NULL;
}
fdnpwm = open(buf, O_RDONLY | O_CLOEXEC);
if (fdnpwm < 0) {
close(fd);
return NULL;
}
ret = read(fdnpwm, buf, sizeof(buf) - 1);
close(fdnpwm);
if (ret < 0) {
close(fd);
return NULL;
}
buf[ret] = '\0';
npwm = atoi(buf);
if (npwm > 128) {
close(fd);
errno = EINVAL;
return NULL;
}
chip_cdev = calloc(1, sizeof(*chip_cdev) + npwm * sizeof(chip_cdev->pwms[0]));
if (!chip_cdev) {
close(fd);
return NULL;
}
chip = &chip_cdev->chip;
chip->close = pwm_chip_cdev_close;
chip->get_pwm = pwm_chip_cdev_get_pwm;
chip->round_waveform = pwm_chip_cdev_round_waveform;
chip->set_waveform = pwm_chip_cdev_set_waveform;
chip->set_waveform_exact = pwm_chip_cdev_set_waveform_exact;
chip->get_waveform = pwm_chip_cdev_get_waveform;
chip->npwm = npwm;
chip_cdev->fd = fd;
return chip;
}