1 /*
2  * Copyright (c) 2021, Piotr Mienkowski
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT atmel_sam_tc_qdec
8 
9 /** @file
10  * @brief Atmel SAM MCU family Quadrature Decoder (QDEC/TC) driver.
11  */
12 
13 #include <errno.h>
14 #include <zephyr/sys/__assert.h>
15 #include <zephyr/sys/util.h>
16 #include <zephyr/device.h>
17 #include <zephyr/init.h>
18 #include <soc.h>
19 #include <zephyr/drivers/sensor.h>
20 #include <zephyr/drivers/pinctrl.h>
21 #include <zephyr/drivers/clock_control/atmel_sam_pmc.h>
22 
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(qdec_sam, CONFIG_SENSOR_LOG_LEVEL);
25 
26 /* Device constant configuration parameters */
27 struct qdec_sam_dev_cfg {
28 	Tc *regs;
29 	const struct atmel_sam_pmc_config clock_cfg[TCCHANNEL_NUMBER];
30 	const struct pinctrl_dev_config *pcfg;
31 };
32 
33 /* Device run time data */
34 struct qdec_sam_dev_data {
35 	uint16_t position;
36 };
37 
qdec_sam_fetch(const struct device * dev,enum sensor_channel chan)38 static int qdec_sam_fetch(const struct device *dev, enum sensor_channel chan)
39 {
40 	const struct qdec_sam_dev_cfg *const dev_cfg = dev->config;
41 	struct qdec_sam_dev_data *const dev_data = dev->data;
42 	Tc *const tc = dev_cfg->regs;
43 	TcChannel *tc_ch0 = &tc->TcChannel[0];
44 
45 	/* Read position register content */
46 	dev_data->position = tc_ch0->TC_CV;
47 
48 	return 0;
49 }
50 
qdec_sam_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)51 static int qdec_sam_get(const struct device *dev, enum sensor_channel chan,
52 			struct sensor_value *val)
53 {
54 	struct qdec_sam_dev_data *const dev_data = dev->data;
55 
56 	if (chan == SENSOR_CHAN_ROTATION) {
57 		val->val1 = dev_data->position;
58 		val->val2 = 0;
59 	} else {
60 		return -ENOTSUP;
61 	}
62 
63 	return 0;
64 }
65 
qdec_sam_start(Tc * const tc)66 static void qdec_sam_start(Tc *const tc)
67 {
68 	TcChannel *tc_ch0 = &tc->TcChannel[0];
69 
70 	/* Enable Channel 0 Clock and reset counter*/
71 	tc_ch0->TC_CCR =  TC_CCR_CLKEN
72 			| TC_CCR_SWTRG;
73 }
74 
qdec_sam_configure(const struct device * dev)75 static void qdec_sam_configure(const struct device *dev)
76 {
77 	const struct qdec_sam_dev_cfg *const dev_cfg = dev->config;
78 	Tc *const tc = dev_cfg->regs;
79 	TcChannel *tc_ch0 = &tc->TcChannel[0];
80 
81 	/* Clock, Trigger Edge, Trigger and Mode Selection */
82 	tc_ch0->TC_CMR =  TC_CMR_TCCLKS_XC0
83 			| TC_CMR_ETRGEDG_NONE
84 			| TC_CMR_ABETRG;
85 
86 	/* Enable QDEC in Position Mode*/
87 	tc->TC_BMR =  TC_BMR_QDEN
88 		    | TC_BMR_POSEN
89 		    | TC_BMR_EDGPHA
90 		    | TC_BMR_MAXFILT(1);
91 
92 	qdec_sam_start(tc);
93 }
94 
qdec_sam_initialize(const struct device * dev)95 static int qdec_sam_initialize(const struct device *dev)
96 {
97 	__ASSERT_NO_MSG(dev != NULL);
98 	const struct qdec_sam_dev_cfg *const dev_cfg = dev->config;
99 	int retval;
100 
101 	/* Connect pins to the peripheral */
102 	retval = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT);
103 	if (retval < 0) {
104 		return retval;
105 	}
106 
107 	for (int i = 0; i < ARRAY_SIZE(dev_cfg->clock_cfg); i++) {
108 		/* Enable TC clock in PMC */
109 		(void)clock_control_on(SAM_DT_PMC_CONTROLLER,
110 				       (clock_control_subsys_t)&dev_cfg->clock_cfg[i]);
111 	}
112 
113 	qdec_sam_configure(dev);
114 
115 	LOG_INF("Device %s initialized", dev->name);
116 
117 	return 0;
118 }
119 
120 static DEVICE_API(sensor, qdec_sam_driver_api) = {
121 	.sample_fetch = qdec_sam_fetch,
122 	.channel_get = qdec_sam_get,
123 };
124 
125 #define QDEC_SAM_INIT(n)						\
126 	PINCTRL_DT_INST_DEFINE(n);					\
127 	static const struct qdec_sam_dev_cfg qdec##n##_sam_config = {	\
128 		.regs = (Tc *)DT_REG_ADDR(DT_INST_PARENT(n)),		\
129 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),		\
130 		.clock_cfg = SAM_DT_CLOCKS_PMC_CFG(DT_INST_PARENT(n)),	\
131 	};								\
132 									\
133 	static struct qdec_sam_dev_data qdec##n##_sam_data;		\
134 									\
135 	SENSOR_DEVICE_DT_INST_DEFINE(n, qdec_sam_initialize, NULL,	\
136 			    &qdec##n##_sam_data, &qdec##n##_sam_config, \
137 			    POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,	\
138 			    &qdec_sam_driver_api);
139 
140 DT_INST_FOREACH_STATUS_OKAY(QDEC_SAM_INIT)
141