1 /*
2  * Copyright (c) 2019 Aurelien Jarno
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT atmel_sam_pwm
8 
9 #include <zephyr/device.h>
10 #include <errno.h>
11 #include <zephyr/drivers/pwm.h>
12 #include <zephyr/drivers/pinctrl.h>
13 #include <zephyr/drivers/clock_control/atmel_sam_pmc.h>
14 #include <soc.h>
15 
16 #include <zephyr/logging/log.h>
17 
18 LOG_MODULE_REGISTER(pwm_sam, CONFIG_PWM_LOG_LEVEL);
19 
20 /* Some SoCs use a slightly different naming scheme */
21 #if !defined(PWMCHNUM_NUMBER) && defined(PWMCH_NUM_NUMBER)
22 #define PWMCHNUM_NUMBER PWMCH_NUM_NUMBER
23 #endif
24 
25 struct sam_pwm_config {
26 	Pwm *regs;
27 	const struct atmel_sam_pmc_config clock_cfg;
28 	const struct pinctrl_dev_config *pcfg;
29 	uint8_t prescaler;
30 	uint8_t divider;
31 };
32 
sam_pwm_get_cycles_per_sec(const struct device * dev,uint32_t channel,uint64_t * cycles)33 static int sam_pwm_get_cycles_per_sec(const struct device *dev,
34 				      uint32_t channel, uint64_t *cycles)
35 {
36 	const struct sam_pwm_config *config = dev->config;
37 	uint8_t prescaler = config->prescaler;
38 	uint8_t divider = config->divider;
39 
40 	*cycles = SOC_ATMEL_SAM_MCK_FREQ_HZ /
41 		  ((1 << prescaler) * divider);
42 
43 	return 0;
44 }
45 
sam_pwm_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)46 static int sam_pwm_set_cycles(const struct device *dev, uint32_t channel,
47 			      uint32_t period_cycles, uint32_t pulse_cycles,
48 			      pwm_flags_t flags)
49 {
50 	const struct sam_pwm_config *config = dev->config;
51 
52 	Pwm * const pwm = config->regs;
53 	uint32_t cmr;
54 
55 	if (channel >= PWMCHNUM_NUMBER) {
56 		return -EINVAL;
57 	}
58 
59 	if (period_cycles == 0U) {
60 		return -ENOTSUP;
61 	}
62 
63 	if (period_cycles > 0xffff) {
64 		return -ENOTSUP;
65 	}
66 
67 	/* Select clock A */
68 	cmr = PWM_CMR_CPRE_CLKA;
69 
70 	if ((flags & PWM_POLARITY_MASK) == PWM_POLARITY_NORMAL) {
71 		cmr |= PWM_CMR_CPOL;
72 	}
73 
74 	/* Disable the output if changing polarity (or clock) */
75 	if (pwm->PWM_CH_NUM[channel].PWM_CMR != cmr) {
76 		pwm->PWM_DIS = 1 << channel;
77 
78 		pwm->PWM_CH_NUM[channel].PWM_CMR = cmr;
79 		pwm->PWM_CH_NUM[channel].PWM_CPRD = period_cycles;
80 		pwm->PWM_CH_NUM[channel].PWM_CDTY = pulse_cycles;
81 	} else {
82 		/* Update period and pulse using the update registers, so that the
83 		 * change is triggered at the next PWM period.
84 		 */
85 		pwm->PWM_CH_NUM[channel].PWM_CPRDUPD = period_cycles;
86 		pwm->PWM_CH_NUM[channel].PWM_CDTYUPD = pulse_cycles;
87 	}
88 
89 	/* Enable the output */
90 	pwm->PWM_ENA = 1 << channel;
91 
92 	return 0;
93 }
94 
sam_pwm_init(const struct device * dev)95 static int sam_pwm_init(const struct device *dev)
96 {
97 	const struct sam_pwm_config *config = dev->config;
98 
99 	Pwm * const pwm = config->regs;
100 	uint8_t prescaler = config->prescaler;
101 	uint8_t divider = config->divider;
102 	int retval;
103 
104 	/* FIXME: way to validate prescaler & divider */
105 
106 	/* Enable PWM clock in PMC */
107 	(void)clock_control_on(SAM_DT_PMC_CONTROLLER,
108 			       (clock_control_subsys_t)&config->clock_cfg);
109 
110 	retval = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
111 	if (retval < 0) {
112 		return retval;
113 	}
114 
115 	/* Configure the clock A that will be used by all 4 channels */
116 	pwm->PWM_CLK = PWM_CLK_PREA(prescaler) | PWM_CLK_DIVA(divider);
117 
118 	return 0;
119 }
120 
121 static const struct pwm_driver_api sam_pwm_driver_api = {
122 	.set_cycles = sam_pwm_set_cycles,
123 	.get_cycles_per_sec = sam_pwm_get_cycles_per_sec,
124 };
125 
126 #define SAM_INST_INIT(inst)						\
127 	PINCTRL_DT_INST_DEFINE(inst);					\
128 	static const struct sam_pwm_config sam_pwm_config_##inst = {	\
129 		.regs = (Pwm *)DT_INST_REG_ADDR(inst),			\
130 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),		\
131 		.clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(inst),		\
132 		.prescaler = DT_INST_PROP(inst, prescaler),		\
133 		.divider = DT_INST_PROP(inst, divider),			\
134 	};								\
135 									\
136 	DEVICE_DT_INST_DEFINE(inst,					\
137 			    &sam_pwm_init, NULL,			\
138 			    NULL, &sam_pwm_config_##inst,		\
139 			    POST_KERNEL,				\
140 			    CONFIG_PWM_INIT_PRIORITY,			\
141 			    &sam_pwm_driver_api);
142 
143 DT_INST_FOREACH_STATUS_OKAY(SAM_INST_INIT)
144