1 /*
2  * Copyright (c) 2018 Piotr Mienkowski
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /** @file
8  * @brief Serial Wire Output (SWO) backend implementation.
9  *
10  * SWO/SWV has been developed by ARM. The following code works only on ARM
11  * architecture.
12  *
13  * An SWO viewer program will typically set-up the SWO port including its
14  * frequency when connected to the debug probe. Such configuration can persist
15  * only until the MCU reset. The SWO backend initialization function will
16  * re-configure the SWO port upon boot and set the frequency as specified by
17  * the LOG_BACKEND_SWO_FREQ_HZ Kconfig option. To ensure flawless operation
18  * this frequency should much the one set by the SWO viewer program.
19  *
20  * The initialization code assumes that SWO core frequency is equal to HCLK
21  * as defined by the clock-frequency property in the CPU node. This may require
22  * additional, vendor specific configuration.
23  */
24 
25 #include <zephyr/logging/log_backend.h>
26 #include <zephyr/logging/log_core.h>
27 #include <zephyr/logging/log_output.h>
28 #include <zephyr/logging/log_backend_std.h>
29 #include <zephyr/drivers/pinctrl.h>
30 #include <soc.h>
31 
32 /** The stimulus port from which SWO data is received and displayed */
33 #define ITM_PORT_LOGGER       0
34 
35 /* If ITM has pin control properties, apply them for SWO pins */
36 #if DT_NODE_HAS_PROP(DT_NODELABEL(itm), pinctrl_0)
37 PINCTRL_DT_DEFINE(DT_NODELABEL(itm));
38 #endif
39 
40 /* Set TPIU prescaler for the current debug trace clock frequency. */
41 #if CONFIG_LOG_BACKEND_SWO_FREQ_HZ == 0
42 #define SWO_FREQ_DIV  1
43 #else
44 
45 #if CONFIG_LOG_BACKEND_SWO_REF_FREQ_HZ == 0
46 #error "SWO reference frequency is not configured"
47 #endif
48 
49 #define SWO_FREQ_DIV \
50 	((CONFIG_LOG_BACKEND_SWO_REF_FREQ_HZ + (CONFIG_LOG_BACKEND_SWO_FREQ_HZ / 2)) / \
51 		CONFIG_LOG_BACKEND_SWO_FREQ_HZ)
52 
53 #if SWO_FREQ_DIV > 0xFFFF
54 #error CONFIG_LOG_BACKEND_SWO_FREQ_HZ is too low. SWO clock divider is 16-bit. \
55 	Minimum supported SWO clock frequency is \
56 	[Reference Clock Frequency]/2^16.
57 #endif
58 
59 #endif
60 
61 static uint8_t buf[1];
62 static uint32_t log_format_current = CONFIG_LOG_BACKEND_SWO_OUTPUT_DEFAULT;
63 
char_out(uint8_t * data,size_t length,void * ctx)64 static int char_out(uint8_t *data, size_t length, void *ctx)
65 {
66 	ARG_UNUSED(ctx);
67 
68 	for (size_t i = 0; i < length; i++) {
69 		ITM_SendChar(data[i]);
70 	}
71 
72 	return length;
73 }
74 
75 LOG_OUTPUT_DEFINE(log_output_swo, char_out, buf, sizeof(buf));
76 
log_backend_swo_process(const struct log_backend * const backend,union log_msg_generic * msg)77 static void log_backend_swo_process(const struct log_backend *const backend,
78 				    union log_msg_generic *msg)
79 {
80 	uint32_t flags = log_backend_std_get_flags();
81 
82 	log_format_func_t log_output_func = log_format_func_t_get(log_format_current);
83 
84 	log_output_func(&log_output_swo, &msg->log, flags);
85 }
86 
format_set(const struct log_backend * const backend,uint32_t log_type)87 static int format_set(const struct log_backend *const backend, uint32_t log_type)
88 {
89 	log_format_current = log_type;
90 	return 0;
91 }
92 
log_backend_swo_init(struct log_backend const * const backend)93 static void log_backend_swo_init(struct log_backend const *const backend)
94 {
95 	/* Enable DWT and ITM units */
96 	CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
97 	/* Enable access to ITM registers */
98 	ITM->LAR  = 0xC5ACCE55;
99 	/* Disable stimulus ports ITM_STIM0-ITM_STIM31 */
100 	ITM->TER  = 0x0;
101 	/* Disable ITM */
102 	ITM->TCR  = 0x0;
103 	/* Select TPIU encoding protocol */
104 	TPI->SPPR = IS_ENABLED(CONFIG_LOG_BACKEND_SWO_PROTOCOL_NRZ) ? 2 : 1;
105 	/* Set SWO baud rate prescaler value: SWO_clk = ref_clock/(ACPR + 1) */
106 	TPI->ACPR = SWO_FREQ_DIV - 1;
107 	/* Enable unprivileged access to ITM stimulus ports */
108 	ITM->TPR  = 0x0;
109 	/* Configure Debug Watchpoint and Trace */
110 	DWT->CTRL &= (DWT_CTRL_POSTPRESET_Msk | DWT_CTRL_POSTINIT_Msk | DWT_CTRL_CYCCNTENA_Msk);
111 	DWT->CTRL |= (DWT_CTRL_POSTPRESET_Msk | DWT_CTRL_POSTINIT_Msk);
112 	/* Configure Formatter and Flush Control Register */
113 	TPI->FFCR = 0x00000100;
114 	/* Enable ITM, set TraceBusID=1, no local timestamp generation */
115 	ITM->TCR  = 0x0001000D;
116 	/* Enable stimulus port used by the logger */
117 	ITM->TER  = 1 << ITM_PORT_LOGGER;
118 
119 	/* Initialize pin control settings, if any are defined */
120 #if DT_NODE_HAS_PROP(DT_NODELABEL(itm), pinctrl_0)
121 	const struct pinctrl_dev_config *pincfg =
122 		PINCTRL_DT_DEV_CONFIG_GET(DT_NODELABEL(itm));
123 
124 	pinctrl_apply_state(pincfg, PINCTRL_STATE_DEFAULT);
125 #endif
126 }
127 
log_backend_swo_panic(struct log_backend const * const backend)128 static void log_backend_swo_panic(struct log_backend const *const backend)
129 {
130 }
131 
dropped(const struct log_backend * const backend,uint32_t cnt)132 static void dropped(const struct log_backend *const backend, uint32_t cnt)
133 {
134 	ARG_UNUSED(backend);
135 
136 	log_backend_std_dropped(&log_output_swo, cnt);
137 }
138 
139 const struct log_backend_api log_backend_swo_api = {
140 	.process = log_backend_swo_process,
141 	.panic = log_backend_swo_panic,
142 	.init = log_backend_swo_init,
143 	.dropped = IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE) ? NULL : dropped,
144 	.format_set = format_set,
145 };
146 
147 LOG_BACKEND_DEFINE(log_backend_swo, log_backend_swo_api, true);
148