/* Copyright (C) 2012, Aaron Lindsay This file is part of Aedrix. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #define VC_MMU_IO_OFFSET(addr) (addr - 0x7E000000 + 0x20000000) /* Auxiliary IO registers (includes both mini-UART and SPI) */ #define AUX_IRQ VC_MMU_IO_OFFSET(0x7E215000) #define AUX_ENABLES VC_MMU_IO_OFFSET(0x7E215004) #define AUX_MU_IO_REG VC_MMU_IO_OFFSET(0x7E215040) #define AUX_MU_IER_REG VC_MMU_IO_OFFSET(0x7E215044) #define AUX_MU_IIR_REG VC_MMU_IO_OFFSET(0x7E215048) #define AUX_MU_LCR_REG VC_MMU_IO_OFFSET(0x7E21504C) #define AUX_MU_MCR_REG VC_MMU_IO_OFFSET(0x7E215050) #define AUX_MU_LSR_REG VC_MMU_IO_OFFSET(0x7E215054) #define AUX_MU_MSR_REG VC_MMU_IO_OFFSET(0x7E215058) #define AUX_MU_SCRATCH VC_MMU_IO_OFFSET(0x7E21505C) #define AUX_MU_CNTL_REG VC_MMU_IO_OFFSET(0x7E215060) #define AUX_MU_STAT_REG VC_MMU_IO_OFFSET(0x7E215064) #define AUX_MU_BAUD_REG VC_MMU_IO_OFFSET(0x7E215068) #define AUX_SPI0_CNTL0_REG VC_MMU_IO_OFFSET(0x7E215080) #define AUX_SPI0_CNTL1_REG VC_MMU_IO_OFFSET(0x7E215084) #define AUX_SPI0_STAT_REG VC_MMU_IO_OFFSET(0x7E215088) #define AUX_SPI0_IO_REG VC_MMU_IO_OFFSET(0x7E215090) #define AUX_SPI0_PEEK_REG VC_MMU_IO_OFFSET(0x7E215094) #define AUX_SPI1_CNTL0_REG VC_MMU_IO_OFFSET(0x7E2150C0) #define AUX_SPI1_CNTL1_REG VC_MMU_IO_OFFSET(0x7E2150C4) #define AUX_SPI1_STAT_REG VC_MMU_IO_OFFSET(0x7E2150C8) #define AUX_SPI1_IO_REG VC_MMU_IO_OFFSET(0x7E2150D0) #define AUX_SPI1_PEEK_REG VC_MMU_IO_OFFSET(0x7E2150D4) /* Constants for mini-UART operation */ #define MINI_UART_TX_EMPTY 0x20 /* General GPIO pin registers */ #define GPFSEL1 VC_MMU_IO_OFFSET(0x7E200004) #define GPSET0 VC_MMU_IO_OFFSET(0x7E20001C) #define GPCLR0 VC_MMU_IO_OFFSET(0x7E200028) #define GPPUD VC_MMU_IO_OFFSET(0x7E200094) #define GPPUDCLK0 VC_MMU_IO_OFFSET(0x7E200098) void mini_uart_device_init() { uint32 aux_enables; uint32 gpfsel1; unsigned int i; /* Enable the mini-UART */ aux_enables = (*(uint32 *)AUX_ENABLES) & 0x7; *(uint32 *)AUX_ENABLES = aux_enables | 0x1; //TODO are these all necessary? *(uint32 *)AUX_MU_IER_REG = 0; //disable interrupts *(uint32 *)AUX_MU_CNTL_REG = 0; //disable RX and TX *(uint32 *)AUX_MU_LCR_REG = 3; //set to 8-bit mode (though this only takes setting bit 0, no clue why bit 1 is set too) *(uint32 *)AUX_MU_MCR_REG = 0; //"If clear the UART1_RTS line is high If set the UART1_RTS line is low" *(uint32 *)AUX_MU_IER_REG = 0; //disable interrupts again? *(uint32 *)AUX_MU_IIR_REG = 0xC6; //why are we setting the interrupt status - this looks like a read-only register? //also, the manual says setting bits 2&3 simultaneously shouldn't be possible... // baudrate = system_clock_freq / ( 8 * (baudrate_reg + 1)) // baudrate_reg = system_clock_freq / baudrate / 8 - 1 // baudrate_reg = 250000000 / 115200 / 8 - 1 = 270 *(uint32 *)AUX_MU_BAUD_REG = 270; //set the GPIO pin to the alternate function for this UART gpfsel1 = (*(uint32 *)GPFSEL1) & ~(7<<12); //get the GPIO Function Select 1 register, and keep all the bits except those that are for GPIO pin 14 gpfsel1 |= 2<<12; // set to 010 (GPIO Pin 14 takes alternate function 5) *(uint32 *)GPFSEL1 = gpfsel1; //Follow the procedure for change pull-up/pull-down status (see page 101 of the BCM2835 ARM Peripherals datasheet) *(uint32 *)GPPUD = 0; //disable pull-up/pull-down for those pins which receive a write to their bit in GPPUDCLK0/1 for(i = 0; i < 150; i++) asm(""); //wait at least 150 cycles *(uint32 *)GPPUDCLK0 = 1<<14; //"Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to modify" (in our case, this is 14) for(i = 0; i < 150; i++) asm(""); //wait at least 150 cycles // *(uint32 *)GPPUD = 0; //remove the signal (shouldn't really be necessary since it was 0 anyway in our case) *(uint32 *)GPPUDCLK0 = 0; //clear the clock *(uint32 *)AUX_MU_CNTL_REG = 2; //enable TX } int mini_uart_putc(char c) { /* Wait until the serial buffer is empty */ while (!(*(volatile uint32*)AUX_MU_LSR_REG & MINI_UART_TX_EMPTY)); /* When it's empty, write our character */ *(volatile uint32*)AUX_MU_IO_REG = c; return 0; } struct serial_dev mini_uart_dev = { .putc = &mini_uart_putc }; void mini_uart_init() { mini_uart_device_init(); serial_register_device(&mini_uart_dev); } early_initcall(mini_uart_init);