From patchwork Wed Oct 31 16:52:10 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pantelis Antoniou X-Patchwork-Id: 1673121 Return-Path: X-Original-To: patchwork-linux-omap@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork1.kernel.org (Postfix) with ESMTP id D556F3FD2B for ; Tue, 30 Oct 2012 19:01:58 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934221Ab2J3TBH (ORCPT ); Tue, 30 Oct 2012 15:01:07 -0400 Received: from li42-95.members.linode.com ([209.123.162.95]:40547 "EHLO li42-95.members.linode.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S965587Ab2J3S7T (ORCPT ); Tue, 30 Oct 2012 14:59:19 -0400 Received: from sles11esa.localdomain (unknown [195.97.110.117]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) (Authenticated sender: panto) by li42-95.members.linode.com (Postfix) with ESMTPSA id 2CD479C1E8; Tue, 30 Oct 2012 18:59:16 +0000 (UTC) From: Pantelis Antoniou To: Tony Lindgren Cc: Pantelis Antoniou , linux-arm-kernel@lists.infradead.org, devicetree-discuss@lists.ozlabs.org, linux-kernel@vger.kernel.org, Koen Kooi , Matt Porter , Russ Dill , linux-omap@vger.kernel.org Subject: [RFC 4/7] capebus: Beaglebone geiger cape support Date: Wed, 31 Oct 2012 18:52:10 +0200 Message-Id: <1351702333-8456-5-git-send-email-panto@antoniou-consulting.com> X-Mailer: git-send-email 1.7.12 In-Reply-To: <1351702333-8456-1-git-send-email-panto@antoniou-consulting.com> References: <1351702333-8456-1-git-send-email-panto@antoniou-consulting.com> Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org Support beaglebone's geiger cape. The geiger cape allows you to measure the amount of ionising radiation in your area, and as an example of how to create a complex non-generic cape driver. Signed-off-by: Pantelis Antoniou --- drivers/capebus/capes/Kconfig | 7 + drivers/capebus/capes/Makefile | 1 + drivers/capebus/capes/bone-geiger-cape.c | 506 +++++++++++++++++++++++++++++++ 3 files changed, 514 insertions(+) create mode 100644 drivers/capebus/capes/bone-geiger-cape.c diff --git a/drivers/capebus/capes/Kconfig b/drivers/capebus/capes/Kconfig index bfe54a6..0418bef 100644 --- a/drivers/capebus/capes/Kconfig +++ b/drivers/capebus/capes/Kconfig @@ -4,3 +4,10 @@ config CAPEBUS_BONE_GENERIC default n help "Select this to enable a generic cape driver; LCD/DVI capes etc" + +config CAPEBUS_BONE_GEIGER + tristate "Beaglebone Geiger cape driver" + depends on CAPEBUS_BONE_CONTROLLER + default n + help + "Select this to enable a driver for the geiger cape" diff --git a/drivers/capebus/capes/Makefile b/drivers/capebus/capes/Makefile index 83da381..d6f94ce 100644 --- a/drivers/capebus/capes/Makefile +++ b/drivers/capebus/capes/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_CAPEBUS_BONE_GENERIC) += bone-generic-cape.o +obj-$(CONFIG_CAPEBUS_BONE_GEIGER) += bone-geiger-cape.o diff --git a/drivers/capebus/capes/bone-geiger-cape.c b/drivers/capebus/capes/bone-geiger-cape.c new file mode 100644 index 0000000..880eaae --- /dev/null +++ b/drivers/capebus/capes/bone-geiger-cape.c @@ -0,0 +1,506 @@ +/* + * Driver for beaglebone Geiger cape + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* fwd decl. */ +extern struct cape_driver bonegeiger_driver; + +struct bone_geiger_info { + struct cape_dev *dev; + struct bone_capebus_generic_info *geninfo; + struct pwm_device *pwm_dev; + int pwm_frequency; + int pwm_duty_cycle; + int run; + atomic64_t counter; + int event_gpio; + int event_irq; + struct led_trigger *event_led; /* event detect */ + struct led_trigger *run_led; /* running */ + unsigned long event_blink_delay; + struct sysfs_dirent *counter_sd; /* notifier */ + const char *vsense_name; + unsigned int vsense_scale; + struct iio_channel *vsense_channel; +}; + +static const struct of_device_id bonegeiger_of_match[] = { + { + .compatible = "bone-geiger-cape", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, bonegeiger_of_match); + +static int bonegeiger_start(struct cape_dev *dev) +{ + struct bone_geiger_info *info = dev->drv_priv; + int duty, period; + + if (info->run != 0) + return 0; + + /* checks */ + if (info->pwm_frequency < 1000 || info->pwm_frequency > 50000) { + dev_err(&dev->dev, "Cowardly refusing to use a " + "frequency of %d\n", + info->pwm_frequency); + return -EINVAL; + } + if (info->pwm_duty_cycle > 80) { + dev_err(&dev->dev, "Cowardly refusing to use a " + "duty cycle of %d\n", + info->pwm_duty_cycle); + return -EINVAL; + } + + period = div_u64(1000000000LLU, info->pwm_frequency); + duty = (period * info->pwm_duty_cycle) / 100; + + dev_info(&dev->dev, "starting geiger tube with " + "duty=%duns period=%dus\n", + duty, period); + + pwm_config(info->pwm_dev, duty, period); + pwm_enable(info->pwm_dev); + + info->run = 1; + led_trigger_event(info->run_led, LED_FULL); + + return 0; +} + +static int bonegeiger_stop(struct cape_dev *dev) +{ + struct bone_geiger_info *info = dev->drv_priv; + + if (info->run == 0) + return 0; + + dev_info(&dev->dev, "disabling geiger tube\n"); + pwm_config(info->pwm_dev, 0, 50000); /* 0% duty cycle, 20KHz */ + pwm_disable(info->pwm_dev); + + info->run = 0; + led_trigger_event(info->run_led, LED_OFF); + + return 0; +} + +static ssize_t bonegeiger_show_run(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cape_dev *cdev = to_cape_dev(dev); + struct bone_geiger_info *info = cdev->drv_priv; + + return sprintf(buf, "%d\n", info->run); +} + +static ssize_t bonegeiger_store_run(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cape_dev *cdev = to_cape_dev(dev); + int run, err; + + if (sscanf(buf, "%i", &run) != 1) + return -EINVAL; + + if (run) + err = bonegeiger_start(cdev); + else + err = bonegeiger_stop(cdev); + + return err ? err : count; +} + +static ssize_t bonegeiger_show_counter(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cape_dev *cdev = to_cape_dev(dev); + struct bone_geiger_info *info = cdev->drv_priv; + + return sprintf(buf, "%llu\n", atomic64_read(&info->counter)); +} + +static ssize_t bonegeiger_store_counter(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cape_dev *cdev = to_cape_dev(dev); + struct bone_geiger_info *info = cdev->drv_priv; + + atomic64_set(&info->counter, 0); /* just reset */ + return count; +} + +static ssize_t bonegeiger_show_vsense(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cape_dev *cdev = to_cape_dev(dev); + struct bone_geiger_info *info = cdev->drv_priv; + int ret, val; + u32 mvolts; + + ret = iio_read_channel_raw(info->vsense_channel, &val); + if (ret < 0) + return ret; + + /* V = (1800 / 4096) * val * scale) = (1.8 * val * scale / 4096) */ + mvolts = div_u64(1800 * info->vsense_scale * (u64)val, 4096 * 100); + + return sprintf(buf, "%d\n", mvolts); +} + +static DEVICE_ATTR(run, S_IRUGO | S_IWUSR, + bonegeiger_show_run, bonegeiger_store_run); +static DEVICE_ATTR(counter, S_IRUGO | S_IWUSR, + bonegeiger_show_counter, bonegeiger_store_counter); +static DEVICE_ATTR(vsense, S_IRUGO, + bonegeiger_show_vsense, NULL); + +static int bonegeiger_sysfs_register(struct cape_dev *cdev) +{ + int err; + + err = device_create_file(&cdev->dev, &dev_attr_run); + if (err != 0) + goto err_no_run; + + err = device_create_file(&cdev->dev, &dev_attr_counter); + if (err != 0) + goto err_no_counter; + + err = device_create_file(&cdev->dev, &dev_attr_vsense); + if (err != 0) + goto err_no_vsense; + + return 0; + +err_no_vsense: + device_remove_file(&cdev->dev, &dev_attr_counter); +err_no_counter: + device_remove_file(&cdev->dev, &dev_attr_run); +err_no_run: + return err; +} + +static void bonegeiger_sysfs_unregister(struct cape_dev *cdev) +{ + device_remove_file(&cdev->dev, &dev_attr_vsense); + device_remove_file(&cdev->dev, &dev_attr_counter); + device_remove_file(&cdev->dev, &dev_attr_run); +} + +static irqreturn_t bonegeiger_irq_handler(int irq, void *dev_id) +{ + struct cape_dev *dev = dev_id; + struct bone_geiger_info *info = dev->drv_priv; + + atomic64_inc(&info->counter); + + led_trigger_blink_oneshot(info->event_led, + &info->event_blink_delay, &info->event_blink_delay, 0); + + sysfs_notify_dirent(info->counter_sd); + + return IRQ_HANDLED; +} + +static int bonegeiger_probe(struct cape_dev *dev, const struct cape_device_id *id) +{ + char boardbuf[33]; + char versionbuf[5]; + const char *board_name; + const char *version; + struct bone_geiger_info *info; + struct pinctrl *pinctrl; + struct device_node *node, *pwm_node; + phandle phandle; + u32 val; + int err; + + /* boiler plate probing */ + err = bone_capebus_probe_prolog(dev, id); + if (err != 0) + return err; + + /* get the board name (after check of cntrlboard match) */ + board_name = bone_capebus_id_get_field(id, BONE_CAPEBUS_BOARD_NAME, + boardbuf, sizeof(boardbuf)); + /* get the board version */ + version = bone_capebus_id_get_field(id, BONE_CAPEBUS_VERSION, + versionbuf, sizeof(versionbuf)); + /* should never happen; but check anyway */ + if (board_name == NULL || version == NULL) + return -ENODEV; + + dev->drv_priv = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL); + if (dev->drv_priv == NULL) { + dev_err(&dev->dev, "Failed to allocate info\n"); + err = -ENOMEM; + goto err_no_mem; + } + info = dev->drv_priv; + + pinctrl = devm_pinctrl_get_select_default(&dev->dev); + if (IS_ERR(pinctrl)) + dev_warn(&dev->dev, + "pins are not configured from the driver\n"); + + node = capebus_of_find_property_node(dev, "version", version, "pwms"); + if (node == NULL) { + dev_err(&dev->dev, "unable to find pwms property\n"); + err = -ENODEV; + goto err_no_pwm; + } + + err = of_property_read_u32(node, "pwms", &val); + if (err != 0) { + dev_err(&dev->dev, "unable to read pwm handle\n"); + goto err_no_pwm; + } + phandle = val; + + pwm_node = of_find_node_by_phandle(phandle); + if (pwm_node == NULL) { + dev_err(&dev->dev, "Failed to pwm node\n"); + err = -EINVAL; + goto err_no_pwm; + } + + err = capebus_of_platform_device_enable(pwm_node); + of_node_put(pwm_node); + if (err != 0) { + dev_err(&dev->dev, "Failed to pwm node\n"); + goto err_no_pwm; + } + + info->pwm_dev = of_pwm_request(node, NULL); + of_node_put(node); + if (IS_ERR(info->pwm_dev)) { + dev_err(&dev->dev, "unable to request PWM\n"); + err = PTR_ERR(info->pwm_dev); + goto err_no_pwm; + } + + if (capebus_of_property_read_u32(dev, + "version", version, + "pwm-frequency", &val) != 0) { + val = 20000; + dev_warn(&dev->dev, "Could not read pwm-frequency property; " + "using default %u\n", + val); + } + info->pwm_frequency = val; + + if (capebus_of_property_read_u32(dev, + "version", version, + "pwm-duty-cycle", &val) != 0) { + val = 60; + dev_warn(&dev->dev, "Could not read pwm-duty-cycle property; " + "using default %u\n", + val); + } + info->pwm_duty_cycle = val; + + node = capebus_of_find_property_node(dev, "gpios", version, "pwms"); + info->event_gpio = of_get_gpio_flags(node, 0, NULL); + of_node_put(node); + if (IS_ERR_VALUE(info->event_gpio)) { + dev_err(&dev->dev, "unable to get event GPIO\n"); + err = info->event_gpio; + goto err_no_gpio; + } + + err = gpio_request_one(info->event_gpio, + GPIOF_DIR_IN | GPIOF_EXPORT, + "bone-geiger-cape-event"); + if (err != 0) { + dev_err(&dev->dev, "failed to request event GPIO\n"); + goto err_no_gpio; + } + + atomic64_set(&info->counter, 0); + + info->event_irq = gpio_to_irq(info->event_gpio); + if (IS_ERR_VALUE(info->event_irq)) { + dev_err(&dev->dev, "unable to get event GPIO IRQ\n"); + err = info->event_irq; + goto err_no_irq; + } + + err = request_irq(info->event_irq, bonegeiger_irq_handler, + IRQF_TRIGGER_RISING | IRQF_SHARED, + "bone-geiger-irq", dev); + if (err != 0) { + dev_err(&dev->dev, "unable to request irq\n"); + goto err_no_irq; + } + + err = bonegeiger_sysfs_register(dev); + if (err != 0) { + dev_err(&dev->dev, "unable to register sysfs\n"); + goto err_no_sysfs; + } + + info->counter_sd = sysfs_get_dirent(dev->dev.kobj.sd, NULL, "counter"); + if (info->counter_sd == NULL) { + dev_err(&dev->dev, "unable to get dirent of counter\n"); + err = -ENODEV; + goto err_no_counter_dirent; + } + + led_trigger_register_simple("geiger-event", &info->event_led); + led_trigger_register_simple("geiger-run", &info->run_led); + + /* pick up the generics; tsc & leds */ + info->geninfo = bone_capebus_probe_generic(dev, id); + if (info->geninfo == NULL) { + dev_err(&dev->dev, "Could not probe generic\n"); + goto err_no_generic; + } + + led_trigger_event(info->run_led, LED_OFF); + + /* default */ + if (capebus_of_property_read_u32(dev, + "version", version, + "event-blink-delay", &val) != 0) { + val = 30; + dev_warn(&dev->dev, "Could not read event-blink-delay " + "property; using default %u\n", + val); + } + info->event_blink_delay = val; + + /* default */ + if (capebus_of_property_read_string(dev, + "version", version, + "vsense-name", &info->vsense_name) != 0) { + info->vsense_name = "AIN5"; + dev_warn(&dev->dev, "Could not read vsense-name property; " + "using default %u\n", + val); + } + + if (capebus_of_property_read_u32(dev, + "version", version, + "vsense-scale", &info->vsense_scale) != 0) { + info->vsense_scale = 37325; /* 373.25 */ + dev_warn(&dev->dev, "Could not read vsense-scale property; " + "using default %u\n", + info->vsense_scale); + } + + info->vsense_channel = iio_channel_get(NULL, info->vsense_name); + if (IS_ERR(info->vsense_channel)) { + dev_err(&dev->dev, "Could not get AIN5 analog input\n"); + err = PTR_ERR(info->vsense_channel); + goto err_no_vsense; + } + + dev_info(&dev->dev, "ready\n"); + + err = bonegeiger_start(dev); + if (err != 0) { + dev_err(&dev->dev, "Could not start geiger device\n"); + goto err_no_start; + } + + return 0; + +err_no_start: + iio_channel_release(info->vsense_channel); +err_no_vsense: + bone_capebus_remove_generic(info->geninfo); +err_no_generic: + led_trigger_unregister_simple(info->run_led); + led_trigger_unregister_simple(info->event_led); + sysfs_put(info->counter_sd); +err_no_counter_dirent: + bonegeiger_sysfs_unregister(dev); +err_no_sysfs: + free_irq(info->event_irq, dev); +err_no_irq: + gpio_free(info->event_gpio); +err_no_gpio: + pwm_put(info->pwm_dev); +err_no_pwm: + devm_kfree(&dev->dev, info); +err_no_mem: + return err; +} + +static void bonegeiger_remove(struct cape_dev *dev) +{ + struct bone_geiger_info *info = dev->drv_priv; + + dev_info(&dev->dev, "Removing geiger cape driver...\n"); + + bonegeiger_stop(dev); + + iio_channel_release(info->vsense_channel); + bone_capebus_remove_generic(info->geninfo); + led_trigger_unregister_simple(info->run_led); + led_trigger_unregister_simple(info->event_led); + sysfs_put(info->counter_sd); + bonegeiger_sysfs_unregister(dev); + free_irq(info->event_irq, dev); + gpio_free(info->event_gpio); + pwm_put(info->pwm_dev); +} + +struct cape_driver bonegeiger_driver = { + .driver = { + .name = "bonegeiger", + .owner = THIS_MODULE, + .of_match_table = bonegeiger_of_match, + }, + .probe = bonegeiger_probe, + .remove = bonegeiger_remove, +}; + +module_capebus_driver(bonegeiger_driver); + +MODULE_AUTHOR("Pantelis Antoniou"); +MODULE_DESCRIPTION("Beaglebone geiger cape"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bone-geiger-cape");