| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Touch Bus Negotiator for Google Pixel devices. |
| * |
| * Copyright (C) 2021 Google, Inc. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/net.h> |
| #include <linux/printk.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/of_gpio.h> |
| #include <linux/delay.h> |
| #include "touch_bus_negotiator.h" |
| |
| #define TBN_MODULE_NAME "touch_bus_negotiator" |
| |
| static struct tbn_context *tbn_context = NULL; |
| |
| enum tbn_operation { |
| AP_RELEASE_BUS, |
| AP_REQUEST_BUS, |
| }; |
| |
| static irqreturn_t tbn_aoc2ap_irq_thread(int irq, void *ptr) |
| { |
| struct tbn_context *tbn = ptr; |
| |
| if (completion_done(&tbn->bus_released) && completion_done(&tbn->bus_requested)) |
| return IRQ_HANDLED; |
| |
| /* |
| * For bus release, there two possibilities: |
| * 1. aoc2ap gpio value already changed to AOC |
| * 2. tbn_release_bus() with TBN_RELEASE_BUS_TIMEOUT_MS timeout |
| * for complete_all(&tbn->bus_released); |
| */ |
| while (!completion_done(&tbn->bus_released)) { |
| if (gpio_get_value(tbn->aoc2ap_gpio) == TBN_BUS_OWNER_AOC) |
| complete_all(&tbn->bus_released); |
| else |
| usleep_range(10000, 10000); /* wait 10 ms for gpio stablized */ |
| } |
| |
| /* |
| * For bus request, there two possibilities: |
| * 1. aoc2ap gpio value already changed to AP |
| * 2. tbn_request_bus() with TBN_REQUEST_BUS_TIMEOUT_MS timeout |
| * for complete_all(&tbn->bus_requested); |
| */ |
| while (!completion_done(&tbn->bus_requested)) { |
| if (gpio_get_value(tbn->aoc2ap_gpio) == TBN_BUS_OWNER_AP) |
| complete_all(&tbn->bus_requested); |
| else |
| usleep_range(10000, 10000); /* wait 10 ms for gpio stablized */ |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| int tbn_handshaking(struct tbn_context *tbn, enum tbn_operation operation) |
| { |
| struct completion *wait_for_completion; |
| enum tbn_bus_owner bus_owner; |
| unsigned int irq_type; |
| unsigned int timeout; |
| const char *msg; |
| int ret = 0; |
| |
| if (!tbn || tbn->registered_mask == 0) |
| return 0; |
| |
| if (operation == AP_REQUEST_BUS) { |
| wait_for_completion = &tbn->bus_requested; |
| bus_owner = TBN_BUS_OWNER_AP; |
| irq_type = IRQF_TRIGGER_FALLING; |
| timeout = TBN_REQUEST_BUS_TIMEOUT_MS; |
| msg = "request"; |
| } else { |
| wait_for_completion = &tbn->bus_released; |
| bus_owner = TBN_BUS_OWNER_AOC; |
| irq_type = IRQF_TRIGGER_RISING; |
| timeout = TBN_RELEASE_BUS_TIMEOUT_MS; |
| msg = "release"; |
| } |
| |
| reinit_completion(wait_for_completion); |
| |
| if (tbn->mode == TBN_MODE_GPIO) { |
| irq_set_irq_type(tbn->aoc2ap_irq, irq_type); |
| enable_irq(tbn->aoc2ap_irq); |
| gpio_direction_output(tbn->ap2aoc_gpio, bus_owner); |
| if (wait_for_completion_timeout(wait_for_completion, |
| msecs_to_jiffies(timeout)) == 0) { |
| dev_err(tbn->dev, "AP %s bus ... timeout!\n", msg); |
| complete_all(wait_for_completion); |
| ret = -ETIMEDOUT; |
| } else |
| dev_info(tbn->dev, "AP %s bus ... SUCCESS!\n", msg); |
| disable_irq_nosync(tbn->aoc2ap_irq); |
| } |
| |
| return ret; |
| } |
| |
| int tbn_request_bus(u32 dev_mask) |
| { |
| int ret = 0; |
| |
| if (!tbn_context) |
| return -ENODEV; |
| |
| mutex_lock(&tbn_context->dev_mask_mutex); |
| |
| if ((dev_mask & tbn_context->registered_mask) == 0) { |
| mutex_unlock(&tbn_context->dev_mask_mutex); |
| dev_err(tbn_context->dev, "%s: dev_mask %#x is invalid.\n", |
| __func__, dev_mask); |
| return -EINVAL; |
| } |
| |
| if (tbn_context->requested_dev_mask == 0) { |
| ret = tbn_handshaking(tbn_context, AP_REQUEST_BUS); |
| } else { |
| dev_dbg(tbn_context->dev, |
| "%s: Bus already requested, requested_dev_mask %#x dev_mask %#x.\n", |
| __func__, tbn_context->requested_dev_mask, dev_mask); |
| } |
| tbn_context->requested_dev_mask |= dev_mask; |
| |
| mutex_unlock(&tbn_context->dev_mask_mutex); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(tbn_request_bus); |
| |
| int tbn_release_bus(u32 dev_mask) |
| { |
| int ret = 0; |
| |
| if (!tbn_context) |
| return -ENODEV; |
| |
| mutex_lock(&tbn_context->dev_mask_mutex); |
| |
| if ((dev_mask & tbn_context->registered_mask) == 0) { |
| mutex_unlock(&tbn_context->dev_mask_mutex); |
| dev_err(tbn_context->dev, "%s: dev_mask %#x is invalid.\n", |
| __func__, dev_mask); |
| return -EINVAL; |
| } |
| |
| if (tbn_context->requested_dev_mask == 0) { |
| dev_warn(tbn_context->dev, |
| "%s: Bus already released, dev_mask %#x.\n", |
| __func__, dev_mask); |
| mutex_unlock(&tbn_context->dev_mask_mutex); |
| return 0; |
| } |
| |
| /* Release the bus when the last requested_dev_mask bit releases. */ |
| if (tbn_context->requested_dev_mask == dev_mask) { |
| ret = tbn_handshaking(tbn_context, AP_RELEASE_BUS); |
| } else { |
| dev_dbg(tbn_context->dev, |
| "%s: Bus is still in use, requested_dev_mask %#x dev_mask %#x.\n", |
| __func__, tbn_context->requested_dev_mask, dev_mask); |
| } |
| |
| tbn_context->requested_dev_mask &= ~dev_mask; |
| |
| mutex_unlock(&tbn_context->dev_mask_mutex); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(tbn_release_bus); |
| |
| int register_tbn(u32 *output) |
| { |
| u32 i = 0; |
| |
| if (!tbn_context) { |
| pr_warn("%s: tbn_context doesn't exist.", __func__); |
| return 0; |
| } |
| |
| mutex_lock(&tbn_context->dev_mask_mutex); |
| for (i = 0; i < tbn_context->max_devices; i++) { |
| if (tbn_context->registered_mask & BIT_MASK(i)) |
| continue; |
| tbn_context->registered_mask |= BIT_MASK(i); |
| /* Assume screen is on while registering tbn. */ |
| tbn_context->requested_dev_mask |= BIT_MASK(i); |
| *output = BIT_MASK(i); |
| break; |
| } |
| |
| mutex_unlock(&tbn_context->dev_mask_mutex); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(register_tbn); |
| |
| void unregister_tbn(u32 *output) |
| { |
| if (!tbn_context) |
| return ; |
| |
| mutex_lock(&tbn_context->dev_mask_mutex); |
| tbn_context->registered_mask &= ~(*output); |
| *output = 0; |
| mutex_unlock(&tbn_context->dev_mask_mutex); |
| } |
| EXPORT_SYMBOL_GPL(unregister_tbn); |
| |
| static int tbn_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct tbn_context *tbn = NULL; |
| struct device_node *np = dev->of_node; |
| int err = 0; |
| |
| tbn = devm_kzalloc(dev, sizeof(struct tbn_context), GFP_KERNEL); |
| if (!tbn) |
| goto failed; |
| |
| tbn->dev = dev; |
| tbn_context = tbn; |
| dev_set_drvdata(tbn->dev, tbn); |
| |
| if (of_property_read_u32(np, "tbn,max_devices", &tbn->max_devices)) |
| tbn->max_devices = 1; |
| |
| if (of_property_read_bool(np, "tbn,ap2aoc_gpio") && |
| of_property_read_bool(np, "tbn,aoc2ap_gpio")) { |
| tbn->mode = TBN_MODE_GPIO; |
| |
| tbn->ap2aoc_gpio = of_get_named_gpio(np, "tbn,ap2aoc_gpio", 0); |
| if (gpio_is_valid(tbn->ap2aoc_gpio)) { |
| err = devm_gpio_request_one(tbn->dev, tbn->ap2aoc_gpio, |
| GPIOF_OUT_INIT_LOW, "tbn,ap2aoc_gpio"); |
| if (err) { |
| dev_err(tbn->dev, "%s: Unable to request ap2aoc_gpio %d, err %d!\n", |
| __func__, tbn->ap2aoc_gpio, err); |
| goto failed; |
| } |
| } |
| |
| tbn->aoc2ap_gpio = of_get_named_gpio(np, "tbn,aoc2ap_gpio", 0); |
| if (gpio_is_valid(tbn->aoc2ap_gpio)) { |
| err = devm_gpio_request_one(tbn->dev, tbn->aoc2ap_gpio, |
| GPIOF_DIR_IN, "tbn,aoc2ap_gpio"); |
| if (err) { |
| dev_err(tbn->dev, "%s: Unable to request aoc2ap_gpio %d, err %d!\n", |
| __func__, tbn->aoc2ap_gpio, err); |
| goto failed; |
| } |
| tbn->aoc2ap_irq = gpio_to_irq(tbn->aoc2ap_gpio); |
| err = devm_request_threaded_irq(tbn->dev, |
| tbn->aoc2ap_irq, NULL, |
| tbn_aoc2ap_irq_thread, |
| IRQF_TRIGGER_RISING | |
| IRQF_ONESHOT, "tbn", tbn); |
| if (err) { |
| dev_err(tbn->dev, |
| "%s: Unable to request_threaded_irq, err %d!\n", |
| __func__, err); |
| goto failed; |
| } |
| disable_irq_nosync(tbn->aoc2ap_irq); |
| } else { |
| dev_err(tbn->dev, "%s: invalid aoc2ap_gpio %d!\n", |
| __func__, tbn->aoc2ap_gpio); |
| goto failed; |
| } |
| } else { |
| tbn->mode = TBN_MODE_DISABLED; |
| } |
| |
| mutex_init(&tbn->dev_mask_mutex); |
| |
| init_completion(&tbn->bus_requested); |
| init_completion(&tbn->bus_released); |
| complete_all(&tbn->bus_requested); |
| complete_all(&tbn->bus_released); |
| |
| dev_info(tbn->dev, |
| "%s: gpios(aoc2ap: %d ap2aoc: %d), mode %d\n", |
| __func__, tbn->aoc2ap_gpio, tbn->ap2aoc_gpio, tbn->mode); |
| |
| dev_dbg(tbn->dev, "bus negotiator initialized: %pK\n", tbn); |
| |
| failed: |
| return err; |
| } |
| |
| static int tbn_remove(struct platform_device *pdev) |
| { |
| struct tbn_context *tbn = dev_get_drvdata(&(pdev->dev)); |
| |
| free_irq(tbn->aoc2ap_irq, tbn); |
| if (gpio_is_valid(tbn->aoc2ap_gpio)) |
| gpio_free(tbn->aoc2ap_gpio); |
| if (gpio_is_valid(tbn->aoc2ap_gpio)) |
| gpio_free(tbn->aoc2ap_gpio); |
| |
| return 0; |
| } |
| |
| static struct of_device_id tbn_of_match_table[] = { |
| { |
| .compatible = TBN_MODULE_NAME, |
| }, |
| {}, |
| }; |
| |
| static struct platform_driver tbn_driver = { |
| .driver = { |
| .name = TBN_MODULE_NAME, |
| .of_match_table = tbn_of_match_table, |
| }, |
| .probe = tbn_probe, |
| .remove = tbn_remove, |
| }; |
| |
| static int __init tbn_init(void) |
| { |
| return platform_driver_register(&tbn_driver); |
| } |
| |
| static void __exit tbn_exit(void) |
| { |
| platform_driver_unregister(&tbn_driver); |
| } |
| module_init(tbn_init); |
| module_exit(tbn_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Touch Bus Negotiator"); |
| MODULE_AUTHOR("Google, Inc."); |