blob: 203109e6ac775060ca78ab46011747177424a5d2 [file] [log] [blame]
/*
* Copyright (C) 2012-2014 Motorola, Inc.
*
* 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., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307, USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/rtc.h>
#include <linux/gpio.h>
#include <linux/string.h>
#include <linux/m4sensorhub/MemMapLog.h>
#include <linux/m4sensorhub.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#define M4SENSORHUB_NUM_GPIOS 6
/* --------------- Global Declarations -------------- */
char m4sensorhub_debug;
EXPORT_SYMBOL_GPL(m4sensorhub_debug);
/* ------------ Local Function Prototypes ----------- */
/* -------------- Local Data Structures ------------- */
static struct miscdevice m4sensorhub_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = M4SENSORHUB_DRIVER_NAME,
};
struct init_call {
int(*initcb)(struct init_calldata *);
void *pdata;
struct init_call *next;
};
/* --------------- Local Declarations -------------- */
static struct m4sensorhub_data m4sensorhub_misc_data;
static struct init_call *inithead;
static struct init_call *preflash_head;
static char tcmd_exec_status;
unsigned short force_upgrade;
module_param(force_upgrade, short, 0644);
MODULE_PARM_DESC(force_upgrade, "Force FW download ignoring version check");
unsigned short debug_level;
module_param(debug_level, short, 0644);
MODULE_PARM_DESC(debug_level,
"Set debug level 1 (CRITICAL) to 7 (VERBOSE_DEBUG)");
/* -------------- Global Functions ----------------- */
struct m4sensorhub_data *m4sensorhub_client_get_drvdata(void)
{
return &m4sensorhub_misc_data;
}
EXPORT_SYMBOL_GPL(m4sensorhub_client_get_drvdata);
/* -------------- Local Functions ----------------- */
static ssize_t m4sensorhub_get_dbg(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", m4sensorhub_debug);
}
/* BEGIN BOARD FILE */
/* TODO: replace with request array */
int m4sensorhub_get_current_mode(void)
{
return m4sensorhub_misc_data.mode;
}
EXPORT_SYMBOL_GPL(m4sensorhub_get_current_mode);
int m4sensorhub_set_bootmode(struct m4sensorhub_data *m4sensorhub,
enum m4sensorhub_bootmode bootmode)
{
if (m4sensorhub == NULL) {
pr_err("%s: M4 data is NULL\n", __func__);
return -EINVAL;
}
switch (bootmode) {
case BOOTMODE00:
gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 0);
gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 0);
break;
case BOOTMODE01:
gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 1);
gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 0);
break;
case BOOTMODE10:
gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 0);
gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 1);
break;
case BOOTMODE11:
gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 1);
gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 1);
default:
break;
}
return 0;
}
EXPORT_SYMBOL_GPL(m4sensorhub_set_bootmode);
void m4sensorhub_hw_reset(struct m4sensorhub_data *m4sensorhub)
{
int err = 0;
if (m4sensorhub == NULL) {
pr_err("%s: M4 data is NULL\n", __func__);
err = -ENODATA;
goto m4sensorhub_hw_reset_fail;
} else if (m4sensorhub->i2c_client == NULL) {
pr_err("%s: I2C client is missing\n", __func__);
err = -ENODATA;
goto m4sensorhub_hw_reset_fail;
}
if (m4sensorhub->i2c_client->addr == 0x39) {
err = m4sensorhub_set_bootmode(m4sensorhub, BOOTMODE01);
if (err < 0) {
pr_err("%s: Failed to enter bootmode 01\n", __func__);
goto m4sensorhub_hw_reset_fail;
}
usleep_range(5000, 10000);
gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 0);
usleep_range(10000, 12000);
gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 1);
msleep(400);
} else {
err = m4sensorhub_set_bootmode(m4sensorhub, BOOTMODE00);
if (err < 0) {
pr_err("%s: Failed to enter bootmode 00\n", __func__);
goto m4sensorhub_hw_reset_fail;
}
gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 1);
usleep_range(5000, 10000);
gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 0);
usleep_range(5000, 10000);
gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 1);
}
m4sensorhub_hw_reset_fail:
if (err < 0)
pr_err("%s: Failed with error code %d", __func__, err);
}
EXPORT_SYMBOL_GPL(m4sensorhub_hw_reset);
/* callback from driver to initialize hardware on probe */
static int m4sensorhub_hw_init(struct m4sensorhub_data *m4sensorhub,
struct device_node *node)
{
int gpio;
int err = -EINVAL;
const char *fp = NULL;
if (m4sensorhub == NULL) {
pr_err("%s: M4 data is NULL\n", __func__);
err = -ENODATA;
goto error;
} else if (node == NULL) {
pr_err("%s: Device node is missing\n", __func__);
err = -ENODATA;
goto error;
}
of_property_read_string(node, "mot,fw-filename", &fp);
if (fp == NULL) {
pr_err("%s: Missing M4 sensorhub firmware filename\n",
__func__);
err = -EINVAL;
goto error;
}
m4sensorhub->filename = (char *)fp;
gpio = of_get_named_gpio_flags(node, "mot,irq-gpio", 0, NULL);
err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-intr");
if (err) {
pr_err("Failed acquiring M4 Sensor Hub IRQ GPIO-%d (%d)\n",
gpio, err);
goto error;
}
gpio_direction_input(gpio);
m4sensorhub->hwconfig.irq_gpio = gpio;
gpio = of_get_named_gpio_flags(node, "mot,reset-gpio", 0, NULL);
err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-reset");
if (err) {
pr_err("Failed acquiring M4 Sensor Hub Reset GPIO-%d (%d)\n",
gpio, err);
goto error_reset;
}
/* hold M4 reset till M4 load firmware procduce starts
* this is needed for snowflake touch determination
*/
gpio_direction_output(gpio, 0);
m4sensorhub->hwconfig.reset_gpio = gpio;
gpio = of_get_named_gpio_flags(node, "mot,wake-gpio", 0, NULL);
err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-wake");
if (err) {
pr_err("Failed acquiring M4 Sensor Hub Wake GPIO-%d (%d)\n",
gpio, err);
goto error_wake;
}
gpio_direction_output(gpio, 0);
m4sensorhub->hwconfig.wake_gpio = gpio;
gpio = of_get_named_gpio_flags(node, "mot,boot0-gpio", 0, NULL);
err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-boot0");
if (err) {
pr_err("Failed acquiring M4 Sensor Hub Boot0 GPIO-%d (%d)\n",
gpio, err);
goto error_boot0;
}
gpio_direction_output(gpio, 0);
m4sensorhub->hwconfig.boot0_gpio = gpio;
gpio = of_get_named_gpio_flags(node, "mot,boot1-gpio", 0, NULL);
err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-boot1");
if (err) {
pr_err("Failed acquiring M4 Sensor Hub Boot1 GPIO-%d (%d)\n",
gpio, err);
goto error_boot1;
}
gpio_direction_output(gpio, 0);
m4sensorhub->hwconfig.boot1_gpio = gpio;
gpio = of_get_named_gpio_flags(node, "mot,enable-gpio", 0, NULL);
err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-enable");
if (err) {
pr_err("Failed acquiring M4 Sensor Hub Enable GPIO-%d (%d)\n",
gpio, err);
goto error_enable;
}
gpio_direction_output(gpio, 0);
m4sensorhub->hwconfig.mpu_9150_en_gpio = gpio;
return 0;
error_enable:
gpio_free(m4sensorhub->hwconfig.boot1_gpio);
m4sensorhub->hwconfig.boot1_gpio = -1;
error_boot1:
gpio_free(m4sensorhub->hwconfig.boot0_gpio);
m4sensorhub->hwconfig.boot0_gpio = -1;
error_boot0:
gpio_free(m4sensorhub->hwconfig.wake_gpio);
m4sensorhub->hwconfig.wake_gpio = -1;
error_wake:
gpio_free(m4sensorhub->hwconfig.reset_gpio);
m4sensorhub->hwconfig.reset_gpio = -1;
error_reset:
gpio_free(m4sensorhub->hwconfig.irq_gpio);
m4sensorhub->hwconfig.irq_gpio = -1;
error:
m4sensorhub->filename = NULL;
return err;
}
/* callback from driver to free hardware on shutdown */
static void m4sensorhub_hw_free(struct m4sensorhub_data *m4sensorhub)
{
if (m4sensorhub == NULL) {
pr_err("%s: M4 data is NULL\n", __func__);
return;
}
if (m4sensorhub->hwconfig.irq_gpio >= 0) {
gpio_free(m4sensorhub->hwconfig.irq_gpio);
m4sensorhub->hwconfig.irq_gpio = -1;
}
if (m4sensorhub->hwconfig.reset_gpio >= 0) {
gpio_free(m4sensorhub->hwconfig.reset_gpio);
m4sensorhub->hwconfig.reset_gpio = -1;
}
if (m4sensorhub->hwconfig.wake_gpio >= 0) {
gpio_free(m4sensorhub->hwconfig.wake_gpio);
m4sensorhub->hwconfig.wake_gpio = -1;
}
if (m4sensorhub->hwconfig.boot0_gpio >= 0) {
gpio_free(m4sensorhub->hwconfig.boot0_gpio);
m4sensorhub->hwconfig.boot0_gpio = -1;
}
if (m4sensorhub->hwconfig.boot1_gpio >= 0) {
gpio_free(m4sensorhub->hwconfig.boot1_gpio);
m4sensorhub->hwconfig.boot1_gpio = -1;
}
if (m4sensorhub->hwconfig.mpu_9150_en_gpio >= 0) {
gpio_free(m4sensorhub->hwconfig.mpu_9150_en_gpio);
m4sensorhub->hwconfig.mpu_9150_en_gpio = -1;
}
m4sensorhub->filename = NULL;
}
int m4sensorhub_register_initcall(int(*initfunc)(struct init_calldata *),
void *pdata)
{
struct init_call *inc = NULL;
inc = kzalloc(sizeof(struct init_call), GFP_KERNEL);
if (inc == NULL) {
KDEBUG(M4SH_ERROR, "%s: Unable to allocate init call mem\n",
__func__);
return -ENOMEM;
}
inc->initcb = initfunc;
inc->pdata = pdata;
/* add it to the list */
if (inithead == NULL)
inc->next = NULL;
else
inc->next = inithead;
inithead = inc;
return 0;
}
EXPORT_SYMBOL_GPL(m4sensorhub_register_initcall);
void m4sensorhub_unregister_initcall(int(*initfunc)(struct init_calldata *))
{
struct init_call *node = inithead;
struct init_call *prev;
for (node = inithead, prev = NULL;
node != NULL;
prev = node, node = node->next) {
if (node->initcb == initfunc) {
/* remove this node */
if (node == inithead)
inithead = node->next;
else
prev->next = node->next;
kfree(node);
}
}
}
EXPORT_SYMBOL_GPL(m4sensorhub_unregister_initcall);
int m4sensorhub_register_preflash_callback(
int(*initfunc)(struct init_calldata *), void *pdata)
{
struct init_call *inc = NULL;
inc = kzalloc(sizeof(struct init_call), GFP_KERNEL);
if (inc == NULL) {
KDEBUG(M4SH_ERROR, "%s: Unable to allocate init call mem\n",
__func__);
return -ENOMEM;
}
inc->initcb = initfunc;
inc->pdata = pdata;
/* add it to the list */
if (preflash_head == NULL)
inc->next = NULL;
else
inc->next = preflash_head;
preflash_head = inc;
return 0;
}
EXPORT_SYMBOL_GPL(m4sensorhub_register_preflash_callback);
void m4sensorhub_unregister_preflash_callback(
int(*initfunc)(struct init_calldata *))
{
struct init_call *node = preflash_head;
struct init_call *prev;
for (node = preflash_head, prev = NULL;
node != NULL;
prev = node, node = node->next) {
if (node->initcb == initfunc) {
/* remove this node */
if (node == preflash_head)
preflash_head = node->next;
else
prev->next = node->next;
kfree(node);
}
}
}
EXPORT_SYMBOL_GPL(m4sensorhub_unregister_preflash_callback);
void m4sensorhub_call_preflash_callbacks(void)
{
struct init_calldata arg;
struct init_call *inc = NULL;
struct init_call *prev = NULL;
int err = 0;
/* Call any registered preflash callbacks */
inc = preflash_head;
arg.p_m4sensorhub_data = &m4sensorhub_misc_data;
prev = NULL;
while (inc) {
arg.p_data = inc->pdata;
err = inc->initcb(&arg);
if (err < 0) {
KDEBUG(M4SH_ERROR,
"%s: Callback failed with error code %d\n",
__func__, err);
}
prev = inc;
inc = inc->next;
kfree(prev);
}
return;
}
EXPORT_SYMBOL_GPL(m4sensorhub_call_preflash_callbacks);
bool m4sensorhub_preflash_callbacks_exist(void)
{
if (preflash_head != NULL)
return true;
else
return false;
}
EXPORT_SYMBOL_GPL(m4sensorhub_preflash_callbacks_exist);
/* END BOARD FILE FUNCTIONS */
/* Downloads m4 firmware and also initializes all m4 drivers */
static void m4sensorhub_initialize(const struct firmware *firmware,
void *context)
{
int err = 0;
struct init_call *inc, *prev;
struct init_calldata arg;
if (firmware == NULL) {
KDEBUG(M4SH_ERROR, "%s: No firmware data recieved\n",
__func__);
return;
}
/* Initiate M4 firmware download */
KDEBUG(M4SH_CRITICAL, "%s: Starting M4 download %s = %d\n",
__func__, "with force_upgrade", force_upgrade);
if (m4sensorhub_misc_data.i2c_client->addr == 0x39)
err = m4sensorhub_401_load_firmware(&m4sensorhub_misc_data,
force_upgrade, firmware);
else
err = m4sensorhub_load_firmware(&m4sensorhub_misc_data,
force_upgrade, firmware);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: %s = %d\n",
__func__, "Failed to load M4 firmware", err);
/* Since download failed, return M4 to boot mode */
m4sensorhub_hw_reset(&m4sensorhub_misc_data);
return;
}
err = m4sensorhub_test_m4_reboot(&m4sensorhub_misc_data, false);
if (err < 0) {
/* Getting here is unlikely because failures normally panic */
KDEBUG(M4SH_ERROR, "%s: Testing M4 reboot failed.\n", __func__);
return;
}
err = m4sensorhub_irq_init(&m4sensorhub_misc_data);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: m4sensorhub irq init failed (err=%d)\n",
__func__, err);
return;
}
err = m4sensorhub_extern_init(&m4sensorhub_misc_data);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: Extern init failed.\n", __func__);
return;
}
/* Initialize all the m4 drivers */
inc = inithead;
arg.p_m4sensorhub_data = &m4sensorhub_misc_data;
prev = NULL;
while (inc) {
arg.p_data = inc->pdata;
err = inc->initcb(&arg);
if (err < 0) {
KDEBUG(M4SH_ERROR,
"%s: Callback failed with error code %d\n",
__func__, err);
}
prev = inc;
inc = inc->next;
kfree(prev);
}
/* Now that all drivers are kicked off, flag this
as our normal mode of operation */
m4sensorhub_misc_data.mode = NORMALMODE;
}
static ssize_t m4sensorhub_set_dbg(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned long debug;
if ((kstrtol(buf, 10, &debug) < 0) ||
(debug < M4SH_NODEBUG) || (debug > M4SH_VERBOSE_DEBUG))
return -EINVAL;
m4sensorhub_debug = debug;
KDEBUG(M4SH_CRITICAL, "%s: M4 Sensor Hub debug level = %d\n",
__func__, m4sensorhub_debug);
return count;
}
static DEVICE_ATTR(debug_level, S_IRUSR|S_IWUSR, m4sensorhub_get_dbg,
m4sensorhub_set_dbg);
static ssize_t m4sensorhub_get_loglevel(struct device *dev,
struct device_attribute *attr, char *buf)
{
uint32_t logenable[LOG_EN_SIZE], len, i;
m4sensorhub_reg_read(&m4sensorhub_misc_data,
M4SH_REG_LOG_LOGENABLE, (char *)&logenable);
len = sprintf(buf, "M4 log levels = 0x");
for (i = 0; i < LOG_EN_SIZE; i++)
len += sprintf(buf+len, "%08x", logenable[i]);
KDEBUG(M4SH_INFO, "%s\n", buf);
return snprintf(buf, PAGE_SIZE, "%s\n", buf);
}
void m4sensorhub_update_loglevels(int8_t *tag, int8_t *level,
uint32_t *log_level)
{
uint32_t i;
int32_t levelindex = -1;
int32_t tagindex = -1;
uint32_t mask;
int32_t logenableindex;
int32_t en = LOG_TAGS_PER_ENABLE;
int32_t b = LOG_NO_OF_BITS_PER_TAG;
for (i = 0; i < LOG_LEVELS_MAX; i++) {
if (strcmp(acLogLevels[i], level) == 0) {
levelindex = i;
break;
}
}
for (i = 0; i < LOG_MAX; i++) {
if (strcmp(acLogTags[i], tag) == 0) {
tagindex = i;
break;
}
}
if ((tagindex == -1) || (levelindex == -1))
return;
logenableindex = tagindex/LOG_TAGS_PER_ENABLE;
/*Clear the revelant bits*/
mask = LOG_TAG_MASK;
*(log_level+logenableindex) &= ~(mask << ((tagindex % en) * b));
/*set debug level for the relevant bits*/
*(log_level+logenableindex) |= (levelindex << ((tagindex % en) * b));
KDEBUG(M4SH_DEBUG, "New M4 log levels = ");
for (i = 0; i < LOG_EN_SIZE; i++)
KDEBUG(M4SH_DEBUG, "enable %d = 0x%08x ", i, *(log_level+i));
KDEBUG(M4SH_DEBUG, "\n");
}
/* Usage: adb shell into the directory of sysinterface log_level and
echo LOG_ACCEL=LOG_DEGUB,LOG_POWER=LOG_ERROR > log_level */
static ssize_t m4sensorhub_set_loglevel(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
uint32_t cur_loglevels[LOG_EN_SIZE];
char *tag, *level;
char **logbuf = (char **) &buf;
m4sensorhub_reg_read(&m4sensorhub_misc_data,
M4SH_REG_LOG_LOGENABLE, (char *)cur_loglevels);
while (1) {
tag = strsep(logbuf, "=,\n ");
if ((tag == NULL) || (logbuf == NULL))
break;
level = strsep(logbuf, "=,\n ");
if ((level == NULL) || (logbuf == NULL))
break;
m4sensorhub_update_loglevels(tag, level,
(uint32_t *)cur_loglevels);
}
m4sensorhub_reg_write(&m4sensorhub_misc_data,
M4SH_REG_LOG_LOGENABLE, (char *)cur_loglevels,
m4sh_no_mask);
return count;
}
static DEVICE_ATTR(log_level, S_IRUSR|S_IWUSR, m4sensorhub_get_loglevel,
m4sensorhub_set_loglevel);
static ssize_t m4sensorhub_get_tcmd_response(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (tcmd_exec_status)
return sprintf(buf, "TCMD execution passed\n");
else
return sprintf(buf, "TCMD execution failed\n");
}
static ssize_t m4sensorhub_execute_tcmd(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int opcode, subopcode;
int ret, tcmd_resp_len, i;
char tcmd_buf[20];
tcmd_exec_status = 0;
sscanf(buf, "0x%x 0x%x 0x%x", &opcode, &subopcode, &tcmd_resp_len);
tcmd_buf[0] = M4SH_TYPE_TCMD;
tcmd_buf[1] = (opcode & 0xFF);
tcmd_buf[2] = (subopcode & 0xFF);
ret = m4sensorhub_i2c_write_read(&m4sensorhub_misc_data,
tcmd_buf, 3, tcmd_resp_len);
if (ret < 0) {
KDEBUG(M4SH_ERROR, "m4sensorhub tcmd i2c failed\n");
return ret;
}
if (ret != tcmd_resp_len) {
KDEBUG(M4SH_ERROR, "m4sensorhub tcmd wrong num bytes read\n");
return -EBADE;
}
for (i = 0; i < tcmd_resp_len; i++)
KDEBUG(M4SH_INFO, "%#x ", (unsigned char)tcmd_buf[i]);
KDEBUG(M4SH_INFO, "\n");
if (tcmd_buf[0] == 0x00)
tcmd_exec_status = 1;
return count;
}
static DEVICE_ATTR(tcmd, S_IRUSR|S_IWUSR, m4sensorhub_get_tcmd_response,
m4sensorhub_execute_tcmd);
static ssize_t m4sensorhub_get_download_status(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n",
m4sensorhub_misc_data.mode == NORMALMODE ? "1" : "0");
}
static DEVICE_ATTR(download_status, S_IRUGO,
m4sensorhub_get_download_status, NULL);
static ssize_t m4sensorhub_get_firmware_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%#hx\n",
m4sensorhub_misc_data.mode == NORMALMODE ?
m4sensorhub_misc_data.fw_version : 0xFFFF);
}
static DEVICE_ATTR(firmware_version, S_IRUGO,
m4sensorhub_get_firmware_version, NULL);
static ssize_t m4sensorhub_disable_interrupts(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret;
ret = m4sensorhub_irq_disable_all(&m4sensorhub_misc_data);
if (ret < 0) {
KDEBUG(M4SH_ERROR, "%s: Unable to disable all m4 interrupts\n",
__func__);
return ret;
}
return count;
}
static DEVICE_ATTR(disable_interrupts, S_IWUSR, NULL,
m4sensorhub_disable_interrupts);
/*
This sysfs is to be used only to disable power management. It
cannot be used to enable power management
*/
static ssize_t m4sensorhub_disable_powermanagement(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
char enable = 0;
return m4sensorhub_reg_write_1byte(&m4sensorhub_misc_data,
M4SH_REG_POWER_ENABLE, enable, 0xFF);
}
static DEVICE_ATTR(disable_powermanagement, S_IWUSR, NULL,
m4sensorhub_disable_powermanagement);
/*
This sysfs can be used to read/write m4 registers
This is provided to help in debugging m4 issues
Example to read an m4 register interrupt enable1( bank = 0x02, regaddr = 0x0B)
echo R 0x02 0x0B 0x01 > raw_i2c
R -- indicates read transaction
0x02 -- reg bank address
0x0B -- register address
0x01 -- number of bytes to be read
The result of the read will be printed out in kernel logs
To write to a m4 regiser interrupt enable1( bank = 0x02, regaddr = 0x0B)
echo W 0x02 0x0B 0xFF > raw_i2c
W -- indicates write transaction
0x02 -- reg bank address
0x0b -- register addr
0xFF -- data to be written to this address
Note:
Write trasactions have a limitation to be able to write only 1 byte
*/
static ssize_t m4sensorhub_raw_i2c(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned char buffer[20];
unsigned int bank, addr, len;
unsigned char operation;
int ret, i;
sscanf(buf, "%c 0x%x 0x%x 0x%x", &operation, &bank, &addr, &len);
if (operation == 'R') {
KDEBUG(M4SH_INFO, "Reading %d bytes from bank 0x%x add 0x%x\n",
len, bank, addr);
buffer[0] = (bank & 0xFF);
buffer[1] = (addr & 0xFF);
ret = m4sensorhub_i2c_write_read(&m4sensorhub_misc_data,
buffer, 2, len);
if (ret != len) {
KDEBUG(M4SH_ERROR,
"Failed to read from bank 0x%x addr 0x%x",
bank, addr);
} else {
for (i = 0; i < len; i++)
KDEBUG(M4SH_INFO, "0x%x ",
(unsigned char)buffer[i]);
KDEBUG(M4SH_INFO, "\n");
}
} else if (operation == 'W') {
KDEBUG(M4SH_INFO, "Writing 0x%x to bank 0x%x addr 0x%x\n",
len, bank, addr);
buffer[0] = (bank & 0xFF);
buffer[1] = (addr & 0xFF);
buffer[2] = (len & 0xFF);
ret = m4sensorhub_i2c_write_read(&m4sensorhub_misc_data,
buffer, 3, 0);
if (ret != 1) {
KDEBUG(M4SH_ERROR,
"Failed to write 0x%x from bank 0x%x addr 0x%x",
len, bank, addr);
}
} else {
KDEBUG(M4SH_ERROR, "Unknown operation = %c", operation);
}
return count;
}
static DEVICE_ATTR(raw_i2c, S_IWUSR, NULL, m4sensorhub_raw_i2c);
static struct attribute *m4sensorhub_control_attributes[] = {
&dev_attr_tcmd.attr,
&dev_attr_log_level.attr,
&dev_attr_debug_level.attr,
&dev_attr_firmware_version.attr,
&dev_attr_download_status.attr,
&dev_attr_disable_interrupts.attr,
&dev_attr_disable_powermanagement.attr,
&dev_attr_raw_i2c.attr,
NULL
};
static const struct attribute_group m4sensorhub_control_group = {
.attrs = m4sensorhub_control_attributes,
};
static int m4sensorhub_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct m4sensorhub_data *m4sensorhub = &m4sensorhub_misc_data;
struct device_node *node = client->dev.of_node;
int err = -EINVAL;
/* Set debug based on module argument if set, otherwise use
default logging rate based on build type */
if (debug_level)
m4sensorhub_debug = debug_level;
else {
#ifdef CONFIG_DEBUG_FS
/* engineering build */
m4sensorhub_debug = M4SH_INFO;
#else
/* user/userdebug builds */
m4sensorhub_debug = M4SH_ERROR;
#endif
}
/* Enabling detailed level M4 logs for all builds*/
m4sensorhub_debug = M4SH_INFO;
KDEBUG(M4SH_ERROR, "%s: Initializing M4 Sensor Hub debug=%d\n",
__func__, m4sensorhub_debug);
m4sensorhub->mode = UNINITIALIZED;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
KDEBUG(M4SH_ERROR, "%s: client not i2c capable\n", __func__);
err = -ENODEV;
goto err_unload;
}
/* link m4sensorhub to i2c_client, hw_init uses i2c_client */
m4sensorhub->i2c_client = client;
err = m4sensorhub_hw_init(m4sensorhub, node);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: hw_init failed!", __func__);
goto done;
}
/* link i2c_client to m4sensorhub */
i2c_set_clientdata(client, m4sensorhub);
err = misc_register(&m4sensorhub_misc_device);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: misc_register failed: %d\n",
__func__, err);
goto err_hw_free;
}
err = sysfs_create_group(&client->dev.kobj, &m4sensorhub_control_group);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: Failed to create sysfs group\n",
__func__);
goto err_deregister;
}
if (m4sensorhub->hwconfig.irq_gpio >= 0)
client->irq = gpio_to_irq(m4sensorhub->hwconfig.irq_gpio);
else {
KDEBUG(M4SH_ERROR, "%s: No IRQ configured\n", __func__);
err = -ENODEV;
goto err_unregister_control_group;
}
err = m4sensorhub_panic_init(m4sensorhub);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: Panic init failed\n", __func__);
goto err_reg_shutdown;
}
err = request_firmware_nowait(THIS_MODULE,
FW_ACTION_HOTPLUG, m4sensorhub->filename,
&(m4sensorhub->i2c_client->dev),
GFP_KERNEL, m4sensorhub,
m4sensorhub_initialize);
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: request_firmware_nowait failed: %d\n",
__func__, err);
goto err_panic_shutdown;
}
KDEBUG(M4SH_NOTICE, "Registered M4 Sensor Hub\n");
goto done;
err_panic_shutdown:
m4sensorhub_panic_shutdown(m4sensorhub);
err_reg_shutdown:
m4sensorhub_reg_shutdown(m4sensorhub);
err_unregister_control_group:
sysfs_remove_group(&client->dev.kobj, &m4sensorhub_control_group);
err_deregister:
misc_deregister(&m4sensorhub_misc_device);
err_hw_free:
m4sensorhub->i2c_client = NULL;
i2c_set_clientdata(client, NULL);
m4sensorhub_hw_free(m4sensorhub);
m4sensorhub = NULL;
err_unload:
done:
if (err < 0) {
KDEBUG(M4SH_ERROR, "%s: Probe failed with error code %d\n",
__func__, err);
}
return err;
}
static int __exit m4sensorhub_remove(struct i2c_client *client)
{
struct m4sensorhub_data *m4sensorhub = i2c_get_clientdata(client);
KDEBUG(M4SH_INFO, "Removing M4 Sensor Hub Driver\n");
if (m4sensorhub == NULL)
return 0;
m4sensorhub_irq_shutdown(m4sensorhub);
m4sensorhub_panic_shutdown(m4sensorhub);
m4sensorhub_reg_shutdown(m4sensorhub);
sysfs_remove_group(&client->dev.kobj, &m4sensorhub_control_group);
m4sensorhub_hw_reset(m4sensorhub);
misc_deregister(&m4sensorhub_misc_device);
m4sensorhub->i2c_client = NULL;
i2c_set_clientdata(client, NULL);
m4sensorhub_hw_free(m4sensorhub);
m4sensorhub = NULL;
return 0;
}
#ifdef CONFIG_PM
static int m4sensorhub_suspend(struct i2c_client *client, pm_message_t mesg)
{
KDEBUG(M4SH_DEBUG, "%s\n", __func__);
m4sensorhub_misc_data.irq_dbg.suspend = 1;
return 0;
}
static int m4sensorhub_resume(struct i2c_client *client)
{
KDEBUG(M4SH_DEBUG, "%s\n", __func__);
m4sensorhub_misc_data.irq_dbg.suspend = 0;
return 0;
}
#endif /* CONFIG_PM */
static const struct of_device_id of_m4sensorhub_match[] = {
{ .compatible = "mot,m4sensorhub", },
{},
};
static const struct i2c_device_id m4sensorhub_id[] = {
{M4SENSORHUB_DRIVER_NAME, 0},
{ },
};
MODULE_DEVICE_TABLE(i2c, m4sensorhub_id);
static struct i2c_driver m4sensorhub_driver = {
.driver = {
.name = M4SENSORHUB_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_m4sensorhub_match),
},
.probe = m4sensorhub_probe,
.remove = __exit_p(m4sensorhub_remove),
#ifdef CONFIG_PM
.suspend = m4sensorhub_suspend,
.resume = m4sensorhub_resume,
#endif /* CONFIG_PM */
.id_table = m4sensorhub_id,
};
static int __init m4sensorhub_init(void)
{
return i2c_add_driver(&m4sensorhub_driver);
}
static void __exit m4sensorhub_exit(void)
{
i2c_del_driver(&m4sensorhub_driver);
return;
}
module_init(m4sensorhub_init);
module_exit(m4sensorhub_exit);
MODULE_ALIAS("platform:m4sensorhub");
MODULE_DESCRIPTION("M4 Sensor Hub driver");
MODULE_AUTHOR("Motorola");
MODULE_LICENSE("GPL");