blob: f8eecc5701c39f2e1bb9d7ed39bd6fab2924c2a1 [file] [log] [blame]
/**
* @file nest_doorbell_chime.c
*
* @remark Copyright 2017 NestLabs
*
* @author Dmitry Yatsushkevich <dmitryya@google.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/hrtimer.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/spinlock.h>
#include <linux/sysctl.h>
#include <linux/power/nest_rq_battery.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/input.h>
#include <linux/ktime.h>
#include <linux/nest_gpio_keys.h>
#define DEFAULT_TIMEOUT 500
#define VBRIDGE_RESTORE_TIMEOUT 500
#define MAX_CMD_LEN 1024
#define BATTERY_TEMP_LOW_LIMIT 10000 // 10C
#define BATTERY_TEMP_LOW 18000 // 18C
#define BATTERY_TEMP_HIGH 23000 // 23C
#define BATTERY_TEMP_HIGH_LIMIT 80000 // 80C
#define BATTERY_VOLTAGE_LOW_LIMIT 3800 // 3.80V
#define BATTERY_VOLTAGE_LOW 3950 // 3.95V
#define BATTERY_VOLTAGE_HIGH 4150 // 4.15V
struct doorbell_chime_drvdata {
struct device *dev;
const char *name;
int gpio;
struct hrtimer ring_timer;
struct hrtimer vbridge_timer;
unsigned long duration_ms;
long max_battery_temp_mc;
long min_battery_temp_mc;
unsigned long min_battery_voltage_mv;
struct work_struct work;
struct work_struct callback_work;
char cmd_callback[MAX_CMD_LEN];
atomic_t ongoing;
atomic_t work_ongoing;
atomic_t callback_ongoing;
atomic_t skipped_chime_ring;
int disabled;
int doorbell_key_code;
struct input_handler event_handler;
struct input_handle event_handle;
struct workqueue_struct *workqueue;
};
//#define DEBUG 1
#ifdef DEBUG
ktime_t ring_start_time;
ktime_t ring_end_time;
#endif // DEBUG
static enum hrtimer_restart vbridge_timer_handler(struct hrtimer *handle)
{
struct doorbell_chime_drvdata *pdata =
container_of(handle, struct doorbell_chime_drvdata, vbridge_timer);
nest_rq_battery_vbridge_disable(nest_rq_battery, 0);
#ifdef DEBUG
{
ktime_t vb_diff_time, r_diff_time, all_diff_time;
r_diff_time = ktime_sub(ring_end_time, ring_start_time);
vb_diff_time = ktime_sub(ktime_get(), ring_end_time);
all_diff_time = ktime_sub(ktime_get(), ring_start_time);
dev_dbg(pdata->dev,
" ring duration %llu nsec, vbridge low period %llu nsec, all duration %llu nsec\n",
ktime_to_ns(r_diff_time), ktime_to_ns(vb_diff_time),
ktime_to_ns(all_diff_time));
}
#endif // DEBUG
atomic_set(&pdata->ongoing, 0);
return HRTIMER_NORESTART;
}
static enum hrtimer_restart ring_timer_handler(struct hrtimer *handle)
{
struct doorbell_chime_drvdata *pdata =
container_of(handle, struct doorbell_chime_drvdata, ring_timer);
ktime_t kt;
kt = ktime_set(VBRIDGE_RESTORE_TIMEOUT / 1000 /* sec */ ,
(VBRIDGE_RESTORE_TIMEOUT % 1000) * 1000000 /* nsec */ );
gpio_set_value(pdata->gpio, 0);
#ifdef DEBUG
ring_end_time = ktime_get();
#endif // DEBUG
hrtimer_start(&pdata->vbridge_timer, kt, HRTIMER_MODE_REL);
return HRTIMER_NORESTART;
}
static void stop_ring(struct doorbell_chime_drvdata *pdata)
{
hrtimer_cancel(&pdata->vbridge_timer);
hrtimer_cancel(&pdata->ring_timer);
gpio_set_value(pdata->gpio, 0);
nest_rq_battery_vbridge_disable(nest_rq_battery, 0);
atomic_set(&pdata->ongoing, 0);
}
static void run_cmd_callback(struct doorbell_chime_drvdata *pdata)
{
char **argv = NULL;
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
NULL
};
if (!strlen(pdata->cmd_callback)) {
dev_warn(pdata->dev, " callback command is not present.");
goto fail;
}
argv = argv_split(GFP_KERNEL, pdata->cmd_callback, NULL);
if (!argv) {
dev_warn(pdata->dev, " failed to allocate memory\n");
goto fail;
}
if (call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC)) {
dev_err(pdata->dev, " failed to run callback commamd: '%s'\n", pdata->cmd_callback);
}
fail:
atomic_set(&pdata->callback_ongoing, 0);
if (argv)
argv_free(argv);
dev_info(pdata->dev, " callback ended.");
}
#define BATT_OK 0
#define BATT_TEMP_INVALID 1
#define BATT_TEMP_TOO_HOT 2
#define BATT_TEMP_TOO_COLD 3
#define BATT_VOLTS_INVALID 4
#define BATT_VOLTS_LOW 5
static int is_safe_to_run_from_battery(struct doorbell_chime_drvdata *pdata)
{
int batt_temp, batt_voltage;
int reason = BATT_OK;
int ret = nest_rq_battery_get_temp(nest_rq_battery, &batt_temp); // temp in tenth of C
batt_voltage = nest_rq_battery_get_voltage(nest_rq_battery); // in uV
dev_dbg(pdata->dev, " battery temp %d mC and battery voltage %d mV",
batt_temp, batt_voltage);
if (ret == -1) {
dev_warn(pdata->dev, "battery temp is not valid. Running from battery is not safe");
return BATT_TEMP_INVALID;
}
batt_temp *= 100; // tenth of C to mC
// RQ-1916: Any VBAT, TBAT < 10°C or TBAT > 80°C, don't ring the house chime
if (batt_temp < pdata->min_battery_temp_mc) {
reason = BATT_TEMP_TOO_COLD;
} else if (batt_temp > pdata->max_battery_temp_mc) {
reason = BATT_TEMP_TOO_HOT;
}
if (reason != BATT_OK) {
dev_warn(pdata->dev,
"Battery temperature %dmC is not in safe range > %ldmC and < %ldmC. Running from battery is not safe",
batt_temp,
pdata->min_battery_temp_mc,
pdata->max_battery_temp_mc);
return reason;
}
if (batt_voltage == -1) {
dev_warn(pdata->dev, "battery voltage is not valid. Running from battery is not safe");
return BATT_VOLTS_INVALID;
}
batt_voltage /=1000; // uV -> mV
// RQ-1916: VBAT < 3.80V, any battery temperature: don't ring the house chime
if (batt_voltage < pdata->min_battery_voltage_mv) {
dev_warn(pdata->dev, "Battery voltage %dmV is too low. Running from battery is not safe",
batt_voltage);
return BATT_VOLTS_LOW;
}
// RQ-1916: 3.80V ≤ VBAT < 3.95V, TBAT < 23°C: don't ring the house chime
if ((batt_temp <= BATTERY_TEMP_HIGH) && (batt_voltage <= BATTERY_VOLTAGE_LOW)) {
dev_warn(pdata->dev, "Battery temperature and voltage is not in the safe range: %dmC, %dmV.",
batt_temp, batt_voltage);
return BATT_TEMP_TOO_COLD;
}
// RQ-1916: 3.95V ≤ VBAT < 4.15V, TBAT < 18°C: don't ring the house chime
if ((batt_temp <= BATTERY_TEMP_LOW) && (batt_voltage <= BATTERY_VOLTAGE_HIGH)) {
dev_warn(pdata->dev, "Battery temperature and voltage is not in the safe range: %dmC, %dmV.",
batt_temp, batt_voltage);
return BATT_TEMP_TOO_COLD;
}
return BATT_OK;
}
static void do_ring(struct doorbell_chime_drvdata *pdata)
{
int rv;
if (!atomic_xchg(&pdata->callback_ongoing, 1)) {
queue_work(pdata->workqueue, &pdata->callback_work);
} else {
dev_warn(pdata->dev, " chime callback is in progress\n");
}
if (pdata->disabled) {
dev_warn(pdata->dev, "Chime is disabled. Ignore ring.");
return;
}
if (!atomic_xchg(&pdata->ongoing, 1)) {
ktime_t kt;
kt = ktime_set(pdata->duration_ms / 1000 /* sec */ ,
(pdata->duration_ms % 1000) * 1000000 /* nsec */ );
#ifdef DEBUG
ring_start_time = ktime_get();
#endif // DEBUG
rv = is_safe_to_run_from_battery(pdata);
if (rv != BATT_OK) {
dev_warn(pdata->dev, "is not safe to ring a chime.\n");
atomic_set(&pdata->skipped_chime_ring, rv);
atomic_set(&pdata->ongoing, 0);
return;
}
atomic_set(&pdata->skipped_chime_ring, 0);
dev_info(pdata->dev, "the chime is ringing...\n");
nest_rq_battery_vbridge_disable(nest_rq_battery, 1);
gpio_set_value(pdata->gpio, 1);
hrtimer_start(&pdata->ring_timer, kt, HRTIMER_MODE_REL);
} else {
dev_warn(pdata->dev, "chime ring is in progress....\n");
}
}
static void doorbell_chime_work_func(struct work_struct *work)
{
struct doorbell_chime_drvdata *pdata =
container_of(work, struct doorbell_chime_drvdata, work);
dev_dbg(pdata->dev, " run doorbell chime work\n");
do_ring(pdata);
atomic_set(&pdata->work_ongoing, 0);
}
static void doorbell_chime_callback_work_func(struct work_struct *work)
{
struct doorbell_chime_drvdata *pdata =
container_of(work, struct doorbell_chime_drvdata, callback_work);
dev_dbg(pdata->dev, " run doorbell chime callback work\n");
run_cmd_callback(pdata);
}
// sysfs interfaces
ssize_t doorbell_chime_show_duration(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
return sprintf(buf, "%lu\n", pdata->duration_ms);
}
ssize_t doorbell_chime_store_duration(struct device * dev,
struct device_attribute * attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
unsigned long val;
if (sscanf(buf, "%lu", &val) != 1)
return -EINVAL;
pdata->duration_ms = val;
dev_info(dev, " setting 'duration_ms':%lu\n", pdata->duration_ms);
return count;
}
ssize_t doorbell_chime_show_max_battery_temp(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
return sprintf(buf, "%ld\n", pdata->max_battery_temp_mc);
}
ssize_t doorbell_chime_store_max_battery_temp(struct device * dev,
struct device_attribute * attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
unsigned long val;
if (sscanf(buf, "%ld", &val) != 1)
return -EINVAL;
pdata->max_battery_temp_mc = val;
dev_info(dev, " setting 'max_battery_temp_mc':%lu\n", pdata->max_battery_temp_mc);
return count;
}
ssize_t doorbell_chime_show_min_battery_temp(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
return sprintf(buf, "%ld\n", pdata->min_battery_temp_mc);
}
ssize_t doorbell_chime_store_min_battery_temp(struct device * dev,
struct device_attribute * attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
unsigned long val;
if (sscanf(buf, "%ld", &val) != 1)
return -EINVAL;
pdata->min_battery_temp_mc = val;
dev_info(dev, " setting 'min_battery_temp_mc':%lu\n", pdata->min_battery_temp_mc);
return count;
}
ssize_t doorbell_chime_show_min_battery_voltage(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
return sprintf(buf, "%lu\n", pdata->min_battery_voltage_mv);
}
ssize_t doorbell_chime_store_min_battery_voltage(struct device * dev,
struct device_attribute * attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
unsigned long val;
if (sscanf(buf, "%lu", &val) != 1)
return -EINVAL;
pdata->min_battery_voltage_mv = val;
dev_info(dev, " setting 'min_battery_voltage_mv':%lu\n", pdata->min_battery_voltage_mv);
return count;
}
ssize_t doorbell_chime_show_skipped_chime_ring(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
int val = atomic_xchg(&pdata->skipped_chime_ring, -1);
if (val == -1)
return sprintf(buf, "\n");
return sprintf(buf, "%d\n", val);
}
ssize_t doorbell_chime_show_ongoing(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
int val = atomic_read(&pdata->ongoing);
return sprintf(buf, "%d\n", val);
}
ssize_t doorbell_chime_show_disable(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
const char *STATE[] = { "Enabled", "Disabled" };
return sprintf(buf, "%s\n", STATE[pdata->disabled]);
}
ssize_t doorbell_chime_store_disable(struct device * dev,
struct device_attribute * attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
if (sysfs_streq(buf, "0"))
pdata->disabled = 0;
else if (sysfs_streq(buf, "1"))
pdata->disabled = 1;
else {
dev_warn(dev, " unsupported value: %s\n", buf);
return -1;
}
dev_info(dev, " setting 'disabled':%s\n",
pdata->disabled ? "true" : "false");
return count;
}
ssize_t doorbell_chime_store_ring(struct device * dev,
struct device_attribute * attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
do_ring(pdata);
return count;
}
ssize_t doorbell_chime_store_cancel(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
if (!sysfs_streq(buf, "0")) {
dev_info(dev, " cancel ringing chime\n");
stop_ring(pdata);
}
return count;
}
ssize_t doorbell_chime_show_callback(struct device * dev,
struct device_attribute * attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
return sprintf(buf, "%s\n", pdata->cmd_callback);
}
ssize_t doorbell_chime_store_callback(struct device * dev,
struct device_attribute * attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
size_t len = count;
if (len > MAX_CMD_LEN - 1) {
len = MAX_CMD_LEN - 1;
}
strncpy(pdata->cmd_callback, buf, len);
pdata->cmd_callback[len] = '\0';
dev_info(dev, " setting 'callback':%s\n",
pdata->cmd_callback);
return len;
}
static DEVICE_ATTR(skipped_chime_ring, S_IRUGO, doorbell_chime_show_skipped_chime_ring, NULL);
static DEVICE_ATTR(duration_ms, S_IWUSR | S_IRUGO,
doorbell_chime_show_duration, doorbell_chime_store_duration);
static DEVICE_ATTR(ongoing, S_IRUGO, doorbell_chime_show_ongoing, NULL);
static DEVICE_ATTR(disable, S_IWUSR | S_IRUGO,
doorbell_chime_show_disable, doorbell_chime_store_disable);
static DEVICE_ATTR(ring, S_IWUSR | S_IRUGO, NULL, doorbell_chime_store_ring);
static DEVICE_ATTR(cancel, S_IWUSR | S_IRUGO,
NULL, doorbell_chime_store_cancel);
static DEVICE_ATTR(callback, S_IWUSR | S_IRUGO,
doorbell_chime_show_callback, doorbell_chime_store_callback);
static DEVICE_ATTR(max_battery_temp_mc, S_IWUSR | S_IRUGO,
doorbell_chime_show_max_battery_temp, doorbell_chime_store_max_battery_temp);
static DEVICE_ATTR(min_battery_temp_mc, S_IWUSR | S_IRUGO,
doorbell_chime_show_min_battery_temp, doorbell_chime_store_min_battery_temp);
static DEVICE_ATTR(min_battery_voltage_mv, S_IWUSR | S_IRUGO,
doorbell_chime_show_min_battery_voltage, doorbell_chime_store_min_battery_voltage);
static struct attribute *doorbell_chime_attrs[] = {
&dev_attr_skipped_chime_ring.attr,
&dev_attr_duration_ms.attr,
&dev_attr_ongoing.attr,
&dev_attr_disable.attr,
&dev_attr_ring.attr,
&dev_attr_cancel.attr,
&dev_attr_callback.attr,
&dev_attr_max_battery_temp_mc.attr,
&dev_attr_min_battery_temp_mc.attr,
&dev_attr_min_battery_voltage_mv.attr,
NULL,
};
static struct attribute_group doorbell_chime_attr_group = {
.attrs = doorbell_chime_attrs,
};
static struct doorbell_chime_drvdata *doorbell_chime_get_devtree_pdata(struct device *dev)
{
struct device_node *node;
struct doorbell_chime_drvdata *pdata;
int error;
int gpio;
enum of_gpio_flags flags;
node = dev->of_node;
if (!node) {
error = -ENODEV;
goto err_out;
}
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
error = -ENOMEM;
goto err_out;
}
if (!of_find_property(node, "gpios", NULL)) {
dev_err(dev, "GPIOs is not defined\n");
error = -EINVAL;
goto err_free_pdata;
}
gpio = of_get_gpio_flags(node, 0, &flags);
if (gpio < 0) {
error = gpio;
if (error != -EPROBE_DEFER)
dev_err(dev,
"Failed to get gpio flags, error: %d\n", error);
goto err_free_pdata;
}
if (of_property_read_u32(node, "doorbell-key-code", &pdata->doorbell_key_code)) {
dev_err(dev,
"doorbell-key-code is not defined\n");
goto err_free_pdata;
}
pdata->gpio = gpio;
return pdata;
err_free_pdata:
kfree(pdata);
err_out:
return ERR_PTR(error);
}
static void nest_input_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct doorbell_chime_drvdata *pdata = handle->private;
dev_dbg(pdata->dev, "an input event is detected: type %d code %d value %d\n",
type, code, value);
if ((type == EV_KEY) && (code == pdata->doorbell_key_code) && (value == 1)) {
dev_dbg(pdata->dev, " doorbell button (code %d) press is detected\n", code);
if (!atomic_xchg(&pdata->work_ongoing, 1)) {
dev_dbg(pdata->dev, " schedule the work");
atomic_set(&pdata->skipped_chime_ring, -1);
queue_work(pdata->workqueue, &pdata->work);
} else {
dev_dbg(pdata->dev, " the work is already scheduled....\n");
}
}
}
static int nest_input_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
int error;
struct doorbell_chime_drvdata *pdata =
container_of(handler, struct doorbell_chime_drvdata, event_handler);
pdata->event_handle.dev = input_get_device(dev);
pdata->event_handle.name = "nest_doorbell";
pdata->event_handle.handler = handler;
pdata->event_handle.private = pdata;
error = input_register_handle(&pdata->event_handle);
dev_info(pdata->dev, "connected to '%s'\n", dev->name);
if (error) {
dev_err(pdata->dev,
"Failed to register input handle, error: %d\n", error);
return error;
}
error = input_open_device(&pdata->event_handle);
if (error) {
dev_err(pdata->dev,
"Failed to open input device, error: %d\n", error);
}
return error;
}
static void nest_input_disconnect(struct input_handle *handle)
{
input_close_device(handle);
input_unregister_handle(handle);
}
static const struct input_device_id nest_ids[] = {
{ .flags = INPUT_DEVICE_ID_MATCH_VENDOR,
.vendor = NEST_ID }, /* Matches NEST devices */
{ }, /* Terminating zero entry */
};
#define NEST_MINOR_BASE 13
static int doorbell_chime_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct doorbell_chime_drvdata *pdata = dev_get_platdata(dev);
int error;
if (!pdata) {
pdata = doorbell_chime_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
pdata->dev = dev;
pdata->disabled = 0;
atomic_set(&pdata->ongoing, 0);
atomic_set(&pdata->work_ongoing, 0);
atomic_set(&pdata->callback_ongoing, 0);
atomic_set(&pdata->skipped_chime_ring, -1);
pdata->duration_ms = DEFAULT_TIMEOUT;
pdata->max_battery_temp_mc = BATTERY_TEMP_HIGH_LIMIT;
pdata->min_battery_temp_mc = BATTERY_TEMP_LOW_LIMIT;
pdata->min_battery_voltage_mv = BATTERY_VOLTAGE_LOW_LIMIT;
hrtimer_init(&pdata->ring_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pdata->ring_timer.function = ring_timer_handler;
hrtimer_init(&pdata->vbridge_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pdata->vbridge_timer.function = vbridge_timer_handler;
pdata->event_handler.event = nest_input_event;
pdata->event_handler.connect = nest_input_connect;
pdata->event_handler.disconnect = nest_input_disconnect;
pdata->event_handler.legacy_minors = true;
pdata->event_handler.minor = NEST_MINOR_BASE;
pdata->event_handler.name = "nest_doorbell";
pdata->event_handler.id_table = nest_ids;
platform_set_drvdata(pdev, pdata);
error = sysfs_create_group(&pdev->dev.kobj, &doorbell_chime_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
goto fail;
}
error = input_register_handler(&pdata->event_handler);
if (error) {
dev_err(dev, "Unable to register input handler: %d\n", error);
goto fail;
}
/* used for unplugging and affects IO latency/throughput - HIGHPRI */
pdata->workqueue = alloc_workqueue("kdoorbell",
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
if (!pdata->workqueue)
panic("Failed to create kblockd\n");
INIT_WORK(&pdata->work, doorbell_chime_work_func);
INIT_WORK(&pdata->callback_work, doorbell_chime_callback_work_func);
return 0;
fail:
platform_set_drvdata(pdev, NULL);
kfree(pdata);
return error;
}
static int doorbell_chime_remove(struct platform_device *pdev)
{
struct doorbell_chime_drvdata *pdata = platform_get_drvdata(pdev);
cancel_work_sync(&pdata->work);
cancel_work_sync(&pdata->callback_work);
atomic_set(&pdata->work_ongoing, 0);
atomic_set(&pdata->callback_ongoing, 0);
atomic_set(&pdata->skipped_chime_ring, -1);
input_unregister_handler(&pdata->event_handler);
stop_ring(pdata);
sysfs_remove_group(&pdev->dev.kobj, &doorbell_chime_attr_group);
destroy_workqueue(pdata->workqueue);
if (gpio_is_valid(pdata->gpio))
gpio_free(pdata->gpio);
kfree(pdata);
return 0;
}
static struct of_device_id doorbell_chime_of_match[] = {
{.compatible = "nest-doorbell-chime",},
{},
};
MODULE_DEVICE_TABLE(of, doorbell_chime_of_match);
static struct platform_driver doorbell_chime_device_driver = {
.probe = doorbell_chime_probe,
.remove = doorbell_chime_remove,
.driver = {
.name = "nest_doorbell_chime",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(doorbell_chime_of_match),
}
};
static int __init doorbell_chime_init(void)
{
return platform_driver_register(&doorbell_chime_device_driver);
}
static void __exit doorbell_chime_exit(void)
{
platform_driver_unregister(&doorbell_chime_device_driver);
}
late_initcall(doorbell_chime_init);
module_exit(doorbell_chime_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dmitry Yatsushkevich <dmitryya@google.com>");
MODULE_DESCRIPTION("Nest doorbell chime driver");
MODULE_ALIAS("platform:nest-doorbell-chime");