blob: 2293cfe703ebf76b15aef30f60ab9ba025cff3e6 [file] [log] [blame]
/*****************************************************************************
* Copyright 2010 - 2011 Broadcom Corporation. All rights reserved.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2, available at
* http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a
* license other than the GPL, without Broadcom's express prior written
* consent.
*****************************************************************************/
/**
*
* haptics.c
*
* PURPOSE:
*
* This file contains the haptics driver routines.
*
* NOTES:
*
*****************************************************************************/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/timer.h>
#include <linux/broadcom/bcm_haptics.h>
#include <linux/broadcom/amxr.h>
#include <linux/broadcom/amxr_port.h>
#include "../staging/android/timed_output.h"
static int debug_level = 0;
/* Timer data structures */
static struct timer_list g_haptics_timer; /* timer to control length of vibration */
static unsigned long g_haptics_off_jiffies = 0; /* jiffies of the timer expiration; 0 if timer is not set */
static spinlock_t g_haptics_lock; /* lock for updating g_haptics_off_jiffies */
static atomic_t g_haptics_flag = ATOMIC_INIT(0); /* 1 for vibrating, 0 otherwise */
/* Audio Mixer data structures */
#define HALAUDIO_PORT_HZ 48000
#define AMXR_HAPTICS_FRAMESZ_16BITS 240 /* 48 kHz, 5ms */
static AMXR_HDL g_amixer_fd;
static int16_t *amxr_getsrc(int bytes, void *privdata);
static AMXR_PORT_CB g_amxr_callback =
{
.getsrc = amxr_getsrc,
};
static AMXR_PORT_ID g_hal_port_id;
static AMXR_PORT_ID g_ept_port_d;
static int16_t g_data_on[AMXR_HAPTICS_FRAMESZ_16BITS] = {
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
static int16_t g_data_off[AMXR_HAPTICS_FRAMESZ_16BITS] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
/****************************************************************************
*
* Audio Mixer getsrc callback function
*
****************************************************************************/
static int16_t *amxr_getsrc(int bytes, void *privdata)
{
int flag;
if(debug_level >= 2)
printk(KERN_DEBUG "%s entry\n", __FUNCTION__);
flag = atomic_read(&g_haptics_flag);
if(debug_level >= 2)
printk(KERN_DEBUG "%s flag=%d\n", __FUNCTION__, flag);
return flag ? g_data_on : g_data_off;
}
/****************************************************************************
*
* timed_output get_time callback function
*
****************************************************************************/
static int haptics_get_time(struct timed_output_dev *dev)
{
int ret = 0;
if(debug_level >= 1)
printk(KERN_DEBUG "%s entry\n", __FUNCTION__);
spin_lock_bh(&g_haptics_lock);
if(g_haptics_off_jiffies && g_haptics_off_jiffies > jiffies)
ret = (g_haptics_off_jiffies - jiffies) * 1000 / HZ;
spin_unlock_bh(&g_haptics_lock);
if(debug_level >= 1)
printk(KERN_DEBUG "%s ret=%d\n", __FUNCTION__, ret);
return ret;
}
/****************************************************************************
*
* timed_output enable callback function
*
****************************************************************************/
static void haptics_enable(struct timed_output_dev *dev, int msec)
{
if(debug_level >= 1)
printk(KERN_DEBUG "%s entry\n", __FUNCTION__);
atomic_set(&g_haptics_flag, 1);
spin_lock_bh(&g_haptics_lock);
g_haptics_off_jiffies = jiffies + msec * HZ / 1000;
spin_unlock_bh(&g_haptics_lock);
if(debug_level >= 1)
printk(KERN_DEBUG "haptics_enable msec=%d haptics_off_jiffies=%lu\n", msec, g_haptics_off_jiffies);
mod_timer(&g_haptics_timer, g_haptics_off_jiffies);
if(debug_level >= 1)
printk(KERN_DEBUG "%s exit\n", __FUNCTION__);
}
/****************************************************************************
*
* timed_output driver interface
*
****************************************************************************/
static struct timed_output_dev haptics_dev = {
.name = "vibrator",
.get_time = haptics_get_time,
.enable = haptics_enable,
};
/****************************************************************************
*
* timer callback function
*
****************************************************************************/
static void on_haptics_timer(unsigned long data)
{
if(debug_level >= 2)
printk(KERN_DEBUG "%s entry\n", __FUNCTION__);
atomic_set(&g_haptics_flag, 0);
spin_lock_bh(&g_haptics_lock);
g_haptics_off_jiffies = 0;
spin_unlock_bh(&g_haptics_lock);
if(debug_level >= 2)
printk(KERN_DEBUG "%s exit\n", __FUNCTION__);
}
/****************************************************************************
*
* Function called to initialize haptics driver.
*
****************************************************************************/
static int __init haptics_probe(struct platform_device *pdev)
{
int rc, rc2;
struct bcm_haptics_data *driver_data = NULL;
if(debug_level >= 1)
printk(KERN_DEBUG "%s: entry\n", __FUNCTION__);
if(!pdev || !pdev->dev.platform_data)
{
printk(KERN_ERR "%s: plaftorm data is not set\n", __FUNCTION__);
return -ENXIO;
}
driver_data = (struct bcm_haptics_data *)pdev->dev.platform_data;
/* init spinlock */
spin_lock_init(&g_haptics_lock);
/* init timer */
init_timer(&g_haptics_timer);
g_haptics_timer.function = on_haptics_timer;
if(debug_level >= 1)
printk(KERN_DEBUG "%s: halaudio_port_name=%s ept_port_name=%s\n", __FUNCTION__, driver_data->halaudio_port_name, driver_data->ept_port_name);
/* register timed_output device */
rc = timed_output_dev_register(&haptics_dev);
if(rc < 0) {
printk(KERN_ERR "%s: failed to register timed_output device. rc=%i\n", __FUNCTION__, rc);
goto do_return;
}
/* register audio mixer */
rc = g_amixer_fd = amxrAllocateClient();
if(g_amixer_fd < 0) {
printk(KERN_ERR "%s: failed to allocate Audio Mixer client. rc=%i\n", __FUNCTION__, rc);
goto do_timed_output_unregister;
}
/* create EPT virtual port */
rc = amxrCreatePort(driver_data->ept_port_name,
&g_amxr_callback, NULL /* privdata */,
0, 0, 0, /* dst */
HALAUDIO_PORT_HZ, 1 /* mono */, AMXR_HAPTICS_FRAMESZ_16BITS * 2, /* size in bytes */ /* src */
&g_ept_port_d);
if(rc) {
printk(KERN_ERR "%s: failed to create EPT port. rc=%i\n", __FUNCTION__, rc);
goto do_free_amxr_client;
}
/* connect port with endpoint */
rc = amxrQueryPortByName(g_amixer_fd, driver_data->halaudio_port_name, &g_hal_port_id);
if(rc) {
printk(KERN_ERR "%s: failed to get port ID for %s. rc=%i\n", __FUNCTION__, driver_data->halaudio_port_name, rc);
goto do_remove_amxr_port;
}
rc = amxrConnect(g_amixer_fd, g_ept_port_d, g_hal_port_id, AMXR_CONNECT_MONO2MONO);
if(rc) {
printk(KERN_ERR "%s: failed to connect Audio Mixer port. rc=%i\n", __FUNCTION__, rc);
goto do_remove_amxr_port;
}
if(debug_level >= 1)
printk(KERN_DEBUG "%s OK\n", __FUNCTION__);
return 0;
do_remove_amxr_port:
rc2 = amxrRemovePort(g_ept_port_d);
if(rc2)
printk(KERN_ERR "%s: failed to remove Audio Mixer Port. rc=%i\n", __FUNCTION__, rc2);
do_free_amxr_client:
rc2 = amxrFreeClient(g_amixer_fd);
if(rc2)
printk(KERN_ERR "%s: failed to free Audio Mixer client. rc=%i\n", __FUNCTION__, rc2);
do_timed_output_unregister:
timed_output_dev_unregister(&haptics_dev);
do_return:
return rc;
}
/****************************************************************************
*
* Function called to remove haptics driver.
*
****************************************************************************/
static int haptics_remove(struct platform_device *pdev)
{
int rc;
if(debug_level >= 1)
printk(KERN_DEBUG "%s\n", __FUNCTION__);
rc = amxrDisconnect(g_amixer_fd, g_ept_port_d, g_hal_port_id);
if(rc)
printk(KERN_ERR "%s: failed to disconnect Audio Mixer port. rc=%i\n", __FUNCTION__, rc);
rc = amxrRemovePort(g_ept_port_d);
if(rc)
printk(KERN_ERR "%s: failed to remove Audio Mixer port. rc=%i\n", __FUNCTION__, rc);
rc = amxrFreeClient(g_amixer_fd);
if(rc)
printk(KERN_ERR "%s: failed to free Audio Mixer client rc=%i\n", __FUNCTION__, rc);
timed_output_dev_unregister(&haptics_dev);
return 0;
}
/****************************************************************************
*
* Data structure which defines the haptics driver
*
****************************************************************************/
static struct platform_driver haptics_driver =
{
.probe = haptics_probe,
.remove = haptics_remove,
.driver =
{
.name = BCM_HAPTICS_DRIVER_NAME,
},
};
/***************************************************************************
*
* Driver initialization called when module loaded by kernel
*
* @return
* 0 Success
* -ve Error code
****************************************************************************/
static int __init haptics_init(void)
{
int rc = platform_driver_register(&haptics_driver);
if(debug_level >= 1)
printk(KERN_DEBUG "%s rc=%i\n", __FUNCTION__, rc);
return rc;
}
/***************************************************************************
*
* Driver destructor routine.
*
****************************************************************************/
static void __exit haptics_exit(void)
{
if(debug_level >= 1)
printk(KERN_DEBUG "%s\n", __FUNCTION__);
platform_driver_unregister(&haptics_driver);
}
module_init(haptics_init);
module_exit(haptics_exit);
MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("Haptics Driver");
MODULE_LICENSE("GPL");
module_param(debug_level, int, 0);
MODULE_PARM_DESC(debug_level, "Debug level: 0, 1, or 2");