diff --git a/arch/arm/boot/dts/am335x-bone-common.dtsi b/arch/arm/boot/dts/am335x-bone-common.dtsi index 46d5f27e31469d..301635d7a00a5c 100644 --- a/arch/arm/boot/dts/am335x-bone-common.dtsi +++ b/arch/arm/boot/dts/am335x-bone-common.dtsi @@ -126,6 +126,18 @@ >; }; + bone_nixie_cape_led_pins: pinmux_bone_nixie_cape_led_pins { + pinctrl-single,pins = < + 0x1a4 0x17 /* mcasp0_fsr.gpio3_19, OUTPUT_PULLUP | MODE7 */ + >; + }; + + bone_nixie_cape_pins: pinmux_bone_nixie_cape_pins { + pinctrl-single,pins = < + 0x4c 0x06 /* gpmc_a3.ehrpwm1b, OMAP_MUX_MODE6 | AM33XX_PIN_OUTPUT */ + >; + }; + bone_geiger_cape_pins: pinmux_bone_geiger_cape_pins { pinctrl-single,pins = < 0x48 0x06 /* gpmc_a2.ehrpwm1a, OMAP_MUX_MODE6 | AM33XX_PIN_OUTPUT */ @@ -267,6 +279,10 @@ compatible = "bone-generic-cape"; }; + bone_nixie_cape: cape@6 { + compatible = "bone-nixie-cape"; + }; + /* overrides; no EEPROM (prototyping) */ // override@3 { // compatible = "bone-capebus-slot-override"; @@ -286,6 +302,17 @@ // /* TODO: Add the rest */ // }; + /* overrides; no EEPROM (prototyping) */ +// override@1 { +// compatible = "bone-capebus-slot-override"; +// slot = <1>; +// board-name = "Nixie Cape"; +// version = "00A0"; +// manufacturer = "Ranostay Industries"; +// /* TODO; Add the rest */ +// }; + + /* overrides; no EEPROM (prototyping) */ // override@1 { // compatible = "bone-capebus-slot-override"; // slot = <1>; @@ -480,6 +507,55 @@ }; }; +&bone_nixie_cape { + board-name = "Nixie Cape"; + + /* note that these can't be versioned.... */ + pinctrl-names = "default"; + pinctrl-0 = <&bone_nixie_cape_pins>; + + pwms = <&ehrpwm1 1 500000 0>; + pwm-names = "bone-nixie-cape"; + + pwm-frequency = <9250>; /* 9.250KHz */ + pwm-duty-cycle = <35>; /* 35% */ + + gpio-leds { + compatible = "gpio-leds"; + pinctrl-names = "default"; + pinctrl-0 = <&bone_nixie_cape_led_pins>; + + geiger-led0 { + label = "nixie:green:usr0"; + gpios = <&gpio4 19 0>; + linux,default-trigger = "nixie-run"; + default-state = "off"; + }; + }; + + spi1-devices { + compatible = "spi-dt"; + + #address-cells = <1>; + #size-cells = <0>; + + parent = <&spi1>; + + vfd@0 { + compatible = "bone-spi-vfd,max6921"; + + spi-max-frequency = <4000000>; + reg = <0>; + refresh-rate = <5>; /* in milliseconds */ + + digits-idx = <0x07 0x00 0x06 0x01 0x05 0x02 0x03 0x04 0x08>; + digits-mask = <0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xc0>; + + segments-idx = <0x09 0x12 0x0c 0x0b 0xd 0x13 0x11 0x0a>; + }; + }; +}; + &bone_geiger_cape { board-name = "Geiger Cape"; diff --git a/drivers/capebus/boards/capebus-bone-generic.c b/drivers/capebus/boards/capebus-bone-generic.c index b1b79ebecafd12..1354c9e1ad4957 100644 --- a/drivers/capebus/boards/capebus-bone-generic.c +++ b/drivers/capebus/boards/capebus-bone-generic.c @@ -141,6 +141,12 @@ static const struct bone_capebus_generic_device_data gendevs[] = { { .compatible = "spi-dt", }, { }, }, .units = 0, /* no limit */ + }, { + .name = "spi-vfd", + .of_match = (const struct of_device_id []) { + { .compatible = "spi-vfd", }, { }, + }, + .units = 0, /* no limit */ } }; diff --git a/drivers/capebus/capes/Kconfig b/drivers/capebus/capes/Kconfig index 0418bef90a6436..ae23bc71e4739a 100644 --- a/drivers/capebus/capes/Kconfig +++ b/drivers/capebus/capes/Kconfig @@ -11,3 +11,16 @@ config CAPEBUS_BONE_GEIGER default n help "Select this to enable a driver for the geiger cape" + +config CAPEBUS_BONE_NIXIE + tristate "Beaglebone Nixie cape driver" + depends on CAPEBUS_BONE_CONTROLLER + select CAPEBUS_SPI_VFD + default n + help + "Select this to enable a driver for the nixie cape" + +config CAPEBUS_SPI_VFD + bool + depends on CAPEBUS_BONE_NIXIE + default y diff --git a/drivers/capebus/capes/Makefile b/drivers/capebus/capes/Makefile index d6f94ce40cbf10..fd71bc30459986 100644 --- a/drivers/capebus/capes/Makefile +++ b/drivers/capebus/capes/Makefile @@ -1,2 +1,4 @@ obj-$(CONFIG_CAPEBUS_BONE_GENERIC) += bone-generic-cape.o obj-$(CONFIG_CAPEBUS_BONE_GEIGER) += bone-geiger-cape.o +obj-$(CONFIG_CAPEBUS_BONE_NIXIE) += bone-nixie-cape.o +obj-$(CONFIG_CAPEBUS_SPI_VFD) += bone-spi-vfd.o diff --git a/drivers/capebus/capes/bone-nixie-cape.c b/drivers/capebus/capes/bone-nixie-cape.c new file mode 100644 index 00000000000000..f5a4de3a38a09e --- /dev/null +++ b/drivers/capebus/capes/bone-nixie-cape.c @@ -0,0 +1,342 @@ +/* + * Nixie cape driver + * + * Copyright (C) 2012 Matt Ranostay + * + * Based on original work by + * Copyright (C) 2012 Pantelis Antoniou + * Copyright (C) 2012 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +extern struct cape_driver bonenixie_driver; + +/* IV-18 tube */ +#define DIGIT_COUNT 9 + +struct bone_nixie_info { + struct cape_dev *dev; + struct bone_capebus_generic_info *geninfo; + struct pwm_device *pwm_dev; + struct led_trigger *run_led; /* running */ + + int pwm_frequency; + int pwm_duty_cycle; + int run; +}; + +static const struct of_device_id bonenixie_of_match[] = { + { + .compatible = "bone-nixie-cape", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, bonenixie_of_match); + +static int bonenixie_start(struct cape_dev *dev) +{ + struct bone_nixie_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 nixie 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 bonenixie_stop(struct cape_dev *dev) +{ + struct bone_nixie_info *info = dev->drv_priv; + + if (info->run == 0) + return 0; + + dev_info(&dev->dev, "disabling nixie 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 bonenixie_show_run(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cape_dev *cdev = to_cape_dev(dev); + struct bone_nixie_info *info = cdev->drv_priv; + + return sprintf(buf, "%d\n", info->run); +} + + +static ssize_t bonenixie_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 = bonenixie_start(cdev); + else + err = bonenixie_stop(cdev); + + return err ? err : count; +} + +static DEVICE_ATTR(run, S_IRUGO | S_IWUSR, + bonenixie_show_run, bonenixie_store_run); + +static int bonenixie_sysfs_register(struct cape_dev *cdev) +{ + int err; + + err = device_create_file(&cdev->dev, &dev_attr_run); + if (err != 0) + goto err_no_run; + + return 0; + +err_no_run: + return err; +} + +static void bonenixie_sysfs_unregister(struct cape_dev *cdev) +{ + device_remove_file(&cdev->dev, &dev_attr_run); +} + +static int bonenixie_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_nixie_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 = 9250; + 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 = 35; + dev_warn(&dev->dev, "Could not read pwm-duty-cycle property; " + "using default %u\n", + val); + } + info->pwm_duty_cycle = val; + + err = bonenixie_sysfs_register(dev); + if (err != 0) { + dev_err(&dev->dev, "unable to register sysfs\n"); + goto err_no_sysfs; + } + + led_trigger_register_simple("nixie-run", &info->run_led); + + /* pick up the generics; spi and 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); + + dev_info(&dev->dev, "ready\n"); + + err = bonenixie_start(dev); + if (err != 0) { + dev_err(&dev->dev, "Could not start nixie device\n"); + goto err_no_start; + } + + return 0; + +err_no_start: + led_trigger_unregister_simple(info->run_led); + bone_capebus_remove_generic(info->geninfo); +err_no_generic: + bonenixie_sysfs_unregister(dev); +err_no_sysfs: +err_no_pwm: + devm_kfree(&dev->dev, info); +err_no_mem: + return err; +} + +static void bonenixie_remove(struct cape_dev *dev) +{ + struct bone_nixie_info *info = dev->drv_priv; + + dev_info(&dev->dev, "Remove nixie cape driver...\n"); + + bonenixie_stop(dev); + + bone_capebus_remove_generic(info->geninfo); + led_trigger_unregister_simple(info->run_led); + bonenixie_sysfs_unregister(dev); +} + +struct cape_driver bonenixie_driver = { + .driver = { + .name = "bonenixie", + .owner = THIS_MODULE, + .of_match_table = bonenixie_of_match, + }, + .probe = bonenixie_probe, + .remove = bonenixie_remove, +}; + +module_capebus_driver(bonenixie_driver); + + +MODULE_AUTHOR("Matt Ranostay"); +MODULE_DESCRIPTION("Beaglebone nixie cape"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bone-nixie-cape"); diff --git a/drivers/capebus/capes/bone-spi-vfd.c b/drivers/capebus/capes/bone-spi-vfd.c new file mode 100644 index 00000000000000..51362f3a1e7489 --- /dev/null +++ b/drivers/capebus/capes/bone-spi-vfd.c @@ -0,0 +1,438 @@ +/* + * SPI VFD driver + * + * Copyright (C) 2012 Matt Ranostay + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bone-spi-vfd.h" + +/* + * VFD screen update functions + */ + +static void spi_display_update(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct bonespivfd_info *info = container_of(dwork, + struct bonespivfd_info, vfd_update); + struct spi_device *spi = info->spi; + u8 buf[4]; + int retval; + int i; + + for (i = 0; i < info->max_digits; i++) { + u32 val = info->buf[i]; + + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; + + retval = spi_write(spi, &buf, sizeof(buf)); + if (retval) { + dev_err(&spi->dev, "cannot write vfd data: %x. err. %d\n", + val, retval); + return; + } + } + + memset(&buf, 0, sizeof(buf)); + retval = spi_write(spi, &buf, sizeof(buf)); + if (retval) { + dev_err(&spi->dev, "cannot write blanking data. err. %d\n", retval); + return; + } + + cancel_delayed_work(&info->vfd_update); + schedule_delayed_work(&info->vfd_update, msecs_to_jiffies(info->refresh_rate)); +} + + +static inline int is_valid_value(char val) +{ + int i; + + for (i = 0; i < sizeof(nixie_value_array) - 1; i++) { + if (nixie_value_array[i] == val) + return i; + } + + return -EINVAL; +} + +static uint32_t char_to_segment(struct bonespivfd_info *info, + int digit, int idx, int period) +{ + int i; + uint32_t retval = 0; + uint32_t val = nixie_segment_values[idx]; + + if (period) { + val |= SEG_H; + } + val = val & info->digits_mask[digit]; + + for (i = 0; i < SEGMENT_COUNT; i++) { + if (val & (1 << i)) { + retval |= 1<< info->segments_cache[i]; + } + } + + retval |= 1<< info->digits_cache[digit]; + + return retval; +} + +static void segment_to_char(struct bonespivfd_info *info, char *buf, int idx) +{ + uint32_t retval = 0; + int i; + + for (i = 0; i < SEGMENT_COUNT; i++) { + int seg = 1<< info->segments_cache[i]; + if (!!(seg & info->buf[idx])) { + retval |= 1<max_digits - 1; i >= 0; i--) { + segment_to_char(info, buf, i); + } + strcat(buf, "\n"); + + return strlen(buf); +} + +static ssize_t bonespivfd_store_display(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct bonespivfd_info *info = spi_get_drvdata(spi); + + int digit = 0; + int period = 0; + int val; + int i; + + memset(info->buf, 0, sizeof(u32) * info->max_digits); + + /* + * Cycle right to left from input string + */ + for (i = count; i >= 0; i--) { + char chr = buf[i]; + + /* + * DP are part of digits + */ + if (chr == '.') { + period = 1; + continue; + } + + val = is_valid_value(chr); + if (val < 0) + continue; + + val = char_to_segment(info, digit, val, period); + if (val < 0) + continue; + + info->buf[digit] = val; + period = 0; + digit++; + } + + /* + * corner case with DP leading the other digits + */ + + if (period) { + val = is_valid_value('.'); + if (val < 0) + return count; + val = char_to_segment(info, digit, val ,0); + info->buf[digit] = val; + } + schedule_delayed_work(&info->vfd_update, 0); + + return count; +} + +static DEVICE_ATTR(vfd_display, S_IRUGO | S_IWUSR, + bonespivfd_show_display, bonespivfd_store_display); + + +static int bonespivfd_sysfs_register(struct spi_device *spi) +{ + return device_create_file(&spi->dev, &dev_attr_vfd_display); +}; + +static void bonespivfd_sysfs_unregister(struct spi_device *spi) +{ + device_remove_file(&spi->dev, &dev_attr_vfd_display); +}; + +static const struct spi_device_id spivfd_device_id[] = { + { + .name = "max6921", + .driver_data = MAX6921_DEVICE, + }, { + .name = "max6931", + .driver_data = MAX6931_DEVICE, + }, { + .name = "generic", + .driver_data = GENERIC_DEVICE, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(spi, spivfd_device_id); + +static const struct of_device_id spivfd_of_match[] = { + { .compatible = "bone-spi-vfd,max6921", .data = (void *) MAX6921_DEVICE, }, + { .compatible = "bone-spi-vfd,max6931", .data = (void *) MAX6931_DEVICE, }, + { .compatible = "bone-spi-vfd,generic", .data = (void *) GENERIC_DEVICE, }, +}; +MODULE_DEVICE_TABLE(of, spivfd_of_match); + +static int bonespivfd_parse_dt(struct spi_device *spi, + struct bonespivfd_info *info) +{ + struct device_node *np = spi->dev.of_node; + struct property *prop; + int length; + u32 value; + int ret; + + memset(info, 0, sizeof(*info)); + + if (!np) + return -ENODEV; + + prop = of_find_property(np, "digits-idx", &length); + if (!prop) + return -EINVAL; + + info->max_digits = length / sizeof(u32); + if (info->max_digits > 0) { + size_t size = sizeof(*info->digits_cache) * info->max_digits; + info->digits_cache = devm_kzalloc(&spi->dev, size, GFP_KERNEL); + if (!info->digits_cache) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "digits-idx", + info->digits_cache, + info->max_digits); + if (ret < 0) + return ret; + } + + prop = of_find_property(np, "segments-idx", &length); + if (!prop) + return -EINVAL; + + info->max_segments = length / sizeof(u32); + + if (info->max_segments == SEGMENT_COUNT) { + size_t size = sizeof(*info->segments_cache) * info->max_segments; + info->segments_cache = devm_kzalloc(&spi->dev, size, GFP_KERNEL); + if (!info->segments_cache) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "segments-idx", + info->segments_cache, + info->max_segments); + if (ret < 0) + return ret; + } else { + dev_err(&spi->dev, "invalid number of segments defined!"); + return -EINVAL; + } + + prop = of_find_property(np, "digits-mask", &length); + if (!prop) + return -EINVAL; + + if (info->max_digits == (length / sizeof(u32))) { + size_t size = sizeof(*info->digits_mask) * info->max_digits; + info->digits_mask = devm_kzalloc(&spi->dev, size, GFP_KERNEL); + if (!info->digits_mask) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "digits-mask", + info->digits_mask, + info->max_digits); + if (ret < 0) + return ret; + } else { + dev_err(&spi->dev, "digits segment mask isn't the same size " + "as segments index"); + return -EINVAL; + } + + if (of_property_read_u32(np, "refresh-rate", &value) != 0) { + value = 150; + dev_warn(&spi->dev, "no refresh-rate set defaulting to '%d'", value); + } + info->refresh_rate = value; + + return 0; +} + +static int __devinit bonespivfd_probe(struct spi_device *spi) +{ + struct bonespivfd_info *info; + const struct spi_device_id *spi_id = spi_get_device_id(spi); + int retval = -ENOMEM; + u32 *buf; + + if (!spi_id) { + dev_err(&spi->dev, + "device id not supported!\n"); + return -EINVAL; + } + + info = devm_kzalloc(&spi->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) { + dev_err(&spi->dev, + "no memory left!\n"); + return -ENOMEM; + } + + buf = devm_kzalloc(&spi->dev, + sizeof(u32) * info->max_digits, GFP_KERNEL); + if (buf == NULL) { + dev_err(&spi->dev, + "no memory left for digits buffer!\n"); + retval = -ENOMEM; + goto err_no_mem; + } + + retval = bonespivfd_sysfs_register(spi); + if (retval < 0) { + dev_err(&spi->dev, "unable to register sysfs\n"); + goto err_no_sysfs; + } + + retval = bonespivfd_parse_dt(spi, info); + if (retval < 0) { + dev_err(&spi->dev, "unable to parse dt\n"); + goto err_no_dt; + } + + INIT_DELAYED_WORK(&info->vfd_update, spi_display_update); + + info->spi = spi; + info->buf = buf; + + spi_set_drvdata(spi, info); + return 0; + +err_no_dt: + bonespivfd_sysfs_unregister(spi); +err_no_sysfs: + devm_kfree(&spi->dev, buf); +err_no_mem: + devm_kfree(&spi->dev, info); + + return retval; +} + +static int __devexit bonespivfd_remove(struct spi_device *spi) +{ + struct bonespivfd_info *info = spi_get_drvdata(spi); + + cancel_delayed_work_sync(&info->vfd_update); + bonespivfd_sysfs_unregister(spi); + + spi_set_drvdata(spi, NULL); + + return 0; +} + +static struct spi_driver bonespivfd_driver = { + .id_table = spivfd_device_id, + .driver = { + .name = "bone-spi-vfd", + .owner = THIS_MODULE, + .of_match_table = spivfd_of_match, + }, + .probe = bonespivfd_probe, + .remove = __devexit_p(bonespivfd_remove), +}; + +static int __init bonespivfd_init(void) +{ + return spi_register_driver(&bonespivfd_driver); +} + +static void __exit bonespivfd_exit(void) +{ + spi_unregister_driver(&bonespivfd_driver); +} + +/* ------------------------------------------------------------------------- */ + + +module_init(bonespivfd_init); +module_exit(bonespivfd_exit); + +MODULE_AUTHOR("Matt Ranostay"); +MODULE_DESCRIPTION("VFD display driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/capebus/capes/bone-spi-vfd.h b/drivers/capebus/capes/bone-spi-vfd.h new file mode 100644 index 00000000000000..63a3a1fcd79e08 --- /dev/null +++ b/drivers/capebus/capes/bone-spi-vfd.h @@ -0,0 +1,88 @@ +/* + * drivers/capebus/capes/bone-spi-vfd.h -- VFD driver for Nixie Cape + * + * Copyright (C) 2012, Matt Ranostay + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +/* Placeholder values until we build up an index + * + */ + +#include +#include + +/* + * Not really doing anything with this currently + */ + +enum { + MAX6921_DEVICE, + MAX6931_DEVICE, + GENERIC_DEVICE, +}; + +#define SEGMENT_COUNT 8 + +struct bonespivfd_info { + struct spi_device *spi; + struct delayed_work vfd_update; + int refresh_rate; + + struct shift_register_info *shift; + u32 *buf; + + /* digits cache */ + u32 *digits_cache; + u32 *digits_mask; + int max_digits; + + /* segments cache */ + u32 *segments_cache; + int max_segments; +}; + + + +/* + * We always assume SEG_H is the DP digit + */ + +enum vfd_segments { + SEG_A = 1<<0, + SEG_B = 1<<1, + SEG_C = 1<<2, + SEG_D = 1<<3, + SEG_E = 1<<4, + SEG_F = 1<<5, + SEG_G = 1<<6, + SEG_H = 1<<7, +}; + +/* + * Segments + */ + +static const uint16_t nixie_segment_values[] = { + SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, /* 0 */ + SEG_B | SEG_C, /* 1 */ + SEG_A | SEG_B | SEG_D | SEG_E | SEG_G, /* 2 */ + SEG_A | SEG_B | SEG_C | SEG_D | SEG_G, /* 3 */ + SEG_B | SEG_C | SEG_F | SEG_G, /* 4 */ + SEG_A | SEG_C | SEG_D | SEG_F | SEG_G, /* 5 */ + SEG_A | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, /* 6 */ + SEG_A | SEG_B | SEG_C, /* 7 */ + SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, /* 8 */ + SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G, /* 9 */ + SEG_G, /* (hypen) */ + SEG_H, /* (period) */ + 0, /* (space) */ +}; + +/* + * Characters + */ +static const char nixie_value_array[] = "0123456789-. ";