1*ebb398aeSDavid Lechner // SPDX-License-Identifier: GPL-2.0-only 2*ebb398aeSDavid Lechner /* 3*ebb398aeSDavid Lechner * Copyright (C) 2024 Analog Devices Inc. 4*ebb398aeSDavid Lechner * Copyright (C) 2024 BayLibre, SAS 5*ebb398aeSDavid Lechner * 6*ebb398aeSDavid Lechner * Generic PWM trigger for SPI offload. 7*ebb398aeSDavid Lechner */ 8*ebb398aeSDavid Lechner 9*ebb398aeSDavid Lechner #include <linux/platform_device.h> 10*ebb398aeSDavid Lechner #include <linux/pwm.h> 11*ebb398aeSDavid Lechner #include <linux/mod_devicetable.h> 12*ebb398aeSDavid Lechner #include <linux/spi/offload/provider.h> 13*ebb398aeSDavid Lechner #include <linux/types.h> 14*ebb398aeSDavid Lechner 15*ebb398aeSDavid Lechner struct spi_offload_trigger_pwm_state { 16*ebb398aeSDavid Lechner struct device *dev; 17*ebb398aeSDavid Lechner struct pwm_device *pwm; 18*ebb398aeSDavid Lechner }; 19*ebb398aeSDavid Lechner 20*ebb398aeSDavid Lechner static bool spi_offload_trigger_pwm_match(struct spi_offload_trigger *trigger, 21*ebb398aeSDavid Lechner enum spi_offload_trigger_type type, 22*ebb398aeSDavid Lechner u64 *args, u32 nargs) 23*ebb398aeSDavid Lechner { 24*ebb398aeSDavid Lechner if (nargs) 25*ebb398aeSDavid Lechner return false; 26*ebb398aeSDavid Lechner 27*ebb398aeSDavid Lechner return type == SPI_OFFLOAD_TRIGGER_PERIODIC; 28*ebb398aeSDavid Lechner } 29*ebb398aeSDavid Lechner 30*ebb398aeSDavid Lechner static int spi_offload_trigger_pwm_validate(struct spi_offload_trigger *trigger, 31*ebb398aeSDavid Lechner struct spi_offload_trigger_config *config) 32*ebb398aeSDavid Lechner { 33*ebb398aeSDavid Lechner struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger); 34*ebb398aeSDavid Lechner struct spi_offload_trigger_periodic *periodic = &config->periodic; 35*ebb398aeSDavid Lechner struct pwm_waveform wf = { }; 36*ebb398aeSDavid Lechner int ret; 37*ebb398aeSDavid Lechner 38*ebb398aeSDavid Lechner if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC) 39*ebb398aeSDavid Lechner return -EINVAL; 40*ebb398aeSDavid Lechner 41*ebb398aeSDavid Lechner if (!periodic->frequency_hz) 42*ebb398aeSDavid Lechner return -EINVAL; 43*ebb398aeSDavid Lechner 44*ebb398aeSDavid Lechner wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz); 45*ebb398aeSDavid Lechner /* REVISIT: 50% duty-cycle for now - may add config parameter later */ 46*ebb398aeSDavid Lechner wf.duty_length_ns = wf.period_length_ns / 2; 47*ebb398aeSDavid Lechner 48*ebb398aeSDavid Lechner ret = pwm_round_waveform_might_sleep(st->pwm, &wf); 49*ebb398aeSDavid Lechner if (ret < 0) 50*ebb398aeSDavid Lechner return ret; 51*ebb398aeSDavid Lechner 52*ebb398aeSDavid Lechner periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wf.period_length_ns); 53*ebb398aeSDavid Lechner 54*ebb398aeSDavid Lechner return 0; 55*ebb398aeSDavid Lechner } 56*ebb398aeSDavid Lechner 57*ebb398aeSDavid Lechner static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger *trigger, 58*ebb398aeSDavid Lechner struct spi_offload_trigger_config *config) 59*ebb398aeSDavid Lechner { 60*ebb398aeSDavid Lechner struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger); 61*ebb398aeSDavid Lechner struct spi_offload_trigger_periodic *periodic = &config->periodic; 62*ebb398aeSDavid Lechner struct pwm_waveform wf = { }; 63*ebb398aeSDavid Lechner 64*ebb398aeSDavid Lechner if (config->type != SPI_OFFLOAD_TRIGGER_PERIODIC) 65*ebb398aeSDavid Lechner return -EINVAL; 66*ebb398aeSDavid Lechner 67*ebb398aeSDavid Lechner if (!periodic->frequency_hz) 68*ebb398aeSDavid Lechner return -EINVAL; 69*ebb398aeSDavid Lechner 70*ebb398aeSDavid Lechner wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz); 71*ebb398aeSDavid Lechner /* REVISIT: 50% duty-cycle for now - may add config parameter later */ 72*ebb398aeSDavid Lechner wf.duty_length_ns = wf.period_length_ns / 2; 73*ebb398aeSDavid Lechner 74*ebb398aeSDavid Lechner return pwm_set_waveform_might_sleep(st->pwm, &wf, false); 75*ebb398aeSDavid Lechner } 76*ebb398aeSDavid Lechner 77*ebb398aeSDavid Lechner static void spi_offload_trigger_pwm_disable(struct spi_offload_trigger *trigger) 78*ebb398aeSDavid Lechner { 79*ebb398aeSDavid Lechner struct spi_offload_trigger_pwm_state *st = spi_offload_trigger_get_priv(trigger); 80*ebb398aeSDavid Lechner struct pwm_waveform wf; 81*ebb398aeSDavid Lechner int ret; 82*ebb398aeSDavid Lechner 83*ebb398aeSDavid Lechner ret = pwm_get_waveform_might_sleep(st->pwm, &wf); 84*ebb398aeSDavid Lechner if (ret < 0) { 85*ebb398aeSDavid Lechner dev_err(st->dev, "failed to get waveform: %d\n", ret); 86*ebb398aeSDavid Lechner return; 87*ebb398aeSDavid Lechner } 88*ebb398aeSDavid Lechner 89*ebb398aeSDavid Lechner wf.duty_length_ns = 0; 90*ebb398aeSDavid Lechner 91*ebb398aeSDavid Lechner ret = pwm_set_waveform_might_sleep(st->pwm, &wf, false); 92*ebb398aeSDavid Lechner if (ret < 0) 93*ebb398aeSDavid Lechner dev_err(st->dev, "failed to disable PWM: %d\n", ret); 94*ebb398aeSDavid Lechner } 95*ebb398aeSDavid Lechner 96*ebb398aeSDavid Lechner static const struct spi_offload_trigger_ops spi_offload_trigger_pwm_ops = { 97*ebb398aeSDavid Lechner .match = spi_offload_trigger_pwm_match, 98*ebb398aeSDavid Lechner .validate = spi_offload_trigger_pwm_validate, 99*ebb398aeSDavid Lechner .enable = spi_offload_trigger_pwm_enable, 100*ebb398aeSDavid Lechner .disable = spi_offload_trigger_pwm_disable, 101*ebb398aeSDavid Lechner }; 102*ebb398aeSDavid Lechner 103*ebb398aeSDavid Lechner static void spi_offload_trigger_pwm_release(void *data) 104*ebb398aeSDavid Lechner { 105*ebb398aeSDavid Lechner pwm_disable(data); 106*ebb398aeSDavid Lechner } 107*ebb398aeSDavid Lechner 108*ebb398aeSDavid Lechner static int spi_offload_trigger_pwm_probe(struct platform_device *pdev) 109*ebb398aeSDavid Lechner { 110*ebb398aeSDavid Lechner struct device *dev = &pdev->dev; 111*ebb398aeSDavid Lechner struct spi_offload_trigger_info info = { 112*ebb398aeSDavid Lechner .fwnode = dev_fwnode(dev), 113*ebb398aeSDavid Lechner .ops = &spi_offload_trigger_pwm_ops, 114*ebb398aeSDavid Lechner }; 115*ebb398aeSDavid Lechner struct spi_offload_trigger_pwm_state *st; 116*ebb398aeSDavid Lechner struct pwm_state state; 117*ebb398aeSDavid Lechner int ret; 118*ebb398aeSDavid Lechner 119*ebb398aeSDavid Lechner st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); 120*ebb398aeSDavid Lechner if (!st) 121*ebb398aeSDavid Lechner return -ENOMEM; 122*ebb398aeSDavid Lechner 123*ebb398aeSDavid Lechner info.priv = st; 124*ebb398aeSDavid Lechner st->dev = dev; 125*ebb398aeSDavid Lechner 126*ebb398aeSDavid Lechner st->pwm = devm_pwm_get(dev, NULL); 127*ebb398aeSDavid Lechner if (IS_ERR(st->pwm)) 128*ebb398aeSDavid Lechner return dev_err_probe(dev, PTR_ERR(st->pwm), "failed to get PWM\n"); 129*ebb398aeSDavid Lechner 130*ebb398aeSDavid Lechner /* init with duty_cycle = 0, output enabled to ensure trigger off */ 131*ebb398aeSDavid Lechner pwm_init_state(st->pwm, &state); 132*ebb398aeSDavid Lechner state.enabled = true; 133*ebb398aeSDavid Lechner 134*ebb398aeSDavid Lechner ret = pwm_apply_might_sleep(st->pwm, &state); 135*ebb398aeSDavid Lechner if (ret < 0) 136*ebb398aeSDavid Lechner return dev_err_probe(dev, ret, "failed to apply PWM state\n"); 137*ebb398aeSDavid Lechner 138*ebb398aeSDavid Lechner ret = devm_add_action_or_reset(dev, spi_offload_trigger_pwm_release, st->pwm); 139*ebb398aeSDavid Lechner if (ret) 140*ebb398aeSDavid Lechner return ret; 141*ebb398aeSDavid Lechner 142*ebb398aeSDavid Lechner return devm_spi_offload_trigger_register(dev, &info); 143*ebb398aeSDavid Lechner } 144*ebb398aeSDavid Lechner 145*ebb398aeSDavid Lechner static const struct of_device_id spi_offload_trigger_pwm_of_match_table[] = { 146*ebb398aeSDavid Lechner { .compatible = "pwm-trigger" }, 147*ebb398aeSDavid Lechner { } 148*ebb398aeSDavid Lechner }; 149*ebb398aeSDavid Lechner MODULE_DEVICE_TABLE(of, spi_offload_trigger_pwm_of_match_table); 150*ebb398aeSDavid Lechner 151*ebb398aeSDavid Lechner static struct platform_driver spi_offload_trigger_pwm_driver = { 152*ebb398aeSDavid Lechner .driver = { 153*ebb398aeSDavid Lechner .name = "pwm-trigger", 154*ebb398aeSDavid Lechner .of_match_table = spi_offload_trigger_pwm_of_match_table, 155*ebb398aeSDavid Lechner }, 156*ebb398aeSDavid Lechner .probe = spi_offload_trigger_pwm_probe, 157*ebb398aeSDavid Lechner }; 158*ebb398aeSDavid Lechner module_platform_driver(spi_offload_trigger_pwm_driver); 159*ebb398aeSDavid Lechner 160*ebb398aeSDavid Lechner MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>"); 161*ebb398aeSDavid Lechner MODULE_DESCRIPTION("Generic PWM trigger"); 162*ebb398aeSDavid Lechner MODULE_LICENSE("GPL"); 163