// SPDX-License-Identifier: GPL-2.0+ /* Copyright (c) 2018-2019 AVM GmbH */ #include #include #include #include #include #include #include #include "boardparms.h" #include "bcm_gpio.h" #include "bcm_led.h" #include "bcm_map_part.h" #include "bcm_pinmux.h" #include "mach/avm_gpio.h" #include "bcm_bca_extintr.h" extern spinlock_t bcm_gpio_spinlock; static void brcm_pinctrl_print(unsigned int gpio_pin); static unsigned int bcm_get_pin_diag(unsigned int pin_num); /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ #define AVM_BUTTON_MAX 3 typedef int (*buttonCallback_t)(unsigned int); struct button_info { int gpio; int index; buttonCallback_t callback; struct gpio_desc *gd; }; static struct button_info avm_button_info[AVM_BUTTON_MAX]; /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ int brcm_gpio_init(void) { return GPIO_OK; } /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ int brcm_gpio_ctrl(unsigned int gpio_pin, enum _hw_gpio_function pin_mode, enum _hw_gpio_direction pin_dir) { unsigned long flags; int result = GPIO_FAIL; spin_lock_irqsave(&bcm_gpio_spinlock, flags); if (pin_mode >= 0 && pin_mode < FUNCTION_PIN_NOCHANGE) { bcm_set_pinmux(gpio_pin, pin_mode); BpSetupGpioOptled(gpio_pin, pin_mode); } bcm_gpio_set_dir(gpio_pin, (unsigned int)pin_dir); result = (bcm_gpio_get_dir(gpio_pin) == (unsigned int)pin_dir) ? GPIO_OK : GPIO_FAIL; spin_unlock_irqrestore(&bcm_gpio_spinlock, flags); return result; } EXPORT_SYMBOL(brcm_gpio_ctrl); /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ int brcm_gpio_out_bit(unsigned int gpio_pin, int value) { unsigned long flags; int result = GPIO_FAIL; spin_lock_irqsave(&bcm_gpio_spinlock, flags); if ((bcm_get_pin_diag(gpio_pin) & 0x7) != GPIO_PIN) { bcm_led_driver_set_dim(gpio_pin, value); } else { bcm_gpio_set_data(gpio_pin, (unsigned int)value); result = (bcm_gpio_get_data(gpio_pin) == (unsigned int)value) ? GPIO_OK : GPIO_FAIL; } spin_unlock_irqrestore(&bcm_gpio_spinlock, flags); return result; } EXPORT_SYMBOL(brcm_gpio_out_bit); /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ int brcm_gpio_out_bit_no_sched(unsigned int gpio_pin, int value) { return (brcm_gpio_out_bit(gpio_pin, value)); } EXPORT_SYMBOL(brcm_gpio_out_bit_no_sched); /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ int brcm_gpio_in_bit(unsigned int gpio_pin) { unsigned long flags; int result = GPIO_FAIL; spin_lock_irqsave(&bcm_gpio_spinlock, flags); result = (int)bcm_gpio_get_data(gpio_pin); spin_unlock_irqrestore(&bcm_gpio_spinlock, flags); return result; } EXPORT_SYMBOL(brcm_gpio_in_bit); /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ void brcm_gpio_set_bitmask(uint64_t mask, uint64_t value) { unsigned long flags; spin_lock_irqsave(&bcm_gpio_spinlock, flags); bcm_gpio_set_bitmask(mask, value); spin_unlock_irqrestore(&bcm_gpio_spinlock, flags); return; } EXPORT_SYMBOL(brcm_gpio_set_bitmask); /*----------------------------------------------------------------------------*\ * ret: negval -> not found/no range/no match \*----------------------------------------------------------------------------*/ static int generic_gpio_param_parse(char *string, char *match, int maxval, char *matchstrg1, char *matchstrg2) { char *p = string; int ret = -1; if ((p = strstr(string, match))) { p += strlen(match); while (*p == ' ' || *p == '\t') p++; if (matchstrg1 && strncmp(p, matchstrg1, strlen(matchstrg1)) == 0) { ret = 0; } else if (matchstrg2 && strncmp(p, matchstrg2, strlen(matchstrg2)) == 0) { ret = 1; } else if (*p) { sscanf(p, "%d", &ret); if (ret > maxval) { ret = -1; } } } return ret; } /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ static int brcm_gpio_set(char *string, void *priv __maybe_unused) { int gpio, dir, set, mux; gpio = generic_gpio_param_parse(string, "gpio", GPIO_NUM_MAX - 1, NULL, NULL); dir = generic_gpio_param_parse(string, "dir=", 0, "in", "out"); set = generic_gpio_param_parse(string, "set=", 255, NULL, NULL); mux = generic_gpio_param_parse(string, "mux=", FUNCTION_PINMUX7, NULL, NULL); if ((gpio < 0) || (strstr(string, "help"))) { printk(KERN_ERR "use: gpio dir= set=<0..255> mux=<0..7>\n"); return 0; } if (dir >= 0 || mux >= 0) { brcm_gpio_ctrl(gpio, mux, dir); } if (set >= 0) { brcm_gpio_out_bit(gpio, set); } brcm_pinctrl_print(gpio); return 0; } #define READ_SELECT_CMD 0x23 /* see 63178-PR100 pg. 2185 */ static unsigned int bcm_get_pin_diag(unsigned int pin_num) { GPIO->TestPortBlockDataMSB = 0; GPIO->TestPortBlockDataLSB = pin_num; GPIO->TestPortCmd = READ_SELECT_CMD; return GPIO->DiagReadBack; } /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ static void brcm_gpio_list(struct seq_file *seq, void *priv) { unsigned int gpio_pin, gpio_start = 0, gpio_end = GPIO_NUM_MAX - 1, val; if (priv) { gpio_start = (unsigned int)priv; gpio_end = gpio_start; if (gpio_start >= GPIO_NUM_MAX - 1) { return; } } for (gpio_pin = gpio_start; gpio_pin <= gpio_end; gpio_pin++) { unsigned int dir = bcm_gpio_get_dir(gpio_pin); unsigned int diag = bcm_get_pin_diag(gpio_pin); val = bcm_gpio_get_data(gpio_pin); seq_printf(seq, "gpio%02u %s pinmux=%x%s val=%x diag=%08x\n", gpio_pin, dir ? "OUT" : "IN ", diag & 0x7, (diag & 0x7) == GPIO_PIN ? "(GPIO)" : "", val, diag); } } /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ static void brcm_pinctrl_print(unsigned int gpio_pin) { char txtbuf[256]; struct seq_file s; memset(&s, 0, sizeof(s)); txtbuf[0] = 0; s.buf = txtbuf; s.size = sizeof(txtbuf); brcm_gpio_list(&s, (void *)gpio_pin); printk(KERN_ERR "%s", txtbuf); } static struct proc_dir_entry *gpioprocdir; /*----------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------*/ static int __init avm_gpioproc_init(void) { #define PROC_GPIODIR "avm/gpio" gpioprocdir = proc_mkdir(PROC_GPIODIR, NULL); if (gpioprocdir == NULL) { return 0; } add_simple_proc_file("avm/gpio/list", NULL, brcm_gpio_list, NULL); add_simple_proc_file("avm/gpio/set", brcm_gpio_set, NULL, NULL); return 0; } late_initcall(avm_gpioproc_init); /*--- Kernel-Schnittstellen-Funktion für das LED-Modul ---*/ enum _led_event { /* DUMMY DEFINITION */ LastEvent = 0 }; int (*led_event_action)(int, enum _led_event , unsigned int ) = NULL; EXPORT_SYMBOL(led_event_action); /* * find a button by its gpio number */ static struct button_info *gpio_to_button_info(int gpio) { int i; for (i = 0; i < AVM_BUTTON_MAX; i++) if (avm_button_info[i].gpio == gpio) return &avm_button_info[i]; return NULL; } /* * register a function to be called from the button isr * mode and edge are preconfigured by the device tree they are ignored her */ int brcm_button_request_callback(unsigned int mask, enum _hw_gpio_polarity mode, enum _hw_gpio_sensitivity edge, int (*handle_func)(unsigned int)) { unsigned int gpio = ilog2(mask); struct button_info *bi = gpio_to_button_info(gpio); if (hweight_long((unsigned long)mask) != 1) { pr_err("[%s] MASK MUST HAVE ONE BIT SET! \n", __func__); return -1; } pr_debug("[%s] index %d gpio=%x, mode=%d, edge=%d, func_p=%p\n", __func__, bi->index, gpio, mode, edge, handle_func); if (edge == 0) { pr_err("[%s] LEVEL SENSIVITY NOT SUPPORTED! GPIO: %x\n", __func__, gpio); return -1; } bi->callback = handle_func; return bi->index; } EXPORT_SYMBOL(brcm_button_request_callback); /* * disables the registered button callback */ void brcm_button_disable_callback(int index) { if (index < 0 || index >= AVM_BUTTON_MAX) return; avm_button_info[index].callback = NULL; } EXPORT_SYMBOL(brcm_button_disable_callback); /* * Button interrupt handler to call the callback registered by the led driver */ static irqreturn_t avm_button_isr(int irq, void *info) { int index = (int)info; struct button_info *bi = NULL; /* acknwledge that we handled the external irq */ bcm_bca_extintr_clear(irq); if (index < 0 || index >= AVM_BUTTON_MAX) return IRQ_HANDLED; bi = &avm_button_info[index]; if (bi && bi->callback) bi->callback(1 << bi->gpio); return IRQ_HANDLED; } /* * Reads button definitions from device tree and enables interrupts */ static int avm_button_probe(struct platform_device *pdev) { struct device_node *btn_np = NULL; int ret; int i; struct gpio_desc *gd; for (i = 0; i < AVM_BUTTON_MAX; i++) { btn_np = of_get_next_child(pdev->dev.of_node, btn_np); if (!btn_np) break; pr_debug("[%s] index %d name %s\n", __func__, i, btn_np->name); ret = bcm_bca_extintr_request(&pdev->dev, btn_np, "ext_irq", avm_button_isr, (void*)i, btn_np->name, NULL); if (ret < 0) { dev_err(&pdev->dev, "bcm_bca_extintr_request failed err %d\n", ret); return ret; } gd = bcm_bca_extintr_get_gpiod(ret); if (!gd) { avm_button_info[i].gpio = -1; dev_err(&pdev->dev, "bcm_bca_extintr_get_gpiod for irq %d failed\n", ret); continue; } pr_debug("[%s] index %d irq %d gpio %d\n", __func__, i, ret, desc_to_gpio(gd)); avm_button_info[i].gpio = desc_to_gpio(gd); avm_button_info[i].index = i; avm_button_info[i].gd = gd; } return ret < 0 ? ret : 0; } MODULE_DEVICE_TABLE(of, avm_button_match); static struct of_device_id const avm_button_match[] = { { .compatible = "avm,buttons" }, {} }; static struct platform_driver avm_button_driver = { .driver = { .name = "avm,buttons", .of_match_table = avm_button_match, }, .probe = avm_button_probe, }; static int __init avm_button_init(void) { return platform_driver_register(&avm_button_driver); } subsys_initcall(avm_button_init);