/******************************************************************** * Description: opi_gpio.c * Driver for the Orange Pi (H3/H5) GPIO pins * * Author: MX_Master (mikhail@vydrenko.ru) ********************************************************************/ #include "rtapi.h" /* RTAPI realtime OS API */ #include "rtapi_app.h" /* RTAPI realtime module decls */ /* this also includes config.h */ #include "hal.h" /* HAL public API decls */ #include #include #include #include #include MODULE_AUTHOR("MX_Master"); MODULE_DESCRIPTION("Driver for the Orange Pi (H3/H5) GPIO pins"); MODULE_LICENSE("GPL"); #define PHY_MEM_BLOCK_SIZE 4096 #define GPIO_PHY_MEM_OFFSET1 0x01C20800 // GPIO_A .. GPIO_G #define GPIO_PHY_MEM_OFFSET2 0x01F02C00 // GPIO_L #define GPIO_PORT_COUNT 8 enum { GPIO_A, GPIO_B, GPIO_C, GPIO_D, // GPIO_B actually unused GPIO_E, GPIO_F, GPIO_G, GPIO_L }; struct _GPIO_PORT_REG_t { uint32_t config[4]; uint32_t data; uint32_t drive[2]; uint32_t pull[2]; }; struct _GPIO_PIN_t { int8_t port; int8_t pin; int8_t OPI_pin; }; struct _OPI_PIN_t { int8_t H3_pin; int8_t valid; }; struct _GPIO_PORT_t { int8_t name; int8_t pins_count; int8_t id_offset; }; static const char * comp_name = "opi_gpio"; static struct _GPIO_PORT_REG_t * _GPIO[GPIO_PORT_COUNT] = {0}; static const struct _GPIO_PIN_t _H3_pins[] = { // 0 - 21 {GPIO_A, 0,13},{GPIO_A, 1,11},{GPIO_A, 2,22},{GPIO_A, 3,15},{GPIO_A, 4, 0}, {GPIO_A, 5, 0},{GPIO_A, 6, 7},{GPIO_A, 7,29},{GPIO_A, 8,31},{GPIO_A, 9,33}, {GPIO_A,10,35},{GPIO_A,11, 5},{GPIO_A,12, 3},{GPIO_A,13, 8},{GPIO_A,14,10}, {GPIO_A,15, 0},{GPIO_A,16, 0},{GPIO_A,17, 0},{GPIO_A,18,28},{GPIO_A,19,27}, {GPIO_A,20,37},{GPIO_A,21,26}, // 22 - 40 {GPIO_C, 0,19},{GPIO_C, 1,21},{GPIO_C, 2,23},{GPIO_C, 3,24},{GPIO_C, 4,16}, {GPIO_C, 5, 0},{GPIO_C, 6, 0},{GPIO_C, 7,18},{GPIO_C, 8, 0},{GPIO_C, 9, 0}, {GPIO_C,10, 0},{GPIO_C,11, 0},{GPIO_C,12, 0},{GPIO_C,13, 0},{GPIO_C,14, 0}, {GPIO_C,15, 0},{GPIO_C,16, 0},{GPIO_C,17, 0},{GPIO_C,18, 0}, // 41 - 58 {GPIO_D, 0, 0},{GPIO_D, 1, 0},{GPIO_D, 2, 0},{GPIO_D, 3, 0},{GPIO_D, 4, 0}, {GPIO_D, 5, 0},{GPIO_D, 6, 0},{GPIO_D, 7, 0},{GPIO_D, 8, 0},{GPIO_D, 9, 0}, {GPIO_D,10, 0},{GPIO_D,11, 0},{GPIO_D,12, 0},{GPIO_D,13, 0},{GPIO_D,14,12}, {GPIO_D,15, 0},{GPIO_D,16, 0},{GPIO_D,17, 0}, // 59 - 74 {GPIO_E, 0, 0},{GPIO_E, 1, 0},{GPIO_E, 2, 0},{GPIO_E, 3, 0},{GPIO_E, 4, 0}, {GPIO_E, 5, 0},{GPIO_E, 6, 0},{GPIO_E, 7, 0},{GPIO_E, 8, 0},{GPIO_E, 9, 0}, {GPIO_E,10, 0},{GPIO_E,11, 0},{GPIO_E,12, 0},{GPIO_E,13, 0},{GPIO_E,14, 0}, {GPIO_E,15, 0}, // 75 - 81 {GPIO_F, 0, 0},{GPIO_F, 1, 0},{GPIO_F, 2, 0},{GPIO_F, 3, 0},{GPIO_F, 4, 0}, {GPIO_F, 5, 0},{GPIO_F, 6, 0}, // 82 - 95 {GPIO_G, 0, 0},{GPIO_G, 1, 0},{GPIO_G, 2, 0},{GPIO_G, 3, 0},{GPIO_G, 4, 0}, {GPIO_G, 5, 0},{GPIO_G, 6,38},{GPIO_G, 7,40},{GPIO_G, 8,32},{GPIO_G, 9,36}, {GPIO_G,10, 0},{GPIO_G,11, 0},{GPIO_G,12, 0},{GPIO_G,13, 0}, // 96 - 107 {GPIO_L, 0, 0},{GPIO_L, 1, 0},{GPIO_L, 2, 0},{GPIO_L, 3, 0},{GPIO_L, 4, 0}, {GPIO_L, 5, 0},{GPIO_L, 6, 0},{GPIO_L, 7, 0},{GPIO_L, 8, 0},{GPIO_L, 9, 0}, {GPIO_L,10, 0},{GPIO_L,11, 0} }; #define H3_PINS_COUNT (sizeof _H3_pins / sizeof _H3_pins[0]) static const struct _GPIO_PORT_t _GPIO_port_info[] = { {'A',22, 0}, {'B', 0,22}, // actually unused {'C',19,22}, {'D',18,41}, {'E',16,59}, {'F', 7,75}, {'G',14,82}, {'L',12,96} }; static const struct _OPI_PIN_t _OPI_pins[] = { // dummy {-4,0}, // general pins 1-40 {-2,0}, {-3,0}, // +3.3V +5V {12,1}, {-3,0}, // PA12 +5V {11,1}, {-1,0}, // PA11 GND {6,1}, {13,1}, // PA6 PA13 {-1,0}, {14,1}, // GND PA14 {1,1}, {55,1}, // PA1 PD14 {0,1}, {-1,0}, // PA0 GND {3,1}, {26,1}, // PA3 PC4 {-2,0}, {29,1}, // +3.3V PC7 {22,1}, {-1,0}, // PC0 GND {23,1}, {2,1}, // PC1 PA2 {24,1}, {25,1}, // PC2 PC3 {-1,0}, {21,1}, // GND PA21 {19,1}, {18,1}, // PA19 PA18 {7,1}, {-1,0}, // PA7 GND {8,1}, {90,1}, // PA8 PG8 {9,1}, {-1,0}, // PA9 GND {10,1}, {91,1}, // PA10 PG9 {20,1}, {88,1}, // PA20 PG6 {-1,0}, {89,1}, // GND PG7 // pins 41/42 are serial console TX,RX pins {4,1}, {5,1} // PA4 PA5 }; #define OPI_PINS_COUNT (sizeof _OPI_pins / sizeof _OPI_pins[0]) static uint32_t *vrt_block_addr[2]; static int32_t comp_id; // component ID hal_bit_t **port_data; // port data pins states hal_bit_t **port_data_inv; // port data inverted pins states hal_bit_t *port_param_inv; // port params for the pins invert states hal_bit_t *port_param_reset; // port params for the pins reset states hal_u32_t *port_reset_time; long long port_write_time = 0; static uint8_t input_pins_list[H3_PINS_COUNT] = {0}; static uint8_t input_pins_count = 0; static char *input_pins; RTAPI_MP_STRING(input_pins, "input pins, comma separated"); static uint8_t output_pins_list[H3_PINS_COUNT] = {0}; static uint8_t output_pins_count = 0; static char *output_pins; RTAPI_MP_STRING(output_pins, "output pins, comma separated"); static unsigned long ns2tsc_factor; #define ns2tsc(x) (((x) * (unsigned long long)ns2tsc_factor) >> 12) static void write_port(void *arg, long period); static void reset_port(void *arg, long period); static void read_port(void *arg, long period); static void config_pin_as_input(uint8_t n) { _GPIO[_H3_pins[n].port]->config[_H3_pins[n].pin / 8] &= ~(0b1111 << (_H3_pins[n].pin % 8 * 4)); } static void config_pin_as_output(uint8_t n) { _GPIO[_H3_pins[n].port]->config[_H3_pins[n].pin / 8] &= ~(0b1111 << (_H3_pins[n].pin % 8 * 4)); _GPIO[_H3_pins[n].port]->config[_H3_pins[n].pin / 8] |= (0b0001 << (_H3_pins[n].pin % 8 * 4)); } int32_t rtapi_app_main(void) { int32_t mem_fd; uint32_t vrt_offset = 0; off_t phy_block_addr = 0; int32_t n, retval, p; char *data, *token; uint8_t pin; char name[HAL_NAME_LEN + 1]; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) &&0//FIXME // this calculation fits in a 32-bit unsigned // as long as CPUs are under about 6GHz ns2tsc_factor = (cpu_khz << 6) / 15625ul; #else ns2tsc_factor = 1ll<<12; #endif comp_id = hal_init(comp_name); if (comp_id < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: hal_init() failed\n", comp_name); return -1; } // open physical memory file seteuid(0); setfsuid( geteuid() ); if ( (mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0 ) { setfsuid( getuid() ); rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: can't open /dev/mem file\n", comp_name); return -1; } setfsuid( getuid() ); // calculate phy memory block start vrt_offset = GPIO_PHY_MEM_OFFSET1 % PHY_MEM_BLOCK_SIZE; phy_block_addr = GPIO_PHY_MEM_OFFSET1 - vrt_offset; // make a block of phy memory visible in our user space vrt_block_addr[0] = mmap( NULL, // Any adddress in our space PHY_MEM_BLOCK_SIZE, // Map length PROT_READ | PROT_WRITE, // Enable reading & writting to mapped memory MAP_SHARED, // Shared with other processes mem_fd, // File to map phy_block_addr // Offset to GPIO peripheral ); // exit program if mmap is failed if (vrt_block_addr[0] == MAP_FAILED) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: mmap() failed\n", comp_name); return -1; } // adjust offset to correct value vrt_block_addr[0] += (vrt_offset/4); // add correct address values to global GPIO array for ( n = GPIO_A; n <= GPIO_G; ++n ) { _GPIO[n] = (struct _GPIO_PORT_REG_t *) (vrt_block_addr[0] + n*(0x24/4)); } // calculate phy memory block start vrt_offset = GPIO_PHY_MEM_OFFSET2 % PHY_MEM_BLOCK_SIZE; phy_block_addr = GPIO_PHY_MEM_OFFSET2 - vrt_offset; // make a block of phy memory visible in our user space vrt_block_addr[1] = mmap( NULL, // Any adddress in our space PHY_MEM_BLOCK_SIZE, // Map length PROT_READ | PROT_WRITE, // Enable reading & writting to mapped memory MAP_SHARED, // Shared with other processes mem_fd, // File to map phy_block_addr // Offset to GPIO peripheral ); // exit program if mmap is failed if (vrt_block_addr[1] == MAP_FAILED) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: mmap() failed\n", comp_name); return -1; } // adjust offset to correct value vrt_block_addr[1] += (vrt_offset/4); // add correct address values to global GPIO array _GPIO[GPIO_L] = (struct _GPIO_PORT_REG_t *) vrt_block_addr[1]; // no need to keep phy memory file open after mmap close(mem_fd); // allocate some space for the port data arrays (normal & inverted) port_data = hal_malloc(H3_PINS_COUNT * sizeof(hal_bit_t *)); port_data_inv = hal_malloc(H3_PINS_COUNT * sizeof(hal_bit_t *)); port_param_inv = hal_malloc(H3_PINS_COUNT * sizeof(hal_bit_t)); port_param_reset = hal_malloc(H3_PINS_COUNT * sizeof(hal_bit_t)); port_reset_time = hal_malloc(sizeof(hal_u32_t)); if ( port_data == 0 || port_data_inv == 0 || port_param_inv == 0 || port_param_reset == 0 || port_reset_time == 0 ) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: hal_malloc() failed\n", comp_name); hal_exit(comp_id); return -1; } #define INVALID_PIN_MSG_AND_RETURN \ rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: invalid pin %s\n",\ comp_name, token);\ hal_exit(comp_id);\ return -1; #define PIN_EXPORT_FAILED_MSG_AND_RETURN \ rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: pin %s export failed\n",\ comp_name, token);\ hal_exit(comp_id);\ return -1; // configure input pins if (input_pins != NULL) { data = input_pins; // break input_pins string by comma while ((token = strtok(data, ",")) != NULL) { // get token's size size_t len = strlen(token); // ignore empty tokens if ( len < 1 ) continue; // if we have the GPIO pin name if ( token[0] == 'P' && len >= 3 && len <= 4 ) { uint8_t pin_found = 0; // trying to find a valid port name for ( p = GPIO_PORT_COUNT; p--; ) { // if valid port name found if ( token[1] == _GPIO_port_info[p].name ) { // trying to find a correct pin number pin = (uint8_t) simple_strtol(&token[2], NULL, 10); // if a correct pin number wasn't found if ( pin >= _GPIO_port_info[p].pins_count ) { INVALID_PIN_MSG_AND_RETURN; } // correct pin number found pin += _GPIO_port_info[p].id_offset; pin_found = 1; // export H3 pin input function retval = hal_pin_bit_newf(HAL_OUT, &port_data[pin], comp_id, "%s.pin-%s-in", comp_name, token); if (retval < 0) { PIN_EXPORT_FAILED_MSG_AND_RETURN; } // export H3 pin inverted input function retval = hal_pin_bit_newf(HAL_OUT, &port_data_inv[pin], comp_id, "%s.pin-%s-in-not", comp_name, token); if (retval < 0) { PIN_EXPORT_FAILED_MSG_AND_RETURN; } break; } } // if valid port name wasn't found if ( !pin_found ) { INVALID_PIN_MSG_AND_RETURN; } } // if we have the OPI pin number else if ( token[0] >= '0' && token[0] <= '9' && len <= 2 ) { // trying to find a correct pin number pin = (uint8_t) simple_strtol(token, NULL, 10); // if a correct pin number wasn't found if ( pin < 1 || pin >= OPI_PINS_COUNT || !_OPI_pins[pin].valid ) { INVALID_PIN_MSG_AND_RETURN; } // correct pin number found pin = _OPI_pins[pin].H3_pin; // export OPI pin input function retval = hal_pin_bit_newf(HAL_OUT, &port_data[pin], comp_id, "%s.pin-%02d-in", comp_name, _H3_pins[pin].OPI_pin); if (retval < 0) { PIN_EXPORT_FAILED_MSG_AND_RETURN; } // export OPI pin inverted input function retval = hal_pin_bit_newf(HAL_OUT, &port_data_inv[pin], comp_id, "%s.pin-%02d-in-not", comp_name, _H3_pins[pin].OPI_pin); if (retval < 0) { PIN_EXPORT_FAILED_MSG_AND_RETURN; } } // we have unknown pin else { INVALID_PIN_MSG_AND_RETURN; } // add OPI pin id to the input pins list input_pins_list[input_pins_count] = pin; ++input_pins_count; // configure OrangePi pin as input config_pin_as_input(pin); data = NULL; // after the first call, subsequent calls to // strtok need to be on NULL } } // configure output pins if (output_pins != NULL) { data = output_pins; // break input_pins string by comma while ((token = strtok(data, ",")) != NULL) { // get token's size size_t len = strlen(token); // ignore empty tokens if ( len < 1 ) continue; // if we have the GPIO pin name if ( token[0] == 'P' && len >= 3 && len <= 4 ) { uint8_t pin_found = 0; // trying to find a valid port name for ( p = GPIO_PORT_COUNT; p--; ) { // if valid port name found if ( token[1] == _GPIO_port_info[p].name ) { // trying to find a correct pin number pin = (uint8_t) simple_strtol(&token[2], NULL, 10); // if a correct pin number wasn't found if ( pin >= _GPIO_port_info[p].pins_count ) { INVALID_PIN_MSG_AND_RETURN; } // correct pin number found pin += _GPIO_port_info[p].id_offset; pin_found = 1; // export H3 pin output function retval = hal_pin_bit_newf(HAL_IN, &port_data[pin], comp_id, "%s.pin-%s-out", comp_name, token); if (retval < 0) { PIN_EXPORT_FAILED_MSG_AND_RETURN; } // inverted H3 pin parameter retval = hal_param_bit_newf(HAL_RW, &port_param_inv[pin], comp_id, "%s.pin-%s-out-invert", comp_name, token); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: output pin %s " "invert param export failed\n", comp_name, token); hal_exit(comp_id); return -1; } // reset H3 pin parameter retval = hal_param_bit_newf(HAL_RW, &port_param_reset[pin], comp_id, "%s.pin-%s-out-reset", comp_name, token); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: output pin %s " "reset param export failed\n", comp_name, token); hal_exit(comp_id); return -1; } break; } } // if valid port name wasn't found if ( !pin_found ) { INVALID_PIN_MSG_AND_RETURN; } } // if we have the OPI pin number else if ( token[0] >= '0' && token[0] <= '9' && len <= 2 ) { // trying to find a correct pin number pin = (uint8_t) simple_strtol(token, NULL, 10); // if a correct pin number wasn't found if ( pin < 1 || pin >= OPI_PINS_COUNT || !_OPI_pins[pin].valid ) { INVALID_PIN_MSG_AND_RETURN; } // correct pin number found pin = _OPI_pins[pin].H3_pin; // export OPI pin output function retval = hal_pin_bit_newf(HAL_IN, &port_data[pin], comp_id, "%s.pin-%02d-out", comp_name, _H3_pins[pin].OPI_pin); if (retval < 0) { PIN_EXPORT_FAILED_MSG_AND_RETURN; } // inverted OPI pin parameter retval = hal_param_bit_newf(HAL_RW, &port_param_inv[pin], comp_id, "%s.pin-%02d-out-invert", comp_name, _H3_pins[pin].OPI_pin); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: output pin %s " "invert param export failed\n", comp_name, token); hal_exit(comp_id); return -1; } // reset OPI pin parameter retval = hal_param_bit_newf(HAL_RW, &port_param_reset[pin], comp_id, "%s.pin-%02d-out-reset", comp_name, _H3_pins[pin].OPI_pin); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: output pin %s " "reset param export failed\n", comp_name, token); hal_exit(comp_id); return -1; } } // we have unknown pin else { INVALID_PIN_MSG_AND_RETURN; } // add pin id to the output pins list output_pins_list[output_pins_count] = pin; ++output_pins_count; // configure pin as output config_pin_as_output(pin); data = NULL; // after the first call, subsequent calls to // strtok need to be on NULL } } #undef INVALID_PIN_MSG_AND_RETURN #undef PIN_EXPORT_FAILED_MSG_AND_RETURN // export port reset time parameter retval = hal_param_u32_newf(HAL_RW, port_reset_time, comp_id, "%s.reset-time", comp_name); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: reset-time param export failed\n", comp_name); hal_exit(comp_id); return -1; } // export port WRITE function rtapi_snprintf(name, sizeof(name), "%s.write", comp_name); retval = hal_export_funct(name, write_port, 0, 0, 0, comp_id); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: write funct export failed\n", comp_name); hal_exit(comp_id); return -1; } // export port READ function rtapi_snprintf(name, sizeof(name), "%s.read", comp_name); retval = hal_export_funct(name, read_port, 0, 0, 0, comp_id); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: read funct export failed\n", comp_name); hal_exit(comp_id); return -1; } // export port RESET function rtapi_snprintf(name, sizeof(name), "%s.reset", comp_name); retval = hal_export_funct(name, reset_port, 0, 0, 0, comp_id); if (retval < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: ERROR: reset funct export failed\n", comp_name); hal_exit(comp_id); return -1; } // driver is ready to use rtapi_print_msg(RTAPI_MSG_INFO, "%s: installed driver\n", comp_name); hal_ready(comp_id); // no errors return 0; } void rtapi_app_exit(void) { // unlink phy space from our user space munmap(vrt_block_addr[0], PHY_MEM_BLOCK_SIZE); munmap(vrt_block_addr[1], PHY_MEM_BLOCK_SIZE); hal_exit(comp_id); } #define pd_pin output_pins_list[n] // port_data pin ID #define g_pin _H3_pins[pd_pin].pin // GPIO pin ID #define g_prt _H3_pins[pd_pin].port // GPIO port ID #define g_prt_data _GPIO[g_prt]->data // GPIO port data value static void write_port(void *arg, long period) { static int8_t n = 0; // set GPIO output pins state from the port_data array for ( n = output_pins_count; n--; ) { if ( *(port_data[pd_pin]) ^ port_param_inv[pd_pin] ) { // clear GPIO pin g_prt_data &= ~(1 << g_pin); } else { // set GPIO pin g_prt_data |= (1 << g_pin); } } // save write time for the reset function port_write_time = rtapi_get_clocks(); } static void reset_port(void *arg, long period) { static int8_t n = 0; static long long deadline, reset_time_tsc; // adjust reset time if(*port_reset_time > period/4) *port_reset_time = period/4; reset_time_tsc = ns2tsc(*port_reset_time); // set deadline time and make a busy waiting deadline = port_write_time + reset_time_tsc; while(rtapi_get_clocks() < deadline) {} // reset pin states for ( n = output_pins_count; n--; ) { // do nothing if pin reset param = 0 or pin already reset if ( !port_param_reset[pd_pin] || !(*(port_data[pd_pin])) ) continue; // reset pin if ( port_param_inv[pd_pin] ) { // clear pin state *port_data[pd_pin] = 0; // clear GPIO pin g_prt_data &= ~(1 << g_pin); } else { // set pin state *port_data[pd_pin] = 1; // set GPIO pin g_prt_data |= (1 << g_pin); } } } #undef pd_pin #undef g_pin #undef g_prt #undef g_prt_data #define pd_pin input_pins_list[n] #define g_pin _H3_pins[pd_pin].pin #define g_prt _H3_pins[pd_pin].port #define g_prt_data _GPIO[g_prt]->data static void read_port(void *arg, long period) { static int8_t n = 0; // put GPIO input pins state into the port_data array for ( n = input_pins_count; n--; ) { if ( ((1 << g_pin) & g_prt_data) ) { *port_data[pd_pin] = 1; *port_data_inv[pd_pin] = 0; } else { *port_data[pd_pin] = 0; *port_data_inv[pd_pin] = 1; } } } #undef pd_pin #undef g_pin #undef g_prt #undef g_prt_data