/* * * Copyright 2021 SEC * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_KEYBOARD_STM32_POGO) || IS_ENABLED(CONFIG_KEYBOARD_STM32_POGO_V2) #define POGO_NOTIFIER_ENABLED #endif #ifdef POGO_NOTIFIER_ENABLED #include #endif #include "../../sec_input/stm32/stm32_pogo_i2c.h" /* * Switch events */ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) #define SW_FLIP 0x10 /* set = flip cover open, close*/ #define SW_HALL_LOGICAL 0x0a /* set = logical hall ic attach/detach */ #else #define SW_FLIP 0x15 /* set = flip cover open, close*/ #define SW_HALL_LOGICAL 0x1f /* set = logical hall ic attach/detach */ #endif enum LID_POSITION { E_LID_0 = 1, E_LID_NORMAL = 2, E_LID_360 = 3, }; enum LOGICAL_HALL_STATUS { LOGICAL_HALL_OPEN = 0, LOGICAL_HALL_CLOSE = 1, LOGICAL_HALL_BACK = 2, }; #if IS_ENABLED(CONFIG_DRV_SAMSUNG) struct device *hall_logical; #endif struct hall_drvdata { struct input_dev *input; #ifdef POGO_NOTIFIER_ENABLED struct notifier_block pogo_nb; #endif }; static int hall_logical_status = 0; static int hall_backflip_status = 0; #if IS_ENABLED(CONFIG_DRV_SAMSUNG) static ssize_t hall_logical_detect_show(struct device *dev, struct device_attribute *attr, char *buf) { if (hall_logical_status == LOGICAL_HALL_OPEN) sprintf(buf, "OPEN\n"); else if (hall_logical_status == LOGICAL_HALL_CLOSE) sprintf(buf, "CLOSE\n"); else sprintf(buf, "BACK\n"); return strlen(buf); } static DEVICE_ATTR_RO(hall_logical_detect); #endif static int hall_logical_open(struct input_dev *input) { /* Report current state of buttons that are connected to GPIOs */ input_report_switch(input, SW_FLIP, hall_logical_status); input_sync(input); return 0; } static void hall_logical_close(struct input_dev *input) { } static int of_hall_data_parsing_dt(struct device *dev, struct hall_drvdata *ddata) { return 0; } #ifdef POGO_NOTIFIER_ENABLED static int logical_hallic_notifier_handler(struct notifier_block *nb, unsigned long action, void *data) { struct hall_drvdata *logical_hall_dev = container_of(nb, struct hall_drvdata, pogo_nb); struct pogo_data_struct pogo_data = *(struct pogo_data_struct *)data; int hall_status; switch (action) { case POGO_NOTIFIER_ID_DETACHED: hall_logical_status = LOGICAL_HALL_OPEN; input_report_switch(logical_hall_dev->input, SW_FLIP, hall_logical_status); input_sync(logical_hall_dev->input); break; case POGO_NOTIFIER_EVENTID_HALL: if (pogo_data.size != 1) { pr_info("%s size is wrong. size=%d!\n", __func__, pogo_data.size); break; } hall_status = *pogo_data.data; if (hall_status == E_LID_0) { hall_logical_status = LOGICAL_HALL_CLOSE; input_info(true, &logical_hall_dev->input->dev, "%s hall_status = %d (CLOSE)\n", __func__, hall_status); input_report_switch(logical_hall_dev->input, SW_FLIP, hall_logical_status); input_sync(logical_hall_dev->input); } else if (hall_status == E_LID_NORMAL) { hall_logical_status = LOGICAL_HALL_OPEN; hall_backflip_status = 0; input_info(true, &logical_hall_dev->input->dev, "%s hall_status = %d (NORMAL)\n", __func__, hall_status); input_report_switch(logical_hall_dev->input, SW_FLIP, hall_logical_status); input_report_switch(logical_hall_dev->input, SW_HALL_LOGICAL, hall_backflip_status); input_sync(logical_hall_dev->input); } else if (hall_status == E_LID_360) { hall_backflip_status = 1; input_info(true, &logical_hall_dev->input->dev, "%s hall_status = %d (BACK)\n", __func__, hall_status); input_report_switch(logical_hall_dev->input, SW_HALL_LOGICAL, hall_backflip_status); input_sync(logical_hall_dev->input); } break; }; return NOTIFY_DONE; } #endif static int hall_logical_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct hall_drvdata *ddata; struct input_dev *input; int error; int wakeup = 0; pr_info("%s called", __func__); ddata = kzalloc(sizeof(struct hall_drvdata), GFP_KERNEL); if (!ddata) { dev_err(dev, "failed to allocate state\n"); return -ENOMEM; } if (dev->of_node) { error = of_hall_data_parsing_dt(dev, ddata); if (error < 0) { pr_info("%s : fail to get the dt (HALL)\n", __func__); goto fail1; } } input = input_allocate_device(); if (!input) { dev_err(dev, "failed to allocate state\n"); error = -ENOMEM; goto fail1; } ddata->input = input; platform_set_drvdata(pdev, ddata); input_set_drvdata(input, ddata); input->name = "hall_logical"; input->phys = "hall_logical"; input->dev.parent = &pdev->dev; input->evbit[0] |= BIT_MASK(EV_SW); input_set_capability(input, EV_SW, SW_FLIP); input_set_capability(input, EV_SW, SW_HALL_LOGICAL); input->open = hall_logical_open; input->close = hall_logical_close; /* Enable auto repeat feature of Linux input subsystem */ __set_bit(EV_REP, input->evbit); #if IS_ENABLED(CONFIG_DRV_SAMSUNG) hall_logical = sec_device_create(ddata, "hall_logical"); if (IS_ERR(hall_logical)) { dev_err(dev, "%s: failed to create device for the sysfs\n",__func__); } error = device_create_file(hall_logical, &dev_attr_hall_logical_detect); if (error < 0) { pr_err("Failed to create device file(%s)!, error: %d\n", dev_attr_hall_logical_detect.attr.name, error); } #endif error = input_register_device(input); if (error) { dev_err(dev, "Unable to register input device, error: %d\n", error); goto fail1; } device_init_wakeup(&pdev->dev, wakeup); #ifdef POGO_NOTIFIER_ENABLED pogo_notifier_register(&ddata->pogo_nb, logical_hallic_notifier_handler, POGO_NOTIFY_DEV_HALLIC); #endif input_report_switch(input, SW_FLIP, hall_logical_status); input_report_switch(input, SW_HALL_LOGICAL, hall_backflip_status); input_info(true, dev, "%s hall_status = %d backflip_status = %d\n", __func__, hall_logical_status, hall_backflip_status); pr_info("%s end", __func__); return 0; fail1: kfree(ddata); return error; } static int hall_logical_remove(struct platform_device *pdev) { struct hall_drvdata *ddata = platform_get_drvdata(pdev); struct input_dev *input = ddata->input; pr_info("%s start\n", __func__); device_init_wakeup(&pdev->dev, 0); input_unregister_device(input); #ifdef POGO_NOTIFIER_ENABLED pogo_notifier_unregister(&ddata->pogo_nb); #endif kfree(ddata); return 0; } static const struct of_device_id hall_logical_dt_ids[] = { { .compatible = "hall_logical" }, { }, }; MODULE_DEVICE_TABLE(of, hall_logical_dt_ids); static int hall_logical_suspend(struct device *dev) { struct hall_drvdata *ddata = dev_get_drvdata(dev); struct input_dev *input = ddata->input; pr_info("%s start\n", __func__); if (device_may_wakeup(dev)) { } else { mutex_lock(&input->mutex); if (input->users) hall_logical_close(input); mutex_unlock(&input->mutex); } return 0; } static int hall_logical_resume(struct device *dev) { struct hall_drvdata *ddata = dev_get_drvdata(dev); struct input_dev *input = ddata->input; pr_info("%s start\n", __func__); input_sync(input); return 0; } static SIMPLE_DEV_PM_OPS(hall_logical_pm_ops, hall_logical_suspend, hall_logical_resume); static struct platform_driver hall_device_driver = { .probe = hall_logical_probe, .remove = hall_logical_remove, .driver = { .name = "hall_logical", .owner = THIS_MODULE, .pm = &hall_logical_pm_ops, .of_match_table = hall_logical_dt_ids, } }; int hall_logical_init(void) { pr_info("%s start\n", __func__); return platform_driver_probe(&hall_device_driver, hall_logical_probe); } EXPORT_SYMBOL(hall_logical_init); void hall_logical_exit(void) { pr_info("%s start\n", __func__); platform_driver_unregister(&hall_device_driver); } EXPORT_SYMBOL(hall_logical_exit); MODULE_AUTHOR("Samsung"); MODULE_DESCRIPTION("Hall IC logical driver for GPIOs");