summaryrefslogtreecommitdiff
path: root/chip/stm32/usb_pd_phy.c
blob: 90506d89751a2705756f50658cf12114bb20e8d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
/* Copyright 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "adc.h"
#include "clock.h"
#include "common.h"
#include "console.h"
#include "crc.h"
#include "dma.h"
#include "gpio.h"
#include "hwtimer.h"
#include "hooks.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "usb_pd.h"
#include "usb_pd_config.h"

#ifdef CONFIG_COMMON_RUNTIME
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
#else
#define CPRINTF(format, args...)
#define CPRINTS(format, args...)
#endif

#define PD_DATARATE 300000 /* Hz */

/*
 * Maximum size of a Power Delivery packet (in bits on the wire) :
 *    16-bit header + 0..7 32-bit data objects  (+ 4b5b encoding)
 * 64-bit preamble + SOP (4x 5b) + message in 4b5b + 32-bit CRC + EOP (1x 5b)
 * = 64 + 4*5 + 16 * 5/4 + 7 * 32 * 5/4 + 32 * 5/4 + 5
 */
#define PD_BIT_LEN 429

#define PD_MAX_RAW_SIZE (PD_BIT_LEN*2)

/* maximum number of consecutive similar bits with Biphase Mark Coding */
#define MAX_BITS 2

/* alternating bit sequence used for packet preamble : 00 10 11 01 00 ..  */
#define PD_PREAMBLE 0xB4B4B4B4 /* starts with 0, ends with 1 */

#define TX_CLOCK_DIV ((clock_get_freq() / (2*PD_DATARATE)))

/* threshold for 1 300-khz period */
#define PERIOD 4
#define NB_PERIOD(from, to) ((((to) - (from) + (PERIOD/2)) & 0xFF) / PERIOD)
#define PERIOD_THRESHOLD ((PERIOD + 2*PERIOD) / 2)

static struct pd_physical {
	/* samples for the PD messages */
	uint32_t raw_samples[DIV_ROUND_UP(PD_MAX_RAW_SIZE, sizeof(uint32_t))];

	/* state of the bit decoder */
	int d_toggle;
	int d_lastlen;
	uint32_t d_last;
	int b_toggle;

	/* DMA structures for each PD port */
	struct dma_option dma_tx_option;
	struct dma_option dma_tim_option;

	/* Pointers to timer register for each port */
	timer_ctlr_t *tim_tx;
	timer_ctlr_t *tim_rx;
} pd_phy[CONFIG_USB_PD_PORT_MAX_COUNT];

/* keep track of RX edge timing in order to trigger receive */
static timestamp_t
	rx_edge_ts[CONFIG_USB_PD_PORT_MAX_COUNT][PD_RX_TRANSITION_COUNT];
static int rx_edge_ts_idx[CONFIG_USB_PD_PORT_MAX_COUNT];

/* keep track of transmit polarity for DMA interrupt */
static int tx_dma_polarities[CONFIG_USB_PD_PORT_MAX_COUNT];

void pd_init_dequeue(int port)
{
	/* preamble ends with 1 */
	pd_phy[port].d_toggle = 0;
	pd_phy[port].d_last = 0;
	pd_phy[port].d_lastlen = 0;
}

static int wait_bits(int port, int nb)
{
	int avail;
	stm32_dma_chan_t *rx = dma_get_channel(DMAC_TIM_RX(port));

	avail = dma_bytes_done(rx, PD_MAX_RAW_SIZE);
	if (avail < nb) { /* no received yet ... */
		while ((dma_bytes_done(rx, PD_MAX_RAW_SIZE) < nb)
			&& !(pd_phy[port].tim_rx->sr & 4))
			; /* optimized for latency, not CPU usage ... */
		if (dma_bytes_done(rx, PD_MAX_RAW_SIZE) < nb) {
			CPRINTS("PD TMOUT RX %d/%d",
				dma_bytes_done(rx, PD_MAX_RAW_SIZE), nb);
			return -1;
		}
	}
	return nb;
}

int pd_dequeue_bits(int port, int off, int len, uint32_t *val)
{
	int w;
	uint8_t cnt = 0xff;
	uint8_t *samples = (uint8_t *)pd_phy[port].raw_samples;

	while ((pd_phy[port].d_lastlen < len) && (off < PD_MAX_RAW_SIZE - 1)) {
		w = wait_bits(port, off + 2);
		if (w < 0)
			goto stream_err;
		cnt = samples[off] - samples[off-1];
		if (!cnt || (cnt > 3*PERIOD))
			goto stream_err;
		off++;
		if (cnt <= PERIOD_THRESHOLD) {
			/*
			w = wait_bits(port, off + 1);
			if (w < 0)
				goto stream_err;
			*/
			cnt = samples[off] - samples[off-1];
			if (cnt >  PERIOD_THRESHOLD)
				goto stream_err;
			off++;
		}

		/* enqueue the bit of the last period */
		pd_phy[port].d_last = (pd_phy[port].d_last >> 1)
		       | (cnt <= PERIOD_THRESHOLD ? 0x80000000 : 0);
		pd_phy[port].d_lastlen++;
	}
	if (off < PD_MAX_RAW_SIZE) {
		*val = (pd_phy[port].d_last << (pd_phy[port].d_lastlen - len))
				>> (32 - len);
		pd_phy[port].d_lastlen -= len;
		return off;
	} else {
		return -1;
	}
stream_err:
	/* CPRINTS("PD Invalid %d @%d", cnt, off); */
	return -1;
}

int pd_find_preamble(int port)
{
	int bit;
	uint8_t *vals = (uint8_t *)pd_phy[port].raw_samples;

	/*
	 * Detect preamble
	 * Alternate 1-period 1-period & 2-period.
	 */
	uint32_t all = 0;
	stm32_dma_chan_t *rx = dma_get_channel(DMAC_TIM_RX(port));

	for (bit = 1; bit < PD_MAX_RAW_SIZE - 1; bit++) {
		uint8_t cnt;
		/* wait if the bit is not received yet ... */
		if (PD_MAX_RAW_SIZE - rx->cndtr < bit + 1) {
			while ((PD_MAX_RAW_SIZE - rx->cndtr < bit + 1) &&
				!(pd_phy[port].tim_rx->sr & 4))
				;
			if (pd_phy[port].tim_rx->sr & 4) {
				CPRINTS("PD TMOUT RX %d/%d",
					PD_MAX_RAW_SIZE - rx->cndtr, bit);
				return -1;
			}
		}
		cnt = vals[bit] - vals[bit-1];
		all = (all >> 1) | (cnt <= PERIOD_THRESHOLD ? BIT(31) : 0);
		if (all == 0x36db6db6)
			return bit - 1; /* should be SYNC-1 */
		if (all == 0xF33F3F3F)
			return PD_RX_ERR_HARD_RESET; /* got HARD-RESET */
		if (all == 0x3c7fe0ff)
			return PD_RX_ERR_CABLE_RESET; /* got CABLE-RESET */
	}
	return -1;
}

int pd_write_preamble(int port)
{
	uint32_t *msg = pd_phy[port].raw_samples;

	/* 64-bit x2 preamble */
	msg[0] = PD_PREAMBLE;
	msg[1] = PD_PREAMBLE;
	msg[2] = PD_PREAMBLE;
	msg[3] = PD_PREAMBLE;
	pd_phy[port].b_toggle = 0x3FF; /* preamble ends with 1 */
	return 2*64;
}

int pd_write_sym(int port, int bit_off, uint32_t val10)
{
	uint32_t *msg = pd_phy[port].raw_samples;
	int word_idx = bit_off / 32;
	int bit_idx = bit_off % 32;
	uint32_t val = pd_phy[port].b_toggle ^ val10;
	pd_phy[port].b_toggle = val & 0x200 ? 0x3FF : 0;
	if (bit_idx <= 22) {
		if (bit_idx == 0)
			msg[word_idx] = 0;
		msg[word_idx] |= val << bit_idx;
	} else {
		msg[word_idx] |= val << bit_idx;
		msg[word_idx+1] = val >> (32 - bit_idx);
		/* side effect: clear the new word when starting it */
	}
	return bit_off + 5*2;
}

int pd_write_last_edge(int port, int bit_off)
{
	uint32_t *msg = pd_phy[port].raw_samples;
	int word_idx = bit_off / 32;
	int bit_idx = bit_off % 32;

	if (bit_idx == 0)
		msg[word_idx] = 0;

	if (!pd_phy[port].b_toggle /* last bit was 0 */) {
		/* transition to 1, another 1, then 0 */
		if (bit_idx == 31) {
			msg[word_idx++] |= 1 << bit_idx;
			msg[word_idx] = 1;
		} else {
			msg[word_idx] |= 3 << bit_idx;
		}
	}
	/* ensure that the trailer is 0 */
	msg[word_idx+1] = 0;

	return bit_off + 3;
}

#ifdef CONFIG_COMMON_RUNTIME
void pd_dump_packet(int port, const char *msg)
{
	uint8_t *vals = (uint8_t *)pd_phy[port].raw_samples;
	int bit;

	CPRINTF("ERR %s:\n000:- ", msg);
	/* Packet debug output */
	for (bit = 1; bit <  PD_MAX_RAW_SIZE; bit++) {
		int cnt = NB_PERIOD(vals[bit-1], vals[bit]);
		if ((bit & 31) == 0)
			CPRINTF("\n%03d:", bit);
		CPRINTF("%1d ", cnt);
	}
	CPRINTF("><\n");
	cflush();
	for (bit = 0; bit <  PD_MAX_RAW_SIZE; bit++) {
		if ((bit & 31) == 0)
			CPRINTF("\n%03d:", bit);
		CPRINTF("%02x ", vals[bit]);
	}
	CPRINTF("||\n");
	cflush();
}
#endif /* CONFIG_COMMON_RUNTIME */

/* --- SPI TX operation --- */

void pd_tx_spi_init(int port)
{
	stm32_spi_regs_t *spi = SPI_REGS(port);

	/* Enable Tx DMA for our first transaction */
	spi->cr2 = STM32_SPI_CR2_TXDMAEN | STM32_SPI_CR2_DATASIZE(8);

	/* Enable the slave SPI: LSB first, force NSS, TX only, CPHA */
	spi->cr1 = STM32_SPI_CR1_SPE | STM32_SPI_CR1_LSBFIRST
		 | STM32_SPI_CR1_SSM | STM32_SPI_CR1_BIDIMODE
		 | STM32_SPI_CR1_BIDIOE | STM32_SPI_CR1_CPHA;
}

static void tx_dma_done(void *data)
{
	int port = (int)data;
	int polarity = tx_dma_polarities[port];
	stm32_spi_regs_t *spi = SPI_REGS(port);

	while (spi->sr & STM32_SPI_SR_FTLVL)
		; /* wait for TX FIFO empty */
	while (spi->sr & STM32_SPI_SR_BSY)
		; /* wait for BSY == 0 */

	/* Stop counting */
	pd_phy[port].tim_tx->cr1 &= ~1;

	/* put TX pins and reference in Hi-Z */
	pd_tx_disable(port, polarity);

#if defined(CONFIG_COMMON_RUNTIME) && defined(CONFIG_DMA_DEFAULT_HANDLERS)
	task_set_event(PD_PORT_TO_TASK_ID(port), TASK_EVENT_DMA_TC);
#endif
}

int pd_start_tx(int port, int polarity, int bit_len)
{
	stm32_dma_chan_t *tx = dma_get_channel(DMAC_SPI_TX(port));

#ifndef CONFIG_USB_PD_TX_PHY_ONLY
	/* disable RX detection interrupt */
	pd_rx_disable_monitoring(port);

	/* Check that we are not receiving a frame to avoid collisions */
	if (pd_rx_started(port))
		return -5;
#endif /* !CONFIG_USB_PD_TX_PHY_ONLY */

	/* Initialize spi peripheral to prepare for transmission. */
	pd_tx_spi_init(port);

	/*
	 * Set timer to one tick before reset so that the first tick causes
	 * a rising edge on the output.
	 */
	pd_phy[port].tim_tx->cnt = TX_CLOCK_DIV - 1;

	/* update DMA configuration */
	dma_prepare_tx(&(pd_phy[port].dma_tx_option),
			DIV_ROUND_UP(bit_len, 8),
			pd_phy[port].raw_samples);
	/* Flush data in write buffer so that DMA can get the latest data */
	asm volatile("dmb;");

	/* Kick off the DMA to send the data */
	dma_clear_isr(DMAC_SPI_TX(port));
#if defined(CONFIG_COMMON_RUNTIME) && defined(CONFIG_DMA_DEFAULT_HANDLERS)
	tx_dma_polarities[port] = polarity;
	if (!(pd_phy[port].dma_tx_option.flags & STM32_DMA_CCR_CIRC)) {
		/* Only enable interrupt if not in circular mode */
		dma_enable_tc_interrupt_callback(DMAC_SPI_TX(port),
						 &tx_dma_done,
						 (void *)port);
	}
#endif
	dma_go(tx);

	/*
	 * Drive the CC line from the TX block :
	 * - put SPI function on TX pin.
	 * - set the low level reference.
	 * Call this last before enabling timer in order to meet spec on
	 * timing between enabling TX and clocking out bits.
	 */
	pd_tx_enable(port, polarity);

	/* Start counting at 300Khz*/
	pd_phy[port].tim_tx->cr1 |= 1;

	return bit_len;
}

void pd_tx_done(int port, int polarity)
{
#if defined(CONFIG_COMMON_RUNTIME) && defined(CONFIG_DMA_DEFAULT_HANDLERS)
	/* wait for DMA, DMA interrupt will stop the SPI clock */
	task_wait_event_mask(TASK_EVENT_DMA_TC, DMA_TRANSFER_TIMEOUT_US);
	dma_disable_tc_interrupt(DMAC_SPI_TX(port));
#else
	tx_dma_polarities[port] = polarity;
	tx_dma_done((void *)port);
#endif

	/* Reset SPI to clear remaining data in buffer */
	pd_tx_spi_reset(port);
}

void pd_tx_set_circular_mode(int port)
{
	pd_phy[port].dma_tx_option.flags |= STM32_DMA_CCR_CIRC;
}

void pd_tx_clear_circular_mode(int port)
{
	/* clear the circular mode bit in flag variable */
	pd_phy[port].dma_tx_option.flags &= ~STM32_DMA_CCR_CIRC;
	/* disable dma transaction underway */
	dma_disable(DMAC_SPI_TX(port));
#if defined(CONFIG_COMMON_RUNTIME) && defined(CONFIG_DMA_DEFAULT_HANDLERS)
	tx_dma_done((void *)port);
#endif
}

/* --- RX operation using comparator linked to timer --- */

void pd_rx_start(int port)
{
	/* start sampling the edges on the CC line using the RX timer */
	dma_start_rx(&(pd_phy[port].dma_tim_option), PD_MAX_RAW_SIZE,
			pd_phy[port].raw_samples);
	/* enable TIM2 DMA requests */
	pd_phy[port].tim_rx->egr = 0x0001; /* reset counter / reload PSC */;
	pd_phy[port].tim_rx->sr = 0; /* clear overflows */
	pd_phy[port].tim_rx->cr1 |= 1;
}

void pd_rx_complete(int port)
{
	/* stop stampling TIM2 */
	pd_phy[port].tim_rx->cr1 &= ~1;
	/* stop DMA */
	dma_disable(DMAC_TIM_RX(port));
}

int pd_rx_started(int port)
{
	/* is the sampling timer running ? */
	return pd_phy[port].tim_rx->cr1 & 1;
}

void pd_rx_enable_monitoring(int port)
{
	/* clear comparator external interrupt */
	STM32_EXTI_PR = EXTI_COMP_MASK(port);
	/* enable comparator external interrupt */
	STM32_EXTI_IMR |= EXTI_COMP_MASK(port);
}

void pd_rx_disable_monitoring(int port)
{
	/* disable comparator external interrupt */
	STM32_EXTI_IMR &= ~EXTI_COMP_MASK(port);
	/* clear comparator external interrupt */
	STM32_EXTI_PR = EXTI_COMP_MASK(port);
}

uint64_t get_time_since_last_edge(int port)
{
	int prev_idx = (rx_edge_ts_idx[port] == 0) ?
			PD_RX_TRANSITION_COUNT - 1 :
			rx_edge_ts_idx[port] - 1;
	return get_time().val - rx_edge_ts[port][prev_idx].val;
}

/* detect an edge on the PD RX pin */
void pd_rx_handler(void)
{
	int pending, i;
	int next_idx;
	pending = STM32_EXTI_PR;

#ifdef CONFIG_USB_CTVPD
	/* Charge-Through Side detach event */
	if (pending & EXTI_COMP2_MASK) {
		task_wake(PD_PORT_TO_TASK_ID(0));
		/* Clear interrupt */
		STM32_EXTI_PR = EXTI_COMP2_MASK;
		pending &= ~EXTI_COMP2_MASK;
	}
#endif

	for (i = 0; i < board_get_usb_pd_port_count(); i++) {
		if (pending & EXTI_COMP_MASK(i)) {
			rx_edge_ts[i][rx_edge_ts_idx[i]].val = get_time().val;
			next_idx = (rx_edge_ts_idx[i] ==
					PD_RX_TRANSITION_COUNT - 1) ?
						0 : rx_edge_ts_idx[i] + 1;

#if defined(CONFIG_LOW_POWER_IDLE) &&                        \
defined(CONFIG_USB_PD_LOW_POWER_IDLE_WHEN_CONNECTED)
			/*
			 * Do not deep sleep while waiting for more edges. For
			 * most boards, sleep is already disabled due to being
			 * in PD connected state, but boards which define
			 * CONFIG_USB_PD_LOW_POWER_IDLE_WHEN_CONNECTED can
			 * sleep while connected.
			 */
			disable_sleep(SLEEP_MASK_USB_PD);
#endif

			/*
			 * If we have seen enough edges in a certain amount of
			 * time, then trigger RX start.
			 */
			if ((rx_edge_ts[i][rx_edge_ts_idx[i]].val -
			     rx_edge_ts[i][next_idx].val)
			     < PD_RX_TRANSITION_WINDOW) {
				/* start sampling */
				pd_rx_start(i);
				/*
				 * ignore the comparator IRQ until we are done
				 * with current message
				 */
				pd_rx_disable_monitoring(i);
				/* trigger the analysis in the task */
				pd_rx_event(i);
			} else {
				/* do not trigger RX start, just clear int */
				STM32_EXTI_PR = EXTI_COMP_MASK(i);
			}
			rx_edge_ts_idx[i] = next_idx;
		}
	}
}
#ifdef CONFIG_USB_PD_RX_COMP_IRQ
DECLARE_IRQ(STM32_IRQ_COMP, pd_rx_handler, 1);
#endif

/* --- release hardware --- */
void pd_hw_release(int port)
{
	__hw_timer_enable_clock(TIM_CLOCK_PD_RX(port), 0);
	__hw_timer_enable_clock(TIM_CLOCK_PD_TX(port), 0);
	dma_disable(DMAC_SPI_TX(port));
}

/* --- Startup initialization --- */

void pd_hw_init_rx(int port)
{
	struct pd_physical *phy = &pd_phy[port];

	/* configure registers used for timers */
	phy->tim_rx = (void *)TIM_REG_RX(port);

	/* configure RX DMA */
	phy->dma_tim_option.channel = DMAC_TIM_RX(port);
	phy->dma_tim_option.periph = (void *)(TIM_RX_CCR_REG(port));
	phy->dma_tim_option.flags = STM32_DMA_CCR_MSIZE_8_BIT |
				     STM32_DMA_CCR_PSIZE_16_BIT;

	/* --- set counter for RX timing : 2.4Mhz rate, free-running --- */
	__hw_timer_enable_clock(TIM_CLOCK_PD_RX(port), 1);
	/* Timer configuration */
	phy->tim_rx->cr1 = 0x0000;
	phy->tim_rx->cr2 = 0x0000;
	phy->tim_rx->dier = 0x0000;
	/* Auto-reload value : 16-bit free running counter */
	phy->tim_rx->arr = 0xFFFF;

	/* Timeout for message receive */
	phy->tim_rx->ccr[2] = (2400000 / 1000) * USB_PD_RX_TMOUT_US / 1000;
	/* Timer ICx input configuration */
	if (TIM_RX_CCR_IDX(port) == 1)
		phy->tim_rx->ccmr1 |= TIM_CCR_CS << 0;
	else if (TIM_RX_CCR_IDX(port) == 2)
		phy->tim_rx->ccmr1 |= TIM_CCR_CS << 8;
	else if (TIM_RX_CCR_IDX(port) == 4)
		phy->tim_rx->ccmr2 |= TIM_CCR_CS << 8;
	else
		/*  Unsupported RX timer capture input */
		ASSERT(0);

	phy->tim_rx->ccer = 0xB << ((TIM_RX_CCR_IDX(port) - 1) * 4);
	/* configure DMA request on CCRx update */
	phy->tim_rx->dier |= 1 << (8 + TIM_RX_CCR_IDX(port)); /* CCxDE */;
	/* set prescaler to /26 (F=1.2Mhz, T=0.8us) */
	phy->tim_rx->psc = (clock_get_freq() / 2400000) - 1;
	/* Reload the pre-scaler and reset the counter (clear CCRx) */
	phy->tim_rx->egr = 0x0001 | (1 << TIM_RX_CCR_IDX(port));
	/* clear update event from reloading */
	phy->tim_rx->sr = 0;

	/* --- DAC configuration for comparator at 850mV --- */
#ifdef CONFIG_PD_USE_DAC_AS_REF
	/* Enable DAC interface clock. */
	STM32_RCC_APB1ENR |= BIT(29);
	/* Delay 1 APB clock cycle after the clock is enabled */
	clock_wait_bus_cycles(BUS_APB, 1);
	/* set voltage Vout=0.850V (Vref = 3.0V) */
	STM32_DAC_DHR12RD = 850 * 4096 / 3000;
	/* Start DAC channel 1 */
	STM32_DAC_CR = STM32_DAC_CR_EN1;
#endif

	/* --- COMP2 as comparator for RX vs Vmid = 850mV --- */
#ifdef CONFIG_USB_PD_INTERNAL_COMP
#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3)
	/* turn on COMP/SYSCFG */
	STM32_RCC_APB2ENR |= BIT(0);
	/* Delay 1 APB clock cycle after the clock is enabled */
	clock_wait_bus_cycles(BUS_APB, 1);
	/* currently in hi-speed mode : TODO revisit later, INM = PA0(INM6) */
	STM32_COMP_CSR = STM32_COMP_CMP1MODE_LSPEED |
			 STM32_COMP_CMP1INSEL_INM6 |
			 CMP1OUTSEL |
			 STM32_COMP_CMP1HYST_HI |
			 STM32_COMP_CMP2MODE_LSPEED |
			 STM32_COMP_CMP2INSEL_INM6 |
			 CMP2OUTSEL |
			 STM32_COMP_CMP2HYST_HI;
#elif defined(CHIP_FAMILY_STM32L)
	STM32_RCC_APB1ENR |= BIT(31); /* turn on COMP */

	STM32_COMP_CSR = STM32_COMP_OUTSEL_TIM2_IC4 | STM32_COMP_INSEL_DAC_OUT1
			| STM32_COMP_SPEED_FAST;
	/* route PB4 to COMP input2 through GR6_1 bit 4 (or PB5->GR6_2 bit 5) */
	STM32_RI_ASCR2 |= BIT(4);
#else
#error Unsupported chip family
#endif
#endif /* CONFIG_USB_PD_INTERNAL_COMP */

	/* comparator interrupt setup */
	EXTI_XTSR |= EXTI_COMP_MASK(port);
	STM32_EXTI_IMR |= EXTI_COMP_MASK(port);
	task_enable_irq(IRQ_COMP);
}

void pd_hw_init(int port, enum pd_power_role role)
{
	struct pd_physical *phy = &pd_phy[port];
	uint32_t val;

	/* Initialize all PD pins to default state based on desired role */
	pd_config_init(port, role);

	/* set 40 MHz pin speed on communication pins */
	pd_set_pins_speed(port);

	/* --- SPI init --- */

	/* Enable clocks to SPI module */
	spi_enable_clock(port);

	/* Initialize SPI peripheral registers */
	pd_tx_spi_init(port);

	/* configure TX DMA */
	phy->dma_tx_option.channel = DMAC_SPI_TX(port);
	phy->dma_tx_option.periph = (void *)&SPI_REGS(port)->dr;
	phy->dma_tx_option.flags = STM32_DMA_CCR_MSIZE_8_BIT |
				    STM32_DMA_CCR_PSIZE_8_BIT;
	dma_prepare_tx(&(phy->dma_tx_option), PD_MAX_RAW_SIZE,
			phy->raw_samples);

	/* configure registers used for timers */
	phy->tim_tx = (void *)TIM_REG_TX(port);

	/* --- set the TX timer with updates at 600KHz (BMC frequency) --- */
	__hw_timer_enable_clock(TIM_CLOCK_PD_TX(port), 1);
	/* Timer configuration */
	phy->tim_tx->cr1 = 0x0000;
	phy->tim_tx->cr2 = 0x0000;
	phy->tim_tx->dier = 0x0000;
	/* Auto-reload value : 600000 Khz overflow */
	phy->tim_tx->arr = TX_CLOCK_DIV;
	/* 50% duty cycle on the output */
	phy->tim_tx->ccr[TIM_TX_CCR_IDX(port)] = phy->tim_tx->arr / 2;
	/* Timer channel output configuration */
	val = (6 << 4) | BIT(3);
	if ((TIM_TX_CCR_IDX(port) & 1) == 0) /* CH2 or CH4 */
		val <<= 8;
	if (TIM_TX_CCR_IDX(port) <= 2)
		phy->tim_tx->ccmr1 = val;
	else
		phy->tim_tx->ccmr2 = val;

	phy->tim_tx->ccer = 1 << ((TIM_TX_CCR_IDX(port) - 1) * 4);
	phy->tim_tx->bdtr = 0x8000;
	/* set prescaler to /1 */
	phy->tim_tx->psc = 0;
	/* Reload the pre-scaler and reset the counter */
	phy->tim_tx->egr = 0x0001;
#ifndef CONFIG_USB_PD_TX_PHY_ONLY
	/* Configure the reception side : comparators + edge timer + DMA */
	pd_hw_init_rx(port);
#endif /* CONFIG_USB_PD_TX_PHY_ONLY */

	CPRINTS("USB PD initialized");
}

void pd_set_clock(int port, int freq)
{
	pd_phy[port].tim_tx->arr = clock_get_freq() / (2*freq);
}