|
|
|
@@ -0,0 +1,265 @@
|
|
|
|
|
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2026 Amlogic, Inc. All rights reserved.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Hall Sensor driver for smart cover detection.
|
|
|
|
|
*
|
|
|
|
|
* This driver handles a hall switch like SDC1211 whose OUT pin is
|
|
|
|
|
* connected to a SoC GPIO. The hall switch pulls the GPIO level when
|
|
|
|
|
* the cover magnet is near, the driver reports the state as an
|
|
|
|
|
* EV_SW/SW_LID input event so that user space can turn off/wake up
|
|
|
|
|
* the screen when the cover is closed/opened.
|
|
|
|
|
*
|
|
|
|
|
* DT properties:
|
|
|
|
|
* - hall-gpios : the gpio the hall switch OUT pin connects to,
|
|
|
|
|
* the gpio polarity flags describe the active
|
|
|
|
|
* (cover closed) level.
|
|
|
|
|
* - linux,code : the switch code to report, default SW_LID.
|
|
|
|
|
* - debounce-ms : debounce time in ms, default 30ms.
|
|
|
|
|
* - wakeup-source : enable the hall irq as a wakeup source, the
|
|
|
|
|
* irq is also registered to bl31 so that it can
|
|
|
|
|
* wake up the system from deep suspend.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
|
#include <linux/slab.h>
|
|
|
|
|
#include <linux/of.h>
|
|
|
|
|
#include <linux/input.h>
|
|
|
|
|
#include <linux/irq.h>
|
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
|
#include <linux/gpio/consumer.h>
|
|
|
|
|
#include <linux/pm_wakeup.h>
|
|
|
|
|
#include <linux/amlogic/power_domain.h>
|
|
|
|
|
|
|
|
|
|
#define DEFAULT_DEBOUNCE_MS 30
|
|
|
|
|
#define HALL_SENSOR_NAME "hall_sensor"
|
|
|
|
|
|
|
|
|
|
struct hall_sensor {
|
|
|
|
|
struct device *dev;
|
|
|
|
|
struct input_dev *input;
|
|
|
|
|
struct gpio_desc *desc;
|
|
|
|
|
int irq;
|
|
|
|
|
u32 code;
|
|
|
|
|
u32 debounce_ms;
|
|
|
|
|
int last_state;
|
|
|
|
|
struct delayed_work work;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void hall_sensor_work(struct work_struct *work)
|
|
|
|
|
{
|
|
|
|
|
struct hall_sensor *hall =
|
|
|
|
|
container_of(work, struct hall_sensor, work.work);
|
|
|
|
|
int state;
|
|
|
|
|
|
|
|
|
|
state = gpiod_get_value(hall->desc);
|
|
|
|
|
if (state < 0) {
|
|
|
|
|
dev_err(hall->dev, "failed to get hall gpio value\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (state != hall->last_state) {
|
|
|
|
|
hall->last_state = state;
|
|
|
|
|
input_report_switch(hall->input, hall->code, state);
|
|
|
|
|
input_sync(hall->input);
|
|
|
|
|
dev_info(hall->dev,
|
|
|
|
|
"cover %s\n", state ? "closed" : "opened");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static irqreturn_t hall_sensor_irq_handler(int irq, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct hall_sensor *hall = data;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Do not disable the irq here, just refresh the debounce
|
|
|
|
|
* window so that the irq stays enabled for wakeup.
|
|
|
|
|
*/
|
|
|
|
|
mod_delayed_work(system_wq, &hall->work,
|
|
|
|
|
msecs_to_jiffies(hall->debounce_ms));
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t hall_state_show(struct device *dev,
|
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
|
{
|
|
|
|
|
struct hall_sensor *hall = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
|
|
return sysfs_emit(buf, "%d\n", hall->last_state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DEVICE_ATTR_RO(hall_state);
|
|
|
|
|
|
|
|
|
|
static int hall_sensor_probe(struct platform_device *pdev)
|
|
|
|
|
{
|
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
|
struct hall_sensor *hall;
|
|
|
|
|
bool wakeup_source;
|
|
|
|
|
struct irq_desc *desc;
|
|
|
|
|
unsigned long number;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (!dev->of_node) {
|
|
|
|
|
dev_err(dev, "device node is NULL\n");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hall = devm_kzalloc(dev, sizeof(*hall), GFP_KERNEL);
|
|
|
|
|
if (!hall)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
hall->dev = dev;
|
|
|
|
|
platform_set_drvdata(pdev, hall);
|
|
|
|
|
|
|
|
|
|
hall->desc = devm_gpiod_get(dev, "hall", GPIOD_IN);
|
|
|
|
|
if (IS_ERR(hall->desc)) {
|
|
|
|
|
dev_err(dev, "failed to get hall gpio\n");
|
|
|
|
|
return PTR_ERR(hall->desc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (of_property_read_u32(dev->of_node, "linux,code", &hall->code))
|
|
|
|
|
hall->code = SW_LID;
|
|
|
|
|
|
|
|
|
|
if (of_property_read_u32(dev->of_node,
|
|
|
|
|
"debounce-ms", &hall->debounce_ms))
|
|
|
|
|
hall->debounce_ms = DEFAULT_DEBOUNCE_MS;
|
|
|
|
|
|
|
|
|
|
hall->irq = gpiod_to_irq(hall->desc);
|
|
|
|
|
if (hall->irq < 0) {
|
|
|
|
|
dev_err(dev, "failed to get hall irq\n");
|
|
|
|
|
return hall->irq;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hall->input = devm_input_allocate_device(dev);
|
|
|
|
|
if (!hall->input)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
hall->input->name = HALL_SENSOR_NAME;
|
|
|
|
|
hall->input->phys = HALL_SENSOR_NAME "/input0";
|
|
|
|
|
hall->input->dev.parent = dev;
|
|
|
|
|
hall->input->id.bustype = BUS_ISA;
|
|
|
|
|
hall->input->id.vendor = 0x0001;
|
|
|
|
|
hall->input->id.product = 0x0001;
|
|
|
|
|
hall->input->id.version = 0x0100;
|
|
|
|
|
input_set_capability(hall->input, EV_SW, hall->code);
|
|
|
|
|
|
|
|
|
|
ret = input_register_device(hall->input);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dev, "failed to register input device\n");
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Report the current cover state at boot. */
|
|
|
|
|
hall->last_state = gpiod_get_value(hall->desc);
|
|
|
|
|
if (hall->last_state < 0)
|
|
|
|
|
hall->last_state = 0;
|
|
|
|
|
input_report_switch(hall->input, hall->code, hall->last_state);
|
|
|
|
|
input_sync(hall->input);
|
|
|
|
|
dev_info(dev, "hall cover %s at probe\n",
|
|
|
|
|
hall->last_state ? "closed" : "opened");
|
|
|
|
|
|
|
|
|
|
INIT_DELAYED_WORK(&hall->work, hall_sensor_work);
|
|
|
|
|
ret = devm_request_irq(dev, hall->irq, hall_sensor_irq_handler,
|
|
|
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
|
|
|
|
HALL_SENSOR_NAME, hall);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dev, "failed to request hall irq\n");
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = device_create_file(dev, &dev_attr_hall_state);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dev, "failed to create hall_state sysfs node\n");
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source");
|
|
|
|
|
if (wakeup_source) {
|
|
|
|
|
device_init_wakeup(dev, true);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Register the hall irq to bl31 as a wakeup source,
|
|
|
|
|
* refer to the gpio_keypad driver.
|
|
|
|
|
*/
|
|
|
|
|
desc = irq_to_desc(hall->irq);
|
|
|
|
|
if (desc && desc->irq_data.parent_data) {
|
|
|
|
|
number = desc->irq_data.parent_data->hwirq - 32;
|
|
|
|
|
pwr_ctrl_irq_set(number, 1, 0);
|
|
|
|
|
dev_dbg(dev, "register wakeup irq %lu\n", number);
|
|
|
|
|
} else {
|
|
|
|
|
dev_err(dev, "failed to get hall parent irq\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dev_info(dev, "hall sensor probed, code=%u debounce=%ums\n",
|
|
|
|
|
hall->code, hall->debounce_ms);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void hall_sensor_remove(struct platform_device *pdev)
|
|
|
|
|
{
|
|
|
|
|
struct hall_sensor *hall = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
|
|
cancel_delayed_work_sync(&hall->work);
|
|
|
|
|
device_remove_file(&pdev->dev, &dev_attr_hall_state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int hall_sensor_suspend(struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
struct hall_sensor *hall = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
|
|
if (device_may_wakeup(dev))
|
|
|
|
|
enable_irq_wake(hall->irq);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int hall_sensor_resume(struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
struct hall_sensor *hall = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
|
|
if (device_may_wakeup(dev))
|
|
|
|
|
disable_irq_wake(hall->irq);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The cover state may change during suspend, report it
|
|
|
|
|
* after resume.
|
|
|
|
|
*/
|
|
|
|
|
mod_delayed_work(system_wq, &hall->work, 0);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct dev_pm_ops hall_sensor_pm_ops = {
|
|
|
|
|
.suspend = hall_sensor_suspend,
|
|
|
|
|
.resume = hall_sensor_resume,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct of_device_id hall_sensor_dt_match[] = {
|
|
|
|
|
{ .compatible = "amlogic,hall-sensor", },
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
MODULE_DEVICE_TABLE(of, hall_sensor_dt_match);
|
|
|
|
|
|
|
|
|
|
static struct platform_driver hall_sensor_driver = {
|
|
|
|
|
.probe = hall_sensor_probe,
|
|
|
|
|
.remove = hall_sensor_remove,
|
|
|
|
|
.driver = {
|
|
|
|
|
.name = "hall-sensor",
|
|
|
|
|
.pm = &hall_sensor_pm_ops,
|
|
|
|
|
.of_match_table = hall_sensor_dt_match,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int __init meson_hall_sensor_init(void)
|
|
|
|
|
{
|
|
|
|
|
return platform_driver_register(&hall_sensor_driver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void __exit meson_hall_sensor_exit(void)
|
|
|
|
|
{
|
|
|
|
|
platform_driver_unregister(&hall_sensor_driver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL v2");
|