Commit | Line | Data |
---|---|---|
1dbae815 | 1 | /* |
f30c2269 | 2 | * arch/arm/mach-omap2/serial.c |
1dbae815 TL |
3 | * |
4 | * OMAP2 serial support. | |
5 | * | |
6e81176d | 6 | * Copyright (C) 2005-2008 Nokia Corporation |
1dbae815 TL |
7 | * Author: Paul Mundt <paul.mundt@nokia.com> |
8 | * | |
4af4016c KH |
9 | * Major rework for PM support by Kevin Hilman |
10 | * | |
1dbae815 TL |
11 | * Based off of arch/arm/mach-omap/omap1/serial.c |
12 | * | |
44169075 SS |
13 | * Copyright (C) 2009 Texas Instruments |
14 | * Added OMAP4 support - Santosh Shilimkar <santosh.shilimkar@ti.com | |
15 | * | |
1dbae815 TL |
16 | * This file is subject to the terms and conditions of the GNU General Public |
17 | * License. See the file "COPYING" in the main directory of this archive | |
18 | * for more details. | |
19 | */ | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/init.h> | |
22 | #include <linux/serial_8250.h> | |
23 | #include <linux/serial_reg.h> | |
f8ce2547 | 24 | #include <linux/clk.h> |
fced80c7 | 25 | #include <linux/io.h> |
1dbae815 | 26 | |
a09e64fb RK |
27 | #include <mach/common.h> |
28 | #include <mach/board.h> | |
4af4016c KH |
29 | #include <mach/clock.h> |
30 | #include <mach/control.h> | |
31 | ||
32 | #include "prm.h" | |
33 | #include "pm.h" | |
34 | #include "prm-regbits-34xx.h" | |
35 | ||
36 | #define UART_OMAP_WER 0x17 /* Wake-up enable register */ | |
37 | ||
ba87a9be | 38 | #define DEFAULT_TIMEOUT (5 * HZ) |
4af4016c KH |
39 | |
40 | struct omap_uart_state { | |
41 | int num; | |
42 | int can_sleep; | |
43 | struct timer_list timer; | |
44 | u32 timeout; | |
45 | ||
46 | void __iomem *wk_st; | |
47 | void __iomem *wk_en; | |
48 | u32 wk_mask; | |
49 | u32 padconf; | |
50 | ||
51 | struct clk *ick; | |
52 | struct clk *fck; | |
53 | int clocked; | |
54 | ||
55 | struct plat_serial8250_port *p; | |
56 | struct list_head node; | |
1dbae815 | 57 | |
4af4016c KH |
58 | #if defined(CONFIG_ARCH_OMAP3) && defined(CONFIG_PM) |
59 | int context_valid; | |
60 | ||
61 | /* Registers to be saved/restored for OFF-mode */ | |
62 | u16 dll; | |
63 | u16 dlh; | |
64 | u16 ier; | |
65 | u16 sysc; | |
66 | u16 scr; | |
67 | u16 wer; | |
68 | #endif | |
69 | }; | |
70 | ||
71 | static struct omap_uart_state omap_uart[OMAP_MAX_NR_PORTS]; | |
72 | static LIST_HEAD(uart_list); | |
1dbae815 TL |
73 | |
74 | static struct plat_serial8250_port serial_platform_data[] = { | |
75 | { | |
e8a91c95 RK |
76 | .membase = IO_ADDRESS(OMAP_UART1_BASE), |
77 | .mapbase = OMAP_UART1_BASE, | |
1dbae815 TL |
78 | .irq = 72, |
79 | .flags = UPF_BOOT_AUTOCONF, | |
80 | .iotype = UPIO_MEM, | |
81 | .regshift = 2, | |
6e81176d | 82 | .uartclk = OMAP24XX_BASE_BAUD * 16, |
1dbae815 | 83 | }, { |
e8a91c95 RK |
84 | .membase = IO_ADDRESS(OMAP_UART2_BASE), |
85 | .mapbase = OMAP_UART2_BASE, | |
1dbae815 TL |
86 | .irq = 73, |
87 | .flags = UPF_BOOT_AUTOCONF, | |
88 | .iotype = UPIO_MEM, | |
89 | .regshift = 2, | |
6e81176d | 90 | .uartclk = OMAP24XX_BASE_BAUD * 16, |
1dbae815 | 91 | }, { |
e8a91c95 RK |
92 | .membase = IO_ADDRESS(OMAP_UART3_BASE), |
93 | .mapbase = OMAP_UART3_BASE, | |
1dbae815 TL |
94 | .irq = 74, |
95 | .flags = UPF_BOOT_AUTOCONF, | |
96 | .iotype = UPIO_MEM, | |
97 | .regshift = 2, | |
6e81176d | 98 | .uartclk = OMAP24XX_BASE_BAUD * 16, |
1dbae815 TL |
99 | }, { |
100 | .flags = 0 | |
101 | } | |
102 | }; | |
103 | ||
104 | static inline unsigned int serial_read_reg(struct plat_serial8250_port *up, | |
105 | int offset) | |
106 | { | |
107 | offset <<= up->regshift; | |
108 | return (unsigned int)__raw_readb(up->membase + offset); | |
109 | } | |
110 | ||
111 | static inline void serial_write_reg(struct plat_serial8250_port *p, int offset, | |
112 | int value) | |
113 | { | |
114 | offset <<= p->regshift; | |
e8a91c95 | 115 | __raw_writeb(value, p->membase + offset); |
1dbae815 TL |
116 | } |
117 | ||
118 | /* | |
119 | * Internal UARTs need to be initialized for the 8250 autoconfig to work | |
120 | * properly. Note that the TX watermark initialization may not be needed | |
121 | * once the 8250.c watermark handling code is merged. | |
122 | */ | |
4af4016c | 123 | static inline void __init omap_uart_reset(struct omap_uart_state *uart) |
1dbae815 | 124 | { |
4af4016c KH |
125 | struct plat_serial8250_port *p = uart->p; |
126 | ||
1dbae815 TL |
127 | serial_write_reg(p, UART_OMAP_MDR1, 0x07); |
128 | serial_write_reg(p, UART_OMAP_SCR, 0x08); | |
129 | serial_write_reg(p, UART_OMAP_MDR1, 0x00); | |
671c7235 | 130 | serial_write_reg(p, UART_OMAP_SYSC, (0x02 << 3) | (1 << 2) | (1 << 0)); |
1dbae815 TL |
131 | } |
132 | ||
4af4016c KH |
133 | #if defined(CONFIG_PM) && defined(CONFIG_ARCH_OMAP3) |
134 | ||
135 | static int enable_off_mode; /* to be removed by full off-mode patches */ | |
136 | ||
137 | static void omap_uart_save_context(struct omap_uart_state *uart) | |
6e81176d | 138 | { |
4af4016c KH |
139 | u16 lcr = 0; |
140 | struct plat_serial8250_port *p = uart->p; | |
141 | ||
142 | if (!enable_off_mode) | |
143 | return; | |
144 | ||
145 | lcr = serial_read_reg(p, UART_LCR); | |
146 | serial_write_reg(p, UART_LCR, 0xBF); | |
147 | uart->dll = serial_read_reg(p, UART_DLL); | |
148 | uart->dlh = serial_read_reg(p, UART_DLM); | |
149 | serial_write_reg(p, UART_LCR, lcr); | |
150 | uart->ier = serial_read_reg(p, UART_IER); | |
151 | uart->sysc = serial_read_reg(p, UART_OMAP_SYSC); | |
152 | uart->scr = serial_read_reg(p, UART_OMAP_SCR); | |
153 | uart->wer = serial_read_reg(p, UART_OMAP_WER); | |
154 | ||
155 | uart->context_valid = 1; | |
156 | } | |
157 | ||
158 | static void omap_uart_restore_context(struct omap_uart_state *uart) | |
159 | { | |
160 | u16 efr = 0; | |
161 | struct plat_serial8250_port *p = uart->p; | |
162 | ||
163 | if (!enable_off_mode) | |
164 | return; | |
165 | ||
166 | if (!uart->context_valid) | |
167 | return; | |
168 | ||
169 | uart->context_valid = 0; | |
170 | ||
171 | serial_write_reg(p, UART_OMAP_MDR1, 0x7); | |
172 | serial_write_reg(p, UART_LCR, 0xBF); /* Config B mode */ | |
173 | efr = serial_read_reg(p, UART_EFR); | |
174 | serial_write_reg(p, UART_EFR, UART_EFR_ECB); | |
175 | serial_write_reg(p, UART_LCR, 0x0); /* Operational mode */ | |
176 | serial_write_reg(p, UART_IER, 0x0); | |
177 | serial_write_reg(p, UART_LCR, 0xBF); /* Config B mode */ | |
178 | serial_write_reg(p, UART_DLL, uart->dll); | |
179 | serial_write_reg(p, UART_DLM, uart->dlh); | |
180 | serial_write_reg(p, UART_LCR, 0x0); /* Operational mode */ | |
181 | serial_write_reg(p, UART_IER, uart->ier); | |
182 | serial_write_reg(p, UART_FCR, 0xA1); | |
183 | serial_write_reg(p, UART_LCR, 0xBF); /* Config B mode */ | |
184 | serial_write_reg(p, UART_EFR, efr); | |
185 | serial_write_reg(p, UART_LCR, UART_LCR_WLEN8); | |
186 | serial_write_reg(p, UART_OMAP_SCR, uart->scr); | |
187 | serial_write_reg(p, UART_OMAP_WER, uart->wer); | |
188 | serial_write_reg(p, UART_OMAP_SYSC, uart->sysc); | |
189 | serial_write_reg(p, UART_OMAP_MDR1, 0x00); /* UART 16x mode */ | |
190 | } | |
191 | #else | |
192 | static inline void omap_uart_save_context(struct omap_uart_state *uart) {} | |
193 | static inline void omap_uart_restore_context(struct omap_uart_state *uart) {} | |
194 | #endif /* CONFIG_PM && CONFIG_ARCH_OMAP3 */ | |
195 | ||
196 | static inline void omap_uart_enable_clocks(struct omap_uart_state *uart) | |
197 | { | |
198 | if (uart->clocked) | |
199 | return; | |
200 | ||
201 | clk_enable(uart->ick); | |
202 | clk_enable(uart->fck); | |
203 | uart->clocked = 1; | |
204 | omap_uart_restore_context(uart); | |
205 | } | |
206 | ||
207 | #ifdef CONFIG_PM | |
208 | ||
209 | static inline void omap_uart_disable_clocks(struct omap_uart_state *uart) | |
210 | { | |
211 | if (!uart->clocked) | |
212 | return; | |
213 | ||
214 | omap_uart_save_context(uart); | |
215 | uart->clocked = 0; | |
216 | clk_disable(uart->ick); | |
217 | clk_disable(uart->fck); | |
218 | } | |
219 | ||
220 | static void omap_uart_smart_idle_enable(struct omap_uart_state *uart, | |
221 | int enable) | |
222 | { | |
223 | struct plat_serial8250_port *p = uart->p; | |
224 | u16 sysc; | |
225 | ||
226 | sysc = serial_read_reg(p, UART_OMAP_SYSC) & 0x7; | |
227 | if (enable) | |
228 | sysc |= 0x2 << 3; | |
229 | else | |
230 | sysc |= 0x1 << 3; | |
231 | ||
232 | serial_write_reg(p, UART_OMAP_SYSC, sysc); | |
233 | } | |
234 | ||
235 | static void omap_uart_block_sleep(struct omap_uart_state *uart) | |
236 | { | |
237 | omap_uart_enable_clocks(uart); | |
238 | ||
239 | omap_uart_smart_idle_enable(uart, 0); | |
240 | uart->can_sleep = 0; | |
ba87a9be JH |
241 | if (uart->timeout) |
242 | mod_timer(&uart->timer, jiffies + uart->timeout); | |
243 | else | |
244 | del_timer(&uart->timer); | |
4af4016c KH |
245 | } |
246 | ||
247 | static void omap_uart_allow_sleep(struct omap_uart_state *uart) | |
248 | { | |
249 | if (!uart->clocked) | |
250 | return; | |
251 | ||
252 | omap_uart_smart_idle_enable(uart, 1); | |
253 | uart->can_sleep = 1; | |
254 | del_timer(&uart->timer); | |
255 | } | |
256 | ||
257 | static void omap_uart_idle_timer(unsigned long data) | |
258 | { | |
259 | struct omap_uart_state *uart = (struct omap_uart_state *)data; | |
260 | ||
261 | omap_uart_allow_sleep(uart); | |
262 | } | |
263 | ||
264 | void omap_uart_prepare_idle(int num) | |
265 | { | |
266 | struct omap_uart_state *uart; | |
267 | ||
268 | list_for_each_entry(uart, &uart_list, node) { | |
269 | if (num == uart->num && uart->can_sleep) { | |
270 | omap_uart_disable_clocks(uart); | |
271 | return; | |
272 | } | |
273 | } | |
274 | } | |
275 | ||
276 | void omap_uart_resume_idle(int num) | |
277 | { | |
278 | struct omap_uart_state *uart; | |
279 | ||
280 | list_for_each_entry(uart, &uart_list, node) { | |
281 | if (num == uart->num) { | |
282 | omap_uart_enable_clocks(uart); | |
283 | ||
284 | /* Check for IO pad wakeup */ | |
285 | if (cpu_is_omap34xx() && uart->padconf) { | |
286 | u16 p = omap_ctrl_readw(uart->padconf); | |
287 | ||
288 | if (p & OMAP3_PADCONF_WAKEUPEVENT0) | |
289 | omap_uart_block_sleep(uart); | |
6e81176d | 290 | } |
4af4016c KH |
291 | |
292 | /* Check for normal UART wakeup */ | |
293 | if (__raw_readl(uart->wk_st) & uart->wk_mask) | |
294 | omap_uart_block_sleep(uart); | |
295 | ||
296 | return; | |
297 | } | |
298 | } | |
299 | } | |
300 | ||
301 | void omap_uart_prepare_suspend(void) | |
302 | { | |
303 | struct omap_uart_state *uart; | |
304 | ||
305 | list_for_each_entry(uart, &uart_list, node) { | |
306 | omap_uart_allow_sleep(uart); | |
307 | } | |
308 | } | |
309 | ||
310 | int omap_uart_can_sleep(void) | |
311 | { | |
312 | struct omap_uart_state *uart; | |
313 | int can_sleep = 1; | |
314 | ||
315 | list_for_each_entry(uart, &uart_list, node) { | |
316 | if (!uart->clocked) | |
317 | continue; | |
318 | ||
319 | if (!uart->can_sleep) { | |
320 | can_sleep = 0; | |
321 | continue; | |
6e81176d | 322 | } |
4af4016c KH |
323 | |
324 | /* This UART can now safely sleep. */ | |
325 | omap_uart_allow_sleep(uart); | |
6e81176d | 326 | } |
4af4016c KH |
327 | |
328 | return can_sleep; | |
6e81176d JH |
329 | } |
330 | ||
4af4016c KH |
331 | /** |
332 | * omap_uart_interrupt() | |
333 | * | |
334 | * This handler is used only to detect that *any* UART interrupt has | |
335 | * occurred. It does _nothing_ to handle the interrupt. Rather, | |
336 | * any UART interrupt will trigger the inactivity timer so the | |
337 | * UART will not idle or sleep for its timeout period. | |
338 | * | |
339 | **/ | |
340 | static irqreturn_t omap_uart_interrupt(int irq, void *dev_id) | |
341 | { | |
342 | struct omap_uart_state *uart = dev_id; | |
343 | ||
344 | omap_uart_block_sleep(uart); | |
345 | ||
346 | return IRQ_NONE; | |
347 | } | |
348 | ||
ba87a9be JH |
349 | static u32 sleep_timeout = DEFAULT_TIMEOUT; |
350 | ||
4af4016c KH |
351 | static void omap_uart_idle_init(struct omap_uart_state *uart) |
352 | { | |
353 | u32 v; | |
354 | struct plat_serial8250_port *p = uart->p; | |
355 | int ret; | |
356 | ||
357 | uart->can_sleep = 0; | |
ba87a9be | 358 | uart->timeout = sleep_timeout; |
4af4016c KH |
359 | setup_timer(&uart->timer, omap_uart_idle_timer, |
360 | (unsigned long) uart); | |
361 | mod_timer(&uart->timer, jiffies + uart->timeout); | |
362 | omap_uart_smart_idle_enable(uart, 0); | |
363 | ||
364 | if (cpu_is_omap34xx()) { | |
365 | u32 mod = (uart->num == 2) ? OMAP3430_PER_MOD : CORE_MOD; | |
366 | u32 wk_mask = 0; | |
367 | u32 padconf = 0; | |
368 | ||
369 | uart->wk_en = OMAP34XX_PRM_REGADDR(mod, PM_WKEN1); | |
370 | uart->wk_st = OMAP34XX_PRM_REGADDR(mod, PM_WKST1); | |
371 | switch (uart->num) { | |
372 | case 0: | |
373 | wk_mask = OMAP3430_ST_UART1_MASK; | |
374 | padconf = 0x182; | |
375 | break; | |
376 | case 1: | |
377 | wk_mask = OMAP3430_ST_UART2_MASK; | |
378 | padconf = 0x17a; | |
379 | break; | |
380 | case 2: | |
381 | wk_mask = OMAP3430_ST_UART3_MASK; | |
382 | padconf = 0x19e; | |
383 | break; | |
384 | } | |
385 | uart->wk_mask = wk_mask; | |
386 | uart->padconf = padconf; | |
387 | } else if (cpu_is_omap24xx()) { | |
388 | u32 wk_mask = 0; | |
389 | ||
390 | if (cpu_is_omap2430()) { | |
391 | uart->wk_en = OMAP2430_PRM_REGADDR(CORE_MOD, PM_WKEN1); | |
392 | uart->wk_st = OMAP2430_PRM_REGADDR(CORE_MOD, PM_WKST1); | |
393 | } else if (cpu_is_omap2420()) { | |
394 | uart->wk_en = OMAP2420_PRM_REGADDR(CORE_MOD, PM_WKEN1); | |
395 | uart->wk_st = OMAP2420_PRM_REGADDR(CORE_MOD, PM_WKST1); | |
396 | } | |
397 | switch (uart->num) { | |
398 | case 0: | |
399 | wk_mask = OMAP24XX_ST_UART1_MASK; | |
400 | break; | |
401 | case 1: | |
402 | wk_mask = OMAP24XX_ST_UART2_MASK; | |
403 | break; | |
404 | case 2: | |
405 | wk_mask = OMAP24XX_ST_UART3_MASK; | |
406 | break; | |
407 | } | |
408 | uart->wk_mask = wk_mask; | |
409 | } else { | |
410 | uart->wk_en = 0; | |
411 | uart->wk_st = 0; | |
412 | uart->wk_mask = 0; | |
413 | uart->padconf = 0; | |
414 | } | |
415 | ||
416 | /* Set wake-enable bit */ | |
417 | if (uart->wk_en && uart->wk_mask) { | |
418 | v = __raw_readl(uart->wk_en); | |
419 | v |= uart->wk_mask; | |
420 | __raw_writel(v, uart->wk_en); | |
421 | } | |
422 | ||
423 | /* Ensure IOPAD wake-enables are set */ | |
424 | if (cpu_is_omap34xx() && uart->padconf) { | |
425 | u16 v; | |
426 | ||
427 | v = omap_ctrl_readw(uart->padconf); | |
428 | v |= OMAP3_PADCONF_WAKEUPENABLE0; | |
429 | omap_ctrl_writew(v, uart->padconf); | |
430 | } | |
431 | ||
432 | p->flags |= UPF_SHARE_IRQ; | |
433 | ret = request_irq(p->irq, omap_uart_interrupt, IRQF_SHARED, | |
434 | "serial idle", (void *)uart); | |
435 | WARN_ON(ret); | |
436 | } | |
437 | ||
ba87a9be JH |
438 | static ssize_t sleep_timeout_show(struct kobject *kobj, |
439 | struct kobj_attribute *attr, | |
440 | char *buf) | |
441 | { | |
442 | return sprintf(buf, "%u\n", sleep_timeout / HZ); | |
443 | } | |
444 | ||
445 | static ssize_t sleep_timeout_store(struct kobject *kobj, | |
446 | struct kobj_attribute *attr, | |
447 | const char *buf, size_t n) | |
448 | { | |
449 | struct omap_uart_state *uart; | |
450 | unsigned int value; | |
451 | ||
452 | if (sscanf(buf, "%u", &value) != 1) { | |
453 | printk(KERN_ERR "sleep_timeout_store: Invalid value\n"); | |
454 | return -EINVAL; | |
455 | } | |
456 | sleep_timeout = value * HZ; | |
457 | list_for_each_entry(uart, &uart_list, node) { | |
458 | uart->timeout = sleep_timeout; | |
459 | if (uart->timeout) | |
460 | mod_timer(&uart->timer, jiffies + uart->timeout); | |
461 | else | |
462 | /* A zero value means disable timeout feature */ | |
463 | omap_uart_block_sleep(uart); | |
464 | } | |
465 | return n; | |
466 | } | |
467 | ||
468 | static struct kobj_attribute sleep_timeout_attr = | |
469 | __ATTR(sleep_timeout, 0644, sleep_timeout_show, sleep_timeout_store); | |
470 | ||
4af4016c KH |
471 | #else |
472 | static inline void omap_uart_idle_init(struct omap_uart_state *uart) {} | |
473 | #endif /* CONFIG_PM */ | |
474 | ||
2aa57be2 VP |
475 | static struct platform_device serial_device = { |
476 | .name = "serial8250", | |
477 | .id = PLAT8250_DEV_PLATFORM, | |
478 | .dev = { | |
479 | .platform_data = serial_platform_data, | |
480 | }, | |
481 | }; | |
482 | ||
6e81176d | 483 | void __init omap_serial_init(void) |
1dbae815 | 484 | { |
970a724d | 485 | int i, err; |
1dbae815 | 486 | const struct omap_uart_config *info; |
6e81176d | 487 | char name[16]; |
1dbae815 TL |
488 | |
489 | /* | |
490 | * Make sure the serial ports are muxed on at this point. | |
491 | * You have to mux them off in device drivers later on | |
492 | * if not needed. | |
493 | */ | |
494 | ||
6e81176d | 495 | info = omap_get_config(OMAP_TAG_UART, struct omap_uart_config); |
1dbae815 TL |
496 | |
497 | if (info == NULL) | |
498 | return; | |
44169075 SS |
499 | if (cpu_is_omap44xx()) { |
500 | for (i = 0; i < OMAP_MAX_NR_PORTS; i++) | |
501 | serial_platform_data[i].irq += 32; | |
502 | } | |
1dbae815 TL |
503 | |
504 | for (i = 0; i < OMAP_MAX_NR_PORTS; i++) { | |
505 | struct plat_serial8250_port *p = serial_platform_data + i; | |
4af4016c | 506 | struct omap_uart_state *uart = &omap_uart[i]; |
1dbae815 TL |
507 | |
508 | if (!(info->enabled_uarts & (1 << i))) { | |
c0fc18c5 | 509 | p->membase = NULL; |
1dbae815 TL |
510 | p->mapbase = 0; |
511 | continue; | |
512 | } | |
513 | ||
6e81176d | 514 | sprintf(name, "uart%d_ick", i+1); |
4af4016c KH |
515 | uart->ick = clk_get(NULL, name); |
516 | if (IS_ERR(uart->ick)) { | |
6e81176d | 517 | printk(KERN_ERR "Could not get uart%d_ick\n", i+1); |
4af4016c KH |
518 | uart->ick = NULL; |
519 | } | |
6e81176d JH |
520 | |
521 | sprintf(name, "uart%d_fck", i+1); | |
4af4016c KH |
522 | uart->fck = clk_get(NULL, name); |
523 | if (IS_ERR(uart->fck)) { | |
6e81176d | 524 | printk(KERN_ERR "Could not get uart%d_fck\n", i+1); |
4af4016c KH |
525 | uart->fck = NULL; |
526 | } | |
527 | ||
528 | if (!uart->ick || !uart->fck) | |
529 | continue; | |
530 | ||
531 | uart->num = i; | |
532 | p->private_data = uart; | |
533 | uart->p = p; | |
534 | list_add(&uart->node, &uart_list); | |
1dbae815 | 535 | |
4af4016c KH |
536 | omap_uart_enable_clocks(uart); |
537 | omap_uart_reset(uart); | |
538 | omap_uart_idle_init(uart); | |
1dbae815 | 539 | } |
1dbae815 | 540 | |
970a724d | 541 | err = platform_device_register(&serial_device); |
ba87a9be JH |
542 | |
543 | #ifdef CONFIG_PM | |
970a724d TL |
544 | if (!err) |
545 | err = sysfs_create_file(&serial_device.dev.kobj, | |
ba87a9be JH |
546 | &sleep_timeout_attr.attr); |
547 | #endif | |
970a724d | 548 | |
1dbae815 | 549 | } |
970a724d | 550 |