/* Copyright (c) 2017-2018, NVIDIA CORPORATION. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* NVS = NVidia Sensor framework */ /* See nvs_iio.c and nvs.h for documentation */ #include #include #include #include #include #include #include #include #include #define DEFAULT_ON_MS 1000 struct nvs_led_test_context { struct device *dev; struct nvs_fn_if *nvs; void *nvs_st; struct sensor_cfg cfg; unsigned int sts; unsigned int errs; unsigned int enabled; int in_irq; int out_gpio; int enable_gpio; enum of_gpio_flags enable_gpio_flags; struct delayed_work work; }; static void nvs_led_test_generate_event(struct nvs_led_test_context *ctx, int on_ms) { s64 ts; u32 data = 0xcafebabe; /* * If a valid ms count is provided, and we have an out-gpio, write 1 * to the out-gpio (presumably, to turn on an LED), and schedule * work to turn it back off. */ if (on_ms > 0) { dev_info(ctx->dev, "Setting out-gpio to 1\n"); schedule_delayed_work(&ctx->work, (on_ms * HZ) / 1000); gpio_set_value_cansleep(ctx->out_gpio, 1); } /* * Regardless, generate the test sensor event. For some reason, * we need at least one channel, or the (Android) app doesn't appear * to see the event, so we just insert some bogus data. */ ts = nvs_timestamp(); #ifdef CONFIG_NVS_LED_TRACE_PRINTK trace_printk( "Sensor event occurred at %llu nsec (-500usec(hw/irq latency) -sampling_period)\n", ts); #endif ctx->nvs->handler(ctx->nvs_st, &data, ts); } static void nvs_led_test_work(struct work_struct *work) { struct nvs_led_test_context *ctx = container_of(work, struct nvs_led_test_context, work.work); dev_info(ctx->dev, "Setting out-gpio to 0\n"); gpio_set_value_cansleep(ctx->out_gpio, 0); } static irqreturn_t nvs_led_test_threaded_isr(int irq, void *data) { struct nvs_led_test_context *ctx = data; WARN_ON(!ctx); if (ctx) nvs_led_test_generate_event(ctx, DEFAULT_ON_MS); return IRQ_HANDLED; } static ssize_t nvs_led_test_store_on_ms(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct nvs_led_test_context *ctx = dev_get_drvdata(dev); int on_ms; int err; WARN_ON(!ctx); if (!ctx) return 0; err = kstrtouint(buf, 0, &on_ms); if (err < 0) dev_warn(dev, "Invalid ms %s\n", buf); nvs_led_test_generate_event(ctx, on_ms); return count; } static DEVICE_ATTR(on_ms, S_IWUSR | S_IWGRP, NULL, nvs_led_test_store_on_ms); static int nvs_led_test_enable(void *client, int snsr_id, int enable) { struct nvs_led_test_context *ctx = (struct nvs_led_test_context *)client; if (enable < 0) return ctx->enabled; ctx->enabled = enable; return 0; } static struct sensor_cfg nvs_led_test_cfg_dflt = { .part = "nvs", .name = "test", .vendor = "NVIDIA", .version = 1, .ch_n = 1, .ch_sz = 4, .flags = SENSOR_FLAG_ON_CHANGE_MODE, }; static struct nvs_fn_dev nvs_led_test_fn_dev = { .enable = nvs_led_test_enable, }; static int nvs_led_test_parse_dt(struct nvs_led_test_context *ctx, struct device_node *np) { ctx->in_irq = irq_of_parse_and_map(np, 0); ctx->out_gpio = of_get_named_gpio(np, "out-gpios", 0); ctx->enable_gpio = of_get_named_gpio_flags(np, "enable-gpio", 0, &ctx->enable_gpio_flags); return 0; } static int nvs_led_test_probe(struct platform_device *pdev) { struct nvs_led_test_context *ctx; int err; /* Create and set up our driver-specific context. */ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; memcpy(&ctx->cfg, &nvs_led_test_cfg_dflt, sizeof(ctx->cfg)); INIT_DELAYED_WORK(&ctx->work, nvs_led_test_work); dev_set_drvdata(&pdev->dev, ctx); ctx->dev = &pdev->dev; err = nvs_led_test_parse_dt(ctx, pdev->dev.of_node); if (err) return err; /* * If we are given an irq to trigger the generation of test sensor * events (like, from a button wired to a GPIO), set up the ISR. */ if (ctx->in_irq > 0) { err = devm_request_threaded_irq(&pdev->dev, ctx->in_irq, NULL, nvs_led_test_threaded_isr, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, pdev->name, ctx); if (err) { dev_err(&pdev->dev, "devm_request_irq() failed with err %d\n", err); ctx->in_irq = 0; return err; } dev_info(&pdev->dev, "Set up input gpio\n"); } /* * If there is a required enable-gpio set, then enable that first. */ if (gpio_is_valid(ctx->enable_gpio)) { err = devm_gpio_request(&pdev->dev, ctx->enable_gpio, "NVS LED enable GPIO"); if (err) { dev_err(&pdev->dev, "Failed to request Enable GPIO"); return err; } if (ctx->enable_gpio_flags & OF_GPIO_ACTIVE_LOW) err = gpio_direction_output(ctx->enable_gpio, 0); else err = gpio_direction_output(ctx->enable_gpio, 1); if (err) { dev_err(&pdev->dev, "Failed setting output of gpio"); return err; } } /* * If we are given an output GPIO (like, an LED wired to a GPIO), * set that output GPIO up. */ if (!gpio_is_valid(ctx->out_gpio)) { dev_err(&pdev->dev, "Invalid GPIO set"); return -EINVAL; } err = devm_gpio_request(&pdev->dev, ctx->out_gpio, "NVS LED test Sensor out"); if (err) { dev_err(&pdev->dev, "Failed requesting gpio"); return err; } err = gpio_direction_output(ctx->out_gpio, 0); if (err) { dev_err(&pdev->dev, "Failed setting output gpio"); return err; } dev_info(&pdev->dev, "Set up output gpio\n"); /* Register with NVS */ err = nvs_of_dt(pdev->dev.of_node, &ctx->cfg, NULL); if (err < 0) return err; nvs_led_test_fn_dev.errs = &ctx->errs; nvs_led_test_fn_dev.sts = &ctx->sts; ctx->nvs = nvs_iio(); if (!ctx->nvs) return -ENODEV; err = ctx->nvs->probe(&ctx->nvs_st, ctx, &pdev->dev, &nvs_led_test_fn_dev, &ctx->cfg); if (err) return err; /* Create our sysfs file for triggering test sensor event generation. */ err = device_create_file(&pdev->dev, &dev_attr_on_ms); if (err) return err; dev_info(&pdev->dev, "NVS LED test sensor driver initialized.\n"); return 0; } static int nvs_led_test_remove(struct platform_device *pdev) { struct nvs_led_test_context *ctx = (struct nvs_led_test_context *)dev_get_drvdata(&pdev->dev); WARN_ON(!ctx); if (!ctx) return 0; dev_info(&pdev->dev, "NVS LED test sensor driver removed.\n"); device_remove_file(&pdev->dev, &dev_attr_on_ms); flush_delayed_work(&ctx->work); ctx->nvs->remove(&ctx->nvs_st); return 0; } static const struct of_device_id nvs_led_test_of_match[] = { { .compatible = "nvidia,nvs-led-test", }, {} }; MODULE_DEVICE_TABLE(of, nvs_led_test_of_match); static struct platform_driver nvs_led_test_driver = { .driver = { .name = "nvs-led-test", .of_match_table = of_match_ptr(nvs_led_test_of_match), }, .probe = nvs_led_test_probe, .remove = nvs_led_test_remove, }; module_platform_driver(nvs_led_test_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("NVidia NVS LED Test driver"); MODULE_AUTHOR("NVIDIA Corporation");