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