| /* |
| * Copyright (C) 2011 Broadcom Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/compiler.h> |
| |
| #include <asm/mipsregs.h> |
| #include <linux/brcmstb/brcmstb.h> |
| #include <asm/tlbflush.h> |
| #include <asm/sections.h> |
| #include <asm/cpu-features.h> |
| #include <asm/bmips.h> |
| |
| #define MAX_GP_REGS 16 |
| #define MAX_CP0_REGS 32 |
| #define MAX_IO_REGS (256 - MAX_GP_REGS - MAX_CP0_REGS) |
| |
| #if 0 |
| #define DBG printk |
| #else |
| #define DBG(...) do { } while (0) |
| #endif |
| |
| #define CALCULATE_MEM_HASH (1) |
| #define WARM_BOOT_MAGIC_WORD (0x5AFEB007) |
| #define CFE_PARAMETER_BLOCK (0) |
| #define AUTHENTICATION_REGION_SIZE (16*1024*1024) |
| |
| #define AON_CTRL_HASH_START_INDEX (CFE_PARAMETER_BLOCK+5) |
| #define AON_SAVE_CPB(idx, val) \ |
| BDEV_WR_RB(AON_RAM_BASE + ((CFE_PARAMETER_BLOCK+idx)<<2), val) |
| #define AON_READ_CPB(idx) \ |
| BDEV_RD(AON_RAM_BASE + ((CFE_PARAMETER_BLOCK+idx)<<2)) |
| #define AON_SAVE_HASH(idx, val) \ |
| BDEV_WR_RB(AON_RAM_BASE + ((AON_CTRL_HASH_START_INDEX+idx)<<2), val) |
| #define AON_READ_HASH(idx) \ |
| BDEV_RD(AON_RAM_BASE + ((AON_CTRL_HASH_START_INDEX+idx)<<2)) |
| |
| struct cp0_value { |
| u32 value; |
| u32 index; |
| u32 select; |
| }; |
| |
| struct brcm_pm_s3_context { |
| u32 gp_regs[MAX_GP_REGS]; |
| struct cp0_value cp0_regs[MAX_CP0_REGS]; |
| int cp0_regs_idx; |
| u32 memc0_rts[NUM_MEMC_CLIENTS]; |
| u32 relo_vector_control_0; |
| u32 relo_vector_control_1; |
| u32 sc_boot_vec; |
| }; |
| |
| struct brcm_pm_s3_context s3_context; |
| |
| asmlinkage int brcm_pm_s3_standby_asm(unsigned long options, |
| void (*dram_encoder_start)(void), int dcache_linesz); |
| extern int s3_reentry; |
| |
| extern void bmips_enable_xks01(void); |
| |
| static void __raw_uart_putc(char c); |
| |
| static void brcm_pm_dump_context(struct brcm_pm_s3_context *cxt); |
| |
| #define CPO_SAVE_INFO(cxt, ci, idx, _select) \ |
| do { \ |
| cxt->cp0_regs[ci].index = idx; \ |
| cxt->cp0_regs[ci].select = _select; \ |
| cxt->cp0_regs[ci++].value = read_c0_reg(idx, _select); \ |
| } while (0); |
| #define read_c0_reg(n, s) __read_32bit_c0_register($##n, s) |
| #define write_c0_reg(n, s, v) __write_32bit_c0_register($##n, s, v) |
| |
| static __maybe_unused void brcm_pm_init_cp0_context( |
| struct brcm_pm_s3_context *cxt) |
| { |
| cxt->cp0_regs_idx = 0; |
| } |
| |
| static __maybe_unused void brcm_pm_save_cp0_context( |
| struct brcm_pm_s3_context *cxt) |
| { |
| int ci = cxt->cp0_regs_idx; |
| void __iomem *cbr; |
| |
| /* Generic MIPS */ |
| |
| CPO_SAVE_INFO(cxt, ci, 4, 0); /* context */ |
| CPO_SAVE_INFO(cxt, ci, 4, 2); /* userlocal */ |
| CPO_SAVE_INFO(cxt, ci, 5, 0); /* pagemask */ |
| CPO_SAVE_INFO(cxt, ci, 7, 0); /* hwrena */ |
| CPO_SAVE_INFO(cxt, ci, 11, 0); /* compare */ |
| CPO_SAVE_INFO(cxt, ci, 12, 0); /* status */ |
| |
| /* Broadcom specific */ |
| |
| CPO_SAVE_INFO(cxt, ci, 22, 0); /* config */ |
| CPO_SAVE_INFO(cxt, ci, 22, 1); /* mode */ |
| CPO_SAVE_INFO(cxt, ci, 22, 3); /* eDSP */ |
| |
| switch (current_cpu_type()) { |
| case CPU_BMIPS4380: |
| cbr = BMIPS_GET_CBR(); |
| cxt->relo_vector_control_1 = |
| __raw_readl(cbr + BMIPS_RELO_VECTOR_CONTROL_1); |
| /* falls through */ |
| case CPU_BMIPS3300: |
| case CPU_BMIPS4350: |
| cbr = BMIPS_GET_CBR(); |
| cxt->relo_vector_control_0 = |
| __raw_readl(cbr + BMIPS_RELO_VECTOR_CONTROL_0); |
| break; |
| case CPU_BMIPS5000: |
| CPO_SAVE_INFO(cxt, ci, 22, 4); /* bootvec */ |
| CPO_SAVE_INFO(cxt, ci, 15, 1); /* ebase */ |
| cxt->sc_boot_vec = bmips_read_zscm_reg(0xa0); |
| break; |
| } |
| |
| cxt->cp0_regs_idx = ci; |
| } |
| |
| static __maybe_unused void brcm_pm_restore_cp0_context( |
| struct brcm_pm_s3_context *cxt) |
| { |
| int ci = cxt->cp0_regs_idx; |
| void __iomem *cbr; |
| |
| /* Broadcom specific */ |
| |
| switch (current_cpu_type()) { |
| case CPU_BMIPS4380: |
| cbr = BMIPS_GET_CBR(); |
| __raw_writel(cxt->relo_vector_control_1, |
| cbr + BMIPS_RELO_VECTOR_CONTROL_1); |
| /* falls through */ |
| case CPU_BMIPS3300: |
| case CPU_BMIPS4350: |
| cbr = BMIPS_GET_CBR(); |
| __raw_writel(cxt->relo_vector_control_0, |
| cbr + BMIPS_RELO_VECTOR_CONTROL_0); |
| break; |
| case CPU_BMIPS5000: |
| bmips_write_zscm_reg(0xa0, cxt->sc_boot_vec); |
| write_c0_reg(15, 1, cxt->cp0_regs[--ci].value); /* ebase */ |
| write_c0_reg(22, 4, cxt->cp0_regs[--ci].value); /* bootvec */ |
| break; |
| } |
| |
| write_c0_reg(22, 3, cxt->cp0_regs[--ci].value); /* eDSP */ |
| write_c0_reg(22, 1, cxt->cp0_regs[--ci].value); /* mode */ |
| write_c0_reg(22, 0, cxt->cp0_regs[--ci].value); /* config */ |
| |
| /* Generic MIPS */ |
| |
| write_c0_reg(12, 0, cxt->cp0_regs[--ci].value); /* status */ |
| write_c0_reg(11, 0, cxt->cp0_regs[--ci].value); /* compare */ |
| write_c0_reg(7, 0, cxt->cp0_regs[--ci].value); /* hwrena */ |
| write_c0_reg(5, 0, cxt->cp0_regs[--ci].value); /* pagemask */ |
| write_c0_reg(4, 2, cxt->cp0_regs[--ci].value); /* userlocal */ |
| write_c0_reg(4, 0, cxt->cp0_regs[--ci].value); /* context */ |
| } |
| |
| static __maybe_unused void brcm_pm_cmp_context( |
| struct brcm_pm_s3_context *cxt, |
| struct brcm_pm_s3_context *cxt2, const char* title) |
| { |
| int i; |
| int identical = 1; |
| if (title) |
| DBG("%s\n", title); |
| if (cxt->cp0_regs_idx != cxt2->cp0_regs_idx) { |
| identical = 0; |
| DBG("CP0 reg # is different: %d %d\n", |
| cxt->cp0_regs_idx, cxt2->cp0_regs_idx); |
| } else { |
| for (i = 0; i < cxt->cp0_regs_idx; i++) { |
| if (cxt->cp0_regs[i].value |
| != cxt2->cp0_regs[i].value) { |
| identical = 0; |
| DBG("\tCP0[%02d.%01d]: %08x != %08x\n", |
| cxt->cp0_regs[i].index, |
| cxt->cp0_regs[i].select, |
| cxt->cp0_regs[i].value, |
| cxt2->cp0_regs[i].value); |
| } |
| } |
| } |
| if (identical) |
| DBG("Contexts are identical\n"); |
| else { |
| brcm_pm_dump_context(cxt); |
| brcm_pm_dump_context(cxt2); |
| } |
| } |
| |
| static void brcm_pm_dump_context(struct brcm_pm_s3_context *cxt) |
| { |
| int i; |
| DBG("CPO:\n"); |
| for (i = 0; i < cxt->cp0_regs_idx; i++) { |
| DBG("[%02d.%0d]=%08x ", |
| cxt->cp0_regs[i].index, |
| cxt->cp0_regs[i].select, |
| cxt->cp0_regs[i].value); |
| if (i%8 == 7) |
| DBG("\n"); |
| } |
| DBG("\n\n"); |
| } |
| |
| /*********************************************************************** |
| * Encryption setup for S3 suspend |
| ***********************************************************************/ |
| #if CALCULATE_MEM_HASH |
| /* |
| * Default scheme encrypts specified memory region and writes encoded data |
| * to a temporary buffer, overwriting previous data on every block write. |
| * Upon encoding final block, last 16 bytes are stored into AON, then |
| * entire temporary buffer is wiped out |
| * Bootloader will repeat encoding immediately after S3 wakeup to see |
| * if hash value matches. |
| * brcm_pm_s3_tmpbuf - temporary buffer |
| * hash_pointer - pointer to location in memory where hash value is located |
| * after encoding |
| */ |
| static char __section(.s3_enc_tmpbuf) brcm_pm_s3_tmpbuf[PAGE_SIZE*16]; |
| static u32 *hash_pointer; |
| |
| #define MEM_ENCRYPTED_START_ADDR (0x80000000) |
| #define MEM_ENCRYPTED_LEN (1*1024*1024) |
| #define S3_HASH_LEN 16 |
| |
| #define MAX_TRANSFER_SIZE_MB (16) |
| #define MAX_TRANSFER_SIZE (MAX_TRANSFER_SIZE_MB*1024*1024) |
| |
| #define AES_CBC_ENCRYPTION_KEY_SLOT (5) |
| #define AES_CBC_DECRYPTION_KEY_SLOT (6) |
| |
| /* |
| * brcm_pm_dram_encoder_set_area |
| * Generates a list of descriptors needed to encrypt area |
| * VA _begin.._begin+len-1 into PA outbuf..outbuf+out_len-1 |
| * On return out_len is the length of the last transfer, this |
| * can be used as an offset into the outbuf to extract hash |
| */ |
| static struct brcm_mem_transfer *brcm_pm_dram_encoder_set_area(void *_begin, |
| u32 len, dma_addr_t outbuf, u32 *out_len, |
| struct brcm_mem_transfer *next) |
| { |
| int num_descr = (len + *out_len - 1) / *out_len; |
| struct brcm_mem_transfer *xfer, *x, *last_x; |
| void *_end = _begin + len; |
| |
| BUG_ON(*out_len > MAX_TRANSFER_SIZE); |
| |
| xfer = kzalloc(num_descr * sizeof(struct brcm_mem_transfer), |
| GFP_ATOMIC); |
| |
| if (!xfer) { |
| printk(KERN_ERR "%s: out of memory\n", __func__); |
| return NULL; |
| } |
| |
| last_x = x = xfer; |
| while (_begin < _end) { |
| dma_addr_t pa_src = virt_to_phys(_begin); |
| if (_begin + *out_len > _end) |
| *out_len = _end - _begin; |
| /* workaround - M2M DMA does not like 0 address */ |
| x->len = *out_len; |
| if (!pa_src) { |
| pa_src += 0x10; |
| x->len -= 0x10; |
| } |
| x->pa_src = pa_src; |
| x->pa_dst = outbuf; |
| x->mode = BRCM_MEM_DMA_SCRAM_BLOCK; |
| x->key = AES_CBC_ENCRYPTION_KEY_SLOT; |
| x->next = x + 1; |
| last_x = x; |
| x++; |
| _begin += *out_len; |
| } |
| last_x->next = next; |
| |
| return xfer; |
| } |
| |
| static void brcm_pm_dram_encoder_free_area(struct brcm_mem_transfer *xfer) |
| { |
| kfree(xfer); |
| } |
| |
| /* Memory region over which M2M DMA calculates MAC cannot be modified |
| after a call to brcm_pm_dram_encoder_start() */ |
| void brcm_pm_dram_encode(void) |
| { |
| u32 *hp = hash_pointer; |
| u32 outbuflen = sizeof(brcm_pm_s3_tmpbuf); |
| uint32_t tmp0, tmp1; |
| /* clear temporary buffer */ |
| memset(brcm_pm_s3_tmpbuf, 0, sizeof(brcm_pm_s3_tmpbuf)); |
| _dma_cache_wback_inv(0, ~0); |
| /* start M2M encoder */ |
| brcm_pm_dram_encoder_start(); |
| /* save hash in AON register */ |
| AON_SAVE_HASH(0, *hp++); |
| AON_SAVE_HASH(1, *hp++); |
| AON_SAVE_HASH(2, *hp++); |
| AON_SAVE_HASH(3, *hp++); |
| |
| /* clear temporary buffer |
| * cannot use library function because it will use memory (stack) */ |
| __asm__ __volatile__( |
| " .set noreorder\n" |
| " la %0, brcm_pm_s3_tmpbuf\n" |
| " move %1, $0\n" |
| "1: sw $0, 0(%0)\n" |
| " addi %0, 4\n" |
| " addi %1, 4\n" |
| " bne %2, %1, 1b\n" |
| " nop\n" |
| : "=&r" (tmp0), "=&r" (tmp1) |
| : "r" (outbuflen)); |
| } |
| #else |
| #define brcm_pm_dram_encode NULL |
| #endif |
| |
| int brcm_pm_s3_standby(int dcache_linesz, unsigned long options) |
| { |
| int retval = 0; |
| unsigned long flags; |
| #if CALCULATE_MEM_HASH |
| int r1_len, r2_len, total_len; |
| struct brcm_mem_transfer *xfer[3] = {0}; |
| u32 outlen = sizeof(brcm_pm_s3_tmpbuf); |
| u32 outbuflen = outlen, tmp, ii; |
| u32 region_size = AUTHENTICATION_REGION_SIZE; |
| void *src; |
| u32 hash[4]; |
| |
| /* |
| * We are using bi-directional mapping to avoid synchronizing cache |
| * after encoding |
| */ |
| dma_addr_t pa_dst = dma_map_single(NULL, brcm_pm_s3_tmpbuf, |
| outbuflen, DMA_BIDIRECTIONAL); |
| |
| if (dma_mapping_error(NULL, pa_dst)) { |
| printk(KERN_ERR "%s: cannot remap temp buffer\n", __func__); |
| return -1; |
| } |
| |
| if (region_size < brcm_min_auth_region_size) |
| region_size = brcm_min_auth_region_size; |
| |
| printk(KERN_DEBUG "Authentication region size: 0x%08x\n", region_size); |
| printk(KERN_DEBUG "Output buffer : 0x%08x-0x%08x (0x%08x)\n", |
| (u32)brcm_pm_s3_tmpbuf, |
| (u32)brcm_pm_s3_tmpbuf + outlen, outlen); |
| |
| /* We add areas to the head of the list - in reverse order |
| * of M2M DMA execution */ |
| |
| /* This region starts from _text and bypasses temporary |
| * output buffer */ |
| src = _text; |
| /* check if in/out regions overlap */ |
| if (brcm_pm_s3_tmpbuf >= (char *)(src + region_size) || |
| brcm_pm_s3_tmpbuf + outlen <= (char *)src) { |
| /* non-overlapping case */ |
| r1_len = region_size; |
| r2_len = 0; |
| } else { |
| /* need to cut out the output buffer */ |
| r1_len = (int)(brcm_pm_s3_tmpbuf - (char *)src); |
| r2_len = region_size - r1_len; |
| } |
| total_len = r1_len + r2_len; |
| |
| xfer[0] = brcm_pm_dram_encoder_set_area((void *)src, r1_len, |
| pa_dst, &outlen, NULL); |
| if (!xfer[0]) { |
| printk(KERN_ERR "%s: cannot setup M2M DMA [0]\n", __func__); |
| goto _failed; |
| } |
| |
| printk(KERN_DEBUG "Region 0 : 0x%08x-0x%08x (0x%08x)\n", |
| (u32)src, (u32)src + r1_len, r1_len); |
| |
| if (r2_len > 0) { |
| outlen = outbuflen; |
| src = (void *)((u32)(brcm_pm_s3_tmpbuf + outbuflen)); |
| xfer[1] = xfer[0]; |
| xfer[0] = brcm_pm_dram_encoder_set_area(src, r2_len, |
| pa_dst, &outlen, xfer[1]); |
| if (!xfer[0]) { |
| printk(KERN_ERR "%s: cannot setup M2M DMA [1]\n", |
| __func__); |
| goto _failed; |
| } |
| printk(KERN_DEBUG "Region 1 : 0x%08x-0x%08x (0x%08x)\n", |
| (u32)src, (u32)(src + r2_len), r2_len); |
| } |
| |
| /* First descriptor must include kernel reentry point |
| * We are processing one output buffer worth of data */ |
| src = (void *)((u32)(&s3_reentry) & ~(PAGE_SIZE-1)); |
| tmp = outbuflen; |
| xfer[2] = brcm_pm_dram_encoder_set_area((void *)src, outbuflen, |
| pa_dst, &tmp, xfer[0]); |
| if (!xfer[2]) { |
| printk(KERN_ERR "%s: cannot setup M2M DMA [2]\n", __func__); |
| goto _failed; |
| } |
| printk(KERN_DEBUG "Region 2 : 0x%08x-0x%08x (0x%08x)\n", |
| (u32)src, (u32)src + outbuflen, outbuflen); |
| total_len += outbuflen; |
| if (brcm_pm_dram_encoder_prepare(xfer[2])) { |
| printk(KERN_ERR "%s: cannot initialize M2M DMA\n", __func__); |
| goto _failed; |
| } |
| |
| hash_pointer = (u32 *)(brcm_pm_s3_tmpbuf + |
| (outlen - S3_HASH_LEN + outbuflen) % outbuflen); |
| |
| /* M2M DMA descriptor address */ |
| if (brcm_pm_hash_enabled) |
| AON_SAVE_CPB(2, BDEV_RD(BCHP_MEM_DMA_0_FIRST_DESC)); |
| else |
| AON_SAVE_CPB(2, 0); |
| /* Encryption key slot */ |
| AON_SAVE_CPB(3, AES_CBC_ENCRYPTION_KEY_SLOT); |
| /* Address of hash */ |
| AON_SAVE_CPB(4, (u32)hash_pointer); |
| /* Total authentication region size */ |
| AON_SAVE_CPB(9, total_len); |
| printk(KERN_DEBUG "Encryption slot: %d\n", |
| AES_CBC_ENCRYPTION_KEY_SLOT); |
| printk(KERN_DEBUG "Hash pointer : 0x%p\n", hash_pointer); |
| printk(KERN_DEBUG "Total length : 0x%08x\n", (u32)total_len); |
| printk(KERN_DEBUG "Reentry point : 0x%p\n", &s3_reentry); |
| #else |
| AON_SAVE_CPB(2, 0); |
| #endif |
| |
| /* Warm Boot Magic Word */ |
| AON_SAVE_CPB(0, WARM_BOOT_MAGIC_WORD); |
| /* Kernel Reentry Address */ |
| AON_SAVE_CPB(1, (u32)&s3_reentry); |
| |
| /* clear RESET_HISTORY */ |
| BDEV_WR_F_RB(AON_CTRL_RESET_CTRL, clear_reset_history, 1); |
| |
| local_irq_save(flags); |
| /* save CP0 context */ |
| brcm_pm_init_cp0_context(&s3_context); |
| brcm_pm_save_cp0_context(&s3_context); |
| |
| /* inhibit DDR_RSTb pulse */ |
| BDEV_UNSET(BCHP_DDR40_PHY_CONTROL_REGS_0_STANDBY_CONTROL, 0x0f); |
| BDEV_SET(BCHP_DDR40_PHY_CONTROL_REGS_0_STANDBY_CONTROL, BIT(5) | 0x05); |
| |
| /* save RTS */ |
| brcm_pm_save_restore_rts(BCHP_MEMC_ARB_0_REG_START, |
| s3_context.memc0_rts, 0); |
| |
| /* save I/O context */ |
| |
| local_flush_tlb_all(); |
| _dma_cache_wback_inv(0, ~0); |
| |
| retval = brcm_pm_s3_standby_asm(options, brcm_pm_dram_encode, |
| dcache_linesz); |
| |
| /* CPU reconfiguration */ |
| local_flush_tlb_all(); |
| brcmstb_cpu_setup(); |
| cpumask_clear(&bmips_booted_mask); |
| |
| /* restore RTS */ |
| brcm_pm_save_restore_rts(BCHP_MEMC_ARB_0_REG_START, |
| s3_context.memc0_rts, 1); |
| |
| /* restore I/O context */ |
| board_pinmux_setup(); |
| ebi_restore_settings(); |
| |
| /* restore CP0 context */ |
| brcm_pm_restore_cp0_context(&s3_context); |
| |
| #if defined(BCHP_IRQ0_UART_IRQEN_uarta_MASK) |
| /* 3548 style - separate register */ |
| BDEV_WR(BCHP_IRQ0_UART_IRQEN, BCHP_IRQ0_UART_IRQEN_uarta_MASK | |
| BCHP_IRQ0_UART_IRQEN_uartb_MASK | |
| BCHP_IRQ0_UART_IRQEN_uartc_MASK); |
| BDEV_WR(BCHP_IRQ0_IRQEN, 0); |
| #elif defined(BCHP_IRQ0_IRQEN_uarta_irqen_MASK) |
| /* 7405 style - shared with L2 */ |
| BDEV_WR(BCHP_IRQ0_IRQEN, BCHP_IRQ0_IRQEN_uarta_irqen_MASK |
| | BCHP_IRQ0_IRQEN_uartb_irqen_MASK |
| #if defined(BCHP_IRQ0_IRQEN_uartc_irqen_MASK) |
| | BCHP_IRQ0_IRQEN_uartc_irqen_MASK |
| #endif |
| ); |
| #endif |
| |
| local_irq_restore(flags); |
| |
| #if defined(CONFIG_BRCM_HAS_PCIE) && defined(CONFIG_PCI) |
| BDEV_WR_F_RB(WKTMR_EVENT, wktmr_alarm_event, 1); |
| BDEV_WR_F_RB(WKTMR_PRESCALER, wktmr_prescaler, WKTMR_FREQ); |
| |
| if (brcm_pcie_enabled) { |
| brcm_early_pcie_setup(); |
| brcm_setup_pcie_bridge(); |
| } |
| #endif |
| |
| #if CALCULATE_MEM_HASH |
| for (ii = 0; ii < 4; ii++) |
| hash[ii] = AON_READ_HASH(ii); |
| printk(KERN_DEBUG "hash: %08x:%08x:%08x:%08x\n", |
| hash[0], hash[1], hash[2], hash[3]); |
| |
| brcm_pm_dram_encoder_complete(xfer[0]); |
| _failed: |
| for (ii = 0; ii < 3; ii++) |
| brcm_pm_dram_encoder_free_area(xfer[ii]); |
| dma_unmap_single(NULL, pa_dst, outbuflen, DMA_BIDIRECTIONAL); |
| |
| #endif |
| |
| return retval; |
| } |
| |
| static void __maybe_unused __raw_uart_putc(char c) |
| { |
| while (!(BDEV_RD(BCHP_UARTA_LSR) & BCHP_UARTA_LSR_THRE_MASK)) |
| ; |
| BDEV_WR_RB(BCHP_UARTA_THR, (u32)c); |
| } |