From patchwork Mon Oct 25 13:31:02 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ilkka Koskinen X-Patchwork-Id: 266962 Received: from lists.sourceforge.net (lists.sourceforge.net [216.34.181.88]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id o9PDRtUQ027380 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Mon, 25 Oct 2010 13:28:16 GMT Received: from localhost ([127.0.0.1] helo=sfs-ml-1.v29.ch3.sourceforge.com) by sfs-ml-1.v29.ch3.sourceforge.com with esmtp (Exim 4.69) (envelope-from ) id 1PAN5Z-00044M-11; Mon, 25 Oct 2010 13:27:49 +0000 Received: from sog-mx-4.v43.ch3.sourceforge.com ([172.29.43.194] helo=mx.sourceforge.net) by sfs-ml-1.v29.ch3.sourceforge.com with esmtp (Exim 4.69) (envelope-from ) id 1PAN5Y-00044F-IE for spi-devel-general@lists.sourceforge.net; Mon, 25 Oct 2010 13:27:48 +0000 Received-SPF: pass (sog-mx-4.v43.ch3.sourceforge.com: domain of nokia.com designates 147.243.1.47 as permitted sender) client-ip=147.243.1.47; envelope-from=ilkka.koskinen@nokia.com; helo=mgw-sa01.nokia.com; Received: from smtp.nokia.com ([147.243.1.47] helo=mgw-sa01.nokia.com) by sog-mx-4.v43.ch3.sourceforge.com with esmtps (TLSv1:AES256-SHA:256) (Exim 4.69) id 1PAN5W-0000dX-FZ for spi-devel-general@lists.sourceforge.net; Mon, 25 Oct 2010 13:27:48 +0000 Received: from localhost.localdomain (tumpelo.nmp.nokia.com [172.22.211.13]) by mgw-sa01.nokia.com (Switch-3.4.3/Switch-3.4.3) with ESMTP id o9PDRbMM014818; Mon, 25 Oct 2010 16:27:37 +0300 From: Ilkka Koskinen To: linux-input@vger.kernel.org, dmitry.torokhov@gmail.com Subject: [PATCH] input: spi: Driver for SPI data stream driven vibrator Date: Mon, 25 Oct 2010 16:31:02 +0300 Message-Id: <1288013463-21722-1-git-send-email-ilkka.koskinen@nokia.com> X-Mailer: git-send-email 1.7.0.4 X-Nokia-AV: Clean X-Spam-Score: -1.5 (-) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -1.5 SPF_CHECK_PASS SPF reports sender host as permitted sender for sender-domain -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record 0.0 AWL AWL: From: address is in the auto white-list X-Headers-End: 1PAN5W-0000dX-FZ Cc: spi-devel-general@lists.sourceforge.net, linux-kernel@vger.kernel.org X-BeenThere: spi-devel-general@lists.sourceforge.net X-Mailman-Version: 2.1.9 Precedence: list List-Id: Linux SPI core/device drivers discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: spi-devel-general-bounces@lists.sourceforge.net X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter1.kernel.org [140.211.167.41]); Mon, 25 Oct 2010 13:28:16 +0000 (UTC) diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index b49e233..3441832 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -438,4 +438,9 @@ config INPUT_ADXL34X_SPI To compile this driver as a module, choose M here: the module will be called adxl34x-spi. +config INPUT_SPI_VIBRA + tristate "Support for SPI driven Vibra module" + help + Support for Vibra module that is connected to OMAP SPI bus. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 19ccca7..cde272f 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -41,4 +41,4 @@ obj-$(CONFIG_INPUT_WINBOND_CIR) += winbond-cir.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o - +obj-$(CONFIG_INPUT_SPI_VIBRA) += vibra_spi.o diff --git a/drivers/input/misc/vibra_spi.c b/drivers/input/misc/vibra_spi.c new file mode 100644 index 0000000..551a3b8 --- /dev/null +++ b/drivers/input/misc/vibra_spi.c @@ -0,0 +1,429 @@ +/* + * This file implements a driver for SPI data driven vibrator. + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Ilkka Koskinen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Number of effects handled with memoryless devices */ +#define VIBRA_EFFECTS 36 +#define MAX_EFFECT_SIZE 1024 /* In bytes */ + +#define FF_EFFECT_QUEUED 0 +#define FF_EFFECT_PLAYING 1 +#define FF_EFFECT_ABORTING 2 +#define FF_EFFECT_UPLOADING 3 + +enum vibra_status { + IDLE = 0, + STARTED, + PLAYING, + CLOSING, +}; + +struct effect_info { + char *buf; + int buflen; + unsigned long flags; /* effect state (STARTED, PLAYING, etc) */ + unsigned long stop_at; +}; + +struct vibra_data { + struct device *dev; + struct input_dev *input_dev; + + struct workqueue_struct *workqueue; + struct work_struct play_work; + + struct spi_device *spi_dev; + struct spi_transfer t; + struct spi_message msg; + u32 spi_max_speed_hz; + + void (*set_power)(bool enable); + + enum vibra_status status; + + struct effect_info effects[VIBRA_EFFECTS]; + int next_effect; + int current_effect; + unsigned long stop_at; +}; + +static int vibra_spi_raw_write_effect(struct vibra_data *vibra) +{ + spi_message_init(&vibra->msg); + memset(&vibra->t, 0, sizeof(vibra->t)); + + vibra->t.tx_buf = vibra->effects[vibra->current_effect].buf; + vibra->t.len = vibra->effects[vibra->current_effect].buflen; + spi_message_add_tail(&vibra->t, &vibra->msg); + + return spi_sync(vibra->spi_dev, &vibra->msg); +} + +static void vibra_play_work(struct work_struct *work) +{ + struct vibra_data *vibra = container_of(work, + struct vibra_data, play_work); + struct effect_info *curr, *next; + unsigned long flags; + + while (1) { + spin_lock_irqsave(&vibra->input_dev->event_lock, flags); + curr = &vibra->effects[vibra->current_effect]; + next = &vibra->effects[vibra->next_effect]; + + if (vibra->status == CLOSING) + goto switch_off; + + /* In the case of the first sample, just play it. */ + if (vibra->status == STARTED) { + vibra->current_effect = vibra->next_effect; + vibra->status = PLAYING; + + __set_bit(FF_EFFECT_PLAYING, &curr->flags); + spin_unlock_irqrestore(&vibra->input_dev->event_lock, + flags); + if (vibra->set_power) + vibra->set_power(true); + + vibra_spi_raw_write_effect(vibra); + clear_bit(FF_EFFECT_PLAYING, &curr->flags); + continue; + + } + + /* Shall we replay the current one? */ + if (!test_bit(FF_EFFECT_ABORTING, &curr->flags) && + time_before(jiffies, curr->stop_at)) { + __set_bit(FF_EFFECT_PLAYING, &curr->flags); + spin_unlock_irqrestore(&vibra->input_dev->event_lock, + flags); + + vibra_spi_raw_write_effect(vibra); + clear_bit(FF_EFFECT_PLAYING, &curr->flags); + continue; + } + + __clear_bit(FF_EFFECT_PLAYING, &curr->flags); + + /* Or should we play the next one? */ + if (test_bit(FF_EFFECT_QUEUED, &next->flags) && + time_before(jiffies, next->stop_at)) { + vibra->current_effect = vibra->next_effect; + __set_bit(FF_EFFECT_PLAYING, &next->flags); + spin_unlock_irqrestore(&vibra->input_dev->event_lock, + flags); + + vibra_spi_raw_write_effect(vibra); + clear_bit(FF_EFFECT_PLAYING, &next->flags); + continue; + } + + /* Nothing to play, so switch off the power */ + +switch_off: + if (vibra->set_power) + vibra->set_power(false); + + vibra->status = IDLE; + spin_unlock_irqrestore(&vibra->input_dev->event_lock, flags); + return ; + } +} + +/* + * Input/Force feedback guarantees that playback() is called with spinlock held + * and interrupts off. +*/ +static int vibra_spi_playback(struct input_dev *input, int effect_id, int value) +{ + struct vibra_data *vibra = input_get_drvdata(input); + struct effect_info *einfo = &vibra->effects[effect_id]; + struct ff_effect *ff_effect = &input->ff->effects[effect_id]; + + if (!vibra->workqueue) + return -ENODEV; + + if (test_bit(FF_EFFECT_UPLOADING, &einfo->flags)) + return -EBUSY; + + if (value == 0) { + /* Abort the given effect */ + if (test_bit(FF_EFFECT_PLAYING, &einfo->flags)) + __set_bit(FF_EFFECT_ABORTING, &einfo->flags); + + __clear_bit(FF_EFFECT_QUEUED, &einfo->flags); + } else { + /* Move the given effect as the next one */ + __clear_bit(FF_EFFECT_QUEUED, + &vibra->effects[vibra->next_effect].flags); + + vibra->next_effect = effect_id; + __set_bit(FF_EFFECT_QUEUED, &einfo->flags); + __clear_bit(FF_EFFECT_ABORTING, &einfo->flags); + einfo->stop_at = jiffies + + msecs_to_jiffies(ff_effect->replay.length); + + if (vibra->status == IDLE) { + vibra->status = STARTED; + queue_work(vibra->workqueue, &vibra->play_work); + } + } + + return 0; +} + +static int vibra_spi_upload(struct input_dev *input, struct ff_effect *effect, + struct ff_effect *old) +{ + struct vibra_data *vibra = input_get_drvdata(input); + struct effect_info *einfo = &vibra->effects[effect->id]; + struct ff_periodic_effect *p = &effect->u.periodic; + int datalen, ret = 0; + unsigned long flags; + + if (effect->type != FF_PERIODIC || p->waveform != FF_CUSTOM) + return -EINVAL; + + spin_lock_irqsave(&vibra->input_dev->event_lock, flags); + if (test_bit(FF_EFFECT_QUEUED, &einfo->flags) || + test_bit(FF_EFFECT_PLAYING, &einfo->flags) || + test_bit(FF_EFFECT_UPLOADING, &einfo->flags)) { + spin_unlock_irqrestore(&vibra->input_dev->event_lock, flags); + return -EBUSY; + + } + __set_bit(FF_EFFECT_UPLOADING, &einfo->flags); + spin_unlock_irqrestore(&vibra->input_dev->event_lock, flags); + + datalen = p->custom_len * sizeof(p->custom_data[0]); + if (datalen > MAX_EFFECT_SIZE) { + ret = -ENOSPC; + goto exit; + } + + if (einfo->buf && einfo->buflen != datalen) { + kfree(einfo->buf); + einfo->buf = NULL; + } + + if (!einfo->buf) { + einfo->buf = kzalloc(datalen, GFP_KERNEL | GFP_DMA); + if (!einfo->buf) { + ret = -ENOMEM; + goto exit; + } + } + + memcpy(einfo->buf, p->custom_data, datalen); + einfo->buflen = datalen; + +exit: + __clear_bit(FF_EFFECT_UPLOADING, &einfo->flags); + return ret; +} + +static int vibra_spi_open(struct input_dev *input) +{ + struct vibra_data *vibra = input_get_drvdata(input); + + vibra->workqueue = create_singlethread_workqueue("vibra"); + if (!vibra->workqueue) { + dev_err(&input->dev, "couldn't create workqueue\n"); + return -ENOMEM; + } + + return 0; +} + +static void vibra_spi_close(struct input_dev *input) +{ + struct vibra_data *vibra = input_get_drvdata(input); + + vibra->status = CLOSING; + + cancel_work_sync(&vibra->play_work); + destroy_workqueue(vibra->workqueue); + vibra->workqueue = NULL; + + vibra->status = IDLE; +} + +static ssize_t vibra_spi_show_spi_max_speed(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vibra_data *vibra = dev_get_drvdata(dev); + + return sprintf(buf, "spi_max_speed=%u\n", vibra->spi_max_speed_hz); +} + +static DEVICE_ATTR(spi_max_speed, S_IRUGO, vibra_spi_show_spi_max_speed, NULL); + +static int __devinit vibra_spi_probe(struct spi_device *spi) +{ + struct vibra_data *vibra; + struct ff_device *ff; + struct vibra_spi_platform_data *pdata; + int ret = -ENOMEM; + + vibra = kzalloc(sizeof(*vibra), GFP_KERNEL); + if (!vibra) { + dev_err(&spi->dev, "Not enough memory"); + return -ENOMEM; + } + + vibra->spi_max_speed_hz = spi->max_speed_hz; + + pdata = spi->dev.platform_data; + if (pdata) + vibra->set_power = pdata->set_power; + + INIT_WORK(&vibra->play_work, vibra_play_work); + + vibra->dev = &spi->dev; + spi_set_drvdata(spi, vibra); + vibra->spi_dev = spi; + + spi->bits_per_word = 32; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "spi_setup failed"); + goto err_spi_setup; + } + + vibra->input_dev = input_allocate_device(); + if (!vibra->input_dev) { + dev_err(vibra->dev, "couldn't allocate input device\n"); + ret = -ENOMEM; + goto err_input_alloc; + } + + input_set_drvdata(vibra->input_dev, vibra); + + vibra->input_dev->name = "SPI vibrator"; + vibra->input_dev->id.version = 1; + vibra->input_dev->dev.parent = spi->dev.parent; + vibra->input_dev->open = vibra_spi_open; + vibra->input_dev->close = vibra_spi_close; + + set_bit(FF_PERIODIC, vibra->input_dev->ffbit); + set_bit(FF_CUSTOM, vibra->input_dev->ffbit); + + ret = input_ff_create(vibra->input_dev, VIBRA_EFFECTS); + if (ret) { + dev_err(&spi->dev, "Couldn't create input feedback device"); + goto err_input_ff_create; + } + + ff = vibra->input_dev->ff; + ff->private = vibra; + ff->upload = vibra_spi_upload; + ff->playback = vibra_spi_playback; + + ret = sysfs_create_file(&spi->dev.kobj, &dev_attr_spi_max_speed.attr); + if (ret) { + dev_err(&spi->dev, "Sysfs registration failed\n"); + goto err_sysfs; + } + + ret = input_register_device(vibra->input_dev); + if (ret < 0) { + dev_dbg(&spi->dev, "couldn't register input device\n"); + goto err_input_register; + } + + dev_dbg(&spi->dev, "SPI driven Vibra driver initialized\n"); + return 0; + +err_input_register: + sysfs_remove_file(&spi->dev.kobj, &dev_attr_spi_max_speed.attr); +err_sysfs: + input_ff_destroy(vibra->input_dev); +err_input_ff_create: + input_free_device(vibra->input_dev); +err_input_alloc: +err_spi_setup: + kfree(vibra); + return ret; +} + +static int __devexit vibra_spi_remove(struct spi_device *spi) +{ + struct vibra_data *vibra = dev_get_drvdata(&spi->dev); + int i; + + for (i = 0; i < VIBRA_EFFECTS; i++) + kfree(vibra->effects[i].buf); + + sysfs_remove_file(&spi->dev.kobj, &dev_attr_spi_max_speed.attr); + /* sysfs_remove_group(&spi->dev.kobj, &vibra_spi_attribute_group); */ + input_unregister_device(vibra->input_dev); + kfree(vibra); + return 0; +} + +static struct spi_driver vibra_spi_driver = { + .driver = { + .name = "vibra_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = vibra_spi_probe, + .remove = __devexit_p(vibra_spi_remove), +}; + +static int __init vibra_spi_init(void) +{ + int ret; + + ret = spi_register_driver(&vibra_spi_driver); + if (ret < 0) { + printk(KERN_ERR "failed to register spi driver: %d", ret); + goto out; + } + +out: + return ret; +} + +static void __exit vibra_spi_exit(void) +{ + spi_unregister_driver(&vibra_spi_driver); +} + +module_init(vibra_spi_init); +module_exit(vibra_spi_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ilkka Koskinen "); diff --git a/include/linux/spi/vibra.h b/include/linux/spi/vibra.h new file mode 100644 index 0000000..3c3af3e --- /dev/null +++ b/include/linux/spi/vibra.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Ilkka Koskinen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _LINUX_VIBRA_SPI_H +#define _LINUX_VIBRA_SPI_H + +/** + * struct vibra_spi_platform_data + * @set_power: Called to switch on/off the power. Note that it may sleep when + * switched on, but NOT when switched off + */ +struct vibra_spi_platform_data { + void (*set_power)(bool enable); +}; + +#endif