2021-09-04 17:56:49 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2021, lizhengyang
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*
|
|
|
|
* Change Logs:
|
|
|
|
* Date Author Notes
|
2021-09-06 19:18:27 +08:00
|
|
|
* 2021-09-02 lizhengyang first version
|
2021-09-04 17:56:49 +08:00
|
|
|
*/
|
|
|
|
#include <rtdevice.h>
|
|
|
|
#include <rthw.h>
|
|
|
|
#include "drv_usart.h"
|
|
|
|
#include "board_config.h"
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef RT_USING_SERIAL
|
|
|
|
#if !defined(BSP_USING_UART1) && !defined(BSP_USING_UART2) && !defined(BSP_USING_UART3) && !defined(BSP_USING_UART4)
|
|
|
|
#error "Please define at least one BSP_USING_UARTx"
|
|
|
|
/* UART instance can be selected at menuconfig -> Hardware Drivers Config -> On-chip Peripheral Drivers -> Enable UART */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* HC32 config uart class */
|
|
|
|
struct hc32_uart_config
|
|
|
|
{
|
|
|
|
struct hc32_irq_config rxerr_irq_config;
|
|
|
|
struct hc32_irq_config rx_irq_config;
|
|
|
|
};
|
|
|
|
/* HC32 UART index */
|
|
|
|
struct uart_index
|
|
|
|
{
|
|
|
|
rt_uint32_t index;
|
|
|
|
M4_USART_TypeDef *Instance;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* HC32 UART irq handler */
|
|
|
|
struct uart_irq_handler
|
|
|
|
{
|
|
|
|
void (*rxerr_irq_handler)(void);
|
|
|
|
void (*rx_irq_handler)(void);
|
|
|
|
};
|
|
|
|
|
|
|
|
/* HC32 uart dirver class */
|
|
|
|
struct hc32_uart
|
|
|
|
{
|
|
|
|
const char *name;
|
|
|
|
M4_USART_TypeDef *Instance;
|
|
|
|
struct hc32_uart_config config;
|
|
|
|
struct rt_serial_device serial;
|
|
|
|
};
|
|
|
|
#ifndef UART_CONFIG
|
|
|
|
#define UART_CONFIG(uart_name, USART) \
|
|
|
|
{ \
|
|
|
|
.name = uart_name, \
|
|
|
|
.Instance = M4_##USART, \
|
|
|
|
.config = { \
|
|
|
|
.rxerr_irq_config = { \
|
|
|
|
.irq = USART##_RXERR_INT_IRQn, \
|
|
|
|
.irq_prio = USART##_RXERR_INT_PRIO, \
|
|
|
|
.int_src = INT_##USART##_EI, \
|
|
|
|
}, \
|
|
|
|
.rx_irq_config = { \
|
|
|
|
.irq = USART##_RX_INT_IRQn, \
|
|
|
|
.irq_prio = USART##_RX_INT_PRIO, \
|
|
|
|
.int_src = INT_##USART##_RI, \
|
|
|
|
}, \
|
|
|
|
}, \
|
|
|
|
}
|
|
|
|
#endif /* UART_CONFIG */
|
|
|
|
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
#ifdef BSP_USING_UART1
|
|
|
|
UART1_INDEX,
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART2
|
|
|
|
UART2_INDEX,
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART3
|
|
|
|
UART3_INDEX,
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART4
|
|
|
|
UART4_INDEX,
|
|
|
|
#endif
|
|
|
|
UART_INDEX_MAX,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct uart_index uart_map[] =
|
|
|
|
{
|
|
|
|
#ifdef BSP_USING_UART1
|
|
|
|
{UART1_INDEX, M4_USART1},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART2
|
|
|
|
{UART2_INDEX, M4_USART2},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART3
|
|
|
|
{UART3_INDEX, M4_USART3},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART4
|
|
|
|
{UART4_INDEX, M4_USART4},
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
static const struct uart_index uart_clock_map[] =
|
|
|
|
{
|
|
|
|
#ifdef BSP_USING_UART1
|
|
|
|
{0, M4_USART1},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART2
|
|
|
|
{1, M4_USART2},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART3
|
|
|
|
{2, M4_USART3},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART4
|
|
|
|
{3, M4_USART4},
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct hc32_uart uart_obj[] =
|
|
|
|
{
|
|
|
|
#ifdef BSP_USING_UART1
|
|
|
|
UART_CONFIG("uart1", USART1),
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART2
|
|
|
|
UART_CONFIG("uart2", USART2),
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART3
|
|
|
|
UART_CONFIG("uart3", USART3),
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART4
|
|
|
|
UART_CONFIG("uart4", USART4),
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct uart_irq_handler uart_irq_handlers[sizeof(uart_obj) / sizeof(uart_obj[0])];
|
|
|
|
|
|
|
|
static uint32_t hc32_get_uart_index(M4_USART_TypeDef *Instance)
|
|
|
|
{
|
|
|
|
uint32_t index = UART_INDEX_MAX;
|
|
|
|
|
|
|
|
for (uint8_t i = 0U; i < ARRAY_SZ(uart_map); i++)
|
|
|
|
{
|
|
|
|
if (uart_map[i].Instance == Instance)
|
|
|
|
{
|
|
|
|
index = uart_map[i].index;
|
|
|
|
RT_ASSERT(index < UART_INDEX_MAX)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
static uint32_t hc32_get_uart_clock_index(M4_USART_TypeDef *Instance)
|
|
|
|
{
|
|
|
|
uint32_t index = 4;
|
|
|
|
|
|
|
|
for (uint8_t i = 0U; i < ARRAY_SZ(uart_clock_map); i++)
|
|
|
|
{
|
|
|
|
if (uart_clock_map[i].Instance == Instance)
|
|
|
|
{
|
|
|
|
index = uart_clock_map[i].index;
|
|
|
|
RT_ASSERT(index < 4)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
static uint32_t hc32_get_usart_fcg(M4_USART_TypeDef *Instance)
|
|
|
|
{
|
|
|
|
return (PWC_FCG1_PERIPH_USART1 << hc32_get_uart_clock_index(Instance));
|
|
|
|
}
|
|
|
|
|
|
|
|
static rt_err_t hc32_configure(struct rt_serial_device *serial,
|
|
|
|
struct serial_configure *cfg)
|
|
|
|
{
|
|
|
|
struct hc32_uart *uart;
|
|
|
|
stc_usart_uart_init_t uart_init;
|
|
|
|
|
|
|
|
RT_ASSERT(RT_NULL != cfg);
|
|
|
|
RT_ASSERT(RT_NULL != serial);
|
|
|
|
|
|
|
|
uart = rt_container_of(serial, struct hc32_uart, serial);
|
|
|
|
RT_ASSERT(RT_NULL != uart->Instance);
|
|
|
|
|
|
|
|
/* Configure USART initialization structure */
|
|
|
|
MEM_ZERO_STRUCT(uart_init);
|
|
|
|
uart_init.enSampleMode = UsartSampleBit8;
|
|
|
|
uart_init.enSampleMode = UsartSampleBit8;
|
|
|
|
uart_init.enDetectMode = UsartStartBitFallEdge;
|
|
|
|
uart_init.enHwFlow = UsartRtsEnable;
|
|
|
|
uart_init.enClkMode = UsartIntClkCkNoOutput;
|
|
|
|
uart_init.enClkDiv = UsartClkDiv_1;
|
|
|
|
if (BIT_ORDER_LSB == cfg->bit_order)
|
|
|
|
{
|
|
|
|
uart_init.enDirection = UsartDataLsbFirst;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
uart_init.enDirection = UsartDataMsbFirst;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (cfg->stop_bits)
|
|
|
|
{
|
|
|
|
case STOP_BITS_1:
|
|
|
|
uart_init.enStopBit = UsartOneStopBit;
|
|
|
|
break;
|
|
|
|
case STOP_BITS_2:
|
|
|
|
uart_init.enStopBit = UsartTwoStopBit;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
uart_init.enStopBit = UsartOneStopBit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (cfg->parity)
|
|
|
|
{
|
|
|
|
case PARITY_NONE:
|
|
|
|
uart_init.enParity = UsartParityNone;
|
|
|
|
break;
|
|
|
|
case PARITY_EVEN:
|
|
|
|
uart_init.enParity = UsartParityEven;
|
|
|
|
break;
|
|
|
|
case PARITY_ODD:
|
|
|
|
uart_init.enParity = UsartParityOdd;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
uart_init.enParity = UsartParityNone;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (cfg->data_bits)
|
|
|
|
{
|
|
|
|
case DATA_BITS_8:
|
|
|
|
uart_init.enDataLength = UsartDataBits8;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -RT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Enable USART clock */
|
|
|
|
PWC_Fcg1PeriphClockCmd(hc32_get_usart_fcg(uart->Instance), Enable);
|
|
|
|
|
|
|
|
rt_err_t rt_hw_board_uart_init(M4_USART_TypeDef * USARTx);
|
|
|
|
if (RT_EOK != rt_hw_board_uart_init(uart->Instance))
|
|
|
|
{
|
|
|
|
return -RT_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
USART_DeInit(uart->Instance);
|
|
|
|
if (Error == USART_UART_Init(uart->Instance, &uart_init))
|
|
|
|
{
|
|
|
|
return -RT_ERROR;
|
|
|
|
}
|
|
|
|
USART_SetBaudrate(uart->Instance, cfg->baud_rate);
|
|
|
|
if (RT_EOK != USART_SetBaudrate(uart->Instance, cfg->baud_rate))
|
|
|
|
{
|
|
|
|
return -RT_ERROR;
|
|
|
|
}
|
|
|
|
/* Register RX error interrupt */
|
|
|
|
hc32_install_irq_handler(&uart->config.rxerr_irq_config,
|
|
|
|
uart_irq_handlers[hc32_get_uart_index(uart->Instance)].rxerr_irq_handler,
|
|
|
|
RT_TRUE);
|
|
|
|
|
|
|
|
USART_FuncCmd(uart->Instance, UsartRxInt, Enable);
|
|
|
|
|
|
|
|
if ((serial->parent.flag & RT_DEVICE_FLAG_RDWR) || \
|
|
|
|
(serial->parent.flag & RT_DEVICE_FLAG_RDONLY))
|
|
|
|
{
|
|
|
|
USART_FuncCmd(uart->Instance, UsartRx, Enable);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((serial->parent.flag & RT_DEVICE_FLAG_RDWR) || \
|
|
|
|
(serial->parent.flag & RT_DEVICE_FLAG_WRONLY))
|
|
|
|
{
|
|
|
|
USART_FuncCmd(uart->Instance, UsartTx, Enable);
|
|
|
|
}
|
|
|
|
|
|
|
|
return RT_EOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static rt_err_t hc32_control(struct rt_serial_device *serial, int cmd, void *arg)
|
|
|
|
{
|
|
|
|
struct hc32_uart *uart;
|
|
|
|
uint32_t uart_index;
|
|
|
|
RT_ASSERT(RT_NULL != serial);
|
|
|
|
uart = rt_container_of(serial, struct hc32_uart, serial);
|
|
|
|
RT_ASSERT(RT_NULL != uart->Instance);
|
|
|
|
|
|
|
|
switch (cmd)
|
|
|
|
{
|
|
|
|
/* Disable interrupt */
|
|
|
|
case RT_DEVICE_CTRL_CLR_INT:
|
|
|
|
/* Disable RX irq */
|
|
|
|
NVIC_DisableIRQ(uart->config.rx_irq_config.irq);
|
|
|
|
enIrqResign(uart->config.rx_irq_config.irq);
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Enable interrupt */
|
|
|
|
case RT_DEVICE_CTRL_SET_INT:
|
|
|
|
uart_index = hc32_get_uart_index(uart->Instance);
|
|
|
|
/* Install RX irq handler */
|
|
|
|
hc32_install_irq_handler(&uart->config.rx_irq_config,
|
|
|
|
uart_irq_handlers[uart_index].rx_irq_handler,
|
|
|
|
RT_TRUE);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return RT_EOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hc32_putc(struct rt_serial_device *serial, char c)
|
|
|
|
{
|
|
|
|
struct hc32_uart *uart;
|
|
|
|
|
|
|
|
RT_ASSERT(RT_NULL != serial);
|
|
|
|
|
|
|
|
uart = rt_container_of(serial, struct hc32_uart, serial);
|
|
|
|
RT_ASSERT(RT_NULL != uart->Instance);
|
|
|
|
|
|
|
|
USART_SendData(uart->Instance, c);
|
|
|
|
/* Polling mode. */
|
|
|
|
while (USART_GetStatus(uart->Instance, UsartTxEmpty) != Set);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hc32_getc(struct rt_serial_device *serial)
|
|
|
|
{
|
|
|
|
int ch = -1;
|
|
|
|
struct hc32_uart *uart;
|
|
|
|
|
|
|
|
RT_ASSERT(RT_NULL != serial);
|
|
|
|
|
|
|
|
uart = rt_container_of(serial, struct hc32_uart, serial);
|
|
|
|
RT_ASSERT(RT_NULL != uart->Instance);
|
|
|
|
|
|
|
|
if (Set == USART_GetStatus(uart->Instance, UsartRxNoEmpty))
|
|
|
|
{
|
|
|
|
ch = (rt_uint8_t)USART_RecData(uart->Instance);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hc32_uart_rx_irq_handler(struct hc32_uart *uart)
|
|
|
|
{
|
|
|
|
RT_ASSERT(RT_NULL != uart);
|
|
|
|
|
|
|
|
rt_hw_serial_isr(&uart->serial, RT_SERIAL_EVENT_RX_IND);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hc32_uart_rxerr_irq_handler(struct hc32_uart *uart)
|
|
|
|
{
|
|
|
|
RT_ASSERT(RT_NULL != uart);
|
|
|
|
RT_ASSERT(RT_NULL != uart->Instance);
|
|
|
|
|
|
|
|
if (Set == USART_GetStatus(uart->Instance, UsartParityErr) || \
|
|
|
|
Set == USART_GetStatus(uart->Instance, UsartFrameErr))
|
|
|
|
{
|
|
|
|
USART_RecData(uart->Instance);
|
|
|
|
}
|
|
|
|
USART_ClearStatus(uart->Instance, UsartParityErr);
|
|
|
|
USART_ClearStatus(uart->Instance, UsartFrameErr);
|
|
|
|
USART_ClearStatus(uart->Instance, UsartOverrunErr);
|
|
|
|
}
|
|
|
|
#if defined(BSP_USING_UART1)
|
|
|
|
static void hc32_uart1_rx_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rx_irq_handler(&uart_obj[UART1_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hc32_uart1_rxerr_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rxerr_irq_handler(&uart_obj[UART1_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
#endif /* BSP_USING_UART1 */
|
|
|
|
|
|
|
|
#if defined(BSP_USING_UART2)
|
|
|
|
static void hc32_uart2_rx_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rx_irq_handler(&uart_obj[UART2_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
static void hc32_uart2_rxerr_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rxerr_irq_handler(&uart_obj[UART2_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
#endif /* BSP_USING_UART2 */
|
|
|
|
|
|
|
|
#if defined(BSP_USING_UART3)
|
|
|
|
static void hc32_uart3_rx_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rx_irq_handler(&uart_obj[UART3_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hc32_uart3_rxerr_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rxerr_irq_handler(&uart_obj[UART3_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
#endif /* BSP_USING_UART3 */
|
|
|
|
|
|
|
|
#if defined(BSP_USING_UART4)
|
|
|
|
static void hc32_uart4_rx_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rx_irq_handler(&uart_obj[UART4_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
static void hc32_uart4_rxerr_irq_handler(void)
|
|
|
|
{
|
|
|
|
/* enter interrupt */
|
|
|
|
rt_interrupt_enter();
|
|
|
|
|
|
|
|
hc32_uart_rxerr_irq_handler(&uart_obj[UART4_INDEX]);
|
|
|
|
|
|
|
|
/* leave interrupt */
|
|
|
|
rt_interrupt_leave();
|
|
|
|
}
|
|
|
|
#endif /* BSP_USING_UART4 */
|
|
|
|
|
|
|
|
static const struct uart_irq_handler uart_irq_handlers[] =
|
|
|
|
{
|
|
|
|
#ifdef BSP_USING_UART1
|
|
|
|
{hc32_uart1_rxerr_irq_handler, hc32_uart1_rx_irq_handler},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART2
|
|
|
|
{hc32_uart2_rxerr_irq_handler, hc32_uart2_rx_irq_handler},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART3
|
|
|
|
{hc32_uart3_rxerr_irq_handler, hc32_uart3_rx_irq_handler},
|
|
|
|
#endif
|
|
|
|
#ifdef BSP_USING_UART4
|
|
|
|
{hc32_uart4_rxerr_irq_handler, hc32_uart4_rx_irq_handler},
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
static const struct rt_uart_ops hc32_uart_ops =
|
|
|
|
{
|
|
|
|
.configure = hc32_configure,
|
|
|
|
.control = hc32_control,
|
|
|
|
.putc = hc32_putc,
|
|
|
|
.getc = hc32_getc,
|
|
|
|
};
|
|
|
|
|
|
|
|
int hc32_hw_uart_init(void)
|
|
|
|
{
|
|
|
|
rt_err_t result = RT_EOK;
|
|
|
|
rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct hc32_uart);
|
|
|
|
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
|
|
|
|
|
|
|
|
for (int i = 0; i < obj_num; i++)
|
|
|
|
{
|
|
|
|
/* init UART object */
|
|
|
|
uart_obj[i].serial.ops = &hc32_uart_ops;
|
|
|
|
uart_obj[i].serial.config = config;
|
|
|
|
|
|
|
|
/* register UART device */
|
|
|
|
result = rt_hw_serial_register(&uart_obj[i].serial,
|
|
|
|
uart_obj[i].name,
|
|
|
|
(RT_DEVICE_FLAG_RDWR |
|
|
|
|
RT_DEVICE_FLAG_INT_RX |
|
|
|
|
RT_DEVICE_FLAG_INT_TX),
|
|
|
|
&uart_obj[i]);
|
|
|
|
RT_ASSERT(result == RT_EOK);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
INIT_BOARD_EXPORT(hc32_hw_uart_init);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|