aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc')
-rwxr-xr-x[-rw-r--r--]drivers/misc/Kconfig86
-rwxr-xr-x[-rw-r--r--]drivers/misc/Makefile10
-rw-r--r--drivers/misc/ak8973-reg.h47
-rw-r--r--drivers/misc/ak8973.c407
-rw-r--r--drivers/misc/akm8975.c732
-rw-r--r--drivers/misc/apanic.c606
-rwxr-xr-xdrivers/misc/fsa9480.c736
-rw-r--r--drivers/misc/kr3dm.c508
-rw-r--r--drivers/misc/kr3dm_reg.h169
-rwxr-xr-xdrivers/misc/orientation.c514
-rwxr-xr-xdrivers/misc/pn544.c364
-rw-r--r--drivers/misc/samsung_modemctl/Makefile5
-rwxr-xr-xdrivers/misc/samsung_modemctl/dpram/Makefile1
-rwxr-xr-xdrivers/misc/samsung_modemctl/dpram/dpram.c2513
-rw-r--r--drivers/misc/samsung_modemctl/dpram/dpram.h189
-rw-r--r--drivers/misc/samsung_modemctl/dpram/modemctl.h38
-rw-r--r--drivers/misc/samsung_modemctl/dpram/onedram.h52
-rw-r--r--drivers/misc/samsung_modemctl/modem_ctl.c1212
-rw-r--r--drivers/misc/samsung_modemctl/modem_ctl.h45
-rw-r--r--drivers/misc/samsung_modemctl/modem_ctl_p.h318
-rwxr-xr-xdrivers/misc/samsung_modemctl/modem_ctl_recovery.h85
-rw-r--r--drivers/misc/samsung_modemctl/modem_dbg.c211
-rw-r--r--drivers/misc/samsung_modemctl/modem_io.c724
-rwxr-xr-xdrivers/misc/samsung_modemctl/modemctl/Makefile1
-rw-r--r--drivers/misc/samsung_modemctl/modemctl/modemctl.c875
-rw-r--r--drivers/misc/samsung_modemctl/modemctl/modemctl.h39
-rwxr-xr-xdrivers/misc/samsung_modemctl/onedram/Makefile2
-rw-r--r--drivers/misc/samsung_modemctl/onedram/onedram.c949
-rw-r--r--drivers/misc/samsung_modemctl/onedram/onedram.h54
-rwxr-xr-xdrivers/misc/samsung_modemctl/svnet/Makefile6
-rw-r--r--drivers/misc/samsung_modemctl/svnet/main.c866
-rw-r--r--drivers/misc/samsung_modemctl/svnet/main.h7
-rw-r--r--drivers/misc/samsung_modemctl/svnet/pdp.c110
-rw-r--r--drivers/misc/samsung_modemctl/svnet/pdp.h34
-rw-r--r--drivers/misc/samsung_modemctl/svnet/sipc.h64
-rw-r--r--drivers/misc/samsung_modemctl/svnet/sipc4.c2069
-rw-r--r--drivers/misc/samsung_modemctl/svnet/sipc4.h252
-rwxr-xr-xdrivers/misc/sec_jack.c545
-rw-r--r--drivers/misc/uid_stat.c156
-rw-r--r--drivers/misc/wl127x-rfkill.c121
40 files changed, 15722 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 56c05ef..afdc6bd 100644..100755
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -391,6 +391,29 @@ config HMC6352
This driver provides support for the Honeywell HMC6352 compass,
providing configuration and heading data via sysfs.
+config SENSORS_AK8973
+ tristate "AK8973 magnetometer support"
+ default n
+ depends on I2C
+ help
+ If you say yes here you get support for Asahi Kasei's
+ orientation sensor AK8973.
+
+config SENSORS_AK8975
+ tristate "AK8975 compass support"
+ default n
+ depends on I2C
+ help
+ If you say yes here you get support for Asahi Kasei's
+ orientation sensor AK8975.
+
+config SENSORS_KR3DM
+ tristate "KR3DM acceleration sensor support"
+ depends on I2C
+ default n
+ help
+ Driver for STMicro KR3DM accelerometer - digital motion sensor.
+
config EP93XX_PWM
tristate "EP93xx PWM support"
depends on ARCH_EP93XX
@@ -434,6 +457,10 @@ config TI_DAC7512
This driver can also be built as a module. If so, the module
will be called ti_dac7512.
+config UID_STAT
+ bool "UID based statistics tracking exported to /proc/uid_stat"
+ default n
+
config VMWARE_BALLOON
tristate "VMware Balloon Driver"
depends on X86
@@ -490,6 +517,47 @@ config PCH_PHUB
To compile this driver as a module, choose M here: the module will
be called pch_phub.
+config WL127X_RFKILL
+ tristate "Bluetooth power control driver for TI wl127x"
+ depends on RFKILL
+ default n
+ ---help---
+ Creates an rfkill entry in sysfs for power control of Bluetooth
+ TI wl127x chips.
+
+config APANIC
+ bool "Android kernel panic diagnostics driver"
+ default n
+ ---help---
+ Driver which handles kernel panics and attempts to write
+ critical debugging data to flash.
+
+config APANIC_PLABEL
+ string "Android panic dump flash partition label"
+ depends on APANIC
+ default "kpanic"
+ ---help---
+ If your platform uses a different flash partition label for storing
+ crashdumps, enter it here.
+
+config SAMSUNG_JACK
+ bool "3.5MM ear jack driver for Samsung devices"
+ depends on INPUT
+ default n
+ ---help---
+ This is 3.5MM ear jack driver for Samsung devices.
+
+ If unsure, say N.
+
+config USB_SWITCH_FSA9480
+ tristate "FSA9480 USB Switch"
+ depends on I2C
+ help
+ The FSA9480 is a USB port accessory detector and switch.
+ The FSA9480 is fully controlled using I2C and enables USB data,
+ stereo and mono audio, video, microphone and UART data to use
+ a common connector port.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
@@ -498,4 +566,22 @@ source "drivers/misc/ti-st/Kconfig"
source "drivers/misc/lis3lv02d/Kconfig"
source "drivers/misc/carma/Kconfig"
+config SAMSUNG_MODEMCTL
+ bool "Samsung Modem Control/IO Driver"
+
+config MODEM_HAS_CRAPPY_BOOTLOADER
+ bool "Samsung Modem Control/IO Driver Bootloader is crap"
+ depends on SAMSUNG_MODEMCTL
+ help
+ If you use the Samsung Modem Control driver on the older
+ SGS devices the bootloader won't tell you that it finished
+ loading. This is a workaround for that, it only waits some
+ time and tries to to continue.
+
+config PN544
+ bool "NXP PN544 NFC Controller Driver"
+ default n
+ help
+ NXP PN544 Near Field Communication controller support.
+
endif # MISC_DEVICES
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 5f03172..898516f 100644..100755
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o
obj-$(CONFIG_DS1682) += ds1682.o
obj-$(CONFIG_TI_DAC7512) += ti_dac7512.o
+obj-$(CONFIG_UID_STAT) += uid_stat.o
obj-$(CONFIG_C2PORT) += c2port/
obj-$(CONFIG_IWMC3200TOP) += iwmc3200top/
obj-$(CONFIG_HMC6352) += hmc6352.o
@@ -46,3 +47,12 @@ obj-y += ti-st/
obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o
obj-y += lis3lv02d/
obj-y += carma/
+obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o
+obj-$(CONFIG_APANIC) += apanic.o
+obj-$(CONFIG_SENSORS_AK8973) += ak8973.o
+obj-$(CONFIG_SENSORS_AK8975) += akm8975.o
+obj-$(CONFIG_SENSORS_KR3DM) += kr3dm.o
+obj-$(CONFIG_PN544) += pn544.o
+obj-$(CONFIG_SAMSUNG_JACK) += sec_jack.o
+obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
+obj-$(CONFIG_SAMSUNG_MODEMCTL) += samsung_modemctl/
diff --git a/drivers/misc/ak8973-reg.h b/drivers/misc/ak8973-reg.h
new file mode 100644
index 0000000..1582076
--- /dev/null
+++ b/drivers/misc/ak8973-reg.h
@@ -0,0 +1,47 @@
+/* linux/drivers/misc/ak8973-reg.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * 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.
+*/
+#ifndef __AK8973_REG__
+#define __AK8983_REG__
+
+/* Compass device dependent definition */
+#define AK8973_MODE_MEASURE 0x00 /* Starts measurement. */
+#define AK8973_MODE_E2P_READ 0x02 /* E2P access mode (read). */
+#define AK8973_MODE_POWERDOWN 0x03 /* Power down mode */
+
+/* Rx buffer size. i.e ST,TMPS,H1X,H1Y,H1Z*/
+#define SENSOR_DATA_SIZE 5
+
+/* Read/Write buffer size.*/
+#define RWBUF_SIZE 16
+
+/* AK8973 register address */
+#define AK8973_REG_ST 0xC0
+#define AK8973_REG_TMPS 0xC1
+#define AK8973_REG_H1X 0xC2
+#define AK8973_REG_H1Y 0xC3
+#define AK8973_REG_H1Z 0xC4
+
+#define AK8973_REG_MS1 0xE0
+#define AK8973_REG_HXDA 0xE1
+#define AK8973_REG_HYDA 0xE2
+#define AK8973_REG_HZDA 0xE3
+#define AK8973_REG_HXGA 0xE4
+#define AK8973_REG_HYGA 0xE5
+#define AK8973_REG_HZGA 0xE6
+
+#define AK8973_EEP_ETS 0x62
+#define AK8973_EEP_EVIR 0x63
+#define AK8973_EEP_EIHE 0x64
+#define AK8973_EEP_ETST 0x65
+#define AK8973_EEP_EHXGA 0x66
+#define AK8973_EEP_EHYGA 0x67
+#define AK8973_EEP_EHZGA 0x68
+
+#endif /* __AK8983_REG__ */
diff --git a/drivers/misc/ak8973.c b/drivers/misc/ak8973.c
new file mode 100644
index 0000000..967a707
--- /dev/null
+++ b/drivers/misc/ak8973.c
@@ -0,0 +1,407 @@
+/*
+ * ak8973.c - ak8973 compass driver
+ *
+ * Copyright (C) 2008-2009 HTC Corporation.
+ * Author: viral wang <viralwang@gmail.com>
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/i2c/ak8973.h>
+#include <linux/completion.h>
+#include "ak8973-reg.h"
+
+#define AK8973DRV_DATA_DBG 0
+
+struct akm8973_data {
+ struct i2c_client *this_client;
+ struct akm8973_platform_data *pdata;
+ struct mutex lock;
+ struct miscdevice akmd_device;
+ int irq;
+ struct completion data_ready;
+ wait_queue_head_t state_wq;
+};
+
+static s32 akm8973_ecs_set_mode_power_down(struct akm8973_data *akm)
+{
+ s32 ret;
+
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8973_REG_MS1, AK8973_MODE_POWERDOWN);
+ if (ret < 0)
+ return ret;
+
+ return i2c_smbus_read_byte_data(akm->this_client, AK8973_REG_TMPS);
+}
+
+static int akm8973_ecs_set_mode(struct akm8973_data *akm, char mode)
+{
+ s32 ret;
+
+ switch (mode) {
+ case AK8973_MODE_MEASURE:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8973_REG_MS1, AK8973_MODE_MEASURE);
+ break;
+ case AK8973_MODE_E2P_READ:
+ ret = i2c_smbus_write_byte_data(akm->this_client,
+ AK8973_REG_MS1, AK8973_MODE_E2P_READ);
+ break;
+ case AK8973_MODE_POWERDOWN:
+ ret = akm8973_ecs_set_mode_power_down(akm);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ /* Wait at least 300us after changing mode. */
+ udelay(300);
+
+ return 0;
+}
+
+static void akm8973_reset(struct akm8973_data *akm)
+{
+ gpio_set_value(akm->pdata->reset_line, akm->pdata->reset_asserted);
+ msleep(2);
+ gpio_set_value(akm->pdata->reset_line, !akm->pdata->reset_asserted);
+}
+
+static int akmd_copy_in(unsigned int cmd, void __user *argp,
+ void *buf, size_t buf_size)
+{
+ if (!(cmd & IOC_IN))
+ return 0;
+ if (_IOC_SIZE(cmd) > buf_size)
+ return -EINVAL;
+ if (copy_from_user(buf, argp, _IOC_SIZE(cmd)))
+ return -EFAULT;
+ return 0;
+}
+
+static int akmd_copy_out(unsigned int cmd, void __user *argp,
+ void *buf, size_t buf_size)
+{
+ if (!(cmd & IOC_OUT))
+ return 0;
+ if (_IOC_SIZE(cmd) > buf_size)
+ return -EINVAL;
+ if (copy_to_user(argp, buf, _IOC_SIZE(cmd)))
+ return -EFAULT;
+ return 0;
+}
+
+static void akm8973_disable_irq(struct akm8973_data *akm)
+{
+ disable_irq(akm->irq);
+ if (try_wait_for_completion(&akm->data_ready)) {
+ /* we actually got the interrupt before we could disable it
+ * so we need to enable again to undo our disable since the
+ * irq_handler already disabled it
+ */
+ enable_irq(akm->irq);
+ }
+}
+
+static irqreturn_t akm8973_irq_handler(int irq, void *data)
+{
+ struct akm8973_data *akm = data;
+ disable_irq_nosync(irq);
+ complete(&akm->data_ready);
+ return IRQ_HANDLED;
+}
+
+static int akm8973_wait_for_data_ready(struct akm8973_data *akm)
+{
+ int data_ready = gpio_get_value(akm->pdata->gpio_data_ready_int);
+ int err;
+
+ if (data_ready)
+ return 0;
+
+ enable_irq(akm->irq);
+
+ err = wait_for_completion_timeout(&akm->data_ready, 5*HZ);
+ if (err > 0)
+ return 0;
+
+ akm8973_disable_irq(akm);
+
+ if (err == 0) {
+ pr_err("akm: wait timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ pr_err("akm: wait restart\n");
+ return err;
+}
+
+static long akmd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct akm8973_data *akm = container_of(file->private_data,
+ struct akm8973_data, akmd_device);
+ int ret;
+ union {
+ char raw[RWBUF_SIZE];
+ int status;
+ char mode;
+ u8 data[5];
+ } rwbuf;
+
+ ret = akmd_copy_in(cmd, argp, rwbuf.raw, sizeof(rwbuf));
+ if (ret)
+ return ret;
+
+ switch (cmd) {
+ case ECS_IOCTL_WRITE:
+ if ((rwbuf.raw[0] < 2) || (rwbuf.raw[0] > (RWBUF_SIZE - 1)))
+ return -EINVAL;
+ if (copy_from_user(&rwbuf.raw[2], argp+2, rwbuf.raw[0]-1))
+ return -EFAULT;
+
+ ret = i2c_smbus_write_i2c_block_data(akm->this_client,
+ rwbuf.raw[1],
+ rwbuf.raw[0] - 1,
+ &rwbuf.raw[2]);
+ break;
+ case ECS_IOCTL_READ:
+ if ((rwbuf.raw[0] < 1) || (rwbuf.raw[0] > (RWBUF_SIZE - 1)))
+ return -EINVAL;
+
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client,
+ rwbuf.raw[1],
+ rwbuf.raw[0],
+ &rwbuf.raw[1]);
+ if (ret < 0)
+ return ret;
+ if (copy_to_user(argp+1, rwbuf.raw+1, rwbuf.raw[0]))
+ return -EFAULT;
+ return 0;
+ case ECS_IOCTL_RESET:
+ akm8973_reset(akm);
+ break;
+ case ECS_IOCTL_SET_MODE:
+ ret = akm8973_ecs_set_mode(akm, rwbuf.mode);
+ break;
+ case ECS_IOCTL_GETDATA:
+ ret = akm8973_wait_for_data_ready(akm);
+ if (ret)
+ return ret;
+ ret = i2c_smbus_read_i2c_block_data(akm->this_client,
+ AK8973_REG_ST,
+ sizeof(rwbuf.data),
+ rwbuf.data);
+ if (ret != sizeof(rwbuf.data)) {
+ pr_err("%s : failed to read %d bytes of mag data\n",
+ __func__, sizeof(rwbuf.data));
+ return -EIO;
+ }
+ break;
+ default:
+ return -ENOTTY;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ return akmd_copy_out(cmd, argp, rwbuf.raw, sizeof(rwbuf));
+}
+
+static const struct file_operations akmd_fops = {
+ .owner = THIS_MODULE,
+ .open = nonseekable_open,
+ .unlocked_ioctl = akmd_ioctl,
+};
+
+static int akm8973_setup_irq(struct akm8973_data *akm)
+{
+ int rc = -EIO;
+ struct akm8973_platform_data *pdata = akm->pdata;
+ int irq;
+
+ rc = gpio_request(pdata->gpio_data_ready_int, "gpio_akm_int");
+ if (rc < 0) {
+ pr_err("%s: gpio %d request failed (%d)\n",
+ __func__, pdata->gpio_data_ready_int, rc);
+ return rc;
+ }
+
+ rc = gpio_direction_input(pdata->gpio_data_ready_int);
+ if (rc < 0) {
+ pr_err("%s: failed to set gpio %d as input (%d)\n",
+ __func__, pdata->gpio_data_ready_int, rc);
+ goto err_gpio_direction_input;
+ }
+
+ irq = gpio_to_irq(pdata->gpio_data_ready_int);
+
+ /* trigger high so we don't miss initial interrupt if it
+ * is already pending
+ */
+ rc = request_irq(irq, akm8973_irq_handler, IRQF_TRIGGER_HIGH,
+ "akm_int", akm);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
+ __func__, irq,
+ pdata->gpio_data_ready_int, rc);
+ goto err_request_irq;
+ }
+
+ /* start with interrupt disabled until the driver is enabled */
+ akm->irq = irq;
+ akm8973_disable_irq(akm);
+
+ goto done;
+
+err_request_irq:
+err_gpio_direction_input:
+ gpio_free(pdata->gpio_data_ready_int);
+done:
+ return rc;
+}
+
+int akm8973_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ struct akm8973_data *akm;
+ int err;
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODEV;
+ goto exit_platform_data_null;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C check failed, exiting.\n");
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ akm = kzalloc(sizeof(struct akm8973_data), GFP_KERNEL);
+ if (!akm) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit_alloc_data_failed;
+ }
+
+ akm->pdata = client->dev.platform_data;
+ mutex_init(&akm->lock);
+ init_completion(&akm->data_ready);
+
+ i2c_set_clientdata(client, akm);
+ akm->this_client = client;
+
+ err = gpio_request(akm->pdata->reset_line, "AK8973 Reset Line");
+ if (err < 0)
+ goto exit_reset_gpio_request_failed;
+ gpio_direction_output(akm->pdata->reset_line,
+ !akm->pdata->reset_asserted);
+ akm8973_reset(akm);
+
+ err = akm8973_ecs_set_mode_power_down(akm);
+ if (err < 0)
+ goto exit_set_mode_power_down_failed;
+
+ err = akm8973_setup_irq(akm);
+ if (err) {
+ pr_err("%s: could not setup irq\n", __func__);
+ goto exit_setup_irq;
+ }
+
+ akm->akmd_device.minor = MISC_DYNAMIC_MINOR;
+ akm->akmd_device.name = "akm8973";
+ akm->akmd_device.fops = &akmd_fops;
+
+ err = misc_register(&akm->akmd_device);
+ if (err)
+ goto exit_akmd_device_register_failed;
+
+ init_waitqueue_head(&akm->state_wq);
+
+ return 0;
+
+exit_akmd_device_register_failed:
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+exit_setup_irq:
+exit_set_mode_power_down_failed:
+ gpio_direction_input(akm->pdata->reset_line);
+ gpio_free(akm->pdata->reset_line);
+exit_reset_gpio_request_failed:
+ mutex_destroy(&akm->lock);
+ kfree(akm);
+exit_alloc_data_failed:
+exit_check_functionality_failed:
+exit_platform_data_null:
+ return err;
+}
+
+static int __devexit akm8973_remove(struct i2c_client *client)
+{
+ struct akm8973_data *akm = i2c_get_clientdata(client);
+
+ misc_deregister(&akm->akmd_device);
+ gpio_direction_input(akm->pdata->reset_line);
+ gpio_free(akm->pdata->reset_line);
+ free_irq(akm->irq, akm);
+ gpio_free(akm->pdata->gpio_data_ready_int);
+ mutex_destroy(&akm->lock);
+ kfree(akm);
+ return 0;
+}
+
+static const struct i2c_device_id akm8973_id[] = {
+ {AKM8973_I2C_NAME, 0 },
+ { }
+};
+
+static struct i2c_driver akm8973_driver = {
+ .probe = akm8973_probe,
+ .remove = akm8973_remove,
+ .id_table = akm8973_id,
+ .driver = {
+ .name = AKM8973_I2C_NAME,
+ },
+};
+
+static int __init akm8973_init(void)
+{
+ return i2c_add_driver(&akm8973_driver);
+}
+
+static void __exit akm8973_exit(void)
+{
+ i2c_del_driver(&akm8973_driver);
+}
+
+module_init(akm8973_init);
+module_exit(akm8973_exit);
+
+MODULE_DESCRIPTION("AKM8973 compass driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/akm8975.c b/drivers/misc/akm8975.c
new file mode 100644
index 0000000..830d289
--- /dev/null
+++ b/drivers/misc/akm8975.c
@@ -0,0 +1,732 @@
+/* drivers/misc/akm8975.c - akm8975 compass driver
+ *
+ * Copyright (C) 2007-2008 HTC Corporation.
+ * Author: Hou-Kun Chen <houkun.chen@gmail.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Revised by AKM 2009/04/02
+ * Revised by Motorola 2010/05/27
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/miscdevice.h>
+#include <linux/gpio.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/freezer.h>
+#include <linux/akm8975.h>
+#include <linux/earlysuspend.h>
+
+#define AK8975DRV_CALL_DBG 0
+#if AK8975DRV_CALL_DBG
+#define FUNCDBG(msg) pr_err("%s:%s\n", __func__, msg);
+#else
+#define FUNCDBG(msg)
+#endif
+
+#define AK8975DRV_DATA_DBG 0
+#define MAX_FAILURE_COUNT 10
+
+struct akm8975_data {
+ struct i2c_client *this_client;
+ struct akm8975_platform_data *pdata;
+ struct input_dev *input_dev;
+ struct work_struct work;
+ struct mutex flags_lock;
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ struct early_suspend early_suspend;
+#endif
+};
+
+/*
+* Because misc devices can not carry a pointer from driver register to
+* open, we keep this global. This limits the driver to a single instance.
+*/
+struct akm8975_data *akmd_data;
+
+static DECLARE_WAIT_QUEUE_HEAD(open_wq);
+
+static atomic_t open_flag;
+
+static short m_flag;
+static short a_flag;
+static short t_flag;
+static short mv_flag;
+
+static short akmd_delay;
+
+static ssize_t akm8975_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ return sprintf(buf, "%u\n", i2c_smbus_read_byte_data(client,
+ AK8975_REG_CNTL));
+}
+static ssize_t akm8975_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ unsigned long val;
+ strict_strtoul(buf, 10, &val);
+ if (val > 0xff)
+ return -EINVAL;
+ i2c_smbus_write_byte_data(client, AK8975_REG_CNTL, val);
+ return count;
+}
+static DEVICE_ATTR(akm_ms1, S_IWUSR | S_IRUGO, akm8975_show, akm8975_store);
+
+static int akm8975_i2c_rxdata(struct akm8975_data *akm, char *buf, int length)
+{
+ struct i2c_msg msgs[] = {
+ {
+ .addr = akm->this_client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = buf,
+ },
+ {
+ .addr = akm->this_client->addr,
+ .flags = I2C_M_RD,
+ .len = length,
+ .buf = buf,
+ },
+ };
+
+ FUNCDBG("called");
+
+ if (i2c_transfer(akm->this_client->adapter, msgs, 2) < 0) {
+ pr_err("akm8975_i2c_rxdata: transfer error\n");
+ return EIO;
+ } else
+ return 0;
+}
+
+static int akm8975_i2c_txdata(struct akm8975_data *akm, char *buf, int length)
+{
+ struct i2c_msg msgs[] = {
+ {
+ .addr = akm->this_client->addr,
+ .flags = 0,
+ .len = length,
+ .buf = buf,
+ },
+ };
+
+ FUNCDBG("called");
+
+ if (i2c_transfer(akm->this_client->adapter, msgs, 1) < 0) {
+ pr_err("akm8975_i2c_txdata: transfer error\n");
+ return -EIO;
+ } else
+ return 0;
+}
+
+static void akm8975_ecs_report_value(struct akm8975_data *akm, short *rbuf)
+{
+ struct akm8975_data *data = i2c_get_clientdata(akm->this_client);
+
+ FUNCDBG("called");
+
+#if AK8975DRV_DATA_DBG
+ pr_info("akm8975_ecs_report_value: yaw = %d, pitch = %d, roll = %d\n",
+ rbuf[0], rbuf[1], rbuf[2]);
+ pr_info("tmp = %d, m_stat= %d, g_stat=%d\n", rbuf[3], rbuf[4], rbuf[5]);
+ pr_info("Acceleration: x = %d LSB, y = %d LSB, z = %d LSB\n",
+ rbuf[6], rbuf[7], rbuf[8]);
+ pr_info("Magnetic: x = %d LSB, y = %d LSB, z = %d LSB\n\n",
+ rbuf[9], rbuf[10], rbuf[11]);
+#endif
+ mutex_lock(&akm->flags_lock);
+ /* Report magnetic sensor information */
+ if (m_flag) {
+ input_report_abs(data->input_dev, ABS_RX, rbuf[0]);
+ input_report_abs(data->input_dev, ABS_RY, rbuf[1]);
+ input_report_abs(data->input_dev, ABS_RZ, rbuf[2]);
+ input_report_abs(data->input_dev, ABS_RUDDER, rbuf[4]);
+ }
+
+ /* Report acceleration sensor information */
+ if (a_flag) {
+ input_report_abs(data->input_dev, ABS_X, rbuf[6]);
+ input_report_abs(data->input_dev, ABS_Y, rbuf[7]);
+ input_report_abs(data->input_dev, ABS_Z, rbuf[8]);
+ input_report_abs(data->input_dev, ABS_WHEEL, rbuf[5]);
+ }
+
+ /* Report temperature information */
+ if (t_flag)
+ input_report_abs(data->input_dev, ABS_THROTTLE, rbuf[3]);
+
+ if (mv_flag) {
+ input_report_abs(data->input_dev, ABS_HAT0X, rbuf[9]);
+ input_report_abs(data->input_dev, ABS_HAT0Y, rbuf[10]);
+ input_report_abs(data->input_dev, ABS_BRAKE, rbuf[11]);
+ }
+ mutex_unlock(&akm->flags_lock);
+
+ input_sync(data->input_dev);
+}
+
+static void akm8975_ecs_close_done(struct akm8975_data *akm)
+{
+ FUNCDBG("called");
+ mutex_lock(&akm->flags_lock);
+ m_flag = 1;
+ a_flag = 1;
+ t_flag = 1;
+ mv_flag = 1;
+ mutex_unlock(&akm->flags_lock);
+}
+
+static int akm_aot_open(struct inode *inode, struct file *file)
+{
+ int ret = -1;
+
+ FUNCDBG("called");
+ if (atomic_cmpxchg(&open_flag, 0, 1) == 0) {
+ wake_up(&open_wq);
+ ret = 0;
+ }
+
+ ret = nonseekable_open(inode, file);
+ if (ret)
+ return ret;
+
+ file->private_data = akmd_data;
+
+ return ret;
+}
+
+static int akm_aot_release(struct inode *inode, struct file *file)
+{
+ FUNCDBG("called");
+ atomic_set(&open_flag, 0);
+ wake_up(&open_wq);
+ return 0;
+}
+
+static int akm_aot_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *) arg;
+ short flag;
+ struct akm8975_data *akm = file->private_data;
+
+ FUNCDBG("called");
+
+ switch (cmd) {
+ case ECS_IOCTL_APP_SET_MFLAG:
+ case ECS_IOCTL_APP_SET_AFLAG:
+ case ECS_IOCTL_APP_SET_MVFLAG:
+ if (copy_from_user(&flag, argp, sizeof(flag)))
+ return -EFAULT;
+ if (flag < 0 || flag > 1)
+ return -EINVAL;
+ break;
+ case ECS_IOCTL_APP_SET_DELAY:
+ if (copy_from_user(&flag, argp, sizeof(flag)))
+ return -EFAULT;
+ break;
+ default:
+ break;
+ }
+
+ mutex_lock(&akm->flags_lock);
+ switch (cmd) {
+ case ECS_IOCTL_APP_SET_MFLAG:
+ m_flag = flag;
+ break;
+ case ECS_IOCTL_APP_GET_MFLAG:
+ flag = m_flag;
+ break;
+ case ECS_IOCTL_APP_SET_AFLAG:
+ a_flag = flag;
+ break;
+ case ECS_IOCTL_APP_GET_AFLAG:
+ flag = a_flag;
+ break;
+ case ECS_IOCTL_APP_SET_MVFLAG:
+ mv_flag = flag;
+ break;
+ case ECS_IOCTL_APP_GET_MVFLAG:
+ flag = mv_flag;
+ break;
+ case ECS_IOCTL_APP_SET_DELAY:
+ akmd_delay = flag;
+ break;
+ case ECS_IOCTL_APP_GET_DELAY:
+ flag = akmd_delay;
+ break;
+ default:
+ return -ENOTTY;
+ }
+ mutex_unlock(&akm->flags_lock);
+
+ switch (cmd) {
+ case ECS_IOCTL_APP_GET_MFLAG:
+ case ECS_IOCTL_APP_GET_AFLAG:
+ case ECS_IOCTL_APP_GET_MVFLAG:
+ case ECS_IOCTL_APP_GET_DELAY:
+ if (copy_to_user(argp, &flag, sizeof(flag)))
+ return -EFAULT;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int akmd_open(struct inode *inode, struct file *file)
+{
+ int err = 0;
+
+ FUNCDBG("called");
+ err = nonseekable_open(inode, file);
+ if (err)
+ return err;
+
+ file->private_data = akmd_data;
+ return 0;
+}
+
+static int akmd_release(struct inode *inode, struct file *file)
+{
+ struct akm8975_data *akm = file->private_data;
+
+ FUNCDBG("called");
+ akm8975_ecs_close_done(akm);
+ return 0;
+}
+
+static int akmd_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *) arg;
+
+ char rwbuf[16];
+ int ret = -1;
+ int status;
+ short value[12];
+ short delay;
+ struct akm8975_data *akm = file->private_data;
+
+ FUNCDBG("called");
+
+ switch (cmd) {
+ case ECS_IOCTL_READ:
+ case ECS_IOCTL_WRITE:
+ if (copy_from_user(&rwbuf, argp, sizeof(rwbuf)))
+ return -EFAULT;
+ break;
+
+ case ECS_IOCTL_SET_YPR:
+ if (copy_from_user(&value, argp, sizeof(value)))
+ return -EFAULT;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (cmd) {
+ case ECS_IOCTL_READ:
+ if (rwbuf[0] < 1)
+ return -EINVAL;
+
+ ret = akm8975_i2c_rxdata(akm, &rwbuf[1], rwbuf[0]);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case ECS_IOCTL_WRITE:
+ if (rwbuf[0] < 2)
+ return -EINVAL;
+
+ ret = akm8975_i2c_txdata(akm, &rwbuf[1], rwbuf[0]);
+ if (ret < 0)
+ return ret;
+ break;
+ case ECS_IOCTL_SET_YPR:
+ akm8975_ecs_report_value(akm, value);
+ break;
+
+ case ECS_IOCTL_GET_OPEN_STATUS:
+ wait_event_interruptible(open_wq,
+ (atomic_read(&open_flag) != 0));
+ status = atomic_read(&open_flag);
+ break;
+ case ECS_IOCTL_GET_CLOSE_STATUS:
+ wait_event_interruptible(open_wq,
+ (atomic_read(&open_flag) == 0));
+ status = atomic_read(&open_flag);
+ break;
+
+ case ECS_IOCTL_GET_DELAY:
+ delay = akmd_delay;
+ break;
+
+ default:
+ FUNCDBG("Unknown cmd\n");
+ return -ENOTTY;
+ }
+
+ switch (cmd) {
+ case ECS_IOCTL_READ:
+ if (copy_to_user(argp, &rwbuf, sizeof(rwbuf)))
+ return -EFAULT;
+ break;
+ case ECS_IOCTL_GET_OPEN_STATUS:
+ case ECS_IOCTL_GET_CLOSE_STATUS:
+ if (copy_to_user(argp, &status, sizeof(status)))
+ return -EFAULT;
+ break;
+ case ECS_IOCTL_GET_DELAY:
+ if (copy_to_user(argp, &delay, sizeof(delay)))
+ return -EFAULT;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* needed to clear the int. pin */
+static void akm_work_func(struct work_struct *work)
+{
+ struct akm8975_data *akm =
+ container_of(work, struct akm8975_data, work);
+
+ FUNCDBG("called");
+ enable_irq(akm->this_client->irq);
+}
+
+static irqreturn_t akm8975_interrupt(int irq, void *dev_id)
+{
+ struct akm8975_data *akm = dev_id;
+ FUNCDBG("called");
+
+ disable_irq_nosync(akm->this_client->irq);
+ schedule_work(&akm->work);
+ return IRQ_HANDLED;
+}
+
+static int akm8975_power_off(struct akm8975_data *akm)
+{
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ if (akm->pdata->power_off)
+ akm->pdata->power_off();
+
+ return 0;
+}
+
+static int akm8975_power_on(struct akm8975_data *akm)
+{
+ int err;
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ if (akm->pdata->power_on) {
+ err = akm->pdata->power_on();
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int akm8975_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ /* TO DO: might need more work after power mgmt
+ is enabled */
+ return akm8975_power_off(akm);
+}
+
+static int akm8975_resume(struct i2c_client *client)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ /* TO DO: might need more work after power mgmt
+ is enabled */
+ return akm8975_power_on(akm);
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void akm8975_early_suspend(struct early_suspend *handler)
+{
+ struct akm8975_data *akm;
+ akm = container_of(handler, struct akm8975_data, early_suspend);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ akm8975_suspend(akm->this_client, PMSG_SUSPEND);
+}
+
+static void akm8975_early_resume(struct early_suspend *handler)
+{
+ struct akm8975_data *akm;
+ akm = container_of(handler, struct akm8975_data, early_suspend);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ akm8975_resume(akm->this_client);
+}
+#endif
+
+
+static int akm8975_init_client(struct i2c_client *client)
+{
+ struct akm8975_data *data;
+ int ret;
+
+ data = i2c_get_clientdata(client);
+
+ ret = request_irq(client->irq, akm8975_interrupt, IRQF_TRIGGER_RISING,
+ "akm8975", data);
+
+ if (ret < 0) {
+ pr_err("akm8975_init_client: request irq failed\n");
+ goto err;
+ }
+
+ init_waitqueue_head(&open_wq);
+
+ mutex_lock(&data->flags_lock);
+ m_flag = 1;
+ a_flag = 1;
+ t_flag = 1;
+ mv_flag = 1;
+ mutex_unlock(&data->flags_lock);
+
+ return 0;
+err:
+ return ret;
+}
+
+static const struct file_operations akmd_fops = {
+ .owner = THIS_MODULE,
+ .open = akmd_open,
+ .release = akmd_release,
+ .ioctl = akmd_ioctl,
+};
+
+static const struct file_operations akm_aot_fops = {
+ .owner = THIS_MODULE,
+ .open = akm_aot_open,
+ .release = akm_aot_release,
+ .ioctl = akm_aot_ioctl,
+};
+
+static struct miscdevice akm_aot_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "akm8975_aot",
+ .fops = &akm_aot_fops,
+};
+
+static struct miscdevice akmd_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "akm8975_dev",
+ .fops = &akmd_fops,
+};
+
+int akm8975_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ struct akm8975_data *akm;
+ int err;
+ FUNCDBG("called");
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODEV;
+ goto exit_platform_data_null;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ akm = kzalloc(sizeof(struct akm8975_data), GFP_KERNEL);
+ if (!akm) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit_alloc_data_failed;
+ }
+
+ akm->pdata = client->dev.platform_data;
+
+ mutex_init(&akm->flags_lock);
+ INIT_WORK(&akm->work, akm_work_func);
+ i2c_set_clientdata(client, akm);
+
+ err = akm8975_power_on(akm);
+ if (err < 0)
+ goto exit_power_on_failed;
+
+ akm8975_init_client(client);
+ akm->this_client = client;
+ akmd_data = akm;
+
+ akm->input_dev = input_allocate_device();
+ if (!akm->input_dev) {
+ err = -ENOMEM;
+ dev_err(&akm->this_client->dev,
+ "input device allocate failed\n");
+ goto exit_input_dev_alloc_failed;
+ }
+
+ set_bit(EV_ABS, akm->input_dev->evbit);
+
+ /* yaw */
+ input_set_abs_params(akm->input_dev, ABS_RX, 0, 23040, 0, 0);
+ /* pitch */
+ input_set_abs_params(akm->input_dev, ABS_RY, -11520, 11520, 0, 0);
+ /* roll */
+ input_set_abs_params(akm->input_dev, ABS_RZ, -5760, 5760, 0, 0);
+ /* x-axis acceleration */
+ input_set_abs_params(akm->input_dev, ABS_X, -5760, 5760, 0, 0);
+ /* y-axis acceleration */
+ input_set_abs_params(akm->input_dev, ABS_Y, -5760, 5760, 0, 0);
+ /* z-axis acceleration */
+ input_set_abs_params(akm->input_dev, ABS_Z, -5760, 5760, 0, 0);
+ /* temparature */
+ input_set_abs_params(akm->input_dev, ABS_THROTTLE, -30, 85, 0, 0);
+ /* status of magnetic sensor */
+ input_set_abs_params(akm->input_dev, ABS_RUDDER, 0, 3, 0, 0);
+ /* status of acceleration sensor */
+ input_set_abs_params(akm->input_dev, ABS_WHEEL, 0, 3, 0, 0);
+ /* x-axis of raw magnetic vector */
+ input_set_abs_params(akm->input_dev, ABS_HAT0X, -20480, 20479, 0, 0);
+ /* y-axis of raw magnetic vector */
+ input_set_abs_params(akm->input_dev, ABS_HAT0Y, -20480, 20479, 0, 0);
+ /* z-axis of raw magnetic vector */
+ input_set_abs_params(akm->input_dev, ABS_BRAKE, -20480, 20479, 0, 0);
+
+ akm->input_dev->name = "compass";
+
+ err = input_register_device(akm->input_dev);
+ if (err) {
+ pr_err("akm8975_probe: Unable to register input device: %s\n",
+ akm->input_dev->name);
+ goto exit_input_register_device_failed;
+ }
+
+ err = misc_register(&akmd_device);
+ if (err) {
+ pr_err("akm8975_probe: akmd_device register failed\n");
+ goto exit_misc_device_register_failed;
+ }
+
+ err = misc_register(&akm_aot_device);
+ if (err) {
+ pr_err("akm8975_probe: akm_aot_device register failed\n");
+ goto exit_misc_device_register_failed;
+ }
+
+ err = device_create_file(&client->dev, &dev_attr_akm_ms1);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ akm->early_suspend.suspend = akm8975_early_suspend;
+ akm->early_suspend.resume = akm8975_early_resume;
+ register_early_suspend(&akm->early_suspend);
+#endif
+ return 0;
+
+exit_misc_device_register_failed:
+exit_input_register_device_failed:
+ input_free_device(akm->input_dev);
+exit_input_dev_alloc_failed:
+ akm8975_power_off(akm);
+exit_power_on_failed:
+ kfree(akm);
+exit_alloc_data_failed:
+exit_check_functionality_failed:
+exit_platform_data_null:
+ return err;
+}
+
+static int __devexit akm8975_remove(struct i2c_client *client)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+ FUNCDBG("called");
+ free_irq(client->irq, NULL);
+ input_unregister_device(akm->input_dev);
+ misc_deregister(&akmd_device);
+ misc_deregister(&akm_aot_device);
+ akm8975_power_off(akm);
+ kfree(akm);
+ return 0;
+}
+
+static const struct i2c_device_id akm8975_id[] = {
+ { "akm8975", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, akm8975_id);
+
+static struct i2c_driver akm8975_driver = {
+ .probe = akm8975_probe,
+ .remove = akm8975_remove,
+#ifndef CONFIG_HAS_EARLYSUSPEND
+ .resume = akm8975_resume,
+ .suspend = akm8975_suspend,
+#endif
+ .id_table = akm8975_id,
+ .driver = {
+ .name = "akm8975",
+ },
+};
+
+static int __init akm8975_init(void)
+{
+ pr_info("AK8975 compass driver: init\n");
+ FUNCDBG("AK8975 compass driver: init\n");
+ return i2c_add_driver(&akm8975_driver);
+}
+
+static void __exit akm8975_exit(void)
+{
+ FUNCDBG("AK8975 compass driver: exit\n");
+ i2c_del_driver(&akm8975_driver);
+}
+
+module_init(akm8975_init);
+module_exit(akm8975_exit);
+
+MODULE_AUTHOR("Hou-Kun Chen <hk_chen@htc.com>");
+MODULE_DESCRIPTION("AK8975 compass driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/apanic.c b/drivers/misc/apanic.c
new file mode 100644
index 0000000..ca875f8
--- /dev/null
+++ b/drivers/misc/apanic.c
@@ -0,0 +1,606 @@
+/* drivers/misc/apanic.c
+ *
+ * Copyright (C) 2009 Google, Inc.
+ * Author: San Mehat <san@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/wakelock.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/mtd/mtd.h>
+#include <linux/notifier.h>
+#include <linux/mtd/mtd.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/preempt.h>
+
+extern void ram_console_enable_console(int);
+
+struct panic_header {
+ u32 magic;
+#define PANIC_MAGIC 0xdeadf00d
+
+ u32 version;
+#define PHDR_VERSION 0x01
+
+ u32 console_offset;
+ u32 console_length;
+
+ u32 threads_offset;
+ u32 threads_length;
+};
+
+struct apanic_data {
+ struct mtd_info *mtd;
+ struct panic_header curr;
+ void *bounce;
+ struct proc_dir_entry *apanic_console;
+ struct proc_dir_entry *apanic_threads;
+};
+
+static struct apanic_data drv_ctx;
+static struct work_struct proc_removal_work;
+static DEFINE_MUTEX(drv_mutex);
+
+static unsigned int *apanic_bbt;
+static unsigned int apanic_erase_blocks;
+static unsigned int apanic_good_blocks;
+
+static void set_bb(unsigned int block, unsigned int *bbt)
+{
+ unsigned int flag = 1;
+
+ BUG_ON(block >= apanic_erase_blocks);
+
+ flag = flag << (block%32);
+ apanic_bbt[block/32] |= flag;
+ apanic_good_blocks--;
+}
+
+static unsigned int get_bb(unsigned int block, unsigned int *bbt)
+{
+ unsigned int flag;
+
+ BUG_ON(block >= apanic_erase_blocks);
+
+ flag = 1 << (block%32);
+ return apanic_bbt[block/32] & flag;
+}
+
+static void alloc_bbt(struct mtd_info *mtd, unsigned int *bbt)
+{
+ int bbt_size;
+ apanic_erase_blocks = (mtd->size)>>(mtd->erasesize_shift);
+ bbt_size = (apanic_erase_blocks+32)/32;
+
+ apanic_bbt = kmalloc(bbt_size*4, GFP_KERNEL);
+ memset(apanic_bbt, 0, bbt_size*4);
+ apanic_good_blocks = apanic_erase_blocks;
+}
+static void scan_bbt(struct mtd_info *mtd, unsigned int *bbt)
+{
+ int i;
+
+ for (i = 0; i < apanic_erase_blocks; i++) {
+ if (mtd->block_isbad(mtd, i*mtd->erasesize))
+ set_bb(i, apanic_bbt);
+ }
+}
+
+#define APANIC_INVALID_OFFSET 0xFFFFFFFF
+
+static unsigned int phy_offset(struct mtd_info *mtd, unsigned int offset)
+{
+ unsigned int logic_block = offset>>(mtd->erasesize_shift);
+ unsigned int phy_block;
+ unsigned good_block = 0;
+
+ for (phy_block = 0; phy_block < apanic_erase_blocks; phy_block++) {
+ if (!get_bb(phy_block, apanic_bbt))
+ good_block++;
+ if (good_block == (logic_block + 1))
+ break;
+ }
+
+ if (good_block != (logic_block + 1))
+ return APANIC_INVALID_OFFSET;
+
+ return offset + ((phy_block-logic_block)<<mtd->erasesize_shift);
+}
+
+static void apanic_erase_callback(struct erase_info *done)
+{
+ wait_queue_head_t *wait_q = (wait_queue_head_t *) done->priv;
+ wake_up(wait_q);
+}
+
+static int apanic_proc_read(char *buffer, char **start, off_t offset,
+ int count, int *peof, void *dat)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ size_t file_length;
+ off_t file_offset;
+ unsigned int page_no;
+ off_t page_offset;
+ int rc;
+ size_t len;
+
+ if (!count)
+ return 0;
+
+ mutex_lock(&drv_mutex);
+
+ switch ((int) dat) {
+ case 1: /* apanic_console */
+ file_length = ctx->curr.console_length;
+ file_offset = ctx->curr.console_offset;
+ break;
+ case 2: /* apanic_threads */
+ file_length = ctx->curr.threads_length;
+ file_offset = ctx->curr.threads_offset;
+ break;
+ default:
+ pr_err("Bad dat (%d)\n", (int) dat);
+ mutex_unlock(&drv_mutex);
+ return -EINVAL;
+ }
+
+ if ((offset + count) > file_length) {
+ mutex_unlock(&drv_mutex);
+ return 0;
+ }
+
+ /* We only support reading a maximum of a flash page */
+ if (count > ctx->mtd->writesize)
+ count = ctx->mtd->writesize;
+
+ page_no = (file_offset + offset) / ctx->mtd->writesize;
+ page_offset = (file_offset + offset) % ctx->mtd->writesize;
+
+
+ if (phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize))
+ == APANIC_INVALID_OFFSET) {
+ pr_err("apanic: reading an invalid address\n");
+ mutex_unlock(&drv_mutex);
+ return -EINVAL;
+ }
+ rc = ctx->mtd->read(ctx->mtd,
+ phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize)),
+ ctx->mtd->writesize,
+ &len, ctx->bounce);
+
+ if (page_offset)
+ count -= page_offset;
+ memcpy(buffer, ctx->bounce + page_offset, count);
+
+ *start = count;
+
+ if ((offset + count) == file_length)
+ *peof = 1;
+
+ mutex_unlock(&drv_mutex);
+ return count;
+}
+
+static void mtd_panic_erase(void)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ struct erase_info erase;
+ DECLARE_WAITQUEUE(wait, current);
+ wait_queue_head_t wait_q;
+ int rc, i;
+
+ init_waitqueue_head(&wait_q);
+ erase.mtd = ctx->mtd;
+ erase.callback = apanic_erase_callback;
+ erase.len = ctx->mtd->erasesize;
+ erase.priv = (u_long)&wait_q;
+ for (i = 0; i < ctx->mtd->size; i += ctx->mtd->erasesize) {
+ erase.addr = i;
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&wait_q, &wait);
+
+ if (get_bb(erase.addr>>ctx->mtd->erasesize_shift, apanic_bbt)) {
+ printk(KERN_WARNING
+ "apanic: Skipping erase of bad "
+ "block @%llx\n", erase.addr);
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&wait_q, &wait);
+ continue;
+ }
+
+ rc = ctx->mtd->erase(ctx->mtd, &erase);
+ if (rc) {
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&wait_q, &wait);
+ printk(KERN_ERR
+ "apanic: Erase of 0x%llx, 0x%llx failed\n",
+ (unsigned long long) erase.addr,
+ (unsigned long long) erase.len);
+ if (rc == -EIO) {
+ if (ctx->mtd->block_markbad(ctx->mtd,
+ erase.addr)) {
+ printk(KERN_ERR
+ "apanic: Err marking blk bad\n");
+ goto out;
+ }
+ printk(KERN_INFO
+ "apanic: Marked a bad block"
+ " @%llx\n", erase.addr);
+ set_bb(erase.addr>>ctx->mtd->erasesize_shift,
+ apanic_bbt);
+ continue;
+ }
+ goto out;
+ }
+ schedule();
+ remove_wait_queue(&wait_q, &wait);
+ }
+ printk(KERN_DEBUG "apanic: %s partition erased\n",
+ CONFIG_APANIC_PLABEL);
+out:
+ return;
+}
+
+static void apanic_remove_proc_work(struct work_struct *work)
+{
+ struct apanic_data *ctx = &drv_ctx;
+
+ mutex_lock(&drv_mutex);
+ mtd_panic_erase();
+ memset(&ctx->curr, 0, sizeof(struct panic_header));
+ if (ctx->apanic_console) {
+ remove_proc_entry("apanic_console", NULL);
+ ctx->apanic_console = NULL;
+ }
+ if (ctx->apanic_threads) {
+ remove_proc_entry("apanic_threads", NULL);
+ ctx->apanic_threads = NULL;
+ }
+ mutex_unlock(&drv_mutex);
+}
+
+static int apanic_proc_write(struct file *file, const char __user *buffer,
+ unsigned long count, void *data)
+{
+ schedule_work(&proc_removal_work);
+ return count;
+}
+
+static void mtd_panic_notify_add(struct mtd_info *mtd)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ struct panic_header *hdr = ctx->bounce;
+ size_t len;
+ int rc;
+ int proc_entry_created = 0;
+
+ if (strcmp(mtd->name, CONFIG_APANIC_PLABEL))
+ return;
+
+ ctx->mtd = mtd;
+
+ alloc_bbt(mtd, apanic_bbt);
+ scan_bbt(mtd, apanic_bbt);
+
+ if (apanic_good_blocks == 0) {
+ printk(KERN_ERR "apanic: no any good blocks?!\n");
+ goto out_err;
+ }
+
+ rc = mtd->read(mtd, phy_offset(mtd, 0), mtd->writesize,
+ &len, ctx->bounce);
+ if (rc && rc == -EBADMSG) {
+ printk(KERN_WARNING
+ "apanic: Bad ECC on block 0 (ignored)\n");
+ } else if (rc && rc != -EUCLEAN) {
+ printk(KERN_ERR "apanic: Error reading block 0 (%d)\n", rc);
+ goto out_err;
+ }
+
+ if (len != mtd->writesize) {
+ printk(KERN_ERR "apanic: Bad read size (%d)\n", rc);
+ goto out_err;
+ }
+
+ printk(KERN_INFO "apanic: Bound to mtd partition '%s'\n", mtd->name);
+
+ if (hdr->magic != PANIC_MAGIC) {
+ printk(KERN_INFO "apanic: No panic data available\n");
+ mtd_panic_erase();
+ return;
+ }
+
+ if (hdr->version != PHDR_VERSION) {
+ printk(KERN_INFO "apanic: Version mismatch (%d != %d)\n",
+ hdr->version, PHDR_VERSION);
+ mtd_panic_erase();
+ return;
+ }
+
+ memcpy(&ctx->curr, hdr, sizeof(struct panic_header));
+
+ printk(KERN_INFO "apanic: c(%u, %u) t(%u, %u)\n",
+ hdr->console_offset, hdr->console_length,
+ hdr->threads_offset, hdr->threads_length);
+
+ if (hdr->console_length) {
+ ctx->apanic_console = create_proc_entry("apanic_console",
+ S_IFREG | S_IRUGO, NULL);
+ if (!ctx->apanic_console)
+ printk(KERN_ERR "%s: failed creating procfile\n",
+ __func__);
+ else {
+ ctx->apanic_console->read_proc = apanic_proc_read;
+ ctx->apanic_console->write_proc = apanic_proc_write;
+ ctx->apanic_console->size = hdr->console_length;
+ ctx->apanic_console->data = (void *) 1;
+ proc_entry_created = 1;
+ }
+ }
+
+ if (hdr->threads_length) {
+ ctx->apanic_threads = create_proc_entry("apanic_threads",
+ S_IFREG | S_IRUGO, NULL);
+ if (!ctx->apanic_threads)
+ printk(KERN_ERR "%s: failed creating procfile\n",
+ __func__);
+ else {
+ ctx->apanic_threads->read_proc = apanic_proc_read;
+ ctx->apanic_threads->write_proc = apanic_proc_write;
+ ctx->apanic_threads->size = hdr->threads_length;
+ ctx->apanic_threads->data = (void *) 2;
+ proc_entry_created = 1;
+ }
+ }
+
+ if (!proc_entry_created)
+ mtd_panic_erase();
+
+ return;
+out_err:
+ ctx->mtd = NULL;
+}
+
+static void mtd_panic_notify_remove(struct mtd_info *mtd)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ if (mtd == ctx->mtd) {
+ ctx->mtd = NULL;
+ printk(KERN_INFO "apanic: Unbound from %s\n", mtd->name);
+ }
+}
+
+static struct mtd_notifier mtd_panic_notifier = {
+ .add = mtd_panic_notify_add,
+ .remove = mtd_panic_notify_remove,
+};
+
+static int in_panic = 0;
+
+static int apanic_writeflashpage(struct mtd_info *mtd, loff_t to,
+ const u_char *buf)
+{
+ int rc;
+ size_t wlen;
+ int panic = in_interrupt() | in_atomic();
+
+ if (panic && !mtd->panic_write) {
+ printk(KERN_EMERG "%s: No panic_write available\n", __func__);
+ return 0;
+ } else if (!panic && !mtd->write) {
+ printk(KERN_EMERG "%s: No write available\n", __func__);
+ return 0;
+ }
+
+ to = phy_offset(mtd, to);
+ if (to == APANIC_INVALID_OFFSET) {
+ printk(KERN_EMERG "apanic: write to invalid address\n");
+ return 0;
+ }
+
+ if (panic)
+ rc = mtd->panic_write(mtd, to, mtd->writesize, &wlen, buf);
+ else
+ rc = mtd->write(mtd, to, mtd->writesize, &wlen, buf);
+
+ if (rc) {
+ printk(KERN_EMERG
+ "%s: Error writing data to flash (%d)\n",
+ __func__, rc);
+ return rc;
+ }
+
+ return wlen;
+}
+
+extern int log_buf_copy(char *dest, int idx, int len);
+extern void log_buf_clear(void);
+
+/*
+ * Writes the contents of the console to the specified offset in flash.
+ * Returns number of bytes written
+ */
+static int apanic_write_console(struct mtd_info *mtd, unsigned int off)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ int saved_oip;
+ int idx = 0;
+ int rc, rc2;
+ unsigned int last_chunk = 0;
+
+ while (!last_chunk) {
+ saved_oip = oops_in_progress;
+ oops_in_progress = 1;
+ rc = log_buf_copy(ctx->bounce, idx, mtd->writesize);
+ if (rc < 0)
+ break;
+
+ if (rc != mtd->writesize)
+ last_chunk = rc;
+
+ oops_in_progress = saved_oip;
+ if (rc <= 0)
+ break;
+ if (rc != mtd->writesize)
+ memset(ctx->bounce + rc, 0, mtd->writesize - rc);
+
+ rc2 = apanic_writeflashpage(mtd, off, ctx->bounce);
+ if (rc2 <= 0) {
+ printk(KERN_EMERG
+ "apanic: Flash write failed (%d)\n", rc2);
+ return idx;
+ }
+ if (!last_chunk)
+ idx += rc2;
+ else
+ idx += last_chunk;
+ off += rc2;
+ }
+ return idx;
+}
+
+static int apanic(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ struct panic_header *hdr = (struct panic_header *) ctx->bounce;
+ int console_offset = 0;
+ int console_len = 0;
+ int threads_offset = 0;
+ int threads_len = 0;
+ int rc;
+
+ if (in_panic)
+ return NOTIFY_DONE;
+ in_panic = 1;
+#ifdef CONFIG_PREEMPT
+ /* Ensure that cond_resched() won't try to preempt anybody */
+ add_preempt_count(PREEMPT_ACTIVE);
+#endif
+ touch_softlockup_watchdog();
+
+ if (!ctx->mtd)
+ goto out;
+
+ if (ctx->curr.magic) {
+ printk(KERN_EMERG "Crash partition in use!\n");
+ goto out;
+ }
+ console_offset = ctx->mtd->writesize;
+
+ /*
+ * Write out the console
+ */
+ console_len = apanic_write_console(ctx->mtd, console_offset);
+ if (console_len < 0) {
+ printk(KERN_EMERG "Error writing console to panic log! (%d)\n",
+ console_len);
+ console_len = 0;
+ }
+
+ /*
+ * Write out all threads
+ */
+ threads_offset = ALIGN(console_offset + console_len,
+ ctx->mtd->writesize);
+ if (!threads_offset)
+ threads_offset = ctx->mtd->writesize;
+
+ ram_console_enable_console(0);
+
+ log_buf_clear();
+ show_state_filter(0);
+ threads_len = apanic_write_console(ctx->mtd, threads_offset);
+ if (threads_len < 0) {
+ printk(KERN_EMERG "Error writing threads to panic log! (%d)\n",
+ threads_len);
+ threads_len = 0;
+ }
+
+ /*
+ * Finally write the panic header
+ */
+ memset(ctx->bounce, 0, PAGE_SIZE);
+ hdr->magic = PANIC_MAGIC;
+ hdr->version = PHDR_VERSION;
+
+ hdr->console_offset = console_offset;
+ hdr->console_length = console_len;
+
+ hdr->threads_offset = threads_offset;
+ hdr->threads_length = threads_len;
+
+ rc = apanic_writeflashpage(ctx->mtd, 0, ctx->bounce);
+ if (rc <= 0) {
+ printk(KERN_EMERG "apanic: Header write failed (%d)\n",
+ rc);
+ goto out;
+ }
+
+ printk(KERN_EMERG "apanic: Panic dump sucessfully written to flash\n");
+
+ out:
+#ifdef CONFIG_PREEMPT
+ sub_preempt_count(PREEMPT_ACTIVE);
+#endif
+ in_panic = 0;
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_blk = {
+ .notifier_call = apanic,
+};
+
+static int panic_dbg_get(void *data, u64 *val)
+{
+ apanic(NULL, 0, NULL);
+ return 0;
+}
+
+static int panic_dbg_set(void *data, u64 val)
+{
+ BUG();
+ return -1;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(panic_dbg_fops, panic_dbg_get, panic_dbg_set, "%llu\n");
+
+int __init apanic_init(void)
+{
+ register_mtd_user(&mtd_panic_notifier);
+ atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
+ debugfs_create_file("apanic", 0644, NULL, NULL, &panic_dbg_fops);
+ memset(&drv_ctx, 0, sizeof(drv_ctx));
+ drv_ctx.bounce = (void *) __get_free_page(GFP_KERNEL);
+ INIT_WORK(&proc_removal_work, apanic_remove_proc_work);
+ printk(KERN_INFO "Android kernel panic handler initialized (bind=%s)\n",
+ CONFIG_APANIC_PLABEL);
+ return 0;
+}
+
+module_init(apanic_init);
diff --git a/drivers/misc/fsa9480.c b/drivers/misc/fsa9480.c
new file mode 100755
index 0000000..943aacf
--- /dev/null
+++ b/drivers/misc/fsa9480.c
@@ -0,0 +1,736 @@
+/*
+ * driver/misc/fsa9480.c - FSA9480 micro USB switch device driver
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ * Minkyu Kang <mk7.kang@samsung.com>
+ * Wonguk Jeong <wonguk.jeong@samsung.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/fsa9480.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+/* FSA9480 I2C registers */
+#define FSA9480_REG_DEVID 0x01
+#define FSA9480_REG_CTRL 0x02
+#define FSA9480_REG_INT1 0x03
+#define FSA9480_REG_INT2 0x04
+#define FSA9480_REG_INT1_MASK 0x05
+#define FSA9480_REG_INT2_MASK 0x06
+#define FSA9480_REG_ADC 0x07
+#define FSA9480_REG_TIMING1 0x08
+#define FSA9480_REG_TIMING2 0x09
+#define FSA9480_REG_DEV_T1 0x0a
+#define FSA9480_REG_DEV_T2 0x0b
+#define FSA9480_REG_BTN1 0x0c
+#define FSA9480_REG_BTN2 0x0d
+#define FSA9480_REG_CK 0x0e
+#define FSA9480_REG_CK_INT1 0x0f
+#define FSA9480_REG_CK_INT2 0x10
+#define FSA9480_REG_CK_INTMASK1 0x11
+#define FSA9480_REG_CK_INTMASK2 0x12
+#define FSA9480_REG_MANSW1 0x13
+#define FSA9480_REG_MANSW2 0x14
+
+/* Control */
+#define CON_SWITCH_OPEN (1 << 4)
+#define CON_RAW_DATA (1 << 3)
+#define CON_MANUAL_SW (1 << 2)
+#define CON_WAIT (1 << 1)
+#define CON_INT_MASK (1 << 0)
+#define CON_MASK (CON_SWITCH_OPEN | CON_RAW_DATA | \
+ CON_MANUAL_SW | CON_WAIT)
+
+/* Device Type 1 */
+#define DEV_USB_OTG (1 << 7)
+#define DEV_DEDICATED_CHG (1 << 6)
+#define DEV_USB_CHG (1 << 5)
+#define DEV_CAR_KIT (1 << 4)
+#define DEV_UART (1 << 3)
+#define DEV_USB (1 << 2)
+#define DEV_AUDIO_2 (1 << 1)
+#define DEV_AUDIO_1 (1 << 0)
+
+#define DEV_T1_USB_MASK (DEV_USB_OTG | DEV_USB)
+#define DEV_T1_UART_MASK (DEV_UART)
+#define DEV_T1_CHARGER_MASK (DEV_DEDICATED_CHG | DEV_USB_CHG | DEV_CAR_KIT)
+
+/* Device Type 2 */
+#define DEV_AV (1 << 6)
+#define DEV_TTY (1 << 5)
+#define DEV_PPD (1 << 4)
+#define DEV_JIG_UART_OFF (1 << 3)
+#define DEV_JIG_UART_ON (1 << 2)
+#define DEV_JIG_USB_OFF (1 << 1)
+#define DEV_JIG_USB_ON (1 << 0)
+
+#define DEV_T2_USB_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON)
+#define DEV_T2_UART_MASK DEV_JIG_UART_OFF
+#define DEV_T2_JIG_MASK (DEV_JIG_USB_OFF | DEV_JIG_USB_ON | \
+ DEV_JIG_UART_OFF)
+
+/*
+ * Manual Switch
+ * D- [7:5] / D+ [4:2]
+ * 000: Open all / 001: USB / 010: AUDIO / 011: UART / 100: V_AUDIO
+ */
+#define SW_VAUDIO ((4 << 5) | (4 << 2))
+#define SW_UART ((3 << 5) | (3 << 2))
+#define SW_AUDIO ((2 << 5) | (2 << 2))
+#define SW_DHOST ((1 << 5) | (1 << 2))
+#define SW_AUTO ((0 << 5) | (0 << 2))
+
+/* Interrupt 1 */
+#define INT_DETACH (1 << 1)
+#define INT_ATTACH (1 << 0)
+
+struct fsa9480_usbsw {
+ struct i2c_client *client;
+ struct fsa9480_platform_data *pdata;
+ int dev1;
+ int dev2;
+ int mansw;
+};
+
+static ssize_t fsa9480_show_control(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev);
+ struct i2c_client *client = usbsw->client;
+ int value;
+
+ value = i2c_smbus_read_byte_data(client, FSA9480_REG_CTRL);
+ if (value < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, value);
+
+ return sprintf(buf, "CONTROL: %02x\n", value);
+}
+
+static ssize_t fsa9480_show_device_type(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev);
+ struct i2c_client *client = usbsw->client;
+ int value;
+
+ value = i2c_smbus_read_byte_data(client, FSA9480_REG_DEV_T1);
+ if (value < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, value);
+
+ return sprintf(buf, "DEVICE_TYPE: %02x\n", value);
+}
+
+static ssize_t fsa9480_show_manualsw(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev);
+ struct i2c_client *client = usbsw->client;
+ unsigned int value;
+
+ value = i2c_smbus_read_byte_data(client, FSA9480_REG_MANSW1);
+ if (value < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, value);
+
+ if (value == SW_VAUDIO)
+ return sprintf(buf, "VAUDIO\n");
+ else if (value == SW_UART)
+ return sprintf(buf, "UART\n");
+ else if (value == SW_AUDIO)
+ return sprintf(buf, "AUDIO\n");
+ else if (value == SW_DHOST)
+ return sprintf(buf, "DHOST\n");
+ else if (value == SW_AUTO)
+ return sprintf(buf, "AUTO\n");
+ else
+ return sprintf(buf, "%x", value);
+}
+
+static ssize_t fsa9480_set_manualsw(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fsa9480_usbsw *usbsw = dev_get_drvdata(dev);
+ struct i2c_client *client = usbsw->client;
+ unsigned int value;
+ unsigned int path = 0;
+ int ret;
+
+ value = i2c_smbus_read_byte_data(client, FSA9480_REG_CTRL);
+ if (value < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, value);
+
+ if ((value & ~CON_MANUAL_SW) !=
+ (CON_SWITCH_OPEN | CON_RAW_DATA | CON_WAIT))
+ return 0;
+
+ if (!strncmp(buf, "VAUDIO", 6)) {
+ path = SW_VAUDIO;
+ value &= ~CON_MANUAL_SW;
+ } else if (!strncmp(buf, "UART", 4)) {
+ path = SW_UART;
+ value &= ~CON_MANUAL_SW;
+ } else if (!strncmp(buf, "AUDIO", 5)) {
+ path = SW_AUDIO;
+ value &= ~CON_MANUAL_SW;
+ } else if (!strncmp(buf, "DHOST", 5)) {
+ path = SW_DHOST;
+ value &= ~CON_MANUAL_SW;
+ } else if (!strncmp(buf, "AUTO", 4)) {
+ path = SW_AUTO;
+ value |= CON_MANUAL_SW;
+ } else {
+ dev_err(dev, "Wrong command\n");
+ return 0;
+ }
+
+ usbsw->mansw = path;
+
+ ret = i2c_smbus_write_byte_data(client, FSA9480_REG_MANSW1, path);
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ ret = i2c_smbus_write_byte_data(client, FSA9480_REG_CTRL, value);
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return count;
+}
+
+static DEVICE_ATTR(control, S_IRUGO, fsa9480_show_control, NULL);
+static DEVICE_ATTR(device_type, S_IRUGO, fsa9480_show_device_type, NULL);
+static DEVICE_ATTR(switch, S_IRUGO | S_IWUSR,
+ fsa9480_show_manualsw, fsa9480_set_manualsw);
+
+static struct attribute *fsa9480_attributes[] = {
+ &dev_attr_control.attr,
+ &dev_attr_device_type.attr,
+ &dev_attr_switch.attr,
+ NULL
+};
+
+static const struct attribute_group fsa9480_group = {
+ .attrs = fsa9480_attributes,
+};
+
+static int cardock_enable = 0;
+static int deskdock_enable = 0;
+
+static ssize_t cardock_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf)
+{
+ return sprintf(buf, "%d\n", cardock_enable);
+}
+static ssize_t cardock_enable_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ sscanf(buf, "%d\n", &cardock_enable);
+ return size;
+}
+
+static ssize_t deskdock_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf)
+{
+ return sprintf(buf, "%d\n", deskdock_enable);
+}
+static ssize_t deskdock_enable_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ sscanf(buf, "%d\n", &deskdock_enable);
+ return size;
+}
+
+static DEVICE_ATTR(cardock_enable, S_IRUGO | S_IWUGO,
+ cardock_enable_show, cardock_enable_set);
+static DEVICE_ATTR(deskdock_enable, S_IRUGO | S_IWUGO,
+ deskdock_enable_show, deskdock_enable_set);
+
+static struct attribute *dockaudio_attributes[] = {
+ &dev_attr_cardock_enable,
+ &dev_attr_deskdock_enable,
+ NULL
+};
+
+static struct attribute_group dockaudio_group = {
+ .attrs = dockaudio_attributes,
+};
+
+static struct miscdevice dockaudio_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "dockaudio",
+};
+
+int cardock_status = 0;
+int deskdock_status = 0;
+
+static void fsa9480_detect_dev(struct fsa9480_usbsw *usbsw)
+{
+ int device_type, ret;
+ unsigned char val1, val2;
+ struct fsa9480_platform_data *pdata = usbsw->pdata;
+ struct i2c_client *client = usbsw->client;
+
+ device_type = i2c_smbus_read_word_data(client, FSA9480_REG_DEV_T1);
+ if (device_type < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, device_type);
+
+ val1 = device_type & 0xff;
+ val2 = device_type >> 8;
+
+ dev_info(&client->dev, "dev1: 0x%x, dev2: 0x%x\n", val1, val2);
+
+ /* Attached */
+ if (val1 || val2) {
+ /* USB */
+ if (val1 & DEV_T1_USB_MASK || val2 & DEV_T2_USB_MASK) {
+ if (pdata->usb_cb)
+ pdata->usb_cb(FSA9480_ATTACHED);
+ if (usbsw->mansw) {
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_MANSW1, usbsw->mansw);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+ }
+ /* UART */
+ } else if (val1 & DEV_T1_UART_MASK || val2 & DEV_T2_UART_MASK) {
+ if (pdata->uart_cb)
+ pdata->uart_cb(FSA9480_ATTACHED);
+
+ if (usbsw->mansw) {
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_MANSW1, SW_UART);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+ }
+ /* CHARGER */
+ } else if (val1 & DEV_T1_CHARGER_MASK) {
+ if (pdata->charger_cb)
+ pdata->charger_cb(FSA9480_ATTACHED);
+ /* JIG */
+ } else if (val2 & DEV_T2_JIG_MASK) {
+ if (pdata->jig_cb)
+ pdata->jig_cb(FSA9480_ATTACHED);
+ /* Desk Dock */
+ } else if (val2 & DEV_AV) {
+ if (pdata->deskdock_cb)
+ pdata->deskdock_cb(FSA9480_ATTACHED);
+ deskdock_status = 1;
+
+#if defined(CONFIG_MACH_ARIES)
+#if defined(CONFIG_SAMSUNG_CAPTIVATE) || defined(CONFIG_SAMSUNG_FASCINATE)
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_MANSW1, SW_AUDIO);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+#else
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_MANSW1, SW_VAUDIO);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+#endif
+#else
+
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_MANSW1, SW_DHOST);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+#endif
+
+ ret = i2c_smbus_read_byte_data(client,
+ FSA9480_REG_CTRL);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_CTRL, ret & ~CON_MANUAL_SW);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+ /* Car Dock */
+ } else if (val2 & DEV_JIG_UART_ON) {
+ if (pdata->cardock_cb)
+ pdata->cardock_cb(FSA9480_ATTACHED);
+ cardock_status = 1;
+
+#if defined(CONFIG_MACH_ARIES)
+#if defined(CONFIG_SAMSUNG_CAPTIVATE) || defined(CONFIG_SAMSUNG_FASCINATE)
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_MANSW1, SW_AUDIO);
+
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+#else
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_MANSW1, SW_VAUDIO);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+#endif
+ ret = i2c_smbus_read_byte_data(client,
+ FSA9480_REG_CTRL);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_CTRL, ret & ~CON_MANUAL_SW);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+#endif
+
+ }
+ /* Detached */
+ } else {
+ /* USB */
+ if (usbsw->dev1 & DEV_T1_USB_MASK ||
+ usbsw->dev2 & DEV_T2_USB_MASK) {
+ if (pdata->usb_cb)
+ pdata->usb_cb(FSA9480_DETACHED);
+ /* UART */
+ } else if (usbsw->dev1 & DEV_T1_UART_MASK ||
+ usbsw->dev2 & DEV_T2_UART_MASK) {
+ if (pdata->uart_cb)
+ pdata->uart_cb(FSA9480_DETACHED);
+ /* CHARGER */
+ } else if (usbsw->dev1 & DEV_T1_CHARGER_MASK) {
+ if (pdata->charger_cb)
+ pdata->charger_cb(FSA9480_DETACHED);
+ /* JIG */
+ } else if (usbsw->dev2 & DEV_T2_JIG_MASK) {
+ if (pdata->jig_cb)
+ pdata->jig_cb(FSA9480_DETACHED);
+ /* Desk Dock */
+ } else if (usbsw->dev2 & DEV_AV) {
+ if (pdata->deskdock_cb)
+ pdata->deskdock_cb(FSA9480_DETACHED);
+ deskdock_status = 0;
+
+ ret = i2c_smbus_read_byte_data(client,
+ FSA9480_REG_CTRL);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_CTRL, ret | CON_MANUAL_SW);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+ /* Car Dock */
+ } else if (usbsw->dev2 & DEV_JIG_UART_ON) {
+ if (pdata->cardock_cb)
+ pdata->cardock_cb(FSA9480_DETACHED);
+ cardock_status = 0;
+
+#if defined(CONFIG_MACH_ARIES)
+ ret = i2c_smbus_read_byte_data(client,
+ FSA9480_REG_CTRL);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+
+ ret = i2c_smbus_write_byte_data(client,
+ FSA9480_REG_CTRL, ret | CON_MANUAL_SW);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "%s: err %d\n", __func__, ret);
+#endif
+
+ }
+ }
+
+ usbsw->dev1 = val1;
+ usbsw->dev2 = val2;
+}
+
+int fsa9480_get_dock_status(void)
+{
+ if ((cardock_status && cardock_enable) ||
+ (deskdock_status && deskdock_enable))
+ return 1;
+ else
+ return 0;
+}
+EXPORT_SYMBOL(fsa9480_get_dock_status);
+
+static void fsa9480_reg_init(struct fsa9480_usbsw *usbsw)
+{
+ struct i2c_client *client = usbsw->client;
+ unsigned int ctrl = CON_MASK;
+ int ret;
+
+ /* mask interrupts (unmask attach/detach only) */
+ ret = i2c_smbus_write_word_data(client, FSA9480_REG_INT1_MASK, 0x1ffc);
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ /* mask all car kit interrupts */
+ ret = i2c_smbus_write_word_data(client, FSA9480_REG_CK_INTMASK1, 0x07ff);
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ /* ADC Detect Time: 500ms */
+ ret = i2c_smbus_write_byte_data(client, FSA9480_REG_TIMING1, 0x6);
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ usbsw->mansw = i2c_smbus_read_byte_data(client, FSA9480_REG_MANSW1);
+ if (usbsw->mansw < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, usbsw->mansw);
+
+ if (usbsw->mansw)
+ ctrl &= ~CON_MANUAL_SW; /* Manual Switching Mode */
+
+ ret = i2c_smbus_write_byte_data(client, FSA9480_REG_CTRL, ctrl);
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+}
+
+static irqreturn_t fsa9480_irq_thread(int irq, void *data)
+{
+ struct fsa9480_usbsw *usbsw = data;
+ struct i2c_client *client = usbsw->client;
+ int intr;
+ int max_events = 100;
+ int events_seen = 0;
+
+ /*
+ * the fsa could have queued up a few events if we haven't processed
+ * them promptly
+ */
+ while (max_events-- > 0) {
+ intr = i2c_smbus_read_word_data(client, FSA9480_REG_INT1);
+ if (intr < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, intr);
+ else if (intr == 0)
+ break;
+ else if (intr > 0)
+ events_seen++;
+ }
+ if (!max_events)
+ dev_warn(&client->dev, "too many events. fsa hosed?\n");
+
+ if (!events_seen) {
+ /*
+ * interrupt was fired, but no status bits were set,
+ * so device was reset. In this case, the registers were
+ * reset to defaults so they need to be reinitialised.
+ */
+ fsa9480_reg_init(usbsw);
+ }
+
+ /*
+ * fsa may take some time to update the dev_type reg after reading
+ * the int reg.
+ */
+ usleep_range(200, 300);
+
+ /* device detection */
+ fsa9480_detect_dev(usbsw);
+
+ return IRQ_HANDLED;
+}
+
+static int fsa9480_irq_init(struct fsa9480_usbsw *usbsw)
+{
+ struct i2c_client *client = usbsw->client;
+ int ret;
+
+ if (client->irq) {
+ ret = request_threaded_irq(client->irq, NULL,
+ fsa9480_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "fsa9480 micro USB", usbsw);
+ if (ret) {
+ dev_err(&client->dev, "failed to reqeust IRQ\n");
+ return ret;
+ }
+
+ ret = enable_irq_wake(client->irq);
+ if (ret < 0)
+ dev_err(&client->dev,
+ "failed to enable wakeup src %d\n", ret);
+ }
+
+ return 0;
+}
+
+static int __devinit fsa9480_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct fsa9480_usbsw *usbsw;
+ int ret = 0;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EIO;
+
+ usbsw = kzalloc(sizeof(struct fsa9480_usbsw), GFP_KERNEL);
+ if (!usbsw) {
+ dev_err(&client->dev, "failed to allocate driver data\n");
+ return -ENOMEM;
+ }
+
+ usbsw->client = client;
+ usbsw->pdata = client->dev.platform_data;
+ if (!usbsw->pdata)
+ goto fail1;
+
+ i2c_set_clientdata(client, usbsw);
+
+ if (usbsw->pdata->cfg_gpio)
+ usbsw->pdata->cfg_gpio();
+
+ fsa9480_reg_init(usbsw);
+
+ ret = fsa9480_irq_init(usbsw);
+ if (ret)
+ goto fail1;
+
+ ret = sysfs_create_group(&client->dev.kobj, &fsa9480_group);
+ if (ret) {
+ dev_err(&client->dev,
+ "failed to create fsa9480 attribute group\n");
+ goto fail2;
+ }
+
+ if (usbsw->pdata->reset_cb)
+ usbsw->pdata->reset_cb();
+
+ /* device detection */
+ fsa9480_detect_dev(usbsw);
+
+#if defined(CONFIG_SAMSUNG_CAPTIVATE) || defined(CONFIG_SAMSUNG_FASCINATE)
+ if (misc_register(&dockaudio_device))
+ printk("%s misc_register(%s) failed\n", __FUNCTION__, dockaudio_device.name);
+ else {
+ if (sysfs_create_group(&dockaudio_device.this_device->kobj, &dockaudio_group) < 0)
+ dev_err("failed to create sysfs group for device %s\n", dockaudio_device.name);
+ }
+#endif
+
+ return 0;
+
+fail2:
+ if (client->irq)
+ free_irq(client->irq, usbsw);
+fail1:
+ i2c_set_clientdata(client, NULL);
+ kfree(usbsw);
+ return ret;
+}
+
+static int __devexit fsa9480_remove(struct i2c_client *client)
+{
+ struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client);
+
+#if defined(CONFIG_SAMSUNG_CAPTIVATE) || defined(CONFIG_SAMSUNG_FASCINATE)
+ misc_deregister(&dockaudio_device);
+#endif
+
+ if (client->irq) {
+ disable_irq_wake(client->irq);
+ free_irq(client->irq, usbsw);
+ }
+ i2c_set_clientdata(client, NULL);
+
+ sysfs_remove_group(&client->dev.kobj, &fsa9480_group);
+ kfree(usbsw);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsa9480_resume(struct i2c_client *client)
+{
+ struct fsa9480_usbsw *usbsw = i2c_get_clientdata(client);
+
+ if (client->irq)
+ enable_irq(client->irq);
+ /* device detection */
+ fsa9480_detect_dev(usbsw);
+
+ return 0;
+}
+
+static int fsa9480_suspend(struct i2c_client *client, pm_message_t state)
+{
+ if (client->irq)
+ disable_irq(client->irq);
+
+ return 0;
+}
+
+#else
+
+#define fsa9480_suspend NULL
+#define fsa9480_resume NULL
+
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id fsa9480_id[] = {
+ {"fsa9480", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, fsa9480_id);
+
+static struct i2c_driver fsa9480_i2c_driver = {
+ .driver = {
+ .name = "fsa9480",
+ },
+ .probe = fsa9480_probe,
+ .remove = __devexit_p(fsa9480_remove),
+ .resume = fsa9480_resume,
+ .suspend = fsa9480_suspend,
+ .id_table = fsa9480_id,
+};
+
+static int __init fsa9480_init(void)
+{
+ return i2c_add_driver(&fsa9480_i2c_driver);
+}
+module_init(fsa9480_init);
+
+static void __exit fsa9480_exit(void)
+{
+ i2c_del_driver(&fsa9480_i2c_driver);
+}
+module_exit(fsa9480_exit);
+
+MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>");
+MODULE_DESCRIPTION("FSA9480 USB Switch driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/kr3dm.c b/drivers/misc/kr3dm.c
new file mode 100644
index 0000000..09bc945
--- /dev/null
+++ b/drivers/misc/kr3dm.c
@@ -0,0 +1,508 @@
+/*
+ * STMicroelectronics kr3dm acceleration sensor driver
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/kr3dm.h>
+#include <linux/delay.h>
+#include <linux/completion.h>
+#include "kr3dm_reg.h"
+
+#define kr3dm_dbgmsg(str, args...) pr_debug("%s: " str, __func__, ##args)
+
+/* The default settings when sensor is on is for all 3 axis to be enabled
+ * and output data rate set to 400Hz. Output is via a ioctl read call.
+ * The ioctl blocks on data_ready completion.
+ * The sensor generates an interrupt when the output is ready and the
+ * irq handler atomically sets the completion and wakes any
+ * blocked reader.
+ */
+#define DEFAULT_POWER_ON_SETTING (ODR400 | ENABLE_ALL_AXES)
+#define READ_REPEAT_SHIFT 3
+#define READ_REPEAT (1 << READ_REPEAT_SHIFT)
+
+static const struct odr_delay {
+ u8 odr; /* odr reg setting */
+ s64 delay_ns; /* odr in ns */
+} odr_delay_table[] = {
+ { ODR400, 2500000LL << READ_REPEAT_SHIFT }, /* 400Hz */
+ { ODR100, 10000000LL << READ_REPEAT_SHIFT }, /* 100Hz */
+ { ODR50, 20000000LL << READ_REPEAT_SHIFT }, /* 50Hz */
+ { ODR10, 100000000LL << READ_REPEAT_SHIFT }, /* 10Hz */
+ { ODR5, 200000000LL << READ_REPEAT_SHIFT }, /* 5Hz */
+ { ODR2, 500000000LL << READ_REPEAT_SHIFT }, /* 2Hz */
+ { ODR1, 1000000000LL << READ_REPEAT_SHIFT }, /* 1Hz */
+ { ODRHALF, 2000000000LL << READ_REPEAT_SHIFT }, /* 0.5Hz */
+};
+
+/* KR3DM acceleration data */
+struct kr3dm_acc {
+ s8 x;
+ s8 y;
+ s8 z;
+};
+
+struct kr3dm_data {
+ struct i2c_client *client;
+ struct miscdevice kr3dm_device;
+ struct kr3dm_platform_data *pdata;
+ int irq;
+ u8 ctrl_reg1_shadow;
+ struct completion data_ready;
+ atomic_t opened; /* opened implies enabled */
+ struct mutex read_lock;
+ struct mutex write_lock;
+};
+
+static void kr3dm_disable_irq(struct kr3dm_data *kr3dm)
+{
+ disable_irq(kr3dm->irq);
+ if (try_wait_for_completion(&kr3dm->data_ready)) {
+ /* we actually got the interrupt before we could disable it
+ * so we need to enable again to undo our disable and the
+ * one done in the irq_handler
+ */
+ enable_irq(kr3dm->irq);
+ }
+}
+
+static irqreturn_t kr3dm_irq_handler(int irq, void *data)
+{
+ struct kr3dm_data *kr3dm = data;
+ disable_irq_nosync(irq);
+ complete(&kr3dm->data_ready);
+ return IRQ_HANDLED;
+}
+
+static int kr3dm_wait_for_data_ready(struct kr3dm_data *kr3dm)
+{
+ int err;
+
+ if (gpio_get_value(kr3dm->pdata->gpio_acc_int))
+ return 0;
+
+ enable_irq(kr3dm->irq);
+
+ err = wait_for_completion_timeout(&kr3dm->data_ready, 5*HZ);
+ if (err > 0)
+ return 0;
+
+ kr3dm_disable_irq(kr3dm);
+
+ if (err == 0) {
+ pr_err("kr3dm: wait timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ pr_err("kr3dm: wait restart\n");
+ return err;
+}
+
+/* Read X,Y and Z-axis acceleration data. Blocks until there is
+ * something to read, based on interrupt from chip.
+ */
+static int kr3dm_read_accel_xyz(struct kr3dm_data *kr3dm,
+ struct kr3dm_acc *acc)
+{
+ int err;
+ s8 reg = OUT_X | AC; /* read from OUT_X to OUT_Z by auto-inc */
+ s8 acc_data[5];
+
+ err = kr3dm_wait_for_data_ready(kr3dm);
+ if (err)
+ return err;
+
+ /* OUT_X, OUT_Y, and OUT_Z are single byte registers at
+ * address 0x29, 0x2B, and 0x2D respectively, with 1 dummy
+ * register in between. Rather than doing 3 separate i2c reads,
+ * we do one multi-byte read and just use the bytes we want.
+ */
+ err = i2c_smbus_read_i2c_block_data(kr3dm->client, reg,
+ sizeof(acc_data), acc_data);
+ if (err != sizeof(acc_data)) {
+ pr_err("%s : failed to read 5 bytes for getting x/y/z\n",
+ __func__);
+ return -EIO;
+ }
+
+ if (kr3dm->pdata->rotation) {
+ acc->x = acc_data[0] * kr3dm->pdata->rotation[0] +
+ acc_data[2] * kr3dm->pdata->rotation[1] +
+ acc_data[4] * kr3dm->pdata->rotation[2];
+ acc->y = acc_data[0] * kr3dm->pdata->rotation[3] +
+ acc_data[2] * kr3dm->pdata->rotation[4] +
+ acc_data[4] * kr3dm->pdata->rotation[5];
+ acc->z = acc_data[0] * kr3dm->pdata->rotation[6] +
+ acc_data[2] * kr3dm->pdata->rotation[7] +
+ acc_data[4] * kr3dm->pdata->rotation[8];
+ } else {
+ acc->x = acc_data[0];
+ acc->y = acc_data[2];
+ acc->z = acc_data[4];
+ }
+
+ return 0;
+}
+
+/* open command for KR3DM device file */
+static int kr3dm_open(struct inode *inode, struct file *file)
+{
+ int err = 0;
+ struct kr3dm_data *kr3dm = container_of(file->private_data,
+ struct kr3dm_data,
+ kr3dm_device);
+
+ if (atomic_xchg(&kr3dm->opened, 1)) {
+ pr_err("kr3dm_open() called when already open\n");
+ return -EBUSY;
+ } else {
+ file->private_data = kr3dm;
+ kr3dm->ctrl_reg1_shadow = DEFAULT_POWER_ON_SETTING;
+ err = i2c_smbus_write_byte_data(kr3dm->client, CTRL_REG1,
+ DEFAULT_POWER_ON_SETTING);
+ if (err) {
+ pr_err("kr3dm_open() i2c write ctrl_reg1 failed\n");
+ atomic_set(&kr3dm->opened, 0);
+ }
+ }
+
+ return err;
+}
+
+/* release command for KR3DM device file */
+static int kr3dm_close(struct inode *inode, struct file *file)
+{
+ int err;
+ struct kr3dm_data *kr3dm = file->private_data;
+
+ err = i2c_smbus_write_byte_data(kr3dm->client, CTRL_REG1, PM_OFF);
+ atomic_set(&kr3dm->opened, 0);
+ kr3dm->ctrl_reg1_shadow = PM_OFF;
+
+ return err;
+}
+
+static s64 kr3dm_get_delay(struct kr3dm_data *kr3dm)
+{
+ int i;
+ u8 odr;
+ s64 delay = -1;
+
+ odr = kr3dm->ctrl_reg1_shadow & ODR_MASK;
+ for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) {
+ if (odr == odr_delay_table[i].odr) {
+ delay = odr_delay_table[i].delay_ns;
+ break;
+ }
+ }
+ return delay;
+}
+
+static int kr3dm_set_delay(struct kr3dm_data *kr3dm, s64 delay_ns)
+{
+ int odr_value = ODRHALF;
+ int res = 0;
+ int i;
+ /* round to the nearest delay that is less than
+ * the requested value (next highest freq)
+ */
+ kr3dm_dbgmsg(" passed %lldns\n", delay_ns);
+ for (i = 0; i < ARRAY_SIZE(odr_delay_table); i++) {
+ if (delay_ns < odr_delay_table[i].delay_ns)
+ break;
+ }
+ if (i > 0)
+ i--;
+ kr3dm_dbgmsg("matched rate %lldns, odr = 0x%x\n",
+ odr_delay_table[i].delay_ns,
+ odr_delay_table[i].odr);
+ odr_value = odr_delay_table[i].odr;
+ delay_ns = odr_delay_table[i].delay_ns;
+ mutex_lock(&kr3dm->write_lock);
+ kr3dm_dbgmsg("old = %lldns, new = %lldns\n",
+ kr3dm_get_delay(kr3dm), delay_ns);
+ if (odr_value != (kr3dm->ctrl_reg1_shadow & ODR_MASK)) {
+ u8 ctrl = (kr3dm->ctrl_reg1_shadow & ~ODR_MASK);
+ ctrl |= odr_value;
+ kr3dm->ctrl_reg1_shadow = ctrl;
+ res = i2c_smbus_write_byte_data(kr3dm->client, CTRL_REG1, ctrl);
+ kr3dm_dbgmsg("writing odr value 0x%x\n", odr_value);
+ }
+ mutex_unlock(&kr3dm->write_lock);
+ return res;
+}
+
+/* ioctl command for KR3DM device file */
+static long kr3dm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ struct kr3dm_data *kr3dm = file->private_data;
+ s64 delay_ns;
+ struct kr3dm_acc data;
+ int i;
+ struct kr3dm_acceldata sum = { 0, };
+
+ /* cmd mapping */
+ switch (cmd) {
+ case KR3DM_IOCTL_SET_DELAY:
+ if (copy_from_user(&delay_ns, (void __user *)arg,
+ sizeof(delay_ns)))
+ return -EFAULT;
+ err = kr3dm_set_delay(kr3dm, delay_ns);
+ break;
+ case KR3DM_IOCTL_GET_DELAY:
+ delay_ns = kr3dm_get_delay(kr3dm);
+ if (put_user(delay_ns, (s64 __user *)arg))
+ return -EFAULT;
+ break;
+ case KR3DM_IOCTL_READ_ACCEL_XYZ:
+ mutex_lock(&kr3dm->read_lock);
+ for (i = 0; i < READ_REPEAT; i++) {
+ err = kr3dm_read_accel_xyz(kr3dm, &data);
+ if (err)
+ break;
+ sum.x += data.x;
+ sum.y += data.y;
+ sum.z += data.z;
+ }
+ mutex_unlock(&kr3dm->read_lock);
+ if (err)
+ return err;
+ if (copy_to_user((void __user *)arg, &sum, sizeof(sum)))
+ return -EFAULT;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static int kr3dm_suspend(struct device *dev)
+{
+ int res = 0;
+ struct kr3dm_data *kr3dm = dev_get_drvdata(dev);
+
+ if (atomic_read(&kr3dm->opened))
+ res = i2c_smbus_write_byte_data(kr3dm->client,
+ CTRL_REG1, PM_OFF);
+
+ return res;
+}
+
+static int kr3dm_resume(struct device *dev)
+{
+ int res = 0;
+ struct kr3dm_data *kr3dm = dev_get_drvdata(dev);
+
+ if (atomic_read(&kr3dm->opened))
+ res = i2c_smbus_write_byte_data(kr3dm->client, CTRL_REG1,
+ kr3dm->ctrl_reg1_shadow);
+
+ return res;
+}
+
+
+static const struct dev_pm_ops kr3dm_pm_ops = {
+ .suspend = kr3dm_suspend,
+ .resume = kr3dm_resume,
+};
+
+static const struct file_operations kr3dm_fops = {
+ .owner = THIS_MODULE,
+ .open = kr3dm_open,
+ .release = kr3dm_close,
+ .unlocked_ioctl = kr3dm_ioctl,
+};
+
+static int kr3dm_setup_irq(struct kr3dm_data *kr3dm)
+{
+ int rc = -EIO;
+ struct kr3dm_platform_data *pdata = kr3dm->pdata;
+ int irq;
+
+ rc = gpio_request(pdata->gpio_acc_int, "gpio_acc_int");
+ if (rc < 0) {
+ pr_err("%s: gpio %d request failed (%d)\n",
+ __func__, pdata->gpio_acc_int, rc);
+ return rc;
+ }
+
+ rc = gpio_direction_input(pdata->gpio_acc_int);
+ if (rc < 0) {
+ pr_err("%s: failed to set gpio %d as input (%d)\n",
+ __func__, pdata->gpio_acc_int, rc);
+ goto err_gpio_direction_input;
+ }
+
+ /* configure INT1 to deliver data ready interrupt */
+ rc = i2c_smbus_write_byte_data(kr3dm->client, CTRL_REG3, I1_CFG_DR);
+ if (rc) {
+ pr_err("%s: CTRL_REG3 write failed with error %d\n",
+ __func__, rc);
+ goto err_i2c_write_failed;
+ }
+
+ irq = gpio_to_irq(pdata->gpio_acc_int);
+
+ /* trigger high so we don't miss initial interrupt if it
+ * is already pending
+ */
+ rc = request_irq(irq, kr3dm_irq_handler, IRQF_TRIGGER_HIGH,
+ "acc_int", kr3dm);
+ if (rc < 0) {
+ pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
+ __func__, irq,
+ pdata->gpio_acc_int, rc);
+ goto err_request_irq;
+ }
+
+ /* start with interrupt disabled until the driver is enabled */
+ kr3dm->irq = irq;
+ kr3dm_disable_irq(kr3dm);
+
+ goto done;
+
+err_request_irq:
+err_i2c_write_failed:
+err_gpio_direction_input:
+ gpio_free(pdata->gpio_acc_int);
+done:
+ return rc;
+}
+
+static int kr3dm_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct kr3dm_data *kr3dm;
+ int err;
+ struct kr3dm_platform_data *pdata = client->dev.platform_data;
+
+ if (!pdata) {
+ pr_err("%s: missing pdata!\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+ pr_err("%s: i2c functionality check failed!\n", __func__);
+ err = -ENODEV;
+ goto exit;
+ }
+
+ kr3dm = kzalloc(sizeof(struct kr3dm_data), GFP_KERNEL);
+ if (kr3dm == NULL) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ kr3dm->client = client;
+ kr3dm->pdata = pdata;
+ i2c_set_clientdata(client, kr3dm);
+
+ init_completion(&kr3dm->data_ready);
+ mutex_init(&kr3dm->read_lock);
+ mutex_init(&kr3dm->write_lock);
+ atomic_set(&kr3dm->opened, 0);
+
+ err = kr3dm_setup_irq(kr3dm);
+ if (err) {
+ pr_err("%s: could not setup irq\n", __func__);
+ goto err_setup_irq;
+ }
+
+ /* sensor HAL expects to find /dev/accelerometer */
+ kr3dm->kr3dm_device.minor = MISC_DYNAMIC_MINOR;
+ kr3dm->kr3dm_device.name = "accelerometer";
+ kr3dm->kr3dm_device.fops = &kr3dm_fops;
+
+ err = misc_register(&kr3dm->kr3dm_device);
+ if (err) {
+ pr_err("%s: misc_register failed\n", __FILE__);
+ goto err_misc_register;
+ }
+
+ return 0;
+
+err_misc_register:
+ free_irq(kr3dm->irq, kr3dm);
+ gpio_free(kr3dm->pdata->gpio_acc_int);
+err_setup_irq:
+ mutex_destroy(&kr3dm->read_lock);
+ mutex_destroy(&kr3dm->write_lock);
+ kfree(kr3dm);
+exit:
+ return err;
+}
+
+static int kr3dm_remove(struct i2c_client *client)
+{
+ struct kr3dm_data *kr3dm = i2c_get_clientdata(client);
+
+ misc_deregister(&kr3dm->kr3dm_device);
+ free_irq(kr3dm->irq, kr3dm);
+ gpio_free(kr3dm->pdata->gpio_acc_int);
+ mutex_destroy(&kr3dm->read_lock);
+ mutex_destroy(&kr3dm->write_lock);
+ kfree(kr3dm);
+
+ return 0;
+}
+
+static const struct i2c_device_id kr3dm_id[] = {
+ { "kr3dm", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, kr3dm_id);
+
+static struct i2c_driver kr3dm_driver = {
+ .probe = kr3dm_probe,
+ .remove = __devexit_p(kr3dm_remove),
+ .id_table = kr3dm_id,
+ .driver = {
+ .pm = &kr3dm_pm_ops,
+ .owner = THIS_MODULE,
+ .name = "kr3dm",
+ },
+};
+
+static int __init kr3dm_init(void)
+{
+ return i2c_add_driver(&kr3dm_driver);
+}
+
+static void __exit kr3dm_exit(void)
+{
+ i2c_del_driver(&kr3dm_driver);
+}
+
+module_init(kr3dm_init);
+module_exit(kr3dm_exit);
+
+MODULE_DESCRIPTION("kr3dm accelerometer driver");
+MODULE_AUTHOR("Tim SK Lee Samsung Electronics <tim.sk.lee@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/kr3dm_reg.h b/drivers/misc/kr3dm_reg.h
new file mode 100644
index 0000000..8c57a38
--- /dev/null
+++ b/drivers/misc/kr3dm_reg.h
@@ -0,0 +1,169 @@
+/*
+ * STMicroelectronics kr3dm acceleration sensor driver
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ *
+ * 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.
+ */
+
+
+/* kr3dm i2c slave address & etc */
+#define KR3DM_I2C_ADDR 0x09
+/* kr3dm registers */
+#define WHO_AM_I 0x0F
+#define CTRL_REG1 0x20 /* power control reg */
+#define CTRL_REG2 0x21 /* power control reg */
+#define CTRL_REG3 0x22 /* power control reg */
+#define CTRL_REG4 0x23 /* interrupt control reg */
+#define CTRL_REG5 0x24 /* interrupt control reg */
+#define STATUS_REG 0x27
+#define AXISDATA_REG 0x28
+#define OUT_X 0x29
+#define OUT_Y 0x2B
+#define OUT_Z 0x2D
+#define INT1_CFG 0x30
+#define INT1_SOURCE 0x31
+#define INT1_THS 0x32
+#define INT1_DURATION 0x33
+#define INT2_CFG 0x34
+#define INT2_SOURCE 0x35
+#define INT2_THS 0x36
+#define INT2_DURATION 0x37
+
+#define KR3DM_G_2G 0x00
+#define KR3DM_G_4G 0x10
+#define KR3DM_G_8G 0x30
+
+/* CTRL_REG1 */
+/* ctrl 1: pm2 pm1 pm0 dr1 dr0 x-enable y-enable z-enable */
+#define PM_OFF 0x00
+#define ENABLE_ALL_AXES 0x07
+
+#define ODRHALF 0x40 /* 0.5Hz output data rate */
+#define ODR1 0x60 /* 1Hz output data rate */
+#define ODR2 0x80 /* 2Hz output data rate */
+#define ODR5 0xA0 /* 5Hz output data rate */
+#define ODR10 0xC0 /* 10Hz output data rate */
+#define ODR50 0x20 /* 50Hz output data rate */
+#define ODR100 0x28 /* 100Hz output data rate */
+#define ODR400 0x30 /* 400Hz output data rate */
+
+#define ODR_MASK 0xf8
+
+#define CTRL_REG1_PM2 (1 << 7)
+#define CTRL_REG1_PM1 (1 << 6)
+#define CTRL_REG1_PM0 (1 << 5)
+#define CTRL_REG1_DR1 (1 << 4)
+#define CTRL_REG1_DR0 (1 << 3)
+#define CTRL_REG1_Zen (1 << 2)
+#define CTRL_REG1_Yen (1 << 1)
+#define CTRL_REG1_Xen (1 << 0)
+
+#define PM_down 0x00
+#define PM_Normal (CTRL_REG1_PM0)
+#define PM_Low05 (CTRL_REG1_PM1)
+#define PM_Low1 (CTRL_REG1_PM1|CTRL_REG1_PM0)
+#define PM_Low2 (CTRL_REG1_PM2)
+#define PM_Low5 (CTRL_REG1_PM2|CTRL_REG1_PM0)
+#define PM_Low10 (CTRL_REG1_PM2|CTRL_REG1_PM1)
+
+/* CTRL_REG2 */
+#define CTRL_REG2_BOOT (1 << 7)
+#define CTRL_REG2_HPM1 (1 << 6)
+#define CTRL_REG2_HPM0 (1 << 5)
+#define CTRL_REG2_FDS (1 << 4)
+#define CTRL_REG2_HPen2 (1 << 3)
+#define CTRL_REG2_HPen1 (1 << 2)
+#define CTRL_REG2_HPCF1 (1 << 1)
+#define CTRL_REG2_HPCF0 (1 << 0)
+
+#define HPM_Normal (CTRL_REG2_HPM1)
+#define HPM_Filter (CTRL_REG2_HPM0)
+
+#define HPCF_ft8 0x00
+#define HPCF_ft4 (CTRL_REG2_HPCF0)
+#define HPCF_ft2 (CTRL_REG2_HPCF1)
+#define HPCF_ft1 (CTRL_REG2_HPCF1|CTRL_REG2_HPCF0)
+
+/* CTRL_REG3 */
+#define CTRL_REG3_IHL (1 << 7)
+#define CTRL_REG3_PP_OD (1 << 6)
+#define CTRL_REG3_LIR2 (1 << 5)
+#define CTRL_REG3_I2_CFG1 (1 << 4)
+#define ICTRL_REG3_2_CFG0 (1 << 3)
+#define CTRL_REG3_LIR1 (1 << 2)
+#define CTRL_REG3_I1_CFG1 (1 << 1)
+#define CTRL_REG3_I1_CFG0 (1 << 0)
+
+/* Interrupt 1 (2) source */
+#define I1_CFG_SC (0x00)
+/* Interrupt 1 source OR Interrupt 2 source */
+#define I1_CFG_OR (CTRL_REG3_I1_CFG0)
+/* Data Ready */
+#define I1_CFG_DR (CTRL_REG3_I1_CFG1)
+/* Boot running */
+#define I1_CFG_BR (CTRL_REG3_I1_CFG1|CTRL_REG3_I1_CFG0)
+
+ /* Interrupt 1 (2) source */
+#define I2_CFG_SC (0x00)
+/* Interrupt 1 source OR Interrupt 2 source */
+#define I2_CFG_OR (CTRL_REG3_I2_CFG0)
+/* Data Ready */
+#define I2_CFG_DR (CTRL_REG3_I2_CFG1)
+/* Boot running */
+#define I2_CFG_BR (CTRL_REG3_I2_CFG1|CTRL_REG3_I2_CFG0)
+
+/* CTRL_REG4 */
+#define CTRL_REG4_FS1 (1 << 5)
+#define CTRL_REG4_FS0 (1 << 4)
+#define CTRL_REG4_STsign (1 << 3)
+#define CTRL_REG4_ST (1 << 1)
+#define CTRL_REG4_SIM (1 << 0)
+
+#define FS2g 0x00
+#define FS4g (CTRL_REG4_FS0)
+#define FS8g (CTRL_REG4_FS1|CTRL_REG4_FS0)
+
+/* CTRL_REG5 */
+#define CTRL_REG5_TurnOn1 (1 << 1)
+#define CTRL_REG5_TurnOn0 (1 << 0)
+
+/* STATUS_REG */
+#define ZYXOR (1 << 7)
+#define ZOR (1 << 6)
+#define YOR (1 << 5)
+#define XOR (1 << 4)
+#define ZYXDA (1 << 3)
+#define ZDA (1 << 2)
+#define YDA (1 << 1)
+#define XDA (1 << 0)
+
+/* INT1/2_CFG */
+#define INT_CFG_AOI (1 << 7)
+#define INT_CFG_6D (1 << 6)
+#define INT_CFG_ZHIE (1 << 5)
+#define INT_CFG_ZLIE (1 << 4)
+#define INT_CFG_YHIE (1 << 3)
+#define INT_CFG_YLIE (1 << 2)
+#define INT_CFG_XHIE (1 << 1)
+#define INT_CFG_XLIE (1 << 0)
+
+/* INT1/2_SRC */
+#define IA (1 << 6)
+#define ZH (1 << 5)
+#define ZL (1 << 4)
+#define YH (1 << 3)
+#define YL (1 << 2)
+#define XH (1 << 1)
+#define XL (1 << 0)
+
+/* Register Auto-increase */
+#define AC (1 << 7)
diff --git a/drivers/misc/orientation.c b/drivers/misc/orientation.c
new file mode 100755
index 0000000..052df42
--- /dev/null
+++ b/drivers/misc/orientation.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2010 Yamaha Corporation
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/* for debugging */
+#define DEBUG 0
+
+#define SENSOR_TYPE (3)
+
+#if SENSOR_TYPE == 1
+#define SENSOR_NAME "accelerometer"
+#elif SENSOR_TYPE == 2
+#define SENSOR_NAME "geomagnetic"
+#elif SENSOR_TYPE == 3
+#define SENSOR_NAME "orientation"
+#elif SENSOR_TYPE == 4
+#define SENSOR_NAME "gyroscope"
+#elif SENSOR_TYPE == 5
+#define SENSOR_NAME "light"
+#elif SENSOR_TYPE == 6
+#define SENSOR_NAME "pressure"
+#elif SENSOR_TYPE == 7
+#define SENSOR_NAME "temperature"
+#elif SENSOR_TYPE == 8
+#define SENSOR_NAME "proximity"
+#endif
+
+#define SENSOR_DEFAULT_DELAY (200) /* 200 ms */
+#define SENSOR_MAX_DELAY (2000) /* 2000 ms */
+#define ABS_STATUS (ABS_BRAKE)
+#define ABS_WAKE (ABS_MISC)
+#define ABS_CONTROL_REPORT (ABS_THROTTLE)
+
+static int suspend(void);
+static int resume(void);
+
+struct sensor_data {
+ struct mutex mutex;
+ int enabled;
+ int delay;
+#if DEBUG
+ int suspend;
+#endif
+};
+
+static struct platform_device *sensor_pdev = NULL;
+static struct input_dev *this_data = NULL;
+
+static int
+suspend(void)
+{
+ /* implement suspend of the sensor */
+ printk(KERN_DEBUG "%s: suspend\n", SENSOR_NAME);
+
+ if (strcmp(SENSOR_NAME, "gyroscope") == 0) {
+ /* suspend gyroscope */
+ }
+ else if (strcmp(SENSOR_NAME, "light") == 0) {
+ /* suspend light */
+ }
+ else if (strcmp(SENSOR_NAME, "pressure") == 0) {
+ /* suspend pressure */
+ }
+ else if (strcmp(SENSOR_NAME, "temperature") == 0) {
+ /* suspend temperature */
+ }
+ else if (strcmp(SENSOR_NAME, "proximity") == 0) {
+ /* suspend proximity */
+ }
+
+ return 0;
+}
+
+static int
+resume(void)
+{
+ /* implement resume of the sensor */
+ printk(KERN_DEBUG "%s: resume\n", SENSOR_NAME);
+
+ if (strcmp(SENSOR_NAME, "gyroscope") == 0) {
+ /* resume gyroscope */
+ }
+ else if (strcmp(SENSOR_NAME, "light") == 0) {
+ /* resume light */
+ }
+ else if (strcmp(SENSOR_NAME, "pressure") == 0) {
+ /* resume pressure */
+ }
+ else if (strcmp(SENSOR_NAME, "temperature") == 0) {
+ /* resume temperature */
+ }
+ else if (strcmp(SENSOR_NAME, "proximity") == 0) {
+ /* resume proximity */
+ }
+
+#if DEBUG
+ {
+ struct sensor_data *data = input_get_drvdata(this_data);
+ data->suspend = 0;
+ }
+#endif /* DEBUG */
+
+ return 0;
+}
+
+
+/* Sysfs interface */
+static ssize_t
+sensor_delay_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ struct sensor_data *data = input_get_drvdata(input_data);
+ int delay;
+
+ mutex_lock(&data->mutex);
+
+ delay = data->delay;
+
+ mutex_unlock(&data->mutex);
+
+ return sprintf(buf, "%d\n", delay);
+}
+
+static ssize_t
+sensor_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ struct sensor_data *data = input_get_drvdata(input_data);
+ int value = simple_strtoul(buf, NULL, 10);
+
+ if (value < 0) {
+ return count;
+ }
+
+ if (SENSOR_MAX_DELAY < value) {
+ value = SENSOR_MAX_DELAY;
+ }
+
+ mutex_lock(&data->mutex);
+
+ data->delay = value;
+
+ input_report_abs(input_data, ABS_CONTROL_REPORT, (data->enabled<<16) | value);
+
+ mutex_unlock(&data->mutex);
+
+ return count;
+}
+
+static ssize_t
+sensor_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ struct sensor_data *data = input_get_drvdata(input_data);
+ int enabled;
+
+ mutex_lock(&data->mutex);
+
+ enabled = data->enabled;
+
+ mutex_unlock(&data->mutex);
+
+ return sprintf(buf, "%d\n", enabled);
+}
+
+static ssize_t
+sensor_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ struct sensor_data *data = input_get_drvdata(input_data);
+ int value = simple_strtoul(buf, NULL, 10);
+
+ if (value != 0 && value != 1) {
+ return count;
+ }
+
+ mutex_lock(&data->mutex);
+
+ if (data->enabled && !value) {
+ suspend();
+ }
+ if (!data->enabled && value) {
+ resume();
+ }
+ data->enabled = value;
+
+ input_report_abs(input_data, ABS_CONTROL_REPORT, (value<<16) | data->delay);
+
+ mutex_unlock(&data->mutex);
+
+ return count;
+}
+
+static ssize_t
+sensor_wake_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ static int cnt = 1;
+
+ input_report_abs(input_data, ABS_WAKE, cnt++);
+
+ return count;
+}
+
+static ssize_t
+sensor_data_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ unsigned long flags;
+#if SENSOR_TYPE <= 4
+ int x, y, z;
+#else
+ int x;
+#endif
+
+ spin_lock_irqsave(&input_data->event_lock, flags);
+
+ x = input_data->abs[ABS_X];
+#if SENSOR_TYPE <= 4
+ y = input_data->abs[ABS_Y];
+ z = input_data->abs[ABS_Z];
+#endif
+
+ spin_unlock_irqrestore(&input_data->event_lock, flags);
+
+#if SENSOR_TYPE <= 4
+ return sprintf(buf, "%d %d %d\n", x, y, z);
+#else
+ return sprintf(buf, "%d\n", x);
+#endif
+}
+
+static ssize_t
+sensor_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ unsigned long flags;
+ int status;
+
+ spin_lock_irqsave(&input_data->event_lock, flags);
+
+ status = input_data->abs[ABS_STATUS];
+
+ spin_unlock_irqrestore(&input_data->event_lock, flags);
+
+ return sprintf(buf, "%d\n", status);
+}
+
+#if DEBUG
+
+static ssize_t sensor_debug_suspend_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct input_dev *input_data = to_input_dev(dev);
+ struct sensor_data *data = input_get_drvdata(input_data);
+
+ return sprintf(buf, "%d\n", data->suspend);
+}
+
+static ssize_t sensor_debug_suspend_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long value = simple_strtoul(buf, NULL, 10);
+
+ if (value) {
+ suspend();
+ } else {
+ resume();
+ }
+
+ return count;
+}
+#endif /* DEBUG */
+
+static DEVICE_ATTR(delay, S_IRUGO|S_IWUSR|S_IWGRP,
+ sensor_delay_show, sensor_delay_store);
+static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR|S_IWGRP,
+ sensor_enable_show, sensor_enable_store);
+static DEVICE_ATTR(wake, S_IWUSR|S_IWGRP,
+ NULL, sensor_wake_store);
+static DEVICE_ATTR(data, S_IRUGO, sensor_data_show, NULL);
+static DEVICE_ATTR(status, S_IRUGO, sensor_status_show, NULL);
+
+#if DEBUG
+static DEVICE_ATTR(debug_suspend, S_IRUGO|S_IWUSR,
+ sensor_debug_suspend_show, sensor_debug_suspend_store);
+#endif /* DEBUG */
+
+static struct attribute *sensor_attributes[] = {
+ &dev_attr_delay.attr,
+ &dev_attr_enable.attr,
+ &dev_attr_wake.attr,
+ &dev_attr_data.attr,
+ &dev_attr_status.attr,
+#if DEBUG
+ &dev_attr_debug_suspend.attr,
+#endif /* DEBUG */
+ NULL
+};
+
+static struct attribute_group sensor_attribute_group = {
+ .attrs = sensor_attributes
+};
+
+static int
+sensor_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct sensor_data *data = input_get_drvdata(this_data);
+ int rt = 0;
+
+ mutex_lock(&data->mutex);
+
+ if (data->enabled) {
+ rt = suspend();
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return rt;
+}
+
+static int
+sensor_resume(struct platform_device *pdev)
+{
+ struct sensor_data *data = input_get_drvdata(this_data);
+ int rt = 0;
+
+ mutex_lock(&data->mutex);
+
+ if (data->enabled) {
+ rt = resume();
+ }
+
+ mutex_unlock(&data->mutex);
+
+ return rt;
+}
+
+static int
+sensor_probe(struct platform_device *pdev)
+{
+ struct sensor_data *data = NULL;
+ struct input_dev *input_data = NULL;
+ int input_registered = 0, sysfs_created = 0;
+ int rt;
+
+ data = kzalloc(sizeof(struct sensor_data), GFP_KERNEL);
+ if (!data) {
+ rt = -ENOMEM;
+ goto err;
+ }
+ data->enabled = 0;
+ data->delay = SENSOR_DEFAULT_DELAY;
+
+ input_data = input_allocate_device();
+ if (!input_data) {
+ rt = -ENOMEM;
+ printk(KERN_ERR
+ "sensor_probe: Failed to allocate input_data device\n");
+ goto err;
+ }
+
+ set_bit(EV_ABS, input_data->evbit);
+ input_set_capability(input_data, EV_ABS, ABS_X);
+#if SENSOR_TYPE <= 4
+ input_set_capability(input_data, EV_ABS, ABS_Y);
+ input_set_capability(input_data, EV_ABS, ABS_Z);
+#endif
+ input_set_capability(input_data, EV_ABS, ABS_STATUS); /* status */
+ input_set_capability(input_data, EV_ABS, ABS_WAKE); /* wake */
+ input_set_capability(input_data, EV_ABS, ABS_CONTROL_REPORT); /* enabled/delay */
+ input_data->name = SENSOR_NAME;
+
+ rt = input_register_device(input_data);
+ if (rt) {
+ printk(KERN_ERR
+ "sensor_probe: Unable to register input_data device: %s\n",
+ input_data->name);
+ goto err;
+ }
+ input_set_drvdata(input_data, data);
+ input_registered = 1;
+
+ rt = sysfs_create_group(&input_data->dev.kobj,
+ &sensor_attribute_group);
+ if (rt) {
+ printk(KERN_ERR
+ "sensor_probe: sysfs_create_group failed[%s]\n",
+ input_data->name);
+ goto err;
+ }
+ sysfs_created = 1;
+ mutex_init(&data->mutex);
+ this_data = input_data;
+
+ return 0;
+
+err:
+ if (data != NULL) {
+ if (input_data != NULL) {
+ if (sysfs_created) {
+ sysfs_remove_group(&input_data->dev.kobj,
+ &sensor_attribute_group);
+ }
+ if (input_registered) {
+ input_unregister_device(input_data);
+ }
+ else {
+ input_free_device(input_data);
+ }
+ input_data = NULL;
+ }
+ kfree(data);
+ }
+
+ return rt;
+}
+
+static int
+sensor_remove(struct platform_device *pdev)
+{
+ struct sensor_data *data;
+
+ if (this_data != NULL) {
+ data = input_get_drvdata(this_data);
+ sysfs_remove_group(&this_data->dev.kobj,
+ &sensor_attribute_group);
+ input_unregister_device(this_data);
+ if (data != NULL) {
+ kfree(data);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Module init and exit
+ */
+static struct platform_driver sensor_driver = {
+ .probe = sensor_probe,
+ .remove = sensor_remove,
+ .suspend = sensor_suspend,
+ .resume = sensor_resume,
+ .driver = {
+ .name = SENSOR_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init sensor_init(void)
+{
+ sensor_pdev = platform_device_register_simple(SENSOR_NAME, 0, NULL, 0);
+ if (IS_ERR(sensor_pdev)) {
+ return -1;
+ }
+ return platform_driver_register(&sensor_driver);
+}
+module_init(sensor_init);
+
+static void __exit sensor_exit(void)
+{
+ platform_driver_unregister(&sensor_driver);
+ platform_device_unregister(sensor_pdev);
+}
+module_exit(sensor_exit);
+
+MODULE_AUTHOR("Yamaha Corporation");
+MODULE_LICENSE( "GPL" );
+MODULE_VERSION("1.1.0");
diff --git a/drivers/misc/pn544.c b/drivers/misc/pn544.c
new file mode 100755
index 0000000..b726036
--- /dev/null
+++ b/drivers/misc/pn544.c
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2010 Trusted Logic S.A.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/jiffies.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+#include <linux/pn544.h>
+
+#define MAX_BUFFER_SIZE 512
+
+struct pn544_dev {
+ wait_queue_head_t read_wq;
+ struct mutex read_mutex;
+ struct i2c_client *client;
+ struct miscdevice pn544_device;
+ unsigned int ven_gpio;
+ unsigned int firm_gpio;
+ unsigned int irq_gpio;
+ bool irq_enabled;
+ spinlock_t irq_enabled_lock;
+};
+
+static void pn544_disable_irq(struct pn544_dev *pn544_dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pn544_dev->irq_enabled_lock, flags);
+ if (pn544_dev->irq_enabled) {
+ disable_irq_nosync(pn544_dev->client->irq);
+ pn544_dev->irq_enabled = false;
+ }
+ spin_unlock_irqrestore(&pn544_dev->irq_enabled_lock, flags);
+}
+
+static irqreturn_t pn544_dev_irq_handler(int irq, void *dev_id)
+{
+ struct pn544_dev *pn544_dev = dev_id;
+
+ pn544_disable_irq(pn544_dev);
+
+ /* Wake up waiting readers */
+ wake_up(&pn544_dev->read_wq);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t pn544_dev_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct pn544_dev *pn544_dev = filp->private_data;
+ char tmp[MAX_BUFFER_SIZE];
+ int ret;
+
+ if (count > MAX_BUFFER_SIZE)
+ count = MAX_BUFFER_SIZE;
+
+ pr_debug("%s : reading %zu bytes.\n", __func__, count);
+
+ mutex_lock(&pn544_dev->read_mutex);
+
+ if (!gpio_get_value(pn544_dev->irq_gpio)) {
+ if (filp->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto fail;
+ }
+
+ pn544_dev->irq_enabled = true;
+ enable_irq(pn544_dev->client->irq);
+ ret = wait_event_interruptible(pn544_dev->read_wq,
+ gpio_get_value(pn544_dev->irq_gpio));
+
+ pn544_disable_irq(pn544_dev);
+
+ if (ret)
+ goto fail;
+
+ }
+
+ /* Read data */
+ ret = i2c_master_recv(pn544_dev->client, tmp, count);
+ mutex_unlock(&pn544_dev->read_mutex);
+
+ if (ret < 0) {
+ pr_err("%s: i2c_master_recv returned %d\n", __func__, ret);
+ return ret;
+ }
+ if (ret > count) {
+ pr_err("%s: received too many bytes from i2c (%d)\n",
+ __func__, ret);
+ return -EIO;
+ }
+ if (copy_to_user(buf, tmp, ret)) {
+ pr_warning("%s : failed to copy to user space\n", __func__);
+ return -EFAULT;
+ }
+ return ret;
+
+fail:
+ mutex_unlock(&pn544_dev->read_mutex);
+ return ret;
+}
+
+static ssize_t pn544_dev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct pn544_dev *pn544_dev;
+ char tmp[MAX_BUFFER_SIZE];
+ int ret;
+
+ pn544_dev = filp->private_data;
+
+ if (count > MAX_BUFFER_SIZE)
+ count = MAX_BUFFER_SIZE;
+
+ if (copy_from_user(tmp, buf, count)) {
+ pr_err("%s : failed to copy from user space\n", __func__);
+ return -EFAULT;
+ }
+
+ pr_debug("%s : writing %zu bytes.\n", __func__, count);
+ /* Write data */
+ ret = i2c_master_send(pn544_dev->client, tmp, count);
+ if (ret != count) {
+ pr_err("%s : i2c_master_send returned %d\n", __func__, ret);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static int pn544_dev_open(struct inode *inode, struct file *filp)
+{
+ struct pn544_dev *pn544_dev = container_of(filp->private_data,
+ struct pn544_dev,
+ pn544_device);
+
+ filp->private_data = pn544_dev;
+
+ pr_debug("%s : %d,%d\n", __func__, imajor(inode), iminor(inode));
+
+ return 0;
+}
+
+static long pn544_dev_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct pn544_dev *pn544_dev = filp->private_data;
+
+ switch (cmd) {
+ case PN544_SET_PWR:
+ if (arg == 2) {
+ /* power on with firmware download (requires hw reset)
+ */
+ pr_info("%s power on with firmware\n", __func__);
+ gpio_set_value(pn544_dev->ven_gpio, 1);
+ gpio_set_value(pn544_dev->firm_gpio, 1);
+ msleep(20);
+ gpio_set_value(pn544_dev->ven_gpio, 0);
+ msleep(60);
+ gpio_set_value(pn544_dev->ven_gpio, 1);
+ msleep(20);
+ } else if (arg == 1) {
+ /* power on */
+ pr_info("%s power on\n", __func__);
+ gpio_set_value(pn544_dev->firm_gpio, 0);
+ gpio_set_value(pn544_dev->ven_gpio, 1);
+ msleep(20);
+ } else if (arg == 0) {
+ /* power off */
+ pr_info("%s power off\n", __func__);
+ gpio_set_value(pn544_dev->firm_gpio, 0);
+ gpio_set_value(pn544_dev->ven_gpio, 0);
+ msleep(60);
+ } else {
+ pr_err("%s bad arg %lu\n", __func__, arg);
+ return -EINVAL;
+ }
+ break;
+ default:
+ pr_err("%s bad ioctl %u\n", __func__, cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct file_operations pn544_dev_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = pn544_dev_read,
+ .write = pn544_dev_write,
+ .open = pn544_dev_open,
+ .unlocked_ioctl = pn544_dev_ioctl,
+};
+
+static int pn544_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct pn544_i2c_platform_data *platform_data;
+ struct pn544_dev *pn544_dev;
+
+ platform_data = client->dev.platform_data;
+
+ if (platform_data == NULL) {
+ pr_err("%s : nfc probe fail\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ pr_err("%s : need I2C_FUNC_I2C\n", __func__);
+ return -ENODEV;
+ }
+
+ ret = gpio_request(platform_data->irq_gpio, "nfc_int");
+ if (ret)
+ return -ENODEV;
+ ret = gpio_request(platform_data->ven_gpio, "nfc_ven");
+ if (ret)
+ goto err_ven;
+ ret = gpio_request(platform_data->firm_gpio, "nfc_firm");
+ if (ret)
+ goto err_firm;
+
+ pn544_dev = kzalloc(sizeof(*pn544_dev), GFP_KERNEL);
+ if (pn544_dev == NULL) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ ret = -ENOMEM;
+ goto err_exit;
+ }
+
+ pn544_dev->irq_gpio = platform_data->irq_gpio;
+ pn544_dev->ven_gpio = platform_data->ven_gpio;
+ pn544_dev->firm_gpio = platform_data->firm_gpio;
+ pn544_dev->client = client;
+
+ /* init mutex and queues */
+ init_waitqueue_head(&pn544_dev->read_wq);
+ mutex_init(&pn544_dev->read_mutex);
+ spin_lock_init(&pn544_dev->irq_enabled_lock);
+
+ pn544_dev->pn544_device.minor = MISC_DYNAMIC_MINOR;
+ pn544_dev->pn544_device.name = "pn544";
+ pn544_dev->pn544_device.fops = &pn544_dev_fops;
+
+ ret = misc_register(&pn544_dev->pn544_device);
+ if (ret) {
+ pr_err("%s : misc_register failed\n", __FILE__);
+ goto err_misc_register;
+ }
+
+ /* request irq. the irq is set whenever the chip has data available
+ * for reading. it is cleared when all data has been read.
+ */
+ pr_info("%s : requesting IRQ %d\n", __func__, client->irq);
+ pn544_dev->irq_enabled = true;
+ ret = request_irq(client->irq, pn544_dev_irq_handler,
+ IRQF_TRIGGER_HIGH, client->name, pn544_dev);
+ if (ret) {
+ dev_err(&client->dev, "request_irq failed\n");
+ goto err_request_irq_failed;
+ }
+ pn544_disable_irq(pn544_dev);
+ i2c_set_clientdata(client, pn544_dev);
+
+ return 0;
+
+err_request_irq_failed:
+ misc_deregister(&pn544_dev->pn544_device);
+err_misc_register:
+ mutex_destroy(&pn544_dev->read_mutex);
+ kfree(pn544_dev);
+err_exit:
+ gpio_free(platform_data->firm_gpio);
+err_firm:
+ gpio_free(platform_data->ven_gpio);
+err_ven:
+ gpio_free(platform_data->irq_gpio);
+ return ret;
+}
+
+static int pn544_remove(struct i2c_client *client)
+{
+ struct pn544_dev *pn544_dev;
+
+ pn544_dev = i2c_get_clientdata(client);
+ free_irq(client->irq, pn544_dev);
+ misc_deregister(&pn544_dev->pn544_device);
+ mutex_destroy(&pn544_dev->read_mutex);
+ gpio_free(pn544_dev->irq_gpio);
+ gpio_free(pn544_dev->ven_gpio);
+ gpio_free(pn544_dev->firm_gpio);
+ kfree(pn544_dev);
+
+ return 0;
+}
+
+static const struct i2c_device_id pn544_id[] = {
+ { "pn544", 0 },
+ { }
+};
+
+static struct i2c_driver pn544_driver = {
+ .id_table = pn544_id,
+ .probe = pn544_probe,
+ .remove = pn544_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "pn544",
+ },
+};
+
+/*
+ * module load/unload record keeping
+ */
+
+static int __init pn544_dev_init(void)
+{
+ pr_info("Loading pn544 driver\n");
+ return i2c_add_driver(&pn544_driver);
+}
+module_init(pn544_dev_init);
+
+static void __exit pn544_dev_exit(void)
+{
+ pr_info("Unloading pn544 driver\n");
+ i2c_del_driver(&pn544_driver);
+}
+module_exit(pn544_dev_exit);
+
+MODULE_AUTHOR("Sylvain Fonteneau");
+MODULE_DESCRIPTION("NFC PN544 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/samsung_modemctl/Makefile b/drivers/misc/samsung_modemctl/Makefile
new file mode 100644
index 0000000..a533fe9
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_PHONE_ARIES) += modemctl/
+obj-$(CONFIG_PHONE_ARIES) += onedram/
+obj-$(CONFIG_PHONE_ARIES) += svnet/
+obj-$(CONFIG_PHONE_ARIES_CDMA) += dpram/
+obj-$(CONFIG_PHONE_CRESPO) += modem_ctl.o modem_io.o modem_dbg.o
diff --git a/drivers/misc/samsung_modemctl/dpram/Makefile b/drivers/misc/samsung_modemctl/dpram/Makefile
new file mode 100755
index 0000000..6fdbd20
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/dpram/Makefile
@@ -0,0 +1 @@
+obj-y += dpram.o
diff --git a/drivers/misc/samsung_modemctl/dpram/dpram.c b/drivers/misc/samsung_modemctl/dpram/dpram.c
new file mode 100755
index 0000000..8e7bf9c
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/dpram/dpram.c
@@ -0,0 +1,2513 @@
+/****************************************************************************
+**
+** COPYRIGHT(C) : Samsung Electronics Co.Ltd, 2006-2010 ALL RIGHTS RESERVED
+**
+** 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.
+**
+** Onedram Device Driver
+**
+****************************************************************************/
+
+#define _DEBUG
+/* HSDPA DUN & Internal FTP Throughput Support. @LDK@ */
+#define _HSDPA_DPRAM
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/irq.h>
+#include <linux/poll.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <mach/regs-gpio.h>
+#include <plat/gpio-cfg.h>
+#include <mach/hardware.h>
+
+#ifdef CONFIG_PROC_FS
+#include <linux/proc_fs.h>
+#endif /* CONFIG_PROC_FS */
+
+/***************************************************************************/
+/* GPIO SETTING */
+/***************************************************************************/
+#include <mach/gpio.h>
+
+#define GPIO_LEVEL_LOW 0
+#define GPIO_LEVEL_HIGH 1
+
+#define GPIO_AP_RXD S5PV210_GPA1(2)
+#define GPIO_AP_RXD_AF 0x2 // UART_2_RXD
+
+#define GPIO_AP_TXD S5PV210_GPA1(3)
+#define GPIO_AP_TXD_AF 0x2 // UART_2_TXD
+
+#define GPIO_PHONE_ON S5PV210_GPJ1(0)
+#define GPIO_PHONE_ON_AF 0x1
+
+#define GPIO_PHONE_RST_N S5PV210_GPH3(7)
+#define GPIO_PHONE_RST_N_AF 0x1
+
+#define GPIO_PDA_ACTIVE S5PV210_GPH1(0)
+#define GPIO_PDA_ACTIVE_AF 0x1
+
+#define GPIO_PHONE_ACTIVE S5PV210_GPH1(7)
+#define GPIO_PHONE_ACTIVE_AF 0xff
+
+#define GPIO_ONEDRAM_INT_N S5PV210_GPH1(3)
+#define GPIO_ONEDRAM_INT_N_AF 0xff
+
+#define IRQ_ONEDRAM_INT_N IRQ_EINT11
+#define IRQ_PHONE_ACTIVE IRQ_EINT15
+
+
+/*****************************************************************************/
+/* MULTIPDP FEATURE */
+/*****************************************************************************/
+#include <linux/miscdevice.h>
+#include <linux/netdevice.h>
+/* Device node name for application interface */
+#define APP_DEVNAME "multipdp"
+/* number of PDP context */
+#define NUM_PDP_CONTEXT 3
+
+/* Device types */
+#define DEV_TYPE_NET 0 /* network device for IP data */
+#define DEV_TYPE_SERIAL 1 /* serial device for CSD */
+
+/* Device major & minor number */
+#define CSD_MAJOR_NUM 240
+#define CSD_MINOR_NUM 0
+
+/* Maximum number of PDP context */
+#define MAX_PDP_CONTEXT 10
+
+/* Maximum PDP data length */
+#define MAX_PDP_DATA_LEN 1500
+
+/* Device flags */
+#define DEV_FLAG_STICKY 0x1 /* Sticky */
+/* Maximum PDP packet length including header and start/stop bytes */
+#define MAX_PDP_PACKET_LEN (MAX_PDP_DATA_LEN + 4 + 2)
+
+
+/* Multiple PDP */
+typedef struct pdp_arg {
+ unsigned char id;
+ char ifname[16];
+} __attribute__ ((packed)) pdp_arg_t;
+
+/* PDP data packet header format */
+struct pdp_hdr {
+ u16 len; /* Data length */
+ u8 id; /* Channel ID */
+ u8 control; /* Control field */
+} __attribute__ ((packed));
+
+/* PDP information type */
+struct pdp_info {
+ /* PDP context ID */
+ u8 id;
+
+ /* Device type */
+ unsigned type;
+
+ /* Device flags */
+ unsigned flags;
+
+ /* Tx packet buffer */
+ u8 *tx_buf;
+
+ /* App device interface */
+ union {
+ /* Virtual serial interface */
+ struct {
+ struct tty_driver tty_driver[NUM_PDP_CONTEXT]; // CSD, CDMA, TRFB, CIQ
+ int refcount;
+ struct tty_struct *tty_table[1];
+ struct ktermios *termios[1];
+ struct ktermios *termios_locked[1];
+ char tty_name[16];
+ struct tty_struct *tty;
+ struct semaphore write_lock;
+ } vs_u;
+ } dev_u;
+#define vn_dev dev_u.vnet_u
+#define vs_dev dev_u.vs_u
+};
+
+
+
+static struct pdp_info *pdp_table[MAX_PDP_CONTEXT];
+static DEFINE_MUTEX(pdp_lock);
+
+static inline struct pdp_info * pdp_get_dev(u8 id);
+static inline void check_pdp_table(const char*, int);
+
+/*****************************************************************************/
+
+#include "dpram.h"
+
+#define DRIVER_NAME "DPRAM"
+#define DRIVER_PROC_ENTRY "driver/dpram"
+#define DRIVER_MAJOR_NUM 255
+
+#ifdef _DEBUG
+#define _ENABLE_ERROR_DEVICE
+#endif
+
+#ifdef _DEBUG
+#define dprintk(s, args...) printk(KERN_ERR "[OneDRAM] %s:%d - " s, __func__, __LINE__, ##args)
+#else
+#define dprintk(s, args...)
+#endif /* _DEBUG */
+
+#define WRITE_TO_DPRAM(dest, src, size) \
+ _memcpy((void *)(DPRAM_VBASE + dest), src, size)
+
+#define READ_FROM_DPRAM(dest, src, size) \
+ _memcpy(dest, (void *)(DPRAM_VBASE + src), size)
+
+#ifdef _ENABLE_ERROR_DEVICE
+#define DPRAM_ERR_MSG_LEN 65
+#define DPRAM_ERR_DEVICE "dpramerr"
+#endif /* _ENABLE_ERROR_DEVICE */
+
+static int onedram_get_semaphore(const char*);
+static int return_onedram_semaphore(const char*);
+static void send_interrupt_to_phone_with_semaphore(u16 irq_mask);
+
+static void __iomem *dpram_base = 0;
+static unsigned int *onedram_sem;
+static unsigned int *onedram_mailboxBA; //send mail
+static unsigned int *onedram_mailboxAB; //received mail
+
+static atomic_t onedram_lock;
+static int onedram_lock_with_semaphore(const char*);
+static void onedram_release_lock(const char*);
+static void dpram_drop_data(dpram_device_t *device);
+
+static int requested_semaphore = 0;
+static int unreceived_semaphore = 0;
+static int phone_sync = 0;
+static int dump_on = 0;
+
+static int dpram_phone_getstatus(void);
+#define DPRAM_VBASE dpram_base
+static struct tty_driver *dpram_tty_driver;
+static dpram_tasklet_data_t dpram_tasklet_data[MAX_INDEX];
+static dpram_device_t dpram_table[MAX_INDEX] = {
+ {
+ .in_head_addr = DPRAM_PHONE2PDA_FORMATTED_HEAD_ADDRESS,
+ .in_tail_addr = DPRAM_PHONE2PDA_FORMATTED_TAIL_ADDRESS,
+ .in_buff_addr = DPRAM_PHONE2PDA_FORMATTED_BUFFER_ADDRESS,
+ .in_buff_size = DPRAM_PHONE2PDA_FORMATTED_BUFFER_SIZE,
+ .in_head_saved = 0,
+ .in_tail_saved = 0,
+
+ .out_head_addr = DPRAM_PDA2PHONE_FORMATTED_HEAD_ADDRESS,
+ .out_tail_addr = DPRAM_PDA2PHONE_FORMATTED_TAIL_ADDRESS,
+ .out_buff_addr = DPRAM_PDA2PHONE_FORMATTED_BUFFER_ADDRESS,
+ .out_buff_size = DPRAM_PDA2PHONE_FORMATTED_BUFFER_SIZE,
+ .out_head_saved = 0,
+ .out_tail_saved = 0,
+
+ .mask_req_ack = INT_MASK_REQ_ACK_F,
+ .mask_res_ack = INT_MASK_RES_ACK_F,
+ .mask_send = INT_MASK_SEND_F,
+ },
+ {
+ .in_head_addr = DPRAM_PHONE2PDA_RAW_HEAD_ADDRESS,
+ .in_tail_addr = DPRAM_PHONE2PDA_RAW_TAIL_ADDRESS,
+ .in_buff_addr = DPRAM_PHONE2PDA_RAW_BUFFER_ADDRESS,
+ .in_buff_size = DPRAM_PHONE2PDA_RAW_BUFFER_SIZE,
+ .in_head_saved = 0,
+ .in_tail_saved = 0,
+
+ .out_head_addr = DPRAM_PDA2PHONE_RAW_HEAD_ADDRESS,
+ .out_tail_addr = DPRAM_PDA2PHONE_RAW_TAIL_ADDRESS,
+ .out_buff_addr = DPRAM_PDA2PHONE_RAW_BUFFER_ADDRESS,
+ .out_buff_size = DPRAM_PDA2PHONE_RAW_BUFFER_SIZE,
+ .out_head_saved = 0,
+ .out_tail_saved = 0,
+
+ .mask_req_ack = INT_MASK_REQ_ACK_R,
+ .mask_res_ack = INT_MASK_RES_ACK_R,
+ .mask_send = INT_MASK_SEND_R,
+ },
+};
+
+static struct tty_struct *dpram_tty[MAX_INDEX];
+static struct ktermios *dpram_termios[MAX_INDEX];
+static struct ktermios *dpram_termios_locked[MAX_INDEX];
+struct delayed_work phone_active_delayed_work;
+
+static void res_ack_tasklet_handler(unsigned long data);
+static void fmt_rcv_tasklet_handler(unsigned long data);
+static void raw_rcv_tasklet_handler(unsigned long data);
+
+static DECLARE_TASKLET(fmt_send_tasklet, fmt_rcv_tasklet_handler, 0);
+static DECLARE_TASKLET(raw_send_tasklet, raw_rcv_tasklet_handler, 0);
+static DECLARE_TASKLET(fmt_res_ack_tasklet, res_ack_tasklet_handler,
+ (unsigned long)&dpram_table[FORMATTED_INDEX]);
+static DECLARE_TASKLET(raw_res_ack_tasklet, res_ack_tasklet_handler,
+ (unsigned long)&dpram_table[RAW_INDEX]);
+
+static void semaphore_control_handler(unsigned long data);
+static DECLARE_TASKLET(semaphore_control_tasklet, semaphore_control_handler, 0);
+
+#ifdef _ENABLE_ERROR_DEVICE
+static unsigned int dpram_err_len;
+static char dpram_err_buf[DPRAM_ERR_MSG_LEN];
+
+struct class *dpram_class;
+
+static DECLARE_WAIT_QUEUE_HEAD(dpram_err_wait_q);
+static struct fasync_struct *dpram_err_async_q;
+extern void usb_switch_mode(int);
+#endif /* _ENABLE_ERROR_DEVICE */
+
+/* tty related functions. */
+static inline void byte_align(unsigned long dest, unsigned long src)
+{
+ u16 *p_src;
+ volatile u16 *p_dest;
+
+ if (!(dest % 2) && !(src % 2)) {
+ p_dest = (u16 *)dest;
+ p_src = (u16 *)src;
+
+ *p_dest = (*p_dest & 0xFF00) | (*p_src & 0x00FF);
+ }
+
+ else if ((dest % 2) && (src % 2)) {
+ p_dest = (u16 *)(dest - 1);
+ p_src = (u16 *)(src - 1);
+
+ *p_dest = (*p_dest & 0x00FF) | (*p_src & 0xFF00);
+ }
+
+ else if (!(dest % 2) && (src % 2)) {
+ p_dest = (u16 *)dest;
+ p_src = (u16 *)(src - 1);
+
+ *p_dest = (*p_dest & 0xFF00) | ((*p_src >> 8) & 0x00FF);
+ }
+
+ else if ((dest % 2) && !(src % 2)) {
+ p_dest = (u16 *)(dest - 1);
+ p_src = (u16 *)src;
+
+ *p_dest = (*p_dest & 0x00FF) | ((*p_src << 8) & 0xFF00);
+ }
+
+ else {
+ dprintk(KERN_ERR "oops.~\n");
+ }
+}
+
+static inline void _memcpy(void *p_dest, const void *p_src, int size)
+{
+ unsigned long dest = (unsigned long)p_dest;
+ unsigned long src = (unsigned long)p_src;
+
+ if (!(*onedram_sem)) {
+ printk(KERN_ERR "[OneDRAM] memory access without semaphore!: %d\n", *onedram_sem);
+ return;
+ }
+ if (size <= 0) {
+ return;
+ }
+
+ if (dest & 1) {
+ byte_align(dest, src);
+ dest++, src++;
+ size--;
+ }
+
+ if (size & 1) {
+ byte_align(dest + size - 1, src + size - 1);
+ size--;
+ }
+
+ if (src & 1) {
+ unsigned char *s = (unsigned char *)src;
+ volatile u16 *d = (unsigned short *)dest;
+
+ size >>= 1;
+
+ while (size--) {
+ *d++ = s[0] | (s[1] << 8);
+ s += 2;
+ }
+ }
+
+ else {
+ u16 *s = (u16 *)src;
+ volatile u16 *d = (unsigned short *)dest;
+
+ size >>= 1;
+
+ while (size--) { *d++ = *s++; }
+ }
+}
+
+static inline int _memcmp(u8 *dest, u8 *src, int size)
+{
+ int i = 0;
+ if (!(*onedram_sem)) {
+ printk(KERN_ERR "[OneDRAM] memory access without semaphore!: %d\n", *onedram_sem);
+ return 1;
+ }
+
+ while (i++ < size) {
+ if (*(dest + i) != *(src + i)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static inline int WRITE_TO_DPRAM_VERIFY(u32 dest, void *src, int size)
+{
+ int cnt = 3;
+
+ while (cnt--) {
+ _memcpy((void *)(DPRAM_VBASE + dest), (void *)src, size);
+
+ if (!_memcmp((u8 *)(DPRAM_VBASE + dest), (u8 *)src, size))
+ return 0;
+ }
+
+ return -1;
+}
+
+static inline int READ_FROM_DPRAM_VERIFY(void *dest, u32 src, int size)
+{
+ int cnt = 3;
+
+ while (cnt--) {
+ _memcpy((void *)dest, (void *)(DPRAM_VBASE + src), size);
+
+ if (!_memcmp((u8 *)dest, (u8 *)(DPRAM_VBASE + src), size))
+ return 0;
+ }
+
+ return -1;
+}
+
+static int dpram_write(dpram_device_t *device,
+ const unsigned char *buf, int len)
+{
+ int retval = 0;
+ int size = 0;
+ u16 head, tail;
+ u16 irq_mask = 0;
+
+ if(!onedram_get_semaphore(__func__)) {
+ return -EINTR;
+ }
+
+ if(onedram_lock_with_semaphore(__func__) < 0) {
+ return -EINTR;
+ }
+
+ READ_FROM_DPRAM_VERIFY(&head, device->out_head_addr, sizeof(head));
+ READ_FROM_DPRAM_VERIFY(&tail, device->out_tail_addr, sizeof(tail));
+
+ // +++++++++ head ---------- tail ++++++++++ //
+ if (head < tail) {
+ size = tail - head - 1;
+ size = (len > size) ? size : len;
+
+ WRITE_TO_DPRAM(device->out_buff_addr + head, buf, size);
+ retval = size;
+ }
+
+ // tail +++++++++++++++ head --------------- //
+ else if (tail == 0) {
+ size = device->out_buff_size - head - 1;
+ size = (len > size) ? size : len;
+
+ WRITE_TO_DPRAM(device->out_buff_addr + head, buf, size);
+ retval = size;
+ }
+
+ // ------ tail +++++++++++ head ------------ //
+ else {
+ size = device->out_buff_size - head;
+ size = (len > size) ? size : len;
+
+ WRITE_TO_DPRAM(device->out_buff_addr + head, buf, size);
+ retval = size;
+
+ if (len > retval) {
+ size = (len - retval > tail - 1) ? tail - 1 : len - retval;
+
+ WRITE_TO_DPRAM(device->out_buff_addr, buf + retval, size);
+ retval += size;
+ }
+ }
+
+ /* @LDK@ calculate new head */
+ head = (u16)((head + retval) % device->out_buff_size);
+ WRITE_TO_DPRAM_VERIFY(device->out_head_addr, &head, sizeof(head));
+
+
+ device->out_head_saved = head;
+ device->out_tail_saved = tail;
+
+ /* @LDK@ send interrupt to the phone, if.. */
+ irq_mask = INT_MASK_VALID;
+
+ if (retval > 0)
+ irq_mask |= device->mask_send;
+
+ if (len > retval)
+ irq_mask |= device->mask_req_ack;
+
+ onedram_release_lock(__func__);
+ send_interrupt_to_phone_with_semaphore(irq_mask);
+ return retval;
+
+}
+
+static inline int dpram_tty_insert_data(dpram_device_t *device, const u8 *psrc, u16 size)
+{
+#define CLUSTER_SEGMENT 1500
+
+ u16 copied_size = 0;
+ int retval = 0;
+
+ if (size > CLUSTER_SEGMENT && (device->serial.tty->index == 1)) {
+ while (size) {
+ copied_size = (size > CLUSTER_SEGMENT) ? CLUSTER_SEGMENT : size;
+ tty_insert_flip_string(device->serial.tty, psrc + retval, copied_size);
+
+ size -= copied_size;
+ retval += copied_size;
+ }
+
+ return retval;
+ }
+
+ return tty_insert_flip_string(device->serial.tty, psrc, size);
+}
+
+static int dpram_read_fmt(dpram_device_t *device, const u16 non_cmd)
+{
+ int retval = 0;
+ int retval_add = 0;
+ int size = 0;
+ u16 head, tail;
+
+ if(!*onedram_sem)
+ printk(KERN_ERR "!!!!! %s no sem\n", __func__);
+
+ if(onedram_lock_with_semaphore(__func__) < 0)
+ return -EINTR;
+
+ READ_FROM_DPRAM_VERIFY(&head, device->in_head_addr, sizeof(head));
+ READ_FROM_DPRAM_VERIFY(&tail, device->in_tail_addr, sizeof(tail));
+
+ if (head != tail) {
+ u16 up_tail = 0;
+
+ // ------- tail ++++++++++++ head -------- //
+ if (head > tail) {
+ size = head - tail;
+ retval = dpram_tty_insert_data(device, (u8 *)(DPRAM_VBASE + (device->in_buff_addr + tail)), size);
+ if(size!= retval)
+ printk(KERN_ERR "[OneDRAM: size: %d, retval: %d\n", size, retval);
+ }
+
+ // +++++++ head ------------ tail ++++++++ //
+ else {
+ int tmp_size = 0;
+
+ // Total Size.
+ size = device->in_buff_size - tail + head;
+
+ // 1. tail -> buffer end.
+ tmp_size = device->in_buff_size - tail;
+ retval = dpram_tty_insert_data(device, (u8 *)(DPRAM_VBASE + (device->in_buff_addr + tail)), tmp_size);
+ if(tmp_size!= retval)
+ printk(KERN_ERR "[OneDRAM: size: %d, retval: %d\n", size, retval);
+
+ // 2. buffer start -> head.
+ if (size > tmp_size) {
+ retval_add = dpram_tty_insert_data(device, (u8 *)(DPRAM_VBASE + device->in_buff_addr), size - tmp_size);
+ retval += retval_add;
+
+ if((size - tmp_size)!= retval_add)
+ printk(KERN_ERR "[OneDRAM: size - tmp_size: %d, retval_add: %d\n", size - tmp_size, retval_add);
+
+
+ }
+ }
+
+ /* new tail */
+ up_tail = (u16)((tail + retval) % device->in_buff_size);
+ WRITE_TO_DPRAM_VERIFY(device->in_tail_addr, &up_tail, sizeof(up_tail));
+ }
+
+
+ device->in_head_saved = head;
+ device->in_tail_saved = tail;
+
+ onedram_release_lock(__func__);
+ if (non_cmd & device->mask_req_ack)
+ send_interrupt_to_phone_with_semaphore(INT_NON_COMMAND(device->mask_res_ack));
+
+ return retval;
+
+}
+
+static int dpram_read_raw(dpram_device_t *device, const u16 non_cmd)
+{
+ int retval = 0;
+ int size = 0;
+ u16 head, tail;
+ u16 up_tail = 0;
+
+ int ret;
+ size_t len;
+ struct pdp_info *dev = NULL;
+ struct pdp_hdr hdr;
+ u16 read_offset;
+ u8 len_high, len_low, id, control;
+ u16 pre_data_size;
+ u8 ch;
+
+ if(!*onedram_sem)
+ printk(KERN_ERR "!!!!! %s no sem\n", __func__);
+
+ if(onedram_lock_with_semaphore(__func__) < 0)
+ return -EINTR;
+
+
+ READ_FROM_DPRAM_VERIFY(&head, device->in_head_addr, sizeof(head));
+ READ_FROM_DPRAM_VERIFY(&tail, device->in_tail_addr, sizeof(tail));
+
+ if(head != tail) {
+
+ up_tail = 0;
+
+ if (head > tail) {
+ size = head - tail; /* ----- (tail) 7f 00 00 7e (head) ----- */
+ }
+ else
+ size = device->in_buff_size - tail + head; /* 00 7e (head) ----------- (tail) 7f 00 */
+
+ read_offset = 0;
+
+ while(size){
+ READ_FROM_DPRAM(&ch, device->in_buff_addr +((u16)(tail + read_offset) % device->in_buff_size), sizeof(ch));
+
+ if(ch == 0x7f) {
+ read_offset ++;
+ }
+ else {
+ printk(KERN_ERR "[OneDram] %s failed.. First byte: %d, drop byte: %d\n", __func__, ch, size);
+ printk(KERN_ERR "buff addr: %lu\n", (device->in_buff_addr));
+ printk(KERN_ERR "read addr: %lu\n", (device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size)));
+
+ dpram_drop_data(device);
+ onedram_release_lock(__func__);
+ return -1;
+ }
+
+ len_high = len_low = id = control = 0;
+ READ_FROM_DPRAM(&len_low, device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size) ,sizeof(len_high));
+ read_offset ++;
+ READ_FROM_DPRAM(&len_high, device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size) ,sizeof(len_low));
+ read_offset ++;
+ READ_FROM_DPRAM(&id, device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size) ,sizeof(id));
+ read_offset ++;
+ READ_FROM_DPRAM(&control, device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size) ,sizeof(control));
+ read_offset ++;
+
+ hdr.len = len_high <<8 | len_low;
+ hdr.id = id;
+ hdr.control = control;
+
+ len = hdr.len - sizeof(struct pdp_hdr);
+ if(len <= 0) {
+ printk(KERN_ERR "[OneDram] %s uups..\n", __func__);
+ printk(KERN_ERR "%s, %d read_offset: %d, len: %d hdr.id: %d\n", __func__, __LINE__, read_offset, len, hdr.id);
+
+ dpram_drop_data(device);
+ onedram_release_lock(__func__);
+ return -1;
+
+
+ }
+ dev = pdp_get_dev(hdr.id);
+
+ if(!dev) {
+ printk(KERN_ERR "[OneDram] %s failed.. NULL dev detected \n", __func__);
+ check_pdp_table(__func__, __LINE__);
+ dpram_drop_data(device);
+ onedram_release_lock(__func__);
+ return -1;
+ }
+
+
+ if (dev->vs_dev.tty != NULL && dev->vs_dev.refcount) {
+
+ if((u16)(tail + read_offset) % device->in_buff_size + len < device->in_buff_size) {
+ ret = tty_insert_flip_string(dev->vs_dev.tty, (u8 *)(DPRAM_VBASE + (device->in_buff_addr + (u16)(tail + read_offset) % device->in_buff_size)), len);
+ tty_flip_buffer_push(dev->vs_dev.tty);
+ }else {
+ pre_data_size = device->in_buff_size - (tail + read_offset);
+ ret = tty_insert_flip_string(dev->vs_dev.tty, (u8 *)(DPRAM_VBASE + (device->in_buff_addr + tail + read_offset)), pre_data_size);
+ ret += tty_insert_flip_string(dev->vs_dev.tty, (u8 *)(DPRAM_VBASE + (device->in_buff_addr)),len - pre_data_size);
+ tty_flip_buffer_push(dev->vs_dev.tty);
+ }
+ }
+ else {
+ printk(KERN_ERR "[%s]failed.. tty channel(id:%d) is not opened.\n", __func__, dev->id);
+ ret = len;
+ }
+
+ if(!ret) {
+ printk(KERN_ERR "[OneDram] %s failed.. (tty_insert_flip_string) drop byte: %d\n", __func__, size);
+ printk(KERN_ERR "buff addr: %lu\n", (device->in_buff_addr));
+ printk(KERN_ERR "read addr: %lu\n", (device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size)));
+ dpram_drop_data(device);
+ onedram_release_lock(__func__);
+ return -1;
+ }
+
+ read_offset += ret;
+
+ READ_FROM_DPRAM(&ch, (device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size)), sizeof(ch));
+ if(ch == 0x7e)
+ read_offset ++;
+ else {
+ printk(KERN_ERR "[OneDram] %s failed.. Last byte: %d, drop byte: %d\n", __func__, ch, size);
+ printk(KERN_ERR "buff addr: %lu\n", (device->in_buff_addr));
+ printk(KERN_ERR "read addr: %lu\n", (device->in_buff_addr + ((u16)(tail + read_offset) % device->in_buff_size)));
+ dpram_drop_data(device);
+ onedram_release_lock(__func__);
+ return -1;
+ }
+
+ size -= (ret + sizeof(struct pdp_hdr) + 2);
+ retval += (ret + sizeof(struct pdp_hdr) + 2);
+
+ if(size < 0) {
+ printk(KERN_ERR "something wrong....\n");
+ break;
+ }
+
+ }
+ up_tail = (u16)((tail + read_offset) % device->in_buff_size);
+ WRITE_TO_DPRAM_VERIFY(device->in_tail_addr, &up_tail, sizeof(up_tail));
+ }
+
+ device->in_head_saved = head;
+ device->in_tail_saved = tail;
+
+ onedram_release_lock(__func__);
+ if (non_cmd & device->mask_req_ack)
+ send_interrupt_to_phone_with_semaphore(INT_NON_COMMAND(device->mask_res_ack));
+
+ return retval;
+
+}
+#ifdef _ENABLE_ERROR_DEVICE
+void request_phone_reset()
+{
+ char buf[DPRAM_ERR_MSG_LEN];
+ unsigned long flags;
+
+ memset((void *)buf, 0, sizeof (buf));
+ buf[0] = '9';
+ buf[1] = ' ';
+
+ memcpy(buf+2, "$PHONE-RESET", sizeof("$PHONE-RESET"));
+ printk(KERN_ERR "[PHONE ERROR] ->> %s\n", buf);
+
+ local_irq_save(flags);
+ memcpy(dpram_err_buf, buf, DPRAM_ERR_MSG_LEN);
+ dpram_err_len = 64;
+ local_irq_restore(flags);
+
+ wake_up_interruptible(&dpram_err_wait_q);
+ kill_fasync(&dpram_err_async_q, SIGIO, POLL_IN);
+}
+#endif
+
+static int onedram_get_semaphore(const char *func)
+{
+ int i, req_try = 300;
+
+ const u16 cmd = INT_COMMAND(INT_MASK_CMD_SMP_REQ);
+
+ if(dump_on) return -1;
+
+ for(i = 0; i < req_try; i++) {
+ if(*onedram_sem) {
+ unreceived_semaphore = 0;
+ return 1;
+ }
+ if (i == 0)
+ *onedram_mailboxBA = cmd;
+ udelay(40);
+ }
+
+ unreceived_semaphore++;
+ printk(KERN_ERR "[OneDRAM](%s) Failed to get a Semaphore. sem:%d, PHONE_ACTIVE:%s, fail_cnt:%d\n",
+ func, *onedram_sem, gpio_get_value(GPIO_PHONE_ACTIVE)?"HIGH":"LOW ", unreceived_semaphore);
+
+#ifdef _ENABLE_ERROR_DEVICE
+ if(unreceived_semaphore > 10)
+ request_phone_reset();
+#endif
+
+ return 0;
+}
+
+
+static int onedram_get_semaphore_for_init(const char *func)
+{
+ int i, chk_try = 100;
+ int j, req_try = 3;
+
+ const u16 cmd = INT_COMMAND(INT_MASK_CMD_SMP_REQ);
+
+ if(dump_on) return -1;
+
+ for(j = 0; j < req_try; j++) {
+ for(i = 0; i < chk_try; i++) {
+ if(*onedram_sem) {
+ unreceived_semaphore = 0;
+ return 1;
+ }
+ mdelay(1);
+ }
+ *onedram_mailboxBA = cmd;
+ printk(KERN_ERR "=====> send IRQ: %x\n", cmd);
+ }
+
+ unreceived_semaphore++;
+ printk(KERN_ERR "[OneDRAM](%s) Failed to get a Semaphore. sem:%d, PHONE_ACTIVE:%s, fail_cnt:%d\n",
+ func, *onedram_sem, gpio_get_value(GPIO_PHONE_ACTIVE)?"HIGH":"LOW ", unreceived_semaphore);
+
+#ifdef _ENABLE_ERROR_DEVICE
+ if(unreceived_semaphore > 10)
+ request_phone_reset();
+#endif
+
+ return 0;
+}
+
+static void send_interrupt_to_phone_with_semaphore(u16 irq_mask)
+{
+ if(dump_on) return;
+
+ if(!atomic_read(&onedram_lock))
+ {
+ if(*onedram_sem) {
+ *onedram_sem = 0x0;
+ *onedram_mailboxBA = irq_mask;
+ requested_semaphore = 0;
+ }else {
+ *onedram_mailboxBA = irq_mask;
+ }
+ }else {
+ printk(KERN_ERR "[OneDRAM] (%s) lock set. can't return semaphore.\n", __func__);
+ }
+
+
+}
+static int return_onedram_semaphore(const char* func)
+{
+
+ if(!atomic_read(&onedram_lock))
+ {
+ if(*onedram_sem) { *onedram_sem = 0x0;
+ return 1;
+ }
+ }else {
+ requested_semaphore++;
+ printk(KERN_ERR "[OneDRAM] (%s) PDA is accessing onedram. %d\n", __func__, requested_semaphore);
+ }
+
+ return 0;
+
+}
+
+static int onedram_lock_with_semaphore(const char* func)
+{
+ int lock_value;
+
+ if(!(lock_value = atomic_inc_return(&onedram_lock)))
+ printk(KERN_ERR "[OneDRAM] (%s, lock) fail to locking onedram access. %d\n", func, lock_value);
+
+ if(lock_value != 1)
+ printk(KERN_ERR "[OneDRAM] (%s, lock) lock_value: %d\n", func, lock_value);
+
+ if(*onedram_sem) {
+ return 0;
+ }
+ else{
+ printk(KERN_ERR "[OneDRAM] (%s, lock) failed.. no sem\n", func);
+ if((lock_value = atomic_dec_return(&onedram_lock)) < 0)
+ printk(KERN_ERR "[OneDRAM] (%s, lock) fail to unlocking onedram access. %d\n", func, lock_value);
+
+ if(lock_value != 0)
+ printk(KERN_ERR "[OneDRAM] (%s, lock) lock_value: %d\n", func, lock_value);
+ return -1;
+ }
+}
+
+static void onedram_release_lock(const char* func)
+{
+ int lock_value;
+
+ if((lock_value = atomic_dec_return(&onedram_lock)) < 0)
+ printk(KERN_ERR "[OneDRAM] (%s, release) fail to unlocking onedram access. %d\n", func, lock_value);
+
+ if(requested_semaphore) {
+ if(!atomic_read(&onedram_lock)) {
+ if(*onedram_sem) {
+ printk(KERN_ERR "[OneDRAM] (%s, release) requested semaphore(%d) return to Phone.\n", func, requested_semaphore);
+ *onedram_sem = 0x0;
+ requested_semaphore = 0;
+ }
+ }
+ }
+
+ if(lock_value != 0)
+ printk(KERN_ERR "[OneDRAM] (%s, release) lock_value: %d\n", func, lock_value);
+
+}
+
+static int dpram_shared_bank_remap(void)
+{
+ dpram_base = ioremap_nocache(DPRAM_START_ADDRESS_PHYS + DPRAM_SHARED_BANK, DPRAM_SHARED_BANK_SIZE);
+ if (dpram_base == NULL) {
+ printk(KERN_ERR "failed ioremap\n");
+ return -ENOENT;
+ }
+
+ onedram_sem = DPRAM_VBASE + DPRAM_SMP;
+ onedram_mailboxBA = DPRAM_VBASE + DPRAM_MBX_BA;
+ onedram_mailboxAB = DPRAM_VBASE + DPRAM_MBX_AB;
+ atomic_set(&onedram_lock, 0);
+
+ return 0;
+}
+
+static void dpram_clear(void)
+{
+ long i = 0;
+ unsigned long flags;
+
+ u16 value = 0;
+
+ /* @LDK@ clear DPRAM except interrupt area */
+ local_irq_save(flags);
+
+ for (i = DPRAM_PDA2PHONE_FORMATTED_HEAD_ADDRESS;
+ i < DPRAM_SIZE - (DPRAM_INTERRUPT_PORT_SIZE * 2);
+ i += 2)
+ {
+ *((u16 *)(DPRAM_VBASE + i)) = 0;
+ }
+
+ local_irq_restore(flags);
+
+ value = *onedram_mailboxAB;
+}
+
+static int dpram_init_and_report(void)
+{
+ const u16 magic_code = 0x00aa;
+ const u16 init_end = INT_COMMAND(INT_MASK_CMD_INIT_END);
+ u16 ac_code = 0;
+
+ if (!(*onedram_sem)) {
+ printk(KERN_ERR "[OneDRAM] %s, sem: %d\n", __func__, *onedram_sem);
+ if(!onedram_get_semaphore_for_init(__func__)) {
+ printk(KERN_ERR "[OneDRAM] %s failed to onedram init!!! semaphore: %d\n", __func__, *onedram_sem);
+ return -EINTR;
+ }
+ }
+
+ if(onedram_lock_with_semaphore(__func__) < 0)
+ return -EINTR;
+
+ /* @LDK@ write DPRAM disable code */
+ WRITE_TO_DPRAM(DPRAM_ACCESS_ENABLE_ADDRESS, &ac_code, sizeof(ac_code));
+
+ /* @LDK@ dpram clear */
+ dpram_clear();
+
+ /* @LDK@ write magic code */
+ WRITE_TO_DPRAM(DPRAM_MAGIC_CODE_ADDRESS, &magic_code, sizeof(magic_code));
+
+ /* @LDK@ write DPRAM enable code */
+ ac_code = 0x0001;
+ WRITE_TO_DPRAM(DPRAM_ACCESS_ENABLE_ADDRESS, &ac_code, sizeof(ac_code));
+
+ /* @LDK@ send init end code to phone */
+ onedram_release_lock(__func__);
+ send_interrupt_to_phone_with_semaphore(init_end);
+ printk(KERN_ERR "[OneDRAM] Send 0x%x to MailboxBA (onedram init finish).\n", init_end);
+
+ phone_sync = 1;
+ return 0;
+}
+
+static inline int dpram_get_read_available(dpram_device_t *device)
+{
+ u16 head, tail;
+
+ if(*onedram_sem) {
+
+ READ_FROM_DPRAM_VERIFY(&head, device->in_head_addr, sizeof(head));
+ READ_FROM_DPRAM_VERIFY(&tail, device->in_tail_addr, sizeof(tail));
+
+ return head - tail;
+ }
+ else {
+ return 0;
+ }
+}
+
+static void dpram_drop_data(dpram_device_t *device)
+{
+ u16 head, tail;
+
+ if(*onedram_sem) {
+ READ_FROM_DPRAM_VERIFY(&head, device->in_head_addr, sizeof(head));
+ WRITE_TO_DPRAM_VERIFY(device->in_tail_addr, &head, sizeof(head));
+
+ READ_FROM_DPRAM_VERIFY(&tail, device->in_tail_addr, sizeof(tail));
+ printk(KERN_ERR "[OneDram] %s, head: %d, tail: %d\n", __func__, head, tail);
+
+ }
+}
+
+static void dpram_phone_power_on(void)
+{
+
+ printk(KERN_ERR "[OneDRAM] Phone Power on! sem: %d lock: %d\n", *onedram_sem, atomic_read(&onedram_lock));
+ *onedram_sem = 0x00;
+ printk(KERN_ERR "[OneDRAM] set semaphore: %d\n", *onedram_sem);
+
+ printk(KERN_ERR "[OneDRAM] power control (with GPIO_PHONE_RST_N)\n");
+ gpio_set_value(GPIO_PHONE_ON, GPIO_LEVEL_HIGH);
+ mdelay(50);
+ gpio_set_value(GPIO_PHONE_RST_N, GPIO_LEVEL_LOW);
+ mdelay(100);
+ gpio_set_value(GPIO_PHONE_RST_N, GPIO_LEVEL_HIGH);
+ mdelay(500);
+ gpio_set_value(GPIO_PHONE_ON, GPIO_LEVEL_LOW);
+}
+
+static void dpram_phone_power_off(void)
+{
+ printk(KERN_ERR "[OneDRAM] Phone power Off. - do nothing\n");
+}
+
+static int dpram_phone_getstatus(void)
+{
+ return gpio_get_value(GPIO_PHONE_ACTIVE);
+}
+
+static void dpram_phone_reset(void)
+{
+ printk(KERN_ERR "[OneDRAM] Phone Reset! sem: %d lock: %d\n", *onedram_sem, atomic_read(&onedram_lock));
+ if(*onedram_sem) {
+ *onedram_sem = 0x00;
+ printk(KERN_ERR "[OneDRAM] set semaphore: %d\n", *onedram_sem);
+ }
+
+ gpio_set_value(GPIO_PHONE_RST_N, GPIO_LEVEL_LOW);
+ mdelay(100);
+ gpio_set_value(GPIO_PHONE_RST_N, GPIO_LEVEL_HIGH);
+}
+
+static int dpram_extra_mem_rw(struct _param_em *param)
+{
+
+ if(param->offset + param->size > 0xFFF800) {
+ printk(KERN_ERR "[OneDRAM] %s failed.. wrong rage of external memory access\n", __func__);
+ return -1;
+ }
+
+ if(!onedram_get_semaphore(__func__))
+ return -EINTR;
+
+ if(onedram_lock_with_semaphore(__func__) < 0)
+ return -EINTR;
+
+ if (param->rw) { //write
+ WRITE_TO_DPRAM(param->offset, param->addr, param->size);
+ }
+ else { //read
+ READ_FROM_DPRAM(param->addr, param->offset, param->size);
+ }
+
+ onedram_release_lock(__func__);
+ return 0;
+}
+
+static int dpram_phone_ramdump_on(void)
+{
+ const u16 rdump_flag1 = 0x554C;
+ const u16 rdump_flag2 = 0x454D;
+ const u16 temp1, temp2;
+
+ printk(KERN_ERR "[OneDRAM] Ramdump ON.\n");
+
+ if(!onedram_get_semaphore(__func__))
+ return -EINTR;
+
+ if(onedram_lock_with_semaphore(__func__) < 0)
+ return -EINTR;
+
+ WRITE_TO_DPRAM(DPRAM_MAGIC_CODE_ADDRESS, &rdump_flag1, sizeof(rdump_flag1));
+ WRITE_TO_DPRAM(DPRAM_ACCESS_ENABLE_ADDRESS, &rdump_flag2, sizeof(rdump_flag2));
+
+ READ_FROM_DPRAM((void *)&temp1, DPRAM_MAGIC_CODE_ADDRESS, sizeof(temp1));
+ READ_FROM_DPRAM((void *)&temp2, DPRAM_ACCESS_ENABLE_ADDRESS, sizeof(temp2));
+ printk(KERN_ERR "[OneDRAM] flag1: %x flag2: %x\n", temp1, temp2);
+
+ /* @LDK@ send init end code to phone */
+ onedram_release_lock(__func__);
+
+ dump_on = 1;
+
+ return_onedram_semaphore(__func__);
+ if(*onedram_sem) {
+ printk(KERN_ERR "[OneDRAM] Failed to return semaphore. try again\n");
+ *onedram_sem = 0x00;
+ }
+
+ return 0;
+
+}
+
+static int dpram_phone_ramdump_off(void)
+{
+ dump_on = 0;
+ phone_sync = 0;
+
+ return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+static int dpram_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *p = page;
+ int len;
+
+ u16 magic, enable;
+ u16 fmt_in_head, fmt_in_tail, fmt_out_head, fmt_out_tail;
+ u16 raw_in_head, raw_in_tail, raw_out_head, raw_out_tail;
+ u16 in_interrupt = 0, out_interrupt = 0;
+
+ int fih, fit, foh, fot;
+ int rih, rit, roh, rot;
+ int sem;
+
+#ifdef _ENABLE_ERROR_DEVICE
+ char buf[DPRAM_ERR_MSG_LEN];
+ unsigned long flags;
+#endif /* _ENABLE_ERROR_DEVICE */
+
+ if(*onedram_sem) {
+
+ READ_FROM_DPRAM((void *)&magic, DPRAM_MAGIC_CODE_ADDRESS, sizeof(magic));
+ READ_FROM_DPRAM((void *)&enable, DPRAM_ACCESS_ENABLE_ADDRESS, sizeof(enable));
+ READ_FROM_DPRAM((void *)&fmt_in_head, DPRAM_PHONE2PDA_FORMATTED_HEAD_ADDRESS, sizeof(fmt_in_head));
+ READ_FROM_DPRAM((void *)&fmt_in_tail, DPRAM_PHONE2PDA_FORMATTED_TAIL_ADDRESS, sizeof(fmt_in_tail));
+ READ_FROM_DPRAM((void *)&fmt_out_head, DPRAM_PDA2PHONE_FORMATTED_HEAD_ADDRESS, sizeof(fmt_out_head));
+ READ_FROM_DPRAM((void *)&fmt_out_tail, DPRAM_PDA2PHONE_FORMATTED_TAIL_ADDRESS, sizeof(fmt_out_tail));
+ READ_FROM_DPRAM((void *)&raw_in_head, DPRAM_PHONE2PDA_RAW_HEAD_ADDRESS, sizeof(raw_in_head));
+ READ_FROM_DPRAM((void *)&raw_in_tail, DPRAM_PHONE2PDA_RAW_TAIL_ADDRESS, sizeof(raw_in_tail));
+ READ_FROM_DPRAM((void *)&raw_out_head, DPRAM_PDA2PHONE_RAW_HEAD_ADDRESS, sizeof(raw_out_head));
+ READ_FROM_DPRAM((void *)&raw_out_tail, DPRAM_PDA2PHONE_RAW_TAIL_ADDRESS, sizeof(raw_out_tail));
+ }
+ else {
+ magic = enable = 0;
+ fmt_in_head = fmt_in_tail = fmt_out_head = fmt_out_tail = 0;
+ raw_in_head = raw_in_tail = raw_out_head = raw_out_tail = 0;
+ }
+
+ fih = dpram_table[FORMATTED_INDEX].in_head_saved;
+ fit = dpram_table[FORMATTED_INDEX].in_tail_saved;
+ foh = dpram_table[FORMATTED_INDEX].out_head_saved;
+ fot = dpram_table[FORMATTED_INDEX].out_tail_saved;
+ rih = dpram_table[RAW_INDEX].in_head_saved;
+ rit = dpram_table[RAW_INDEX].in_tail_saved;
+ roh = dpram_table[RAW_INDEX].out_head_saved;
+ rot = dpram_table[RAW_INDEX].out_tail_saved;
+
+ sem = *onedram_sem;
+
+ in_interrupt = *onedram_mailboxAB;
+ out_interrupt = *onedram_mailboxBA;
+
+#ifdef _ENABLE_ERROR_DEVICE
+ memset((void *)buf, '\0', DPRAM_ERR_MSG_LEN);
+ local_irq_save(flags);
+ memcpy(buf, dpram_err_buf, DPRAM_ERR_MSG_LEN - 1);
+ local_irq_restore(flags);
+#endif /* _ENABLE_ERROR_DEVICE */
+
+ p += sprintf(p,
+ "-------------------------------------\n"
+ "| NAME\t\t\t| VALUE\n"
+ "-------------------------------------\n"
+ "|R MAGIC CODE\t\t| 0x%04x\n"
+ "|R ENABLE CODE\t\t| 0x%04x\n"
+ "|R PHONE->PDA FMT HEAD\t| %u\n"
+ "|R PHONE->PDA FMT TAIL\t| %u\n"
+ "|R PDA->PHONE FMT HEAD\t| %u\n"
+ "|R PDA->PHONE FMT TAIL\t| %u\n"
+ "|R PHONE->PDA RAW HEAD\t| %u\n"
+ "|R RPHONE->PDA RAW TAIL\t| %u\n"
+ "|R PDA->PHONE RAW HEAD\t| %u\n"
+ "|R PDA->PHONE RAW TAIL\t| %u\n"
+ "-------------------------------------\n"
+ "| Onedram Semaphore\t| %d\n"
+ "| requested Semaphore\t| %d\n"
+ "| unreceived Semaphore\t| %d\n"
+ "-------------------------------------\n"
+ "| FMT PHONE->PDA HEAD\t| %d\n"
+ "| FMT PHONE->PDA TAIL\t| %d\n"
+ "| FMT PDA->PHONE HEAD\t| %d\n"
+ "| FMT PDA->PHONE TAIL\t| %d\n"
+ "-------------------------------------\n"
+ "| RAW PHONE->PDA HEAD\t| %d\n"
+ "| RAW PHONE->PDA TAIL\t| %d\n"
+ "| RAW PDA->PHONE HEAD\t| %d\n"
+ "| RAW PDA->PHONE TAIL\t| %d\n"
+ "-------------------------------------\n"
+ "| PHONE->PDA MAILBOX\t| 0x%04x\n"
+ "| PDA->PHONE MAILBOX\t| 0x%04x\n"
+ "-------------------------------------\n"
+#ifdef _ENABLE_ERROR_DEVICE
+ "| LAST PHONE ERR MSG\t| %s\n"
+#endif /* _ENABLE_ERROR_DEVICE */
+ "| PHONE ACTIVE\t\t| %s\n"
+ "| DPRAM INT Level\t| %d\n"
+ "-------------------------------------\n",
+ magic, enable,
+ fmt_in_head, fmt_in_tail, fmt_out_head, fmt_out_tail,
+ raw_in_head, raw_in_tail, raw_out_head, raw_out_tail,
+ sem,
+ requested_semaphore,
+ unreceived_semaphore,
+ fih, fit, foh, fot,
+ rih, rit, roh, rot,
+ in_interrupt, out_interrupt,
+
+#ifdef _ENABLE_ERROR_DEVICE
+ (buf[0] != '\0' ? buf : "NONE"),
+#endif /* _ENABLE_ERROR_DEVICE */
+
+ (dpram_phone_getstatus() ? "ACTIVE" : "INACTIVE"),
+ gpio_get_value(IRQ_PHONE_ACTIVE)
+ );
+
+ len = (p - page) - off;
+ if (len < 0) {
+ len = 0;
+ }
+
+ *eof = (len <= count) ? 1 : 0;
+ *start = page + off;
+
+ return len;
+}
+#endif /* CONFIG_PROC_FS */
+
+/* dpram tty file operations. */
+static int dpram_tty_open(struct tty_struct *tty, struct file *file)
+{
+ dpram_device_t *device = &dpram_table[tty->index];
+
+ device->serial.tty = tty;
+ device->serial.open_count++;
+
+ if (device->serial.open_count > 1) {
+ device->serial.open_count--;
+ return -EBUSY;
+ }
+
+ tty->driver_data = (void *)device;
+ tty->low_latency = 1;
+ return 0;
+}
+
+static void dpram_tty_close(struct tty_struct *tty, struct file *file)
+{
+ dpram_device_t *device = (dpram_device_t *)tty->driver_data;
+
+ if (device && (device == &dpram_table[tty->index])) {
+ down(&device->serial.sem);
+ device->serial.open_count--;
+ device->serial.tty = NULL;
+ up(&device->serial.sem);
+ }
+}
+
+static int dpram_tty_write(struct tty_struct *tty,
+ const unsigned char *buffer, int count)
+{
+ dpram_device_t *device = (dpram_device_t *)tty->driver_data;
+
+ if (!device) {
+ return 0;
+ }
+
+ return dpram_write(device, buffer, count);
+}
+
+static int dpram_tty_write_room(struct tty_struct *tty)
+{
+ int avail;
+ u16 head, tail;
+
+ dpram_device_t *device = (dpram_device_t *)tty->driver_data;
+
+ if (device != NULL) {
+ head = device->out_head_saved;
+ tail = device->out_tail_saved;
+ avail = (head < tail) ? tail - head - 1 :
+ device->out_buff_size + tail - head - 1;
+
+ return avail;
+ }
+
+ return 0;
+}
+
+
+static long dpram_tty_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int val;
+ int ret = 0;
+
+ switch (cmd) {
+ case DPRAM_PHONE_ON:
+ phone_sync = 0;
+ dump_on = 0;
+ requested_semaphore = 0;
+ unreceived_semaphore = 0;
+ dpram_phone_power_on();
+ ret = 0;
+ break;
+
+ case DPRAM_PHONE_GETSTATUS:
+ val = dpram_phone_getstatus();
+ ret = copy_to_user((unsigned int *)arg, &val, sizeof(val));
+ break;
+
+ case DPRAM_PHONE_RESET:
+ phone_sync = 0;
+ requested_semaphore = 0;
+ unreceived_semaphore = 0;
+ dpram_phone_reset();
+ ret = 0;
+ break;
+
+ case DPRAM_PHONE_OFF:
+ dpram_phone_power_off();
+ ret = 0;
+ break;
+
+ case DPRAM_PHONE_RAMDUMP_ON:
+ dpram_phone_ramdump_on();
+ ret = 0;
+ break;
+
+ case DPRAM_PHONE_RAMDUMP_OFF:
+ dpram_phone_ramdump_off();
+ ret = 0;
+ break;
+
+ case DPRAM_EXTRA_MEM_RW:
+ {
+ struct _param_em param;
+
+ val = copy_from_user((void *)&param, (void *)arg, sizeof(param));
+ if (dpram_extra_mem_rw(&param) < 0) {
+ printk(KERN_ERR "[OneDRAM] external memory access fail..\n");
+ ret = -1;
+ } else if (!param.rw) { //read
+ ret = copy_to_user((unsigned long *)arg, &param, sizeof(param));
+ } else {
+ ret = 0;
+ }
+ break;
+ }
+
+ default:
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ return ret;
+}
+
+static int dpram_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ int data;
+ u16 head, tail;
+
+ dpram_device_t *device = (dpram_device_t *)tty->driver_data;
+
+ if (device != NULL) {
+ head = device->out_head_saved;
+ tail = device->out_tail_saved;
+ data = (head > tail) ? head - tail - 1 :
+ device->out_buff_size - tail + head;
+
+ return data;
+ }
+
+ return 0;
+}
+
+#ifdef _ENABLE_ERROR_DEVICE
+static int dpram_err_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ unsigned long flags;
+ ssize_t ret;
+ size_t ncopy;
+
+ add_wait_queue(&dpram_err_wait_q, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ while (1) {
+ local_irq_save(flags);
+
+ if (dpram_err_len) {
+ ncopy = min(count, dpram_err_len);
+
+ if (copy_to_user(buf, dpram_err_buf, ncopy)) {
+ ret = -EFAULT;
+ }
+
+ else {
+ ret = ncopy;
+ }
+
+ dpram_err_len = 0;
+
+ local_irq_restore(flags);
+ break;
+ }
+
+ local_irq_restore(flags);
+
+ if (filp->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ break;
+ }
+
+ schedule();
+ }
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&dpram_err_wait_q, &wait);
+
+ return ret;
+}
+
+static int dpram_err_fasync(int fd, struct file *filp, int mode)
+{
+ return fasync_helper(fd, filp, mode, &dpram_err_async_q);
+}
+
+static unsigned int dpram_err_poll(struct file *filp,
+ struct poll_table_struct *wait)
+{
+ poll_wait(filp, &dpram_err_wait_q, wait);
+ return ((dpram_err_len) ? (POLLIN | POLLRDNORM) : 0);
+}
+#endif /* _ENABLE_ERROR_DEVICE */
+
+/* handlers. */
+static void res_ack_tasklet_handler(unsigned long data)
+{
+ dpram_device_t *device = (dpram_device_t *)data;
+
+ if (device && device->serial.tty) {
+ struct tty_struct *tty = device->serial.tty;
+
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc->ops->write_wakeup) {
+ (tty->ldisc->ops->write_wakeup)(tty);
+ } // nandu
+
+ wake_up_interruptible(&tty->write_wait);
+ }
+}
+
+static void fmt_rcv_tasklet_handler(unsigned long data)
+{
+ dpram_tasklet_data_t *tasklet_data = (dpram_tasklet_data_t *)data;
+
+ dpram_device_t *device = tasklet_data->device;
+ u16 non_cmd = tasklet_data->non_cmd;
+
+ int ret = 0;
+ int cnt = 0;
+
+ if (device && device->serial.tty) {
+ struct tty_struct *tty = device->serial.tty;
+
+ while (dpram_get_read_available(device)) {
+ ret = dpram_read_fmt(device, non_cmd);
+
+ if (!ret) cnt++;
+
+ if (cnt > 10) {
+ dpram_drop_data(device);
+ break;
+ }
+ if (ret < 0) {
+ printk(KERN_ERR "%s, dpram_read_fmt failed\n", __func__);
+ /* TODO: ... wrong.. */
+ }
+ tty_flip_buffer_push(tty);
+ }
+ }
+
+ else {
+ dpram_drop_data(device);
+ }
+}
+
+static void raw_rcv_tasklet_handler(unsigned long data)
+{
+ dpram_tasklet_data_t *tasklet_data = (dpram_tasklet_data_t *)data;
+
+ dpram_device_t *device = tasklet_data->device;
+ u16 non_cmd = tasklet_data->non_cmd;
+
+ int ret = 0;
+
+ while (dpram_get_read_available(device)) {
+ ret = dpram_read_raw(device, non_cmd);
+ if (ret < 0) {
+ printk(KERN_ERR "%s, dpram_read failed\n", __func__);
+ /* TODO: ... wrong.. */
+ }
+ }
+}
+
+static void cmd_req_active_handler(void)
+{
+ send_interrupt_to_phone_with_semaphore(INT_COMMAND(INT_MASK_CMD_RES_ACTIVE));
+}
+
+static void cmd_error_display_handler(void)
+{
+
+#ifdef _ENABLE_ERROR_DEVICE
+ char buf[DPRAM_ERR_MSG_LEN];
+ unsigned long flags;
+
+ memset((void *)buf, 0, sizeof (buf));
+
+ if (!dpram_phone_getstatus()) {
+ memcpy((void *)buf, "8 $PHONE-OFF", sizeof("8 $PHONE-OFF"));
+ }
+ else {
+ buf[0] = '1';
+ buf[1] = ' ';
+
+ if(*onedram_sem == 0x1) {
+ READ_FROM_DPRAM((buf + 2), DPRAM_PHONE2PDA_FORMATTED_BUFFER_ADDRESS,
+ sizeof (buf) - 3);
+ }
+ }
+
+ printk(KERN_ERR "[PHONE ERROR] ->> %s\n", buf);
+
+ local_irq_save(flags);
+ memcpy(dpram_err_buf, buf, DPRAM_ERR_MSG_LEN);
+ dpram_err_len = 64;
+ local_irq_restore(flags);
+
+ wake_up_interruptible(&dpram_err_wait_q);
+ kill_fasync(&dpram_err_async_q, SIGIO, POLL_IN);
+
+#endif /* _ENABLE_ERROR_DEVICE */
+
+}
+
+static void cmd_phone_start_handler(void)
+{
+
+
+ printk(KERN_ERR "[OneDRAM] Received 0xc8 from MailboxAB (Phone Boot OK).\n");
+ if(!phone_sync) {
+ dpram_init_and_report();
+ }
+}
+
+static void cmd_req_time_sync_handler(void)
+{
+ /* TODO: add your codes here.. */
+}
+
+static void cmd_phone_deep_sleep_handler(void)
+{
+ /* TODO: add your codes here.. */
+}
+
+static void cmd_nv_rebuilding_handler(void)
+{
+ /* TODO: add your codes here.. */
+}
+
+static void cmd_emer_down_handler(void)
+{
+ /* TODO: add your codes here.. */
+}
+
+static void cmd_smp_rep_handler(void)
+{
+ /* TODO: add your codes here.. */
+ unreceived_semaphore = 0;
+}
+
+static void semaphore_control_handler(unsigned long data)
+{
+ const u16 cmd = INT_COMMAND(INT_MASK_CMD_SMP_REP);
+
+
+ if(return_onedram_semaphore(__func__)) {
+ *onedram_mailboxBA = cmd;
+ }
+}
+
+
+static void command_handler(u16 cmd)
+{
+ switch (cmd) {
+ case INT_MASK_CMD_REQ_ACTIVE:
+ cmd_req_active_handler();
+ break;
+
+ case INT_MASK_CMD_ERR_DISPLAY:
+ cmd_error_display_handler();
+ break;
+
+ case INT_MASK_CMD_PHONE_START:
+ cmd_phone_start_handler();
+ break;
+
+ case INT_MASK_CMD_REQ_TIME_SYNC:
+ cmd_req_time_sync_handler();
+ break;
+
+ case INT_MASK_CMD_PHONE_DEEP_SLEEP:
+ cmd_phone_deep_sleep_handler();
+ break;
+
+ case INT_MASK_CMD_NV_REBUILDING:
+ cmd_nv_rebuilding_handler();
+ break;
+
+ case INT_MASK_CMD_EMER_DOWN:
+ cmd_emer_down_handler();
+ break;
+
+ case INT_MASK_CMD_SMP_REQ:
+ tasklet_schedule(&semaphore_control_tasklet);
+ break;
+
+ case INT_MASK_CMD_SMP_REP:
+ cmd_smp_rep_handler();
+ break;
+
+ default:
+ dprintk(KERN_ERR "Unknown command.. %x\n", cmd);
+ }
+}
+
+static void non_command_handler(u16 non_cmd)
+{
+ u16 head, tail;
+
+ /* @LDK@ formatted check. */
+
+
+ if(!(*onedram_sem)) {
+ return;
+ }
+
+ READ_FROM_DPRAM_VERIFY(&head, DPRAM_PHONE2PDA_FORMATTED_HEAD_ADDRESS, sizeof(head));
+ READ_FROM_DPRAM_VERIFY(&tail, DPRAM_PHONE2PDA_FORMATTED_TAIL_ADDRESS, sizeof(tail));
+
+ if (head != tail) {
+ non_cmd |= INT_MASK_SEND_F;
+ }else {
+ if(non_cmd & INT_MASK_REQ_ACK_F)
+ printk(KERN_ERR "=====> FMT DATA EMPTY & REQ_ACK_F\n");
+ }
+
+ /* @LDK@ raw check. */
+ READ_FROM_DPRAM_VERIFY(&head, DPRAM_PHONE2PDA_RAW_HEAD_ADDRESS, sizeof(head));
+ READ_FROM_DPRAM_VERIFY(&tail, DPRAM_PHONE2PDA_RAW_TAIL_ADDRESS, sizeof(tail));
+
+ if (head != tail) {
+ non_cmd |= INT_MASK_SEND_R;
+ }else {
+ if(non_cmd & INT_MASK_REQ_ACK_R)
+ printk(KERN_ERR "=====> RAW DATA EMPTY & REQ_ACK_R\n");
+ }
+
+ /* @LDK@ +++ scheduling.. +++ */
+ if (non_cmd & INT_MASK_SEND_F) {
+ dpram_tasklet_data[FORMATTED_INDEX].device = &dpram_table[FORMATTED_INDEX];
+ dpram_tasklet_data[FORMATTED_INDEX].non_cmd = non_cmd;
+ fmt_send_tasklet.data = (unsigned long)&dpram_tasklet_data[FORMATTED_INDEX];
+ tasklet_schedule(&fmt_send_tasklet);
+ }
+ if (non_cmd & INT_MASK_SEND_R) {
+ dpram_tasklet_data[RAW_INDEX].device = &dpram_table[RAW_INDEX];
+ dpram_tasklet_data[RAW_INDEX].non_cmd = non_cmd;
+ raw_send_tasklet.data = (unsigned long)&dpram_tasklet_data[RAW_INDEX];
+ /* @LDK@ raw buffer op. -> soft irq level. */
+ tasklet_hi_schedule(&raw_send_tasklet);
+ }
+
+ if (non_cmd & INT_MASK_RES_ACK_F) {
+ tasklet_schedule(&fmt_res_ack_tasklet);
+ }
+
+ if (non_cmd & INT_MASK_RES_ACK_R) {
+ tasklet_hi_schedule(&raw_res_ack_tasklet);
+ }
+
+}
+
+static inline
+void check_int_pin_level(void)
+{
+ u16 mask = 0, cnt = 0;
+
+ while (cnt++ < 3) {
+ mask = *onedram_mailboxAB;
+ if (gpio_get_value(GPIO_ONEDRAM_INT_N))
+ break;
+ }
+}
+
+/* @LDK@ interrupt handlers. */
+static irqreturn_t dpram_irq_handler(int irq, void *dev_id)
+{
+ u16 irq_mask = 0;
+
+ irq_mask = *onedram_mailboxAB;
+
+ /* valid bit verification. @LDK@ */
+ if (!(irq_mask & INT_MASK_VALID)) {
+ printk(KERN_ERR "Invalid interrupt mask: 0x%04x\n", irq_mask);
+ return IRQ_NONE;
+ }
+
+ /* command or non-command? @LDK@ */
+ if (irq_mask & INT_MASK_COMMAND) {
+ irq_mask &= ~(INT_MASK_VALID | INT_MASK_COMMAND);
+ command_handler(irq_mask);
+ }
+ else {
+ irq_mask &= ~INT_MASK_VALID;
+ non_command_handler(irq_mask);
+ }
+
+ return IRQ_HANDLED;
+}
+
+#define DPRAM_USES_DELAYED_PHONE_ACTIVE_IRQ
+
+#ifdef DPRAM_USES_DELAYED_PHONE_ACTIVE_IRQ
+static void phone_active_delayed_work_handler(struct work_struct *ignored);
+
+static void phone_active_delayed_work_handler(struct work_struct *ignored)
+{
+ /* ignore momentary drop in the phone_active gpio */
+ if(gpio_get_value(GPIO_PHONE_ACTIVE) != 0) {
+ printk("[%s] Phone active is now high! Ignored...", __func__);
+ return;
+ }
+
+ printk("Phone active is still low!!!" );
+
+#ifdef _ENABLE_ERROR_DEVICE
+ if(phone_sync)
+ request_phone_reset();
+#endif
+}
+#endif /* DPRAM_USES_DELAYED_PHONE_ACTIVE_IRQ */
+
+static irqreturn_t phone_active_irq_handler(int irq, void *dev_id)
+{
+ printk(KERN_ERR "[OneDRAM] PHONE_ACTIVE level: %s, sem: %d, phone_sync: %d\n",
+ gpio_get_value(GPIO_PHONE_ACTIVE)?"HIGH":"LOW ", *onedram_sem, phone_sync);
+
+#ifdef DPRAM_USES_DELAYED_PHONE_ACTIVE_IRQ
+ if(gpio_get_value(GPIO_PHONE_ACTIVE) == 0) {
+ schedule_delayed_work(&phone_active_delayed_work, msecs_to_jiffies(1));
+ }
+#endif
+
+#ifdef _ENABLE_ERROR_DEVICE
+ if((phone_sync) && (!gpio_get_value(GPIO_PHONE_ACTIVE)))
+ request_phone_reset();
+#endif
+
+ return IRQ_HANDLED;
+}
+
+/* basic functions. */
+#ifdef _ENABLE_ERROR_DEVICE
+static struct file_operations dpram_err_ops = {
+ .owner = THIS_MODULE,
+ .read = dpram_err_read,
+ .fasync = dpram_err_fasync,
+ .poll = dpram_err_poll,
+ .llseek = no_llseek,
+
+ /* TODO: add more operations */
+};
+#endif /* _ENABLE_ERROR_DEVICE */
+
+static struct tty_operations dpram_tty_ops = {
+ .open = dpram_tty_open,
+ .close = dpram_tty_close,
+ .write = dpram_tty_write,
+ .write_room = dpram_tty_write_room,
+ .ioctl = dpram_tty_ioctl,
+ .chars_in_buffer = dpram_tty_chars_in_buffer,
+
+ /* TODO: add more operations */
+};
+
+#ifdef _ENABLE_ERROR_DEVICE
+
+static void unregister_dpram_err_device(void)
+{
+ unregister_chrdev(DRIVER_MAJOR_NUM, DPRAM_ERR_DEVICE);
+ class_destroy(dpram_class);
+}
+
+static int register_dpram_err_device(void)
+{
+ /* @LDK@ 1 = formatted, 2 = raw, so error device is '0' */
+ struct device *dpram_err_dev_t;
+ int ret = register_chrdev(DRIVER_MAJOR_NUM, DPRAM_ERR_DEVICE, &dpram_err_ops);
+
+ if ( ret < 0 )
+ {
+ return ret;
+ }
+
+ dpram_class = class_create(THIS_MODULE, "err");
+
+ if (IS_ERR(dpram_class))
+ {
+ unregister_dpram_err_device();
+ return -EFAULT;
+ }
+
+ dpram_err_dev_t = device_create(dpram_class, NULL,
+ MKDEV(DRIVER_MAJOR_NUM, 0), NULL, DPRAM_ERR_DEVICE);
+
+ if (IS_ERR(dpram_err_dev_t))
+ {
+ unregister_dpram_err_device();
+ return -EFAULT;
+ }
+
+ return 0;
+}
+#endif /* _ENABLE_ERROR_DEVICE */
+
+static int register_dpram_driver(void)
+{
+ int retval = 0;
+
+ /* @LDK@ allocate tty driver */
+ dpram_tty_driver = alloc_tty_driver(MAX_INDEX);
+
+ if (!dpram_tty_driver) {
+ return -ENOMEM;
+ }
+
+ /* @LDK@ initialize tty driver */
+ dpram_tty_driver->owner = THIS_MODULE;
+ dpram_tty_driver->magic = TTY_DRIVER_MAGIC;
+ dpram_tty_driver->driver_name = DRIVER_NAME;
+ dpram_tty_driver->name = "dpram";
+ dpram_tty_driver->major = DRIVER_MAJOR_NUM;
+ dpram_tty_driver->minor_start = 1;
+ dpram_tty_driver->num = 2;
+ dpram_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ dpram_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ dpram_tty_driver->flags = TTY_DRIVER_REAL_RAW;
+ dpram_tty_driver->init_termios = tty_std_termios;
+ dpram_tty_driver->init_termios.c_cflag =
+ (B115200 | CS8 | CREAD | CLOCAL | HUPCL);
+
+ tty_set_operations(dpram_tty_driver, &dpram_tty_ops);
+
+ dpram_tty_driver->ttys = dpram_tty;
+ dpram_tty_driver->termios = dpram_termios;
+ dpram_tty_driver->termios_locked = dpram_termios_locked;
+
+ /* @LDK@ register tty driver */
+ retval = tty_register_driver(dpram_tty_driver);
+
+ if (retval) {
+ dprintk(KERN_ERR "tty_register_driver error\n");
+ put_tty_driver(dpram_tty_driver);
+ return retval;
+ }
+
+ return 0;
+}
+
+static void unregister_dpram_driver(void)
+{
+ tty_unregister_driver(dpram_tty_driver);
+}
+
+static int multipdp_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return -EINVAL;
+}
+
+static struct file_operations multipdp_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = multipdp_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct miscdevice multipdp_dev = {
+ .minor = 132, //MISC_DYNAMIC_MINOR,
+ .name = APP_DEVNAME,
+ .fops = &multipdp_fops,
+};
+
+static inline struct pdp_info * pdp_get_serdev(const char *name)
+{
+ int slot;
+ struct pdp_info *dev;
+
+ for (slot = 0; slot < MAX_PDP_CONTEXT; slot++) {
+ dev = pdp_table[slot];
+ if (dev && dev->type == DEV_TYPE_SERIAL &&
+ strcmp(name, dev->vs_dev.tty_name) == 0) {
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+
+static inline struct pdp_info * pdp_remove_dev(u8 id)
+{
+ int slot;
+ struct pdp_info *dev;
+
+ for (slot = 0; slot < MAX_PDP_CONTEXT; slot++) {
+ if (pdp_table[slot] && pdp_table[slot]->id == id) {
+ dev = pdp_table[slot];
+ pdp_table[slot] = NULL;
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+
+static int vs_open(struct tty_struct *tty, struct file *filp)
+{
+ struct pdp_info *dev;
+
+ dev = pdp_get_serdev(tty->driver->name); // 2.6 kernel porting
+
+ if (dev == NULL) {
+ return -ENODEV;
+ }
+
+ tty->driver_data = (void *)dev;
+ tty->low_latency = 1;
+ dev->vs_dev.tty = tty;
+ dev->vs_dev.refcount++;
+ printk(KERN_ERR "[%s] %s, refcount: %d \n", __func__, tty->driver->name, dev->vs_dev.refcount);
+
+ return 0;
+}
+
+static void vs_close(struct tty_struct *tty, struct file *filp)
+{
+ struct pdp_info *dev;
+
+ dev = pdp_get_serdev(tty->driver->name);
+
+ if (!dev )
+ return;
+ dev->vs_dev.refcount--;
+ printk(KERN_ERR "[%s] %s, refcount: %d \n", __func__, tty->driver->name, dev->vs_dev.refcount);
+
+ // TODO..
+
+ return;
+}
+
+static int pdp_mux(struct pdp_info *dev, const void *data, size_t len )
+{
+ int ret;
+ size_t nbytes;
+ u8 *tx_buf;
+ struct pdp_hdr *hdr;
+ const u8 *buf;
+
+ tx_buf = dev->tx_buf;
+ hdr = (struct pdp_hdr *)(tx_buf + 1);
+ buf = data;
+
+ hdr->id = dev->id;
+ hdr->control = 0;
+
+ while (len) {
+ if (len > MAX_PDP_DATA_LEN) {
+ nbytes = MAX_PDP_DATA_LEN;
+ } else {
+ nbytes = len;
+ }
+ hdr->len = nbytes + sizeof(struct pdp_hdr);
+
+ tx_buf[0] = 0x7f;
+
+ memcpy(tx_buf + 1 + sizeof(struct pdp_hdr), buf, nbytes);
+
+ tx_buf[1 + hdr->len] = 0x7e;
+
+ ret = dpram_write(&dpram_table[RAW_INDEX], tx_buf, hdr->len + 2);
+
+ if (ret < 0) {
+ printk(KERN_ERR "write_to_dpram() failed: %d\n", ret);
+ return ret;
+ }
+ buf += nbytes;
+ len -= nbytes;
+ }
+ return 0;
+}
+
+
+static int vs_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ int ret;
+ struct pdp_info *dev = (struct pdp_info *)tty->driver_data;
+
+ ret = pdp_mux(dev, buf, count);
+
+ if (ret == 0) {
+ ret = count;
+ }
+
+ return ret;
+}
+
+static int vs_write_room(struct tty_struct *tty)
+{
+ return 8192*2;
+}
+
+static int vs_chars_in_buffer(struct tty_struct *tty)
+{
+ return 0;
+}
+
+static int vs_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return -ENOIOCTLCMD;
+}
+
+
+
+static struct tty_operations multipdp_tty_ops = {
+ .open = vs_open,
+ .close = vs_close,
+ .write = vs_write,
+ .write_room = vs_write_room,
+ .ioctl = vs_ioctl,
+ .chars_in_buffer = vs_chars_in_buffer,
+
+ /* TODO: add more operations */
+};
+
+static struct tty_driver* get_tty_driver_by_id(struct pdp_info *dev)
+{
+ int index = 0;
+
+ switch (dev->id) {
+ case 1: index = 0; break;
+ case 7: index = 1; break;
+ case 9: index = 2; break;
+ default: index = 0;
+ }
+
+ return &dev->vs_dev.tty_driver[index];
+}
+
+static int get_minor_start_index(int id)
+{
+ int start = 0;
+
+ switch (id) {
+ case 1: start = 0; break;
+ case 7: start = 1; break;
+ case 9: start = 2; break;
+ default: start = 0;
+ }
+
+ return start;
+}
+
+
+static int vs_add_dev(struct pdp_info *dev)
+{
+ struct tty_driver *tty_driver;
+
+ tty_driver = get_tty_driver_by_id(dev);
+
+ if (!tty_driver) {
+ printk(KERN_ERR "tty driver is NULL!\n");
+ return -1;
+ }
+
+ kref_init(&tty_driver->kref);
+
+ tty_driver->magic = TTY_DRIVER_MAGIC;
+ tty_driver->driver_name = "multipdp";
+ tty_driver->name = dev->vs_dev.tty_name;
+ tty_driver->major = CSD_MAJOR_NUM;
+ tty_driver->minor_start = get_minor_start_index(dev->id);
+ tty_driver->num = 1;
+ tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ tty_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_driver->ttys = dev->vs_dev.tty_table; // 2.6 kernel porting
+ tty_driver->termios = dev->vs_dev.termios;
+ tty_driver->termios_locked = dev->vs_dev.termios_locked;
+
+ tty_set_operations(tty_driver, &multipdp_tty_ops);
+ return tty_register_driver(tty_driver);
+}
+
+static void vs_del_dev(struct pdp_info *dev)
+{
+ struct tty_driver *tty_driver = NULL;
+
+ tty_driver = get_tty_driver_by_id(dev);
+ tty_unregister_driver(tty_driver);
+}
+
+static inline void check_pdp_table(const char * func, int line)
+{
+ int slot;
+ for (slot = 0; slot < MAX_PDP_CONTEXT; slot++) {
+ if(pdp_table[slot])
+ printk(KERN_ERR "----->[%s,%d] slot: %d id: %d, name: %s\n", func, line, slot, pdp_table[slot]->id, pdp_table[slot]->vs_dev.tty_name);
+ }
+
+
+}
+
+static inline struct pdp_info * pdp_get_dev(u8 id)
+{
+ int slot;
+
+
+ for (slot = 0; slot < MAX_PDP_CONTEXT; slot++) {
+ if (pdp_table[slot] && pdp_table[slot]->id == id) {
+ return pdp_table[slot];
+ }
+ }
+ return NULL;
+}
+
+static inline int pdp_add_dev(struct pdp_info *dev)
+{
+ int slot;
+
+ if (pdp_get_dev(dev->id)) {
+ return -EBUSY;
+ }
+
+ for (slot = 0; slot < MAX_PDP_CONTEXT; slot++) {
+ if (pdp_table[slot] == NULL) {
+ pdp_table[slot] = dev;
+ return slot;
+ }
+ }
+ return -ENOSPC;
+}
+
+
+static int pdp_activate(pdp_arg_t *pdp_arg, unsigned type, unsigned flags)
+{
+ int ret;
+ struct pdp_info *dev;
+
+ printk(KERN_ERR "%s, id: %d\n", __func__, pdp_arg->id);
+
+ dev = kmalloc(sizeof(struct pdp_info) + MAX_PDP_PACKET_LEN, GFP_KERNEL);
+ if (dev == NULL) {
+ printk(KERN_ERR "out of memory\n");
+ return -ENOMEM;
+ }
+ memset(dev, 0, sizeof(struct pdp_info));
+
+ dev->id = pdp_arg->id;
+
+ dev->type = type;
+ dev->flags = flags;
+ dev->tx_buf = (u8 *)(dev + 1);
+
+ if (type == DEV_TYPE_SERIAL) {
+ sema_init(&dev->vs_dev.write_lock, 1);
+ strcpy(dev->vs_dev.tty_name, pdp_arg->ifname);
+
+ ret = vs_add_dev(dev);
+ if (ret < 0) {
+ kfree(dev);
+ return ret;
+ }
+
+ mutex_lock(&pdp_lock);
+ ret = pdp_add_dev(dev);
+ if (ret < 0) {
+ printk(KERN_ERR "pdp_add_dev() failed\n");
+ mutex_unlock(&pdp_lock);
+ vs_del_dev(dev);
+ kfree(dev);
+ return ret;
+ }
+ mutex_unlock(&pdp_lock);
+
+ {
+ struct tty_driver * tty_driver = get_tty_driver_by_id(dev);
+
+ printk(KERN_ERR "%s(id: %u) serial device is created.\n",
+ tty_driver->name, dev->id);
+ }
+ }
+
+ return 0;
+}
+
+static int multipdp_init(void)
+{
+ int i;
+
+ pdp_arg_t pdp_args[NUM_PDP_CONTEXT] = {
+ { .id = 1, .ifname = "ttyCSD" },
+ { .id = 7, .ifname = "ttyCDMA" },
+ { .id = 9, .ifname = "ttyTRFB" },
+ };
+
+
+ /* create serial device for Circuit Switched Data */
+ for (i = 0; i < NUM_PDP_CONTEXT; i++) {
+ if (pdp_activate(&pdp_args[i], DEV_TYPE_SERIAL, DEV_FLAG_STICKY) < 0) {
+ printk(KERN_ERR "failed to create a serial device for %s\n", pdp_args[i].ifname);
+ }
+ }
+
+ return 0;
+}
+
+
+static void init_devices(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_INDEX; i++) {
+ sema_init(&dpram_table[i].serial.sem, 1);
+
+ dpram_table[i].serial.open_count = 0;
+ dpram_table[i].serial.tty = NULL;
+ }
+}
+
+static void init_hw_setting(void)
+{
+ /* initial pin settings - dpram driver control */
+ s3c_gpio_cfgpin(GPIO_PHONE_ACTIVE, S3C_GPIO_SFN(GPIO_PHONE_ACTIVE_AF));
+ s3c_gpio_setpull(GPIO_PHONE_ACTIVE, S3C_GPIO_PULL_NONE);
+ irq_set_irq_type(IRQ_PHONE_ACTIVE, IRQ_TYPE_EDGE_BOTH);
+
+ s3c_gpio_cfgpin(GPIO_ONEDRAM_INT_N, S3C_GPIO_SFN(GPIO_ONEDRAM_INT_N_AF));
+ s3c_gpio_setpull(GPIO_ONEDRAM_INT_N, S3C_GPIO_PULL_NONE);
+ irq_set_irq_type(IRQ_ONEDRAM_INT_N, IRQ_TYPE_EDGE_FALLING);
+
+ if (gpio_is_valid(GPIO_PHONE_ON)) {
+ if (gpio_request(GPIO_PHONE_ON, "dpram/GPIO_PHONE_ON"))
+ printk(KERN_ERR "Filed to request GPIO_PHONE_ON!\n");
+ gpio_direction_output(GPIO_PHONE_ON, GPIO_LEVEL_LOW);
+ }
+ s3c_gpio_setpull(GPIO_PHONE_ON, S3C_GPIO_PULL_NONE);
+ gpio_set_value(GPIO_PHONE_ON, GPIO_LEVEL_LOW);
+
+ if (gpio_is_valid(GPIO_PHONE_RST_N)) {
+ if (gpio_request(GPIO_PHONE_RST_N, "dpram/GPIO_PHONE_RST_N"))
+ printk(KERN_ERR "Filed to request GPIO_PHONE_RST_N!\n");
+ gpio_direction_output(GPIO_PHONE_RST_N, GPIO_LEVEL_HIGH);
+ }
+ s3c_gpio_setpull(GPIO_PHONE_RST_N, S3C_GPIO_PULL_NONE);
+
+ if (gpio_is_valid(GPIO_PDA_ACTIVE)) {
+ if (gpio_request(GPIO_PDA_ACTIVE, "dpram/GPIO_PDA_ACTIVE"))
+ printk(KERN_ERR "Filed to request GPIO_PDA_ACTIVE!\n");
+ gpio_direction_output(GPIO_PDA_ACTIVE, GPIO_LEVEL_HIGH);
+ }
+ s3c_gpio_setpull(GPIO_PDA_ACTIVE, S3C_GPIO_PULL_NONE);
+
+}
+
+static void kill_tasklets(void)
+{
+ tasklet_kill(&fmt_res_ack_tasklet);
+ tasklet_kill(&raw_res_ack_tasklet);
+
+ tasklet_kill(&fmt_send_tasklet);
+ tasklet_kill(&raw_send_tasklet);
+}
+
+static int register_interrupt_handler(void)
+{
+
+ unsigned int dpram_irq, phone_active_irq;
+ int retval = 0;
+
+ dpram_irq = IRQ_ONEDRAM_INT_N;
+ phone_active_irq = IRQ_PHONE_ACTIVE;
+
+ /* @LDK@ dpram interrupt */
+ retval = request_irq(dpram_irq, dpram_irq_handler, IRQF_DISABLED, "dpram irq", NULL);
+
+ if (retval) {
+ dprintk(KERN_ERR "DPRAM interrupt handler failed.\n");
+ unregister_dpram_driver();
+ return -1;
+ }
+
+ /* @LDK@ phone active interrupt */
+ retval = request_irq(phone_active_irq, phone_active_irq_handler, IRQF_DISABLED, "Phone Active", NULL);
+
+ enable_irq_wake(dpram_irq);
+ enable_irq_wake(phone_active_irq);
+
+ if (retval) {
+ dprintk(KERN_ERR "Phone active interrupt handler failed.\n");
+ free_irq(phone_active_irq, NULL);
+ unregister_dpram_driver();
+ return -1;
+ }
+
+ return 0;
+}
+
+static void check_miss_interrupt(void)
+{
+ unsigned long flags;
+
+ if (gpio_get_value(GPIO_PHONE_ACTIVE) &&
+ (!gpio_get_value(GPIO_ONEDRAM_INT_N))) {
+ dprintk(KERN_ERR "there is a missed interrupt. try to read it!\n");
+
+ if (!(*onedram_sem)) {
+ printk(KERN_ERR "[OneDRAM] (%s) semaphore: %d\n", __func__, *onedram_sem);
+ onedram_get_semaphore(__func__);
+ }
+
+ local_irq_save(flags);
+ dpram_irq_handler(IRQ_ONEDRAM_INT_N, NULL);
+ local_irq_restore(flags);
+ }
+}
+
+static int dpram_suspend(struct platform_device *dev, pm_message_t state)
+{
+ gpio_set_value(GPIO_PDA_ACTIVE, GPIO_LEVEL_LOW);
+ flush_work(&phone_active_delayed_work.work);
+ if(requested_semaphore)
+ printk(KERN_ERR "=====> %s requested semaphore: %d\n", __func__, requested_semaphore);
+ return 0;
+}
+
+static int dpram_resume(struct platform_device *dev)
+{
+ gpio_set_value(GPIO_PDA_ACTIVE, GPIO_LEVEL_HIGH);
+ if(requested_semaphore)
+ printk(KERN_ERR "=====> %s requested semaphore: %d\n", __func__, requested_semaphore);
+ check_miss_interrupt();
+ return 0;
+}
+
+static int dpram_shutdown(struct platform_Device *dev)
+{
+ int ret = 0;
+ printk("\ndpram_shutdown !!!!!!!!!!!!!!!!!!!!!\n");
+ printk("\ndpram_shutdown ret : %d\n", ret);
+
+ unregister_dpram_driver();
+#ifdef _ENABLE_ERROR_DEVICE
+ unregister_dpram_err_device();
+#endif
+
+ free_irq(IRQ_ONEDRAM_INT_N, NULL);
+ free_irq(IRQ_PHONE_ACTIVE, NULL);
+
+ kill_tasklets();
+ return 0;
+}
+
+static int __devinit dpram_probe(struct platform_device *dev)
+{
+ int retval;
+
+ /* @LDK@ register dpram (tty) driver */
+ retval = register_dpram_driver();
+ if (retval) {
+ dprintk(KERN_ERR "Failed to register dpram (tty) driver.\n");
+ return -1;
+ }
+
+#ifdef _ENABLE_ERROR_DEVICE
+ /* @LDK@ register dpram error device */
+ retval = register_dpram_err_device();
+ if (retval) {
+ dprintk(KERN_ERR "Failed to register dpram error device.\n");
+
+ unregister_dpram_driver();
+ return -1;
+ }
+
+ memset((void *)dpram_err_buf, '\0', sizeof dpram_err_buf);
+#endif /* _ENABLE_ERROR_DEVICE */
+
+ /* create app. interface device */
+ retval = misc_register(&multipdp_dev);
+ if (retval < 0) {
+ printk(KERN_ERR "misc_register() failed\n");
+ return -1;
+ }
+ multipdp_init();
+
+ /* @LDK@ H/W setting */
+ init_hw_setting();
+
+ dpram_shared_bank_remap();
+
+ /* @LDK@ initialize device table */
+ init_devices();
+
+ INIT_DELAYED_WORK(&phone_active_delayed_work, phone_active_delayed_work_handler);
+
+ /* @LDK@ register interrupt handler */
+ if ((retval = register_interrupt_handler()) < 0) {
+ return -1;
+ }
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry(DRIVER_PROC_ENTRY, 0, 0, dpram_read_proc, NULL);
+#endif /* CONFIG_PROC_FS */
+
+ return 0;
+}
+
+static int __devexit dpram_remove(struct platform_device *dev)
+{
+ /* @LDK@ unregister dpram (tty) driver */
+ unregister_dpram_driver();
+
+ /* @LDK@ unregister dpram error device */
+#ifdef _ENABLE_ERROR_DEVICE
+ unregister_dpram_err_device();
+#endif
+
+ /* remove app. interface device */
+ misc_deregister(&multipdp_dev);
+
+ /* @LDK@ unregister irq handler */
+ free_irq(IRQ_ONEDRAM_INT_N, NULL);
+ free_irq(IRQ_PHONE_ACTIVE, NULL);
+
+ flush_work(&phone_active_delayed_work.work);
+
+ kill_tasklets();
+
+ return 0;
+}
+
+static struct platform_driver platform_dpram_driver = {
+ .probe = dpram_probe,
+ .remove = __devexit_p(dpram_remove),
+ .suspend = dpram_suspend,
+ .resume = dpram_resume,
+ .shutdown = dpram_shutdown,
+ .driver = {
+ .name = "dpram-device",
+ },
+};
+
+/* init & cleanup. */
+static int __init dpram_init(void)
+{
+ return platform_driver_register(&platform_dpram_driver);
+}
+
+static void __exit dpram_exit(void)
+{
+ platform_driver_unregister(&platform_dpram_driver);
+}
+
+module_init(dpram_init);
+module_exit(dpram_exit);
+
+MODULE_AUTHOR("SAMSUNG ELECTRONICS CO., LTD");
+MODULE_DESCRIPTION("Onedram Device Driver.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/samsung_modemctl/dpram/dpram.h b/drivers/misc/samsung_modemctl/dpram/dpram.h
new file mode 100644
index 0000000..b18a06f
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/dpram/dpram.h
@@ -0,0 +1,189 @@
+/****************************************************************************
+
+**
+
+** COPYRIGHT(C) : Samsung Electronics Co.Ltd, 2006-2010 ALL RIGHTS RESERVED
+
+**
+
+** AUTHOR : Kim, Geun-Young <geunyoung.kim@samsung.com> @LDK@
+
+** @LDK@
+
+****************************************************************************/
+
+#ifndef __DPRAM_H__
+#define __DPRAM_H__
+
+/* 32KB Size */
+#define DPRAM_SIZE 0x8000
+
+/* Memory Address */
+#define DPRAM_START_ADDRESS 0x0000
+#define DPRAM_MAGIC_CODE_ADDRESS (DPRAM_START_ADDRESS)
+#define DPRAM_ACCESS_ENABLE_ADDRESS (DPRAM_START_ADDRESS + 0x0002)
+
+#define DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS (DPRAM_START_ADDRESS + 0x0004)
+#define DPRAM_PDA2PHONE_FORMATTED_HEAD_ADDRESS (DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS)
+#define DPRAM_PDA2PHONE_FORMATTED_TAIL_ADDRESS (DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS + 0x0002)
+#define DPRAM_PDA2PHONE_FORMATTED_BUFFER_ADDRESS (DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS + 0x0004)
+#define DPRAM_PDA2PHONE_FORMATTED_BUFFER_SIZE 4092
+
+#define DPRAM_PDA2PHONE_RAW_START_ADDRESS (DPRAM_PDA2PHONE_FORMATTED_START_ADDRESS + (DPRAM_PDA2PHONE_FORMATTED_BUFFER_SIZE+4))
+#define DPRAM_PDA2PHONE_RAW_HEAD_ADDRESS (DPRAM_PDA2PHONE_RAW_START_ADDRESS)
+#define DPRAM_PDA2PHONE_RAW_TAIL_ADDRESS (DPRAM_PDA2PHONE_RAW_START_ADDRESS + 2)
+#define DPRAM_PDA2PHONE_RAW_BUFFER_ADDRESS (DPRAM_PDA2PHONE_RAW_START_ADDRESS + 4)
+#define DPRAM_PDA2PHONE_RAW_BUFFER_SIZE 12272
+
+#define DPRAM_PHONE2PDA_FORMATTED_START_ADDRESS (DPRAM_PDA2PHONE_RAW_START_ADDRESS + (DPRAM_PDA2PHONE_RAW_BUFFER_SIZE+4))
+#define DPRAM_PHONE2PDA_FORMATTED_HEAD_ADDRESS (DPRAM_PHONE2PDA_FORMATTED_START_ADDRESS)
+#define DPRAM_PHONE2PDA_FORMATTED_TAIL_ADDRESS (DPRAM_PHONE2PDA_FORMATTED_START_ADDRESS + 0x0002)
+#define DPRAM_PHONE2PDA_FORMATTED_BUFFER_ADDRESS (DPRAM_PHONE2PDA_FORMATTED_START_ADDRESS + 0x0004)
+#define DPRAM_PHONE2PDA_FORMATTED_BUFFER_SIZE 4092
+
+
+#define DPRAM_PHONE2PDA_RAW_START_ADDRESS (DPRAM_PHONE2PDA_FORMATTED_START_ADDRESS + (DPRAM_PHONE2PDA_FORMATTED_BUFFER_SIZE+4))
+#define DPRAM_PHONE2PDA_RAW_HEAD_ADDRESS (DPRAM_PHONE2PDA_RAW_START_ADDRESS)
+#define DPRAM_PHONE2PDA_RAW_TAIL_ADDRESS (DPRAM_PHONE2PDA_RAW_START_ADDRESS + 0x0002)
+#define DPRAM_PHONE2PDA_RAW_BUFFER_ADDRESS (DPRAM_PHONE2PDA_RAW_START_ADDRESS + 0x0004)
+#define DPRAM_PHONE2PDA_RAW_BUFFER_SIZE 12272
+
+#define DPRAM_INTERRUPT_PORT_SIZE 2
+#define DPRAM_START_ADDRESS_PHYS 0x30000000
+#define DPRAM_SHARED_BANK 0x5000000
+
+#define DPRAM_SHARED_BANK_SIZE 0x1000000
+#define MAX_MODEM_IMG_SIZE 0x1000000 //16 * 1024 * 1024
+#define MAX_DBL_IMG_SIZE 0x5000 //20 * 1024
+
+#define DPRAM_SFR 0xFFF800
+#define DPRAM_SMP DPRAM_SFR //semaphore
+
+
+#define DPRAM_MBX_AB DPRAM_SFR + 0x20 //mailbox a -> b
+#define DPRAM_MBX_BA DPRAM_SFR + 0x40 //mailbox b -> a
+#define DPRAM_CHECK_BA DPRAM_SFR + 0xC0 //check mailbox b -> a read
+
+
+#define DPRAM_PDA2PHONE_INTERRUPT_ADDRESS DPRAM_MBX_BA
+#define DPRAM_PHONE2PDA_INTERRUPT_ADDRESS DPRAM_MBX_AB
+
+#define PARTITION_ID_MODEM_IMG 0x08
+#define TRUE 1
+#define FALSE 0
+
+/*
+ * interrupt masks.
+ */
+#define INT_MASK_VALID 0x0080
+#define INT_MASK_COMMAND 0x0040
+#define INT_MASK_REQ_ACK_F 0x0020
+#define INT_MASK_REQ_ACK_R 0x0010
+#define INT_MASK_RES_ACK_F 0x0008
+#define INT_MASK_RES_ACK_R 0x0004
+#define INT_MASK_SEND_F 0x0002
+#define INT_MASK_SEND_R 0x0001
+
+#define INT_MASK_CMD_INIT_START 0x0001
+#define INT_MASK_CMD_INIT_END 0x0002
+#define INT_MASK_CMD_REQ_ACTIVE 0x0003
+#define INT_MASK_CMD_RES_ACTIVE 0x0004
+#define INT_MASK_CMD_REQ_TIME_SYNC 0x0005
+#define INT_MASK_CMD_PHONE_START 0x0008
+#define INT_MASK_CMD_ERR_DISPLAY 0x0009
+#define INT_MASK_CMD_PHONE_DEEP_SLEEP 0x000A
+#define INT_MASK_CMD_NV_REBUILDING 0x000B
+#define INT_MASK_CMD_EMER_DOWN 0x000C
+#define INT_MASK_CMD_SMP_REQ 0x000D
+#define INT_MASK_CMD_SMP_REP 0x000E
+
+#define INT_COMMAND(x) (INT_MASK_VALID | INT_MASK_COMMAND | x)
+#define INT_NON_COMMAND(x) (INT_MASK_VALID | x)
+
+#define FORMATTED_INDEX 0
+#define RAW_INDEX 1
+#define MAX_INDEX 2
+
+/* ioctl command definitions. */
+#define IOC_MZ_MAGIC ('o')
+#define DPRAM_PHONE_POWON _IO(IOC_MZ_MAGIC, 0xd0)
+#define DPRAM_PHONEIMG_LOAD _IO(IOC_MZ_MAGIC, 0xd1)
+#define DPRAM_NVDATA_LOAD _IO(IOC_MZ_MAGIC, 0xd2)
+#define DPRAM_PHONE_BOOTSTART _IO(IOC_MZ_MAGIC, 0xd3)
+
+struct _param_nv {
+ unsigned char *addr;
+ unsigned int size;
+};
+
+struct _param_em {
+ unsigned int offset;
+ unsigned char *addr;
+ unsigned int size;
+ int rw;
+};
+
+
+#define IOC_SEC_MAGIC (0xf0)
+#define DPRAM_PHONE_ON _IO(IOC_SEC_MAGIC, 0xc0)
+#define DPRAM_PHONE_OFF _IO(IOC_SEC_MAGIC, 0xc1)
+#define DPRAM_PHONE_GETSTATUS _IOR(IOC_SEC_MAGIC, 0xc2, unsigned int)
+#define DPRAM_PHONE_RESET _IO(IOC_SEC_MAGIC, 0xc5)
+#define DPRAM_PHONE_RAMDUMP_ON _IO(IOC_SEC_MAGIC, 0xc6)
+#define DPRAM_PHONE_RAMDUMP_OFF _IO(IOC_SEC_MAGIC, 0xc7)
+#define DPRAM_EXTRA_MEM_RW _IOWR(IOC_SEC_MAGIC, 0xc8, unsigned long)
+
+/*
+ * structure definitions.
+ */
+typedef struct dpram_serial {
+ /* pointer to the tty for this device */
+ struct tty_struct *tty;
+
+ /* number of times this port has been opened */
+ int open_count;
+
+ /* locks this structure */
+ struct semaphore sem;
+} dpram_serial_t;
+
+typedef struct dpram_device {
+ /* DPRAM memory addresses */
+ unsigned long in_head_addr;
+ unsigned long in_tail_addr;
+ unsigned long in_buff_addr;
+ unsigned long in_buff_size;
+
+ unsigned long out_head_addr;
+ unsigned long out_tail_addr;
+ unsigned long out_buff_addr;
+ unsigned long out_buff_size;
+
+ unsigned int in_head_saved;
+ unsigned int in_tail_saved;
+ unsigned int out_head_saved;
+ unsigned int out_tail_saved;
+
+ u_int16_t mask_req_ack;
+ u_int16_t mask_res_ack;
+ u_int16_t mask_send;
+
+ dpram_serial_t serial;
+} dpram_device_t;
+
+typedef struct dpram_tasklet_data {
+ dpram_device_t *device;
+ u_int16_t non_cmd;
+} dpram_tasklet_data_t;
+
+struct _mem_param {
+ unsigned short addr;
+ unsigned long data;
+ int dir;
+};
+
+
+/* TODO: add more definitions */
+
+#endif /* __DPRAM_H__ */
+
diff --git a/drivers/misc/samsung_modemctl/dpram/modemctl.h b/drivers/misc/samsung_modemctl/dpram/modemctl.h
new file mode 100644
index 0000000..ee59364
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/dpram/modemctl.h
@@ -0,0 +1,38 @@
+/**
+ * header for modem control
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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 __MODEM_CONTROL_H__
+#define __MODEM_CONTROL_H__
+
+struct modemctl_platform_data {
+ const char *name;
+
+ unsigned gpio_phone_on;
+ unsigned gpio_phone_active;
+ unsigned gpio_pda_active;
+ unsigned gpio_cp_reset;
+ unsigned gpio_usim_boot;
+ unsigned gpio_flm_sel;
+ unsigned gpio_sim_ndetect;
+
+ void (*cfg_gpio)(void);
+};
+
+#endif /* __MODEM_CONTROL_H__ */
diff --git a/drivers/misc/samsung_modemctl/dpram/onedram.h b/drivers/misc/samsung_modemctl/dpram/onedram.h
new file mode 100644
index 0000000..64214aa
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/dpram/onedram.h
@@ -0,0 +1,52 @@
+/**
+ * header for onedram driver
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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 __ONEDRAM_H__
+#define __ONEDRAM_H__
+
+#include <linux/ioport.h>
+#include <linux/types.h>
+
+struct onedram_platform_data {
+ void (*cfg_gpio)(void);
+};
+
+extern int onedram_register_handler(void (*handler)(u32, void *), void *data);
+extern int onedram_unregister_handler(void (*handler)(u32, void *));
+
+extern struct resource* onedram_request_region(resource_size_t start,
+ resource_size_t size, const char *name);
+extern void onedram_release_region(resource_size_t start,
+ resource_size_t size);
+
+extern int onedram_read_mailbox(u32 *);
+extern int onedram_write_mailbox(u32);
+
+extern int onedram_get_auth(u32 cmd);
+extern int onedram_put_auth(int release);
+
+extern int onedram_rel_sem(void);
+extern int onedram_read_sem(void);
+
+#define ONEDRAM_GET_AUTH _IOW('o', 0x20, u32)
+#define ONEDRAM_PUT_AUTH _IO('o', 0x21)
+#define ONEDRAM_REL_SEM _IO('o', 0x22)
+
+#endif /* __ONEDRAM_H__ */
diff --git a/drivers/misc/samsung_modemctl/modem_ctl.c b/drivers/misc/samsung_modemctl/modem_ctl.c
new file mode 100644
index 0000000..0cd10c8
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modem_ctl.c
@@ -0,0 +1,1212 @@
+/* modem_ctl.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ * Copyright (C) 2010 Kolja Dummann (k.dummann@gmail.com)
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+
+#include "modem_ctl.h"
+#include "modem_ctl_p.h"
+
+/* supports modem delta update */
+#include "modem_ctl_recovery.h"
+
+/* Defines the primitives for writing and reading from and to the oneDRAM.
+ * All these primitives are used by the functions of file operation.
+ */
+static u32 onedram_checksum(u32 org, u8 *data, u32 len)
+{
+ u32 temp = org;
+ u32 i;
+
+ for (i = 0; i < len; i++)
+ temp += *(data + i);
+
+ return temp;
+}
+
+static inline u32 read_semaphore(struct modemctl *mc)
+{
+ return ioread32(mc->mmio + OFF_SEM) & 1;
+}
+
+static void return_semaphore(struct modemctl *mc)
+{
+ iowrite32(0, mc->mmio + OFF_SEM);
+}
+
+static u32 get_mailbox_ab(struct modemctl *mc)
+{
+ return ioread32(mc->mmio + OFF_MBOX_BP);
+}
+
+static void set_mailbox_ba(struct modemctl *mc, u32 data)
+{
+ iowrite32(data, mc->mmio + OFF_MBOX_AP);
+}
+
+static void write_single_data(struct modemctl *mc, int offset, int data)
+{
+ *(u32 *)(mc->mmio + offset) = data;
+}
+
+static int read_multiple_data(struct modemctl *mc, int offset, char *buf,
+ size_t size)
+{
+ if (!read_semaphore(mc)) {
+ pr_err("Semaphore is held by modem!");
+ return -EINVAL;
+ }
+
+ if (!buf) {
+ pr_err("Invalid buffer!");
+ return -EINVAL;
+ }
+
+ memcpy(buf, mc->mmio + offset, size);
+
+ return size;
+}
+
+static int dpram_write_from_user(struct modemctl *mc, int addr,
+ const char __user *data, size_t size)
+{
+ if (!read_semaphore(mc)) {
+ pr_err("Semaphore is held by modem!");
+ return -EINVAL;
+ }
+
+ if (copy_from_user(mc->mmio + addr, data, size) < 0) {
+ pr_err("[%s:%d] Copy from user failed\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int modem_pwr_status(struct modemctl *mc)
+{
+ pr_debug("%s\n", __func__);
+ return gpio_get_value(mc->gpio_phone_active);
+}
+
+
+static int dpram_modem_pwroff(struct modemctl *mc)
+{
+ pr_debug("%s\n", __func__);
+
+ gpio_set_value(mc->gpio_phone_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ mdelay(100);
+
+ return 0;
+}
+
+static int dpram_modem_reset(struct modemctl *mc)
+{
+ pr_debug("%s\n", __func__);
+
+ gpio_set_value(mc->gpio_phone_on, 1);
+ msleep(50);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100);
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(500);
+ gpio_set_value(mc->gpio_phone_on, 0);
+
+ return 0;
+}
+
+static int dpram_modem_pwron(struct modemctl *mc)
+{
+ int err = -1;
+ int msec;
+
+ pr_debug("%s\n", __func__);
+
+ return_semaphore(mc);
+
+ dpram_modem_reset(mc);
+
+ for (msec = 0; msec < 10000; msec++) {
+ if (modem_pwr_status(mc)) {
+ err = 0;
+ break;
+ }
+ msleep(1);
+ }
+
+ return err;
+}
+
+static int acquire_semaphore(struct modemctl *mc)
+{
+ int retrycnt = 20;
+
+ while (!read_semaphore(mc)) {
+ set_mailbox_ba(mc, DPRAM_BOOT_SEM_REQ);
+ dpram_modem_reset(mc);
+
+ msleep(100);
+ if (!(retrycnt--)) {
+ pr_debug("failed to get semaphore!");
+ return -1;
+ }
+ }
+
+ pr_debug("We have Semaphore!");
+ dpram_modem_pwroff(mc);
+ set_mailbox_ba(mc, 0x0);
+ return 0;
+}
+
+static int dpram_write_delta(struct modemctl *mc,
+ char __user *firmware, int size)
+{
+ int ret = 0;
+
+ pr_debug("%s\n", __func__);
+ acquire_semaphore(mc);
+ /* write the sizeof firmware */
+ write_single_data(mc, DPRAM_FIRMWARE_SIZE_ADDR, size);
+ /* write the data of firmware */
+ dpram_write_from_user(mc, DPRAM_FIRMWARE_ADDR, firmware, size);
+ return ret;
+}
+
+static int dpram_write_full(struct modemctl *mc,
+ char __user *firmware, int size)
+{
+ struct onedram_head_t onedram;
+ u32 data_offset = ONEDRAM_DL_DATA_OFFSET;
+ u32 head_offset = ONEDRAM_DL_HEADER_OFFSET;
+ u32 checksum;
+
+ pr_debug("%s\n", __func__);
+
+ acquire_semaphore(mc);
+
+ /* write full data of firmware */
+ dpram_write_from_user(mc, data_offset, firmware, size);
+ /* check checksum */
+ checksum = onedram_checksum(0, (u8 *)(mc->mmio + data_offset), size);
+
+ onedram.signature = ONEDRAM_DL_SIGNATURE;
+ onedram.is_boot_update = 0;
+ onedram.is_nv_update = 0;
+ onedram.length = size;
+ onedram.checksum = checksum;
+
+ /* write header info */
+ memcpy(mc->mmio + head_offset, (u8 *)&onedram, sizeof(onedram));
+
+ return 0;
+}
+
+static int dpram_update_delta(struct modemctl *mc)
+{
+ int err = 0;
+ int msec = 0;
+ u32 val;
+
+ pr_debug("%s\n", __func__);
+
+ acquire_semaphore(mc);
+ /* write boot magic */
+ write_single_data(mc, DPRAM_BOOT_MAGIC_ADDR,
+ DPRAM_BOOT_MAGIC_RECOVERY_FOTA);
+ write_single_data(mc, DPRAM_BOOT_TYPE_ADDR,
+ DPRAM_BOOT_TYPE_DPRAM_DELTA);
+ /* At this point modem is powered off. So power on modem */
+ err = dpram_modem_pwron(mc);
+ if (err < 0) {
+ pr_err("modem_reset() fail : %d", modem_pwr_status(mc));
+ return err;
+ }
+ /* clear mailboxBA */
+ set_mailbox_ba(mc, 0xFFFFFFFF);
+ /* wait for job sync message */
+ while (true) {
+ val = get_mailbox_ab(mc);
+ if ((val & STATUS_JOB_MAGIC_M) == STATUS_JOB_MAGIC_CODE) {
+ err = 0;
+ break;
+ }
+ msleep(1);
+ if (++msec > 20000) {
+ err = -2;
+ pr_err("Failed to sync with modem (%x)", val);
+ return err;
+ }
+ if ((msec % 1000) == 0)
+ pr_info("Waiting for sync message... 0x%08x (pwr:%s)", \
+ val, modem_pwr_status(mc) ? "ON" : "OFF");
+ }
+ if (err == 0) {
+ pr_info("Modem ready to start the firmware update");
+ /* let modem start the job */
+ set_mailbox_ba(mc, STATUS_JOB_MAGIC_CODE);
+ /* If we have the semaphore, toss it to modem. */
+ return_semaphore(mc);
+ }
+
+ return err;
+
+}
+
+static int dpram_update_full(struct modemctl *mc)
+{
+ int err;
+
+ pr_debug("%s\n", __func__);
+
+ err = dpram_modem_pwron(mc);
+ if (err < 0) {
+ pr_err("dpram_modem_pwron() fail : %d", modem_pwr_status(mc));
+ return -1;
+ }
+ /* clear mailboxBA */
+ set_mailbox_ba(mc, 0xFFFFFFFF);
+
+ return 0;
+}
+
+static int dpram_process_modem_update(struct modemctl *mc,
+ struct dpram_firmware *pfw)
+{
+ int ret = 0;
+
+ if (pfw->is_delta) {
+ mc->is_modem_delta_update = 1;
+
+ if (dpram_write_delta(mc, pfw->firmware, pfw->size) < 0) {
+ pr_err("firmware write failed\n");
+ ret = -1;
+ } else if (dpram_update_delta(mc) < 0) {
+ pr_err("Firmware update failed\n");
+ ret = -1;
+ }
+ } else {
+ if (dpram_write_full(mc, pfw->firmware, pfw->size) < 0) {
+ pr_err("firmware full write failed\n");
+ ret = -1;
+ } else if (dpram_update_full(mc) < 0) {
+ pr_err("Firmware full update failed\n");
+ ret = -1;
+ }
+ }
+
+ return ret;
+}
+
+static int dpram_chk_delta_update(struct modemctl *mc,
+ int __user *pct, char __user *msg)
+{
+ u32 status;
+ int percent = 0;
+ int err = 0;
+ char buf[DPRAM_MODEM_MSG_SIZE];
+ int debugprint = false;
+ int wait = 0;
+ /* check mailboxAB for the modem status */
+ status = get_mailbox_ab(mc);
+
+ debugprint = (mc->dpram_prev_status != status);
+ if (debugprint)
+ pr_info("Job status : 0x%08x (pwr:%s)\n", status, \
+ modem_pwr_status(mc) ? "ON" : "OFF");
+
+ if ((status & STATUS_JOB_MAGIC_M) != STATUS_JOB_MAGIC_CODE) {
+ if (debugprint)
+ pr_info("Job not accepted yet\n");
+ err = 1;
+ percent = 0;
+ strncpy(buf, "Job not accepted yet", DPRAM_MODEM_MSG_SIZE);
+ goto out;
+ }
+ if (status & STATUS_JOB_STARTED_M) {
+ return_semaphore(mc);
+ percent = status & STATUS_JOB_PROGRESS_M;
+ if (debugprint)
+ pr_info("Job progress pct=%d\n", percent);
+ err = 3;
+ } else {
+ percent = 0;
+ if (debugprint)
+ pr_info("Job NOT started yet...\n");
+ err = 2;
+ }
+ if (status & STATUS_JOB_ENDED_M) {
+ percent = status & STATUS_JOB_PROGRESS_M;
+ /* wait till we have semaphore */
+ pr_info("Wait for semaphore");
+ while (true) {
+ msleep(10);
+ if (read_semaphore(mc)) {
+ pr_info("We have semaphore");
+ break;
+ }
+ if (wait++ > 1000) {
+ pr_info("Proceeding without semaphore");
+ break;
+ }
+ }
+ read_multiple_data(mc, DPRAM_MODEM_STRING_MSG_ADDR, buf,
+ DPRAM_MODEM_MSG_SIZE);
+ if (status & STATUS_JOB_ERROR_M) {
+ err = -1;
+ pr_err("Job ended with error msg : %s\n", buf);
+ } else if (status & STATUS_JOB_COMPLETE_M) {
+ err = 0;
+ pr_info("Job completed successfully : %s\n", buf);
+ }
+ }
+out:
+ mc->dpram_prev_status = status;
+ if (put_user(percent, pct) < 0)
+ pr_err("[%s:%d] Copy to user failed\n", __func__, __LINE__);
+ if (copy_to_user((void *)msg, (void *)buf, DPRAM_MODEM_MSG_SIZE) < 0)
+ pr_err("[%s:%d] Copy to user failed\n", __func__, __LINE__);
+ return err;
+}
+
+static int dpram_chk_full_update(struct modemctl *mc,
+ int __user *pct, char __user *msg)
+{
+ int err;
+ u32 status = 0;
+ u32 phone_active = 0;
+ bool is_reboot = 0;
+
+ err = 3;
+ mc->dpram_prev_status = 0xFFFFFFFF;
+ mc->dpram_prev_phone_active = 0xFFFFFFFF;
+
+retry:
+ phone_active = modem_pwr_status(mc);
+ status = get_mailbox_ab(mc);
+
+ pr_debug("PHONE %d Mailbox 0x%x\n", phone_active, status);
+ if ((mc->dpram_prev_phone_active != phone_active) ||
+ (mc->dpram_prev_status != status)) {
+ mc->dpram_prev_phone_active = phone_active;
+ mc->dpram_prev_status = status;
+ }
+
+ if (!phone_active) {
+ if (status == ONEDRAM_DL_COMPLETE) {
+ pr_info("*OK* ONEDRAM_DL_COMPLETE\n");
+ err = 0;
+ } else if (status == ONEDRAM_DL_DONE_AND_RESET) {
+ pr_info("*OK* ONEDRAM_DONE_AND_RESET\n");
+ dpram_modem_pwron(mc);
+ goto retry;
+ }
+ }
+
+ if (status == ONEDRAM_DL_CHECKSUM_ERR) {
+ pr_info("*ERROR* ONEDRAM_DL_CHECKSUM_ERR\n");
+ is_reboot = 1;
+ } else if (status == ONEDRAM_DL_ERASE_WRITE_ERR) {
+ pr_info("*ERROR* ONEDRAM_DL_ERASE_WRITE_ERR\n");
+ is_reboot = 1;
+ } else if (status == ONEDRAM_DL_BOOT_UPDATE_ERR) {
+ pr_info("*ERROR* ONEDRAM_DL_BOOT_UPDATE_ERR\n");
+ is_reboot = 1;
+ } else if (status == ONEDRAM_DL_REWRITE_FAIL_ERR) {
+ pr_info("*ERROR* ONEDRAM_DL_REWRITE_FAIL_ERR\n");
+ is_reboot = 1;
+ } else if (status == ONEDRAM_DL_LENGTH_CH_FAIL) {
+ pr_info("*ERROR* ONEDRAM_DL_LENGTH_CH_FAIL\n");
+ is_reboot = 1;
+ } else {
+ if (status != mc->dpram_prev_status)
+ pr_info("*ERROR* %d, 0x%x\n", phone_active, status);
+ }
+
+ if (is_reboot) {
+ pr_info("system reboot necessary\n");
+ err = -1;
+ }
+
+ if (err <= 0) {
+ pr_info("Update done.\n");
+ acquire_semaphore(mc);
+ write_single_data(mc, 0, 0xffffffff);
+ }
+
+ return err;
+}
+
+/* The modem_ctl portion of this driver handles modem lifecycle
+ * transitions (OFF -> ON -> RUNNING -> ABNORMAL), the firmware
+ * download mechanism (via /dev/modem_ctl), and interrupts from
+ * the modem (direct and via onedram mailbox interrupt).
+ *
+ * It also handles tracking the ownership of the onedram "semaphore"
+ * which governs which processor (AP or BP) has access to the 16MB
+ * shared memory region. The modem_mmio_{acquire,release,request}
+ * primitives are used by modem_io.c to obtain access to the shared
+ * memory region when necessary to do io.
+ *
+ * Further, modem_update_state() and modem_handle_io() are called
+ * when we gain control over the shared memory region (to update
+ * fifo state info) and when there may be io to process, respectively.
+ *
+ */
+
+#define WAIT_TIMEOUT (HZ*5)
+
+void modem_request_sem(struct modemctl *mc)
+{
+ writel(MB_COMMAND | MB_VALID | MBC_REQ_SEM,
+ mc->mmio + OFF_MBOX_AP);
+}
+
+static inline int mmio_sem(struct modemctl *mc)
+{
+ return readl(mc->mmio + OFF_SEM) & 1;
+}
+
+int modem_request_mmio(struct modemctl *mc)
+{
+ unsigned long flags;
+ int ret;
+ spin_lock_irqsave(&mc->lock, flags);
+ mc->mmio_req_count++;
+ ret = mc->mmio_owner;
+ if (!ret) {
+ if (mmio_sem(mc) == 1) {
+ /* surprise! we already have control */
+ ret = mc->mmio_owner = 1;
+ wake_up(&mc->wq);
+ modem_update_state(mc);
+ MODEM_COUNT(mc,request_no_wait);
+ } else {
+ /* ask the modem for mmio access */
+ if (modem_running(mc))
+ modem_request_sem(mc);
+ MODEM_COUNT(mc,request_wait);
+ }
+ } else {
+ MODEM_COUNT(mc,request_no_wait);
+ }
+ /* TODO: timer to retry? */
+ spin_unlock_irqrestore(&mc->lock, flags);
+ return ret;
+}
+
+void modem_release_mmio(struct modemctl *mc, unsigned bits)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&mc->lock, flags);
+ mc->mmio_req_count--;
+ mc->mmio_signal_bits |= bits;
+ if ((mc->mmio_req_count == 0) && modem_running(mc)) {
+ if (mc->mmio_bp_request) {
+ mc->mmio_bp_request = 0;
+ writel(0, mc->mmio + OFF_SEM);
+ writel(MB_COMMAND | MB_VALID | MBC_RES_SEM,
+ mc->mmio + OFF_MBOX_AP);
+ MODEM_COUNT(mc,release_bp_waiting);
+ } else if (mc->mmio_signal_bits) {
+ writel(0, mc->mmio + OFF_SEM);
+ writel(MB_VALID | mc->mmio_signal_bits,
+ mc->mmio + OFF_MBOX_AP);
+ MODEM_COUNT(mc,release_bp_signaled);
+ } else {
+ MODEM_COUNT(mc,release_no_action);
+ }
+ mc->mmio_owner = 0;
+ mc->mmio_signal_bits = 0;
+ }
+ spin_unlock_irqrestore(&mc->lock, flags);
+}
+
+static int mmio_owner_p(struct modemctl *mc)
+{
+ unsigned long flags;
+ int ret;
+ spin_lock_irqsave(&mc->lock, flags);
+ ret = mc->mmio_owner || modem_offline(mc);
+ spin_unlock_irqrestore(&mc->lock, flags);
+ return ret;
+}
+
+int modem_acquire_mmio(struct modemctl *mc)
+{
+ if (modem_request_mmio(mc) == 0) {
+ int ret = wait_event_interruptible_timeout(
+ mc->wq, mmio_owner_p(mc), 5 * HZ);
+ if (ret <= 0) {
+ modem_release_mmio(mc, 0);
+ if (ret == 0) {
+ pr_err("modem_acquire_mmio() TIMEOUT\n");
+ return -ENODEV;
+ } else {
+ return -ERESTARTSYS;
+ }
+ }
+ }
+ if (!modem_running(mc)) {
+ modem_release_mmio(mc, 0);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static int modemctl_open(struct inode *inode, struct file *filp)
+{
+ struct modemctl *mc = to_modemctl(filp->private_data);
+ filp->private_data = mc;
+
+ if (mc->open_count)
+ return -EBUSY;
+
+ mc->open_count++;
+ return 0;
+}
+
+static int modemctl_release(struct inode *inode, struct file *filp)
+{
+ struct modemctl *mc = filp->private_data;
+
+ mc->open_count = 0;
+ filp->private_data = NULL;
+ return 0;
+}
+
+static ssize_t modemctl_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct modemctl *mc = filp->private_data;
+ loff_t pos;
+ int ret;
+
+ mutex_lock(&mc->ctl_lock);
+ pos = mc->ramdump_pos;
+ if (mc->status != MODEM_DUMPING) {
+ pr_err("[MODEM] not in ramdump mode\n");
+ ret = -ENODEV;
+ goto done;
+ }
+ if (pos < 0) {
+ ret = -EINVAL;
+ goto done;
+ }
+ if (pos >= mc->ramdump_size) {
+ pr_err("[MODEM] ramdump EOF\n");
+ ret = 0;
+ goto done;
+ }
+ if (count > mc->ramdump_size - pos)
+ count = mc->ramdump_size - pos;
+
+ ret = copy_to_user(buf, mc->mmio + pos, count);
+ if (ret) {
+ ret = -EFAULT;
+ goto done;
+ }
+ pos += count;
+ ret = count;
+
+ if (pos == mc->ramdump_size) {
+ if (mc->ramdump_size == RAMDUMP_LARGE_SIZE) {
+ mc->ramdump_size = 0;
+ pr_info("[MODEM] requesting more ram\n");
+ writel(0, mc->mmio + OFF_SEM);
+ writel(MODEM_CMD_RAMDUMP_MORE, mc->mmio + OFF_MBOX_AP);
+ wait_event_timeout(mc->wq, mc->ramdump_size != 0, 10 * HZ);
+ } else {
+ pr_info("[MODEM] no more ram to dump\n");
+ mc->ramdump_size = 0;
+ }
+ mc->ramdump_pos = 0;
+ } else {
+ mc->ramdump_pos = pos;
+ }
+
+done:
+ mutex_unlock(&mc->ctl_lock);
+ return ret;
+
+}
+
+static ssize_t modemctl_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct modemctl *mc = filp->private_data;
+ u32 owner;
+ char *data;
+ loff_t pos = *ppos;
+ unsigned long ret;
+
+ mutex_lock(&mc->ctl_lock);
+ data = (char __force *)mc->mmio + pos;
+ owner = mmio_sem(mc);
+
+ if (mc->status != MODEM_POWER_ON) {
+ pr_err("modemctl_write: modem not powered on\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (!owner) {
+ pr_err("modemctl_write: doesn't own semaphore\n");
+ ret = -EIO;
+ goto done;
+ }
+
+ if (pos < 0) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (pos >= mc->mmsize) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (count > mc->mmsize - pos)
+ count = mc->mmsize - pos;
+
+ ret = copy_from_user(data, buf, count);
+ if (ret) {
+ ret = -EFAULT;
+ goto done;
+ }
+ *ppos = pos + count;
+ ret = count;
+
+done:
+ mutex_unlock(&mc->ctl_lock);
+ return ret;
+}
+
+
+static int modem_start(struct modemctl *mc, int ramdump)
+{
+ int ret;
+
+ pr_info("[MODEM] modem_start() %s\n",
+ ramdump ? "ramdump" : "normal");
+
+ if (mc->status != MODEM_POWER_ON) {
+ pr_err("[MODEM] modem not powered on\n");
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_MODEM_HAS_CRAPPY_BOOTLOADER
+
+ /* we do this as the BP bootloader from the SGS is a little bit
+ crapy it does not send the magic data MODEM_MSG_SBL_DONE when
+ it has finished loading. so we wait some amount of time */
+
+ pr_info("[MODEM] we have a crappy bootloader an wait for it");
+
+ //waiting 1500 ms should be enough, maybe we can decrease this but unsure
+ msleep(1500);
+
+#else
+ if (!mc->is_cdma_modem &&
+ readl(mc->mmio + OFF_MBOX_BP) != MODEM_MSG_SBL_DONE) {
+ pr_err("[MODEM] bootloader not ready\n");
+ return -EIO;
+ }
+#endif
+
+ writel(0, mc->mmio + OFF_SEM);
+ if (ramdump) {
+ mc->status = MODEM_BOOTING_RAMDUMP;
+ mc->ramdump_size = 0;
+ mc->ramdump_pos = 0;
+ writel(MODEM_CMD_RAMDUMP_START, mc->mmio + OFF_MBOX_AP);
+
+ ret = wait_event_timeout(mc->wq, mc->status == MODEM_DUMPING, 25 * HZ);
+ if (ret == 0)
+ return -ENODEV;
+ } else {
+ if (mc->is_cdma_modem)
+ mc->status = MODEM_RUNNING;
+ else {
+ mc->status = MODEM_BOOTING_NORMAL;
+ writel(MODEM_CMD_BINARY_LOAD, mc->mmio + OFF_MBOX_AP);
+
+ ret = wait_event_timeout(mc->wq,
+ modem_running(mc), 25 * HZ);
+ if (ret == 0)
+ return -ENODEV;
+ }
+ }
+
+ pr_info("[MODEM] modem_start() DONE\n");
+ return 0;
+}
+
+static int modem_reset(struct modemctl *mc)
+{
+ pr_info("[MODEM] modem_reset()\n");
+
+ /* ensure phone active pin irq type */
+ set_irq_type(mc->gpio_phone_active, IRQ_TYPE_EDGE_BOTH);
+
+ /* ensure pda active pin set to low */
+ gpio_set_value(mc->gpio_pda_active, 0);
+
+ /* read inbound mbox to clear pending IRQ */
+ (void) readl(mc->mmio + OFF_MBOX_BP);
+
+ /* write outbound mbox to assert outbound IRQ */
+ writel(0, mc->mmio + OFF_MBOX_AP);
+
+ if (mc->is_cdma_modem) {
+ gpio_set_value(mc->gpio_phone_on, 1);
+ msleep(50);
+
+ /* ensure cp_reset pin set to low */
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ msleep(500);
+
+ } else {
+ /* ensure cp_reset pin set to low */
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100);
+
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100);
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+
+ /* Follow RESET timming delay not Power-On timming,
+ because CP_RST & PHONE_ON have been set high already. */
+ msleep(100); /*wait modem stable */
+ }
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ mc->status = MODEM_POWER_ON;
+
+ return 0;
+}
+
+static int modem_off(struct modemctl *mc)
+{
+ pr_info("[MODEM] modem_off()\n");
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ mc->status = MODEM_OFF;
+ return 0;
+}
+
+static long modemctl_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct modemctl *mc = filp->private_data;
+ struct dpram_firmware fw;
+ struct stat_info *pst;
+ int ret = 0;
+
+ mutex_lock(&mc->ctl_lock);
+ switch (cmd) {
+ case IOCTL_MODEM_RESET:
+ ret = modem_reset(mc);
+ MODEM_COUNT(mc,resets);
+ break;
+ case IOCTL_MODEM_START:
+ ret = modem_start(mc, 0);
+ break;
+ case IOCTL_MODEM_RAMDUMP:
+ ret = modem_start(mc, 1);
+ break;
+ case IOCTL_MODEM_OFF:
+ ret = modem_off(mc);
+ break;
+
+ /* CDMA modem update in recovery mode */
+ case IOCTL_MODEM_FW_UPDATE:
+ pr_info("IOCTL_MODEM_FW_UPDATE\n");
+ if (arg == NULL) {
+ pr_err("No firmware");
+ break;
+ }
+
+ if (copy_from_user((void *)&fw, (void *)arg, sizeof(fw)) < 0) {
+ pr_err("copy from user failed!");
+ ret = -EINVAL;
+ } else if (dpram_process_modem_update(mc, &fw) < 0) {
+ pr_err("firmware write failed\n");
+ ret = -EIO;
+ }
+ break;
+ case IOCTL_MODEM_CHK_STAT:
+ pst = (struct stat_info *)arg;
+ if (mc->is_modem_delta_update)
+ ret = dpram_chk_delta_update(mc, &(pst->pct), pst->msg);
+ else
+ ret = dpram_chk_full_update(mc, &(pst->pct), pst->msg);
+ break;
+ case IOCTL_MODEM_PWROFF:
+ pr_info("IOCTL_MODEM_PWROFF\n");
+ dpram_modem_pwroff(mc);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&mc->ctl_lock);
+ pr_info("modemctl_ioctl() %d\n", ret);
+ return ret;
+}
+
+static const struct file_operations modemctl_fops = {
+ .owner = THIS_MODULE,
+ .llseek = default_llseek,
+ .open = modemctl_open,
+ .release = modemctl_release,
+ .read = modemctl_read,
+ .write = modemctl_write,
+ .unlocked_ioctl = modemctl_ioctl,
+};
+
+static irqreturn_t modemctl_bp_irq_handler(int irq, void *_mc)
+{
+ int value = 0;
+
+ value = gpio_get_value(((struct modemctl *)_mc)->gpio_phone_active);
+ pr_info("[MODEM] bp_irq() PHONE_ACTIVE_PIN=%d\n", value);
+ return IRQ_HANDLED;
+
+}
+
+static void modemctl_handle_offline(struct modemctl *mc, unsigned cmd)
+{
+ switch (mc->status) {
+ case MODEM_BOOTING_NORMAL:
+ if (cmd == MODEM_MSG_BINARY_DONE) {
+ pr_info("[MODEM] binary load done\n");
+ mc->status = MODEM_RUNNING;
+ wake_up(&mc->wq);
+ }
+ break;
+ case MODEM_BOOTING_RAMDUMP:
+ case MODEM_DUMPING:
+ if (cmd == MODEM_MSG_RAMDUMP_LARGE) {
+ mc->status = MODEM_DUMPING;
+ mc->ramdump_size = RAMDUMP_LARGE_SIZE;
+ wake_up(&mc->wq);
+ pr_info("[MODEM] ramdump - %d bytes available\n",
+ mc->ramdump_size);
+ } else if (cmd == MODEM_MSG_RAMDUMP_SMALL) {
+ mc->status = MODEM_DUMPING;
+ mc->ramdump_size = RAMDUMP_SMALL_SIZE;
+ wake_up(&mc->wq);
+ pr_info("[MODEM] ramdump - %d bytes available\n",
+ mc->ramdump_size);
+ } else {
+ pr_err("[MODEM] unknown msg %08x in ramdump mode\n", cmd);
+ }
+ break;
+ }
+}
+
+static irqreturn_t modemctl_mbox_irq_handler(int irq, void *_mc)
+{
+ struct modemctl *mc = _mc;
+ unsigned cmd;
+ unsigned long flags;
+
+ cmd = readl(mc->mmio + OFF_MBOX_BP);
+
+ if (unlikely(mc->status != MODEM_RUNNING)) {
+ modemctl_handle_offline(mc, cmd);
+ return IRQ_HANDLED;
+ }
+
+ if (!(cmd & MB_VALID)) {
+ if (cmd == MODEM_MSG_LOGDUMP_DONE) {
+ pr_info("modem: logdump done!\n");
+ mc->logdump_data = 1;
+ wake_up(&mc->wq);
+ } else {
+ pr_info("modem: what is %08x\n",cmd);
+ }
+ return IRQ_HANDLED;
+ }
+
+ spin_lock_irqsave(&mc->lock, flags);
+
+ if (cmd & MB_COMMAND) {
+ switch (cmd & 15) {
+ case MBC_REQ_SEM:
+ if (mmio_sem(mc) == 0) {
+ /* Sometimes the modem may ask for the
+ * sem when it already owns it. Humor
+ * it and ack that request.
+ */
+ writel(MB_COMMAND | MB_VALID | MBC_RES_SEM,
+ mc->mmio + OFF_MBOX_AP);
+ MODEM_COUNT(mc,bp_req_confused);
+ } else if (mc->mmio_req_count == 0) {
+ /* No references? Give it to the modem. */
+ modem_update_state(mc);
+ mc->mmio_owner = 0;
+ writel(0, mc->mmio + OFF_SEM);
+ writel(MB_COMMAND | MB_VALID | MBC_RES_SEM,
+ mc->mmio + OFF_MBOX_AP);
+ MODEM_COUNT(mc,bp_req_instant);
+ goto done;
+ } else {
+ /* Busy now, remember the modem needs it. */
+ mc->mmio_bp_request = 1;
+ MODEM_COUNT(mc,bp_req_delayed);
+ break;
+ }
+ case MBC_RES_SEM:
+ break;
+ case MBC_PHONE_START:
+ /* TODO: should we avoid sending any other messages
+ * to the modem until this message is received and
+ * acknowledged?
+ */
+ writel(MB_COMMAND | MB_VALID |
+ MBC_INIT_END | CP_BOOT_AIRPLANE | AP_OS_ANDROID,
+ mc->mmio + OFF_MBOX_AP);
+
+ /* TODO: probably unsafe to send this back-to-back
+ * with the INIT_END message.
+ */
+ /* if somebody is waiting for mmio access... */
+ if (mc->mmio_req_count)
+ modem_request_sem(mc);
+ break;
+ case MBC_RESET:
+ pr_err("$$$ MODEM RESET $$$\n");
+ mc->status = MODEM_CRASHED;
+ wake_up(&mc->wq);
+ break;
+ case MBC_ERR_DISPLAY: {
+ char buf[SIZ_ERROR_MSG + 1];
+ int i;
+ pr_err("$$$ MODEM ERROR $$$\n");
+ mc->status = MODEM_CRASHED;
+ wake_up(&mc->wq);
+ memcpy(buf, mc->mmio + OFF_ERROR_MSG, SIZ_ERROR_MSG);
+ for (i = 0; i < SIZ_ERROR_MSG; i++)
+ if ((buf[i] < 0x20) || (buf[1] > 0x7e))
+ buf[i] = 0x20;
+ buf[i] = 0;
+ i--;
+ while ((i > 0) && (buf[i] == 0x20))
+ buf[i--] = 0;
+ pr_err("$$$ %s $$$\n", buf);
+ break;
+ }
+ case MBC_SUSPEND:
+ break;
+ case MBC_RESUME:
+ break;
+ }
+ }
+
+ /* On *any* interrupt from the modem it may have given
+ * us ownership of the mmio hw semaphore. If that
+ * happens, we should claim the semaphore if we have
+ * threads waiting for it and we should process any
+ * messages that the modem has enqueued in its fifos
+ * by calling modem_handle_io().
+ */
+ if (mmio_sem(mc) == 1) {
+ if (!mc->mmio_owner) {
+ modem_update_state(mc);
+ if (mc->mmio_req_count) {
+ mc->mmio_owner = 1;
+ wake_up(&mc->wq);
+ }
+ }
+
+ modem_handle_io(mc);
+
+ /* If we have a signal to send and we're not
+ * hanging on to the mmio hw semaphore, give
+ * it back to the modem and send the signal.
+ * Otherwise this will happen when we give up
+ * the mmio hw sem in modem_release_mmio().
+ */
+ if (mc->mmio_signal_bits && !mc->mmio_owner) {
+ writel(0, mc->mmio + OFF_SEM);
+ writel(MB_VALID | mc->mmio_signal_bits,
+ mc->mmio + OFF_MBOX_AP);
+ mc->mmio_signal_bits = 0;
+ }
+ }
+done:
+ spin_unlock_irqrestore(&mc->lock, flags);
+ return IRQ_HANDLED;
+}
+
+void modem_force_crash(struct modemctl *mc)
+{
+ unsigned long int flags;
+ pr_info("modem_force_crash() BOOM!\n");
+ spin_lock_irqsave(&mc->lock, flags);
+ mc->status = MODEM_CRASHED;
+ wake_up(&mc->wq);
+ spin_unlock_irqrestore(&mc->lock, flags);
+}
+
+static int __devinit modemctl_probe(struct platform_device *pdev)
+{
+ int r = -ENOMEM;
+ struct modemctl *mc;
+ struct modemctl_data *pdata;
+ struct resource *res;
+
+ pdata = pdev->dev.platform_data;
+
+ mc = kzalloc(sizeof(*mc), GFP_KERNEL);
+ if (!mc)
+ return -ENOMEM;
+
+ init_waitqueue_head(&mc->wq);
+ spin_lock_init(&mc->lock);
+ mutex_init(&mc->ctl_lock);
+
+ mc->irq_bp = platform_get_irq_byname(pdev, "active");
+ mc->irq_mbox = platform_get_irq_byname(pdev, "onedram");
+
+ mc->gpio_phone_active = pdata->gpio_phone_active;
+ mc->gpio_pda_active = pdata->gpio_pda_active;
+ mc->gpio_cp_reset = pdata->gpio_cp_reset;
+ mc->gpio_phone_on = pdata->gpio_phone_on;
+ mc->is_cdma_modem = pdata->is_cdma_modem;
+ if (pdata->num_pdp_contexts)
+ mc->num_pdp_contexts = pdata->num_pdp_contexts;
+ else
+ mc->num_pdp_contexts = 1;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ goto err_free;
+ mc->mmbase = res->start;
+ mc->mmsize = resource_size(res);
+
+ mc->mmio = ioremap_nocache(mc->mmbase, mc->mmsize);
+ if (!mc->mmio)
+ goto err_free;
+
+ platform_set_drvdata(pdev, mc);
+
+ mc->dev.name = "modem_ctl";
+ mc->dev.minor = MISC_DYNAMIC_MINOR;
+ mc->dev.fops = &modemctl_fops;
+ r = misc_register(&mc->dev);
+ if (r)
+ goto err_ioremap;
+
+ /* hide control registers from userspace */
+ mc->mmsize -= 0x800;
+ mc->status = MODEM_OFF;
+
+ wake_lock_init(&mc->ip_tx_wakelock,
+ WAKE_LOCK_SUSPEND, "modem_ip_tx");
+ wake_lock_init(&mc->ip_rx_wakelock,
+ WAKE_LOCK_SUSPEND, "modem_ip_rx");
+
+ modem_io_init(mc, mc->mmio);
+
+ r = request_irq(mc->irq_bp, modemctl_bp_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "modemctl_bp", mc);
+ if (r)
+ goto err_ioremap;
+
+ r = request_irq(mc->irq_mbox, modemctl_mbox_irq_handler,
+ IRQF_TRIGGER_LOW, "modemctl_mbox", mc);
+ if (r)
+ goto err_irq_bp;
+
+ enable_irq_wake(mc->irq_bp);
+ enable_irq_wake(mc->irq_mbox);
+
+ modem_debugfs_init(mc);
+
+ return 0;
+
+err_irq_mbox:
+ free_irq(mc->irq_mbox, mc);
+err_irq_bp:
+ free_irq(mc->irq_bp, mc);
+err_ioremap:
+ iounmap(mc->mmio);
+err_free:
+ kfree(mc);
+ return r;
+}
+
+static int modemctl_suspend(struct device *pdev)
+{
+ struct modemctl *mc = dev_get_drvdata(pdev);
+ gpio_set_value(mc->gpio_pda_active, 0);
+ return 0;
+}
+
+static int modemctl_resume(struct device *pdev)
+{
+ struct modemctl *mc = dev_get_drvdata(pdev);
+ gpio_set_value(mc->gpio_pda_active, 1);
+ return 0;
+}
+
+static const struct dev_pm_ops modemctl_pm_ops = {
+ .suspend = modemctl_suspend,
+ .resume = modemctl_resume,
+};
+
+static struct platform_driver modemctl_driver = {
+ .probe = modemctl_probe,
+ .driver = {
+ .name = "modemctl",
+ .pm = &modemctl_pm_ops,
+ },
+};
+
+static int __init modemctl_init(void)
+{
+ return platform_driver_register(&modemctl_driver);
+}
+
+module_init(modemctl_init);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Samsung Modem Control Driver");
+
diff --git a/drivers/misc/samsung_modemctl/modem_ctl.h b/drivers/misc/samsung_modemctl/modem_ctl.h
new file mode 100644
index 0000000..b4ad920
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modem_ctl.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MODEM_CONTROL_H__
+#define __MODEM_CONTROL_H__
+
+#define IOCTL_MODEM_RAMDUMP _IO('o', 0x19)
+#define IOCTL_MODEM_RESET _IO('o', 0x20)
+#define IOCTL_MODEM_START _IO('o', 0x21)
+#define IOCTL_MODEM_OFF _IO('o', 0x22)
+
+#define IOCTL_MODEM_SEND _IO('o', 0x23)
+#define IOCTL_MODEM_RECV _IO('o', 0x24)
+
+struct modem_io {
+ uint32_t size;
+ uint32_t id;
+ uint32_t cmd;
+ void *data;
+};
+
+/* platform data */
+struct modemctl_data {
+ const char *name;
+ unsigned gpio_phone_active;
+ unsigned gpio_pda_active;
+ unsigned gpio_cp_reset;
+ unsigned gpio_phone_on;
+ bool is_cdma_modem; /* 1:CDMA Modem */
+ int num_pdp_contexts;
+};
+
+#endif
diff --git a/drivers/misc/samsung_modemctl/modem_ctl_p.h b/drivers/misc/samsung_modemctl/modem_ctl_p.h
new file mode 100644
index 0000000..6223b20
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modem_ctl_p.h
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MODEM_CONTROL_P_H__
+#define __MODEM_CONTROL_P_H__
+
+#define MODEM_OFF 0
+#define MODEM_CRASHED 1
+#define MODEM_RAMDUMP 2
+#define MODEM_POWER_ON 3
+#define MODEM_BOOTING_NORMAL 4
+#define MODEM_BOOTING_RAMDUMP 5
+#define MODEM_DUMPING 6
+#define MODEM_RUNNING 7
+
+#define modem_offline(mc) ((mc)->status < MODEM_POWER_ON)
+#define modem_running(mc) ((mc)->status == MODEM_RUNNING)
+
+#define M_PIPE_MAX_HDR 16
+
+struct net_device;
+
+struct m_pipe {
+ int (*push_header)(struct modem_io *io, void *header);
+ int (*pull_header)(struct modem_io *io, void *header);
+
+ unsigned header_size;
+
+ struct m_fifo *tx;
+ struct m_fifo *rx;
+
+ struct modemctl *mc;
+ unsigned ready;
+
+ struct miscdevice dev;
+
+ struct mutex tx_lock;
+ struct mutex rx_lock;
+
+ struct wake_lock wakelock;
+};
+#define to_m_pipe(misc) container_of(misc, struct m_pipe, dev)
+
+struct m_fifo {
+ unsigned *head;
+ unsigned *tail;
+ unsigned size;
+ void *data;
+
+ unsigned avail;
+ unsigned bits;
+ unsigned unused1;
+ unsigned unused2;
+};
+
+struct modemstats {
+ unsigned request_no_wait;
+ unsigned request_wait;
+
+ unsigned release_no_action;
+ unsigned release_bp_waiting;
+ unsigned release_bp_signaled;
+
+ unsigned bp_req_instant;
+ unsigned bp_req_delayed;
+ unsigned bp_req_confused;
+
+ unsigned rx_unknown;
+ unsigned rx_dropped;
+ unsigned rx_purged;
+ unsigned rx_received;
+
+ unsigned tx_no_delay;
+ unsigned tx_queued;
+ unsigned tx_bp_signaled;
+ unsigned tx_fifo_full;
+
+ unsigned pipe_tx;
+ unsigned pipe_rx;
+ unsigned pipe_tx_delayed;
+ unsigned pipe_rx_purged;
+
+ unsigned resets;
+};
+
+#define MODEM_COUNT(mc,s) (((mc)->stats.s)++)
+
+struct modemctl {
+ void __iomem *mmio;
+ struct modemstats stats;
+
+ /* lock and waitqueue for shared memory state */
+ spinlock_t lock;
+ wait_queue_head_t wq;
+
+ /* shared memory semaphore management */
+ unsigned mmio_req_count;
+ unsigned mmio_bp_request;
+ unsigned mmio_owner;
+ unsigned mmio_signal_bits;
+
+ struct m_fifo fmt_tx;
+ struct m_fifo fmt_rx;
+ struct m_fifo raw_tx;
+ struct m_fifo raw_rx;
+ struct m_fifo rfs_tx;
+ struct m_fifo rfs_rx;
+
+ struct wake_lock ip_tx_wakelock;
+ struct wake_lock ip_rx_wakelock;
+
+ struct net_device **ndev;
+
+ int open_count;
+ int status;
+
+ unsigned mmbase;
+ unsigned mmsize;
+
+ int irq_bp;
+ int irq_mbox;
+
+ unsigned gpio_phone_active;
+ unsigned gpio_pda_active;
+ unsigned gpio_cp_reset;
+ unsigned gpio_phone_on;
+ bool is_cdma_modem;
+ int num_pdp_contexts;
+ bool is_modem_delta_update;
+ unsigned dpram_prev_phone_active;
+ unsigned dpram_prev_status;
+
+ struct miscdevice dev;
+
+ struct m_pipe cmd_pipe;
+ struct m_pipe rfs_pipe;
+
+ struct mutex ctl_lock;
+ ktime_t mmio_t0;
+
+ /* used for ramdump mode */
+ unsigned ramdump_size;
+ loff_t ramdump_pos;
+
+ unsigned logdump;
+ unsigned logdump_data;
+};
+#define to_modemctl(misc) container_of(misc, struct modemctl, dev)
+
+
+/* called when semaphore is held and there may be io to process */
+void modem_handle_io(struct modemctl *mc);
+void modem_update_state(struct modemctl *mc);
+
+/* called once at probe() */
+int modem_io_init(struct modemctl *mc, void __iomem *mmio);
+
+/* called when modem boots and goes offline */
+void modem_io_enable(struct modemctl *mc);
+void modem_io_disable(struct modemctl *mc);
+
+
+/* Block until control of mmio area is obtained (0)
+ * or interrupt (-ERESTARTSYS) or failure (-ENODEV)
+ * occurs.
+ */
+int modem_acquire_mmio(struct modemctl *mc);
+
+/* Request control of mmio area. Returns 1 if
+ * control obtained, 0 if not (request pending).
+ * Either way, release_mmio() must be called to
+ * balance this.
+ */
+int modem_request_mmio(struct modemctl *mc);
+
+/* Return control of mmio area once requested
+ * by modem_request_mmio() or acquired by a
+ * successful modem_acquire_mmio().
+ *
+ * The onedram semaphore is only actually returned
+ * to the BP if there is an outstanding request
+ * for it from the BP, or if the bits argument
+ * to one of the release_mmio() calls was nonzero.
+ */
+void modem_release_mmio(struct modemctl *mc, unsigned bits);
+
+/* Send a request for the hw mmio sem to the modem.
+ * Used ONLY by the internals of modem_request_mmio() and
+ * some trickery in vnet_xmit(). Please do not use elsewhere.
+ */
+void modem_request_sem(struct modemctl *mc);
+
+
+/* internal glue */
+void modem_debugfs_init(struct modemctl *mc);
+void modem_force_crash(struct modemctl *mc);
+
+/* protocol definitions */
+#define MB_VALID 0x0080
+#define MB_COMMAND 0x0040
+
+/* CMD_INIT_END extended bit */
+#define CP_BOOT_ONLINE 0x0000
+#define CP_BOOT_AIRPLANE 0x1000
+#define AP_OS_ANDROID 0x0100
+#define AP_OS_WINMOBILE 0x0200
+#define AP_OS_LINUX 0x0300
+#define AP_OS_SYMBIAN 0x0400
+
+/* CMD_PHONE_START extended bit */
+#define CP_QUALCOMM 0x0100
+#define CP_INFINEON 0x0200
+#define CP_BROADCOM 0x0300
+
+#define MBC_NONE 0x0000
+#define MBC_INIT_START 0x0001
+#define MBC_INIT_END 0x0002
+#define MBC_REQ_ACTIVE 0x0003
+#define MBC_RES_ACTIVE 0x0004
+#define MBC_TIME_SYNC 0x0005
+#define MBC_POWER_OFF 0x0006
+#define MBC_RESET 0x0007
+#define MBC_PHONE_START 0x0008
+#define MBC_ERR_DISPLAY 0x0009
+#define MBC_SUSPEND 0x000A
+#define MBC_RESUME 0x000B
+#define MBC_EMER_DOWN 0x000C
+#define MBC_REQ_SEM 0x000D
+#define MBC_RES_SEM 0x000E
+#define MBC_MAX 0x000F
+
+/* data mailbox flags */
+#define MBD_SEND_FMT 0x0002
+#define MBD_SEND_RAW 0x0001
+#define MBD_SEND_RFS 0x0100
+
+#define MODEM_MSG_SBL_DONE 0x12341234
+#define MODEM_CMD_BINARY_LOAD 0x45674567
+#define MODEM_MSG_BINARY_DONE 0xabcdabcd
+
+#define MODEM_CMD_RAMDUMP_START 0xDEADDEAD
+#define MODEM_MSG_RAMDUMP_LARGE 0x0ADD0ADD // 16MB - 2KB
+#define MODEM_CMD_RAMDUMP_MORE 0xEDEDEDED
+#define MODEM_MSG_RAMDUMP_SMALL 0xFADEFADE // 5MB + 4KB
+
+#define MODEM_CMD_LOGDUMP_START 0x19732864
+//#define MODEM_MSG_LOGDUMP_DONE 0x28641973
+#define MODEM_MSG_LOGDUMP_DONE 0x00001973
+
+#define RAMDUMP_LARGE_SIZE (16*1024*1024 - 2*1024)
+#define RAMDUMP_SMALL_SIZE (5*1024*1024 + 4*1024)
+
+
+/* onedram shared memory map */
+#define OFF_MAGIC 0x00000000
+#define OFF_ACCESS 0x00000004
+
+#define OFF_FMT_TX_HEAD 0x00000010
+#define OFF_FMT_TX_TAIL 0x00000014
+#define OFF_FMT_RX_HEAD 0x00000018
+#define OFF_FMT_RX_TAIL 0x0000001C
+#define OFF_RAW_TX_HEAD 0x00000020
+#define OFF_RAW_TX_TAIL 0x00000024
+#define OFF_RAW_RX_HEAD 0x00000028
+#define OFF_RAW_RX_TAIL 0x0000002C
+#define OFF_RFS_TX_HEAD 0x00000030
+#define OFF_RFS_TX_TAIL 0x00000034
+#define OFF_RFS_RX_HEAD 0x00000038
+#define OFF_RFS_RX_TAIL 0x0000003C
+
+#define OFF_ERROR_MSG 0x00001000
+#define SIZ_ERROR_MSG 160
+
+#define OFF_FMT_TX_DATA 0x000FE000
+#define OFF_FMT_RX_DATA 0x000FF000
+#define SIZ_FMT_DATA 0x00001000
+#define OFF_RAW_TX_DATA 0x00100000
+#define OFF_RAW_RX_DATA 0x00200000
+#define SIZ_RAW_DATA 0x00100000
+#define OFF_RFS_TX_DATA 0x00300000
+#define OFF_RFS_RX_DATA 0x00400000
+#define SIZ_RFS_DATA 0x00100000
+
+#define OFF_LOGDUMP_DATA 0x00A00000
+#define SIZ_LOGDUMP_DATA 0x00300000
+
+#define INIT_M_FIFO(name, type, dir, base) \
+ name.head = base + OFF_##type##_##dir##_HEAD; \
+ name.tail = base + OFF_##type##_##dir##_TAIL; \
+ name.data = base + OFF_##type##_##dir##_DATA; \
+ name.size = SIZ_##type##_DATA;
+
+/* onedram registers */
+
+/* Mailboxes are named based on who writes to them.
+ * MBOX_BP is written to by the (B)aseband (P)rocessor
+ * and only readable by the (A)pplication (P)rocessor.
+ * MBOX_AP is the opposite.
+ */
+#define OFF_SEM 0xFFF800
+#define OFF_MBOX_BP 0xFFF820
+#define OFF_MBOX_AP 0xFFF840
+#define OFF_CHECK_BP 0xFFF8A0
+#define OFF_CHECK_AP 0xFFF8C0
+
+#endif
diff --git a/drivers/misc/samsung_modemctl/modem_ctl_recovery.h b/drivers/misc/samsung_modemctl/modem_ctl_recovery.h
new file mode 100755
index 0000000..6d62e9f
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modem_ctl_recovery.h
@@ -0,0 +1,85 @@
+#ifndef __MODEM_CTL_RECOVERY_H__
+#define __MODEM_CTL_RECOVERY_H__
+
+/* The below macros define the offsets from the base shared memory */
+#define DPRAM_BOOT_MAGIC_ADDR 0x00
+#define DPRAM_BOOT_TYPE_ADDR 0x04
+#define DPRAM_MODEM_STATUS_ADDR 0x08
+#define DPRAM_AP_STATUS_ADDR 0x0C
+#define DPRAM_FIRMWARE_SIZE_ADDR 0x10
+#define DPRAM_MODEM_STRING_MSG_ADDR 0x100
+#define DPRAM_FIRMWARE_ADDR 0x1000
+
+/* Max. length of the message from modem */
+#define DPRAM_MODEM_MSG_SIZE 0x100
+
+#define DPRAM_BOOT_MAGIC_RECOVERY_FOTA 0x56434552
+#define DPRAM_BOOT_TYPE_DPRAM_DELTA 0x41544c44
+#define DPRAM_BOOT_SEM_REQ 0x5555ffff
+
+/* ioctl commands of updating modem binary */
+#define IOCTL_MODEM_FW_UPDATE _IO('D', 0x1)
+#define IOCTL_MODEM_CHK_STAT _IO('D', 0x2)
+#define IOCTL_MODEM_PWROFF _IO('D', 0x3)
+
+/*
+* All status values are kept through out the process.
+* So the final status value for a successful job will be 0xB60x1164
+* This means that magic code is B6xxxxxx
+* Job was started xxxxx1xx
+* Job is done 100% xxxxxx64 (0x64 is 100 in hex)
+* Job is completed xxxx1xxx
+* This way we can just check the final value and know the status of the job.
+*/
+#define STATUS_JOB_MAGIC_CODE 0xB6000000
+#define STATUS_JOB_MAGIC_M 0xFF000000
+#define STATUS_JOB_STARTED_M 0x00000100
+#define STATUS_JOB_PROGRESS_M 0x000000FF
+#define STATUS_JOB_COMPLETE_M 0x00001000
+#define STATUS_JOB_DEBUG_M 0x000F0000
+#define STATUS_JOB_ERROR_M 0x00F00000
+#define STATUS_JOB_ENDED_M (0x00F00000|0x00001000)
+
+
+#define DPRAM_MEMORY_SIZE 0xFFF800
+
+/* read modem delta file to the buffer of this type */
+struct dpram_firmware {
+ char *firmware;
+ int size;
+ int is_delta;
+};
+
+/* the progress status of modem updage */
+struct stat_info {
+ int pct;
+ char msg[DPRAM_MODEM_MSG_SIZE];
+};
+
+/* Define Full modem update interface between AP and modem */
+#define ONEDRAM_DL_SIGNATURE 0x4F4E
+#define ONEDRAM_DL_SMD_SIGNATURE 0x605F
+#define ONEDRAM_DL_COMPLETE 0x56781234
+#define ONEDRAM_DL_CHECKSUM_ERR 0x4444
+#define ONEDRAM_DL_ERASE_WRITE_ERR 0x77779999
+#define ONEDRAM_DL_BOOT_UPDATE_ERR 0xBBBBEEEE
+#define ONEDRAM_DL_REWRITE_FAIL_ERR 0xDDDDFFFF
+#define ONEDRAM_DL_DONE_AND_RESET 0xDDDDAAAA
+#define ONEDRAM_DL_LENGTH_CH_FAIL 0x55FF
+
+#define ONEDRAM_DL_HEADER_OFFSET 0x0
+#define ONEDRAM_DL_DATA_OFFSET 0x400
+
+/* typedefs */
+struct onedram_head_t {
+ u16 signature;
+ u8 is_boot_update;
+ u8 is_nv_update;
+ u32 length;
+ u32 checksum;
+} __packed;
+
+
+#endif /* __MODEM_CTL_RECOVERY_H__ */
+
+
diff --git a/drivers/misc/samsung_modemctl/modem_dbg.c b/drivers/misc/samsung_modemctl/modem_dbg.c
new file mode 100644
index 0000000..8a22dad
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modem_dbg.c
@@ -0,0 +1,211 @@
+/* modem_dbg.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/wakelock.h>
+#include <linux/miscdevice.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+
+#include "modem_ctl.h"
+#include "modem_ctl_p.h"
+
+static int crash_open(struct inode *inode, struct file *file)
+{
+ struct modemctl *mc = inode->i_private;
+ modem_force_crash(mc);
+ return 0;
+}
+
+static const struct file_operations crash_ops = {
+ .open = crash_open,
+};
+
+#define SHOW(name) seq_printf(sf, "%-20s %d\n", #name, stats->name)
+
+static int stats_show(struct seq_file *sf, void *unused)
+{
+ struct modemstats *stats = sf->private;
+
+ SHOW(request_no_wait);
+ SHOW(request_wait);
+
+ SHOW(release_no_action);
+ SHOW(release_bp_waiting);
+ SHOW(release_bp_signaled);
+
+ SHOW(bp_req_instant);
+ SHOW(bp_req_delayed);
+ SHOW(bp_req_confused);
+
+ SHOW(rx_unknown);
+ SHOW(rx_dropped);
+ SHOW(rx_purged);
+ SHOW(rx_received);
+
+ SHOW(tx_no_delay);
+ SHOW(tx_queued);
+ SHOW(tx_bp_signaled);
+ SHOW(tx_fifo_full);
+
+ SHOW(pipe_tx);
+ SHOW(pipe_rx);
+ SHOW(pipe_tx_delayed);
+ SHOW(pipe_rx_purged);
+
+ SHOW(resets);
+
+ return 0;
+}
+
+static int stats_open(struct inode *inode, struct file *file)
+{
+ struct modemctl *mc = inode->i_private;
+ struct modemstats *stats;
+ unsigned long int flags;
+ int ret;
+
+ stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+ if (!stats)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&mc->lock, flags);
+ memcpy(stats, &mc->stats, sizeof(*stats));
+ memset(&mc->stats, 0, sizeof(*stats));
+ spin_unlock_irqrestore(&mc->lock, flags);
+
+ ret = single_open(file, stats_show, stats);
+ if (ret)
+ kfree(stats);
+
+ return ret;
+}
+
+static int stats_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct modemstats *stats = seq->private;
+ int ret;
+ ret = single_release(inode, file);
+ kfree(stats);
+ return ret;
+}
+
+static const struct file_operations stats_ops = {
+ .open = stats_open,
+ .release = stats_release,
+ .read = seq_read,
+ .llseek = seq_lseek,
+};
+
+static int log_open(struct inode *inode, struct file *file)
+{
+ struct modemctl *mc = inode->i_private;
+ unsigned long int flags;
+ int ret = 0;
+
+ file->private_data = mc;
+
+ mutex_lock(&mc->ctl_lock);
+ if (mc->logdump) {
+ mutex_unlock(&mc->ctl_lock);
+ return -EBUSY;
+ }
+ mc->logdump = 1;
+ mutex_unlock(&mc->ctl_lock);
+
+ spin_lock_irqsave(&mc->lock, flags);
+ mc->logdump_data = 0;
+ pr_err("modem: send LOGDUMP\n");
+ writel(MODEM_CMD_LOGDUMP_START, mc->mmio + OFF_MBOX_AP);
+ spin_unlock_irqrestore(&mc->lock, flags);
+
+ ret = wait_event_timeout(mc->wq, mc->logdump_data, 10 * HZ);
+ if (ret == 0) {
+ mutex_lock(&mc->ctl_lock);
+ mc->logdump = 0;
+ mutex_unlock(&mc->ctl_lock);
+ return -ETIMEDOUT;
+ } else {
+ return 0;
+ }
+}
+
+static int log_release(struct inode *inode, struct file *file)
+{
+ struct modemctl *mc = file->private_data;
+ mutex_lock(&mc->ctl_lock);
+ mc->logdump = 0;
+ mutex_unlock(&mc->ctl_lock);
+ return 0;
+}
+
+static ssize_t log_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct modemctl *mc = filp->private_data;
+ loff_t pos = *ppos;
+ int ret;
+
+ if (pos < 0)
+ return -EINVAL;
+ if (pos >= SIZ_LOGDUMP_DATA)
+ return 0;
+
+ ret = modem_acquire_mmio(mc);
+ if (ret)
+ return ret;
+
+ mutex_lock(&mc->ctl_lock);
+ if (count > SIZ_LOGDUMP_DATA - pos)
+ count = SIZ_LOGDUMP_DATA - pos;
+ ret = copy_to_user(buf, mc->mmio + OFF_LOGDUMP_DATA + pos, count);
+ if (ret) {
+ ret = -EFAULT;
+ } else {
+ pos += count;
+ ret = count;
+ }
+ *ppos = pos;
+ mutex_unlock(&mc->ctl_lock);
+
+ modem_release_mmio(mc, 0);
+ return ret;
+}
+
+static const struct file_operations log_ops = {
+ .open = log_open,
+ .release = log_release,
+ .read = log_read,
+};
+
+void modem_debugfs_init(struct modemctl *mc)
+{
+ struct dentry *dent;
+
+ dent = debugfs_create_dir("modemctl", 0);
+ if (IS_ERR(dent))
+ return;
+
+ debugfs_create_file("crash", 0200, dent, mc, &crash_ops);
+ debugfs_create_file("stats", 0444, dent, mc, &stats_ops);
+ debugfs_create_file("log", 0440, dent, mc, &log_ops);
+}
diff --git a/drivers/misc/samsung_modemctl/modem_io.c b/drivers/misc/samsung_modemctl/modem_io.c
new file mode 100644
index 0000000..5c815ef
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modem_io.c
@@ -0,0 +1,724 @@
+/* modem_io.c
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (C) 2010 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* TODO
+ * - on modem crash return -ENODEV from recv/send, poll==readable
+ * - ensure all modem off/reset cases fault out io properly
+ * - request thread irq?
+ * - stats/debugfs
+ * - purge txq on restart
+ * - test, test, test
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+
+#include <linux/circ_buf.h>
+#include <linux/wakelock.h>
+
+#include "modem_ctl.h"
+#include "modem_ctl_p.h"
+
+#define RAW_CH_VNET0 10
+#define CHANNEL_TO_NETDEV_ID(id) (id - RAW_CH_VNET0)
+#define NETDEV_TO_CHANNEL_ID(id) (id + RAW_CH_VNET0)
+
+/* general purpose fifo access routines */
+
+typedef void * (*copyfunc)(void *, const void *, __kernel_size_t);
+
+static void *x_copy_to_user(void *dst, const void *src, __kernel_size_t sz)
+{
+ if (copy_to_user((void __user *) dst, src, sz) != 0)
+ pr_err("modemctl: cannot copy userdata\n");
+ return dst;
+}
+
+static void *x_copy_from_user(void *dst, const void *src, __kernel_size_t sz)
+{
+ if (copy_from_user(dst, (const void __user *) src, sz) != 0)
+ pr_err("modemctl: cannot copy userdata\n");
+ return dst;
+}
+
+static unsigned _fifo_read(struct m_fifo *q, void *dst,
+ unsigned count, copyfunc copy)
+{
+ unsigned n;
+ unsigned head = *q->head;
+ unsigned tail = *q->tail;
+ unsigned size = q->size;
+
+ if (CIRC_CNT(head, tail, size) < count)
+ return 0;
+
+ n = CIRC_CNT_TO_END(head, tail, size);
+
+ if (likely(n >= count)) {
+ copy(dst, q->data + tail, count);
+ } else {
+ copy(dst, q->data + tail, n);
+ copy(dst + n, q->data, count - n);
+ }
+ *q->tail = (tail + count) & (size - 1);
+
+ return count;
+}
+
+static unsigned _fifo_write(struct m_fifo *q, void *src,
+ unsigned count, copyfunc copy)
+{
+ unsigned n;
+ unsigned head = *q->head;
+ unsigned tail = *q->tail;
+ unsigned size = q->size;
+
+ if (CIRC_SPACE(head, tail, size) < count)
+ return 0;
+
+ n = CIRC_SPACE_TO_END(head, tail, size);
+
+ if (likely(n >= count)) {
+ copy(q->data + head, src, count);
+ } else {
+ copy(q->data + head, src, n);
+ copy(q->data, src + n, count - n);
+ }
+ *q->head = (head + count) & (size - 1);
+
+ return count;
+}
+
+static void fifo_purge(struct m_fifo *q)
+{
+ *q->head = 0;
+ *q->tail = 0;
+}
+
+static unsigned fifo_skip(struct m_fifo *q, unsigned count)
+{
+ if (CIRC_CNT(*q->head, *q->tail, q->size) < count)
+ return 0;
+ *q->tail = (*q->tail + count) & (q->size - 1);
+ return count;
+}
+
+#define fifo_read(q, dst, count) \
+ _fifo_read(q, dst, count, memcpy)
+#define fifo_read_user(q, dst, count) \
+ _fifo_read(q, dst, count, x_copy_to_user)
+
+#define fifo_write(q, src, count) \
+ _fifo_write(q, src, count, memcpy)
+#define fifo_write_user(q, src, count) \
+ _fifo_write(q, src, count, x_copy_from_user)
+
+#define fifo_count(mf) CIRC_CNT(*(mf)->head, *(mf)->tail, (mf)->size)
+#define fifo_space(mf) CIRC_SPACE(*(mf)->head, *(mf)->tail, (mf)->size)
+
+static void fifo_dump(const char *tag, struct m_fifo *q,
+ unsigned start, unsigned count)
+{
+ if (count > 64)
+ count = 64;
+
+ if ((start + count) <= q->size) {
+ print_hex_dump_bytes(tag, DUMP_PREFIX_ADDRESS,
+ q->data + start, count);
+ } else {
+ print_hex_dump_bytes(tag, DUMP_PREFIX_ADDRESS,
+ q->data + start, q->size - start);
+ print_hex_dump_bytes(tag, DUMP_PREFIX_ADDRESS,
+ q->data, count - (q->size - start));
+ }
+}
+
+
+
+/* Called with mc->lock held whenever we gain access
+ * to the mmio region.
+ */
+void modem_update_state(struct modemctl *mc)
+{
+ /* update our idea of space available in fifos */
+ mc->fmt_tx.avail = fifo_space(&mc->fmt_tx);
+ mc->fmt_rx.avail = fifo_count(&mc->fmt_rx);
+ if (mc->fmt_rx.avail)
+ wake_lock(&mc->cmd_pipe.wakelock);
+ else
+ wake_unlock(&mc->cmd_pipe.wakelock);
+
+ mc->rfs_tx.avail = fifo_space(&mc->rfs_tx);
+ mc->rfs_rx.avail = fifo_count(&mc->rfs_rx);
+ if (mc->rfs_rx.avail)
+ wake_lock(&mc->rfs_pipe.wakelock);
+ else
+ wake_unlock(&mc->rfs_pipe.wakelock);
+
+ mc->raw_tx.avail = fifo_space(&mc->raw_tx);
+ mc->raw_rx.avail = fifo_count(&mc->raw_rx);
+
+ /* wake up blocked or polling read/write operations */
+ wake_up(&mc->wq);
+}
+
+void modem_update_pipe(struct m_pipe *pipe)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&pipe->mc->lock, flags);
+ pipe->tx->avail = fifo_space(pipe->tx);
+ pipe->rx->avail = fifo_count(pipe->rx);
+ if (pipe->rx->avail)
+ wake_lock(&pipe->wakelock);
+ else
+ wake_unlock(&pipe->wakelock);
+ spin_unlock_irqrestore(&pipe->mc->lock, flags);
+}
+
+
+/* must be called with pipe->tx_lock held */
+static int modem_pipe_send(struct m_pipe *pipe, struct modem_io *io)
+{
+ char hdr[M_PIPE_MAX_HDR];
+ static char ftr = 0x7e;
+ unsigned size;
+ int ret;
+
+ ret = pipe->push_header(io, hdr);
+ if (ret)
+ return ret;
+
+ size = io->size + pipe->header_size + 1;
+
+ if (io->size > 0x10000000)
+ return -EINVAL;
+ if (size >= (pipe->tx->size - 1))
+ return -EINVAL;
+
+ for (;;) {
+ ret = modem_acquire_mmio(pipe->mc);
+ if (ret)
+ return ret;
+
+ modem_update_pipe(pipe);
+
+ if (pipe->tx->avail >= size) {
+ fifo_write(pipe->tx, hdr, pipe->header_size);
+ fifo_write_user(pipe->tx, io->data, io->size);
+ fifo_write(pipe->tx, &ftr, 1);
+ modem_update_pipe(pipe);
+ modem_release_mmio(pipe->mc, pipe->tx->bits);
+ MODEM_COUNT(pipe->mc, pipe_tx);
+ return 0;
+ }
+
+ pr_info("modem_pipe_send: wait for space\n");
+ MODEM_COUNT(pipe->mc, pipe_tx_delayed);
+ modem_release_mmio(pipe->mc, 0);
+
+ ret = wait_event_interruptible_timeout(
+ pipe->mc->wq,
+ (pipe->tx->avail >= size) || modem_offline(pipe->mc),
+ 5 * HZ);
+ if (ret == 0)
+ return -ENODEV;
+ if (ret < 0)
+ return ret;
+ }
+}
+
+static int modem_pipe_read(struct m_pipe *pipe, struct modem_io *io)
+{
+ unsigned data_size = io->size;
+ char hdr[M_PIPE_MAX_HDR];
+ int ret;
+
+ if (fifo_read(pipe->rx, hdr, pipe->header_size) == 0)
+ return -EAGAIN;
+
+ ret = pipe->pull_header(io, hdr);
+ if (ret)
+ return ret;
+
+ if (data_size < io->size) {
+ pr_info("modem_pipe_read: discarding packet (%d)\n", io->size);
+ if (fifo_skip(pipe->rx, io->size + 1) != (io->size + 1))
+ return -EIO;
+ return -EAGAIN;
+ } else {
+ if (fifo_read_user(pipe->rx, io->data, io->size) != io->size)
+ return -EIO;
+ if (fifo_skip(pipe->rx, 1) != 1)
+ return -EIO;
+ }
+ return 0;
+}
+
+/* must be called with pipe->rx_lock held */
+static int modem_pipe_recv(struct m_pipe *pipe, struct modem_io *io)
+{
+ int ret;
+
+ ret = modem_acquire_mmio(pipe->mc);
+ if (ret)
+ return ret;
+
+ ret = modem_pipe_read(pipe, io);
+
+ modem_update_pipe(pipe);
+
+ if ((ret != 0) && (ret != -EAGAIN)) {
+ pr_err("[MODEM] purging %s fifo\n", pipe->dev.name);
+ fifo_purge(pipe->rx);
+ MODEM_COUNT(pipe->mc, pipe_rx_purged);
+ } else if (ret == 0) {
+ MODEM_COUNT(pipe->mc, pipe_rx);
+ }
+
+ modem_release_mmio(pipe->mc, 0);
+
+ return ret;
+}
+
+struct raw_hdr {
+ u8 start;
+ u32 len;
+ u8 channel;
+ u8 control;
+} __attribute__ ((packed));
+
+struct vnet {
+ struct modemctl *mc;
+ struct sk_buff_head txq;
+ int rmnet_ch_id;
+};
+
+static void handle_raw_rx(struct modemctl *mc)
+{
+ struct raw_hdr raw;
+ struct sk_buff *skb = NULL;
+ int recvdata = 0;
+
+ /* process inbound packets */
+ while (fifo_read(&mc->raw_rx, &raw, sizeof(raw)) == sizeof(raw)) {
+ struct net_device *dev;
+ unsigned sz = raw.len - (sizeof(raw) - 1);
+
+ if (unlikely(!(raw.channel >= RAW_CH_VNET0 && raw.channel <
+ NETDEV_TO_CHANNEL_ID(mc->num_pdp_contexts)))) {
+
+ MODEM_COUNT(mc, rx_unknown);
+ pr_err("[VNET] unknown channel %d\n", raw.channel);
+ if (fifo_skip(&mc->raw_rx, sz + 1) != (sz + 1))
+ goto purge_raw_fifo;
+ continue;
+ }
+ dev = mc->ndev[CHANNEL_TO_NETDEV_ID(raw.channel)];
+ skb = dev_alloc_skb(sz + NET_IP_ALIGN);
+ if (skb == NULL) {
+ MODEM_COUNT(mc, rx_dropped);
+ /* TODO: consider timer + retry instead of drop? */
+ pr_err("[VNET] cannot alloc %d byte packet\n", sz);
+ if (fifo_skip(&mc->raw_rx, sz + 1) != (sz + 1))
+ goto purge_raw_fifo;
+ continue;
+ }
+ skb->dev = dev;
+ skb_reserve(skb, NET_IP_ALIGN);
+
+ if (fifo_read(&mc->raw_rx, skb_put(skb, sz), sz) != sz)
+ goto purge_raw_fifo;
+ if (fifo_skip(&mc->raw_rx, 1) != 1)
+ goto purge_raw_fifo;
+
+ /* Get the ethertype from the version in the IP header. */
+ if (skb->data[0] >> 4 == 6)
+ skb->protocol = __constant_htons(ETH_P_IPV6);
+ else
+ skb->protocol = __constant_htons(ETH_P_IP);
+ skb_reset_mac_header(skb);
+
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += skb->len;
+
+ netif_rx(skb);
+ recvdata = 1;
+ MODEM_COUNT(mc, rx_received);
+ }
+
+ if (recvdata)
+ wake_lock_timeout(&mc->ip_rx_wakelock, HZ * 2);
+ return;
+
+purge_raw_fifo:
+ if (skb)
+ dev_kfree_skb_irq(skb);
+ pr_err("[VNET] purging raw rx fifo!\n");
+ fifo_purge(&mc->raw_tx);
+ MODEM_COUNT(mc, rx_purged);
+}
+
+int handle_raw_tx(struct modemctl *mc, struct sk_buff *skb)
+{
+ struct raw_hdr raw;
+ unsigned char ftr = 0x7e;
+ unsigned sz;
+ int netdev_id;
+ struct vnet *vn = netdev_priv(skb->dev);
+
+
+ sz = skb->len + sizeof(raw) + 1;
+
+ if (fifo_space(&mc->raw_tx) < sz) {
+ MODEM_COUNT(mc, tx_fifo_full);
+ return -1;
+ }
+
+ raw.start = 0x7f;
+ raw.len = 6 + skb->len;
+ raw.channel = vn->rmnet_ch_id;
+ raw.control = 0;
+
+ fifo_write(&mc->raw_tx, &raw, sizeof(raw));
+ fifo_write(&mc->raw_tx, skb->data, skb->len);
+ fifo_write(&mc->raw_tx, &ftr, 1);
+
+ netdev_id = CHANNEL_TO_NETDEV_ID(vn->rmnet_ch_id);
+ mc->ndev[netdev_id]->stats.tx_packets++;
+ mc->ndev[netdev_id]->stats.tx_bytes += skb->len;
+
+ mc->mmio_signal_bits |= MBD_SEND_RAW;
+
+ dev_kfree_skb_irq(skb);
+ return 0;
+}
+
+void modem_handle_io(struct modemctl *mc)
+{
+ struct sk_buff *skb;
+ struct vnet *vn;
+ int i;
+ int cnt = 0;
+
+ handle_raw_rx(mc);
+
+ for (i = 0; i < mc->num_pdp_contexts; i++) {
+ vn = netdev_priv(mc->ndev[i]);
+ while ((skb = skb_dequeue(&vn->txq)))
+ if (handle_raw_tx(mc, skb)) {
+ skb_queue_head(&vn->txq, skb);
+ break;
+ }
+ if (skb == NULL)
+ cnt++;
+ }
+ if (cnt == mc->num_pdp_contexts)
+ wake_unlock(&mc->ip_tx_wakelock);
+}
+
+static int vnet_open(struct net_device *ndev)
+{
+ netif_start_queue(ndev);
+ return 0;
+}
+
+static int vnet_stop(struct net_device *ndev)
+{
+ netif_stop_queue(ndev);
+ return 0;
+}
+
+static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct vnet *vn = netdev_priv(ndev);
+ struct modemctl *mc = vn->mc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mc->lock, flags);
+ if (readl(mc->mmio + OFF_SEM) & 1) {
+ /* if we happen to hold the hw mmio sem, transmit NOW */
+ if (handle_raw_tx(mc, skb)) {
+ wake_lock(&mc->ip_tx_wakelock);
+ skb_queue_tail(&vn->txq, skb);
+ } else {
+ MODEM_COUNT(mc, tx_no_delay);
+ }
+ if (!mc->mmio_owner) {
+ /* if we don't own the semaphore, immediately
+ * give it back to the modem and signal the modem
+ * to process the packet
+ */
+ writel(0, mc->mmio + OFF_SEM);
+ writel(MB_VALID | MBD_SEND_RAW,
+ mc->mmio + OFF_MBOX_AP);
+ MODEM_COUNT(mc, tx_bp_signaled);
+ }
+ } else {
+ /* otherwise request the hw mmio sem and queue */
+ modem_request_sem(mc);
+ skb_queue_tail(&vn->txq, skb);
+ MODEM_COUNT(mc, tx_queued);
+ }
+ spin_unlock_irqrestore(&mc->lock, flags);
+
+ return NETDEV_TX_OK;
+}
+
+static struct net_device_ops vnet_ops = {
+ .ndo_open = vnet_open,
+ .ndo_stop = vnet_stop,
+ .ndo_start_xmit = vnet_xmit,
+};
+
+static void vnet_setup(struct net_device *ndev)
+{
+ ndev->netdev_ops = &vnet_ops;
+ ndev->type = ARPHRD_PPP;
+ ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+ ndev->hard_header_len = 0;
+ ndev->addr_len = 0;
+ ndev->tx_queue_len = 1000;
+ ndev->mtu = ETH_DATA_LEN;
+ ndev->watchdog_timeo = 5 * HZ;
+}
+
+struct fmt_hdr {
+ u8 start;
+ u16 len;
+ u8 control;
+} __attribute__ ((packed));
+
+static int push_fmt_header(struct modem_io *io, void *header)
+{
+ struct fmt_hdr *fh = header;
+
+ if (io->id)
+ return -EINVAL;
+ if (io->cmd)
+ return -EINVAL;
+ fh->start = 0x7f;
+ fh->len = io->size + 3;
+ fh->control = 0;
+ return 0;
+}
+
+static int pull_fmt_header(struct modem_io *io, void *header)
+{
+ struct fmt_hdr *fh = header;
+
+ if (fh->start != 0x7f)
+ return -EINVAL;
+ if (fh->control != 0x00)
+ return -EINVAL;
+ if (fh->len < 3)
+ return -EINVAL;
+ io->size = fh->len - 3;
+ io->id = 0;
+ io->cmd = 0;
+ return 0;
+}
+
+struct rfs_hdr {
+ u8 start;
+ u32 len;
+ u8 cmd;
+ u8 id;
+} __attribute__ ((packed));
+
+static int push_rfs_header(struct modem_io *io, void *header)
+{
+ struct rfs_hdr *rh = header;
+
+ if (io->id > 0xFF)
+ return -EINVAL;
+ if (io->cmd > 0xFF)
+ return -EINVAL;
+ rh->start = 0x7f;
+ rh->len = io->size + 6;
+ rh->id = io->id;
+ rh->cmd = io->cmd;
+ return 0;
+}
+
+static int pull_rfs_header(struct modem_io *io, void *header)
+{
+ struct rfs_hdr *rh = header;
+
+ if (rh->start != 0x7f)
+ return -EINVAL;
+ if (rh->len < 6)
+ return -EINVAL;
+ io->size = rh->len - 6;
+ io->id = rh->id;
+ io->cmd = rh->cmd;
+ return 0;
+}
+
+static int pipe_open(struct inode *inode, struct file *filp)
+{
+ struct m_pipe *pipe = to_m_pipe(filp->private_data);
+ filp->private_data = pipe;
+ return 0;
+}
+
+static long pipe_ioctl(struct file *filp, unsigned int cmd, unsigned long _arg)
+{
+ void __user *arg = (void *) _arg;
+ struct m_pipe *pipe = filp->private_data;
+ struct modem_io mio;
+ int ret;
+
+ switch (cmd) {
+ case IOCTL_MODEM_SEND:
+ if (copy_from_user(&mio, arg, sizeof(mio)) != 0)
+ return -EFAULT;
+ if (mutex_lock_interruptible(&pipe->tx_lock))
+ return -EINTR;
+ ret = modem_pipe_send(pipe, &mio);
+ mutex_unlock(&pipe->tx_lock);
+ return ret;
+
+ case IOCTL_MODEM_RECV:
+ if (copy_from_user(&mio, arg, sizeof(mio)) != 0)
+ return -EFAULT;
+ if (mutex_lock_interruptible(&pipe->rx_lock))
+ return -EINTR;
+ ret = modem_pipe_recv(pipe, &mio);
+ mutex_unlock(&pipe->rx_lock);
+ if (copy_to_user(arg, &mio, sizeof(mio)) != 0)
+ return -EFAULT;
+ return ret;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static unsigned int pipe_poll(struct file *filp, poll_table *wait)
+{
+ unsigned long flags;
+ struct m_pipe *pipe = filp->private_data;
+ int ret;
+
+ poll_wait(filp, &pipe->mc->wq, wait);
+
+ spin_lock_irqsave(&pipe->mc->lock, flags);
+ if (pipe->rx->avail || modem_offline(pipe->mc))
+ ret = POLLIN | POLLRDNORM;
+ else
+ ret = 0;
+ spin_unlock_irqrestore(&pipe->mc->lock, flags);
+
+ return ret;
+}
+
+static const struct file_operations modem_io_fops = {
+ .owner = THIS_MODULE,
+ .open = pipe_open,
+ .poll = pipe_poll,
+ .unlocked_ioctl = pipe_ioctl,
+};
+
+static int modem_pipe_register(struct m_pipe *pipe, const char *devname)
+{
+ pipe->dev.minor = MISC_DYNAMIC_MINOR;
+ pipe->dev.name = devname;
+ pipe->dev.fops = &modem_io_fops;
+
+ wake_lock_init(&pipe->wakelock, WAKE_LOCK_SUSPEND, devname);
+
+ mutex_init(&pipe->tx_lock);
+ mutex_init(&pipe->rx_lock);
+ return misc_register(&pipe->dev);
+}
+
+int modem_io_init(struct modemctl *mc, void __iomem *mmio)
+{
+ struct vnet *vn;
+ int r;
+ int i;
+ int ch_id;
+
+ INIT_M_FIFO(mc->fmt_tx, FMT, TX, mmio);
+ INIT_M_FIFO(mc->fmt_rx, FMT, RX, mmio);
+ INIT_M_FIFO(mc->raw_tx, RAW, TX, mmio);
+ INIT_M_FIFO(mc->raw_rx, RAW, RX, mmio);
+ INIT_M_FIFO(mc->rfs_tx, RFS, TX, mmio);
+ INIT_M_FIFO(mc->rfs_rx, RFS, RX, mmio);
+
+ mc->ndev = kmalloc(sizeof(struct net_device *) * mc->num_pdp_contexts,
+ GFP_KERNEL);
+ if (!mc->ndev) {
+ pr_err("memory allocation failed for netdev\n");
+ return -ENOMEM;
+ }
+ for (i = 0, ch_id = RAW_CH_VNET0; i < mc->num_pdp_contexts;
+ i++, ch_id++) {
+ mc->ndev[i] = alloc_netdev(0, "rmnet%d", vnet_setup);
+ if (mc->ndev[i]) {
+ vn = netdev_priv(mc->ndev[i]);
+ vn->mc = mc;
+ vn->rmnet_ch_id = ch_id;
+ skb_queue_head_init(&vn->txq);
+ r = register_netdev(mc->ndev[i]);
+ if (r) {
+ free_netdev(mc->ndev[i]);
+ pr_err("failed to register rmnet%d\n", i);
+ goto free;
+ }
+ } else {
+ pr_err("failed to alloc rmnet%d\n", i);
+ goto free;
+ }
+ }
+
+ mc->cmd_pipe.tx = &mc->fmt_tx;
+ mc->cmd_pipe.rx = &mc->fmt_rx;
+ mc->cmd_pipe.tx->bits = MBD_SEND_FMT;
+ mc->cmd_pipe.push_header = push_fmt_header;
+ mc->cmd_pipe.pull_header = pull_fmt_header;
+ mc->cmd_pipe.header_size = sizeof(struct fmt_hdr);
+ mc->cmd_pipe.mc = mc;
+ if (modem_pipe_register(&mc->cmd_pipe, "modem_fmt"))
+ pr_err("failed to register modem_fmt pipe\n");
+
+ mc->rfs_pipe.tx = &mc->rfs_tx;
+ mc->rfs_pipe.rx = &mc->rfs_rx;
+ mc->rfs_pipe.tx->bits = MBD_SEND_RFS;
+ mc->rfs_pipe.push_header = push_rfs_header;
+ mc->rfs_pipe.pull_header = pull_rfs_header;
+ mc->rfs_pipe.header_size = sizeof(struct rfs_hdr);
+ mc->rfs_pipe.mc = mc;
+ if (modem_pipe_register(&mc->rfs_pipe, "modem_rfs"))
+ pr_err("failed to register modem_rfs pipe\n");
+
+ return 0;
+
+free:
+ /* Unregister and free any alloced netdevs */
+ while (--i >= 0) {
+ unregister_netdev(mc->ndev[i]);
+ free_netdev(mc->ndev[i]);
+ }
+ return -ENOMEM;
+}
diff --git a/drivers/misc/samsung_modemctl/modemctl/Makefile b/drivers/misc/samsung_modemctl/modemctl/Makefile
new file mode 100755
index 0000000..f1bc187
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modemctl/Makefile
@@ -0,0 +1 @@
+obj-y += modemctl.o \ No newline at end of file
diff --git a/drivers/misc/samsung_modemctl/modemctl/modemctl.c b/drivers/misc/samsung_modemctl/modemctl/modemctl.c
new file mode 100644
index 0000000..75f5516
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modemctl/modemctl.c
@@ -0,0 +1,875 @@
+/**
+ * header for modem control
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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
+ */
+
+//#define DEBUG
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kdev_t.h>
+#include <linux/workqueue.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+
+#include "modemctl.h"
+
+#ifdef CONFIG_HAS_WAKELOCK
+#include <linux/wakelock.h>
+#define MODEM_CTL_DEFAULT_WAKLOCK_HZ (2*HZ)
+#endif
+
+
+#define DRVNAME "modemctl"
+
+#define SIM_DEBOUNCE_TIME_HZ (HZ)
+
+struct modemctl;
+
+struct modemctl_ops {
+ void (*modem_on)(struct modemctl *);
+ void (*modem_off)(struct modemctl *);
+ void (*modem_reset)(struct modemctl *);
+ void (*modem_boot_on)(struct modemctl *);
+ void (*modem_boot_off)(struct modemctl *);
+};
+
+struct modemctl_info {
+ const char *name;
+ struct modemctl_ops ops;
+};
+
+struct modemctl {
+ int irq_phone_active;
+ int irq_sim_ndetect;
+
+ unsigned gpio_phone_on;
+ unsigned gpio_phone_active;
+ unsigned gpio_pda_active;
+ unsigned gpio_cp_reset;
+ unsigned gpio_reset_req_n;
+ unsigned gpio_usim_boot;
+ unsigned gpio_flm_sel;
+
+ unsigned gpio_sim_ndetect;
+ unsigned sim_reference_level;
+ unsigned sim_change_reset;
+ struct timer_list sim_irq_debounce_timer;
+
+#ifdef CONFIG_HAS_WAKELOCK
+ struct wake_lock mc_wlock;
+ long waketime;
+#endif
+
+ struct modemctl_ops *ops;
+
+ struct class *class;
+ struct device *dev;
+ const struct attribute_group *group;
+
+ struct work_struct work;
+};
+
+enum {
+ SIM_LEVEL_NONE = -1,
+ SIM_LEVEL_STABLE,
+ SIM_LEVEL_CHANGED
+};
+
+#ifdef CONFIG_HAS_WAKELOCK
+static inline void _wake_lock_init(struct modemctl *mc)
+{
+ wake_lock_init(&mc->mc_wlock, WAKE_LOCK_SUSPEND, "modemctl");
+ mc->waketime = MODEM_CTL_DEFAULT_WAKLOCK_HZ;
+}
+
+static inline void _wake_lock_destroy(struct modemctl *mc)
+{
+ wake_lock_destroy(&mc->mc_wlock);
+}
+
+static inline void _wake_lock_timeout(struct modemctl *mc)
+{
+ wake_lock_timeout(&mc->mc_wlock, mc->waketime);
+}
+
+static inline void _wake_lock_settime(struct modemctl *mc, long time)
+{
+ if (mc)
+ mc->waketime = time;
+}
+
+static inline long _wake_lock_gettime(struct modemctl *mc)
+{
+ return mc?mc->waketime:MODEM_CTL_DEFAULT_WAKLOCK_HZ;
+}
+#else
+# define _wake_lock_init(mc) do { } while(0)
+# define _wake_lock_destroy(mc) do { } while(0)
+# define _wake_lock_timeout(mc) do { } while(0)
+# define _wake_lock_settime(mc, time) do { } while(0)
+# define _wake_lock_gettime(mc) (0)
+#endif
+
+static int sim_check_status(struct modemctl *);
+static int sim_get_reference_status(struct modemctl *);
+static void sim_irq_debounce_timer_func(unsigned);
+
+static void xmm_on(struct modemctl *);
+static void xmm_off(struct modemctl *);
+static void xmm_reset(struct modemctl *);
+static void xmm_boot(struct modemctl *);
+
+static struct modemctl_info mdmctl_info[] = {
+ {
+ .name = "xmm",
+ .ops = {
+ .modem_on = xmm_on,
+ .modem_off = xmm_off,
+ .modem_reset = xmm_reset,
+ .modem_boot_on = xmm_boot,
+ },
+ },
+};
+
+static ssize_t show_control(struct device *d,
+ struct device_attribute *attr, char *buf);
+static ssize_t store_control(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t show_status(struct device *d,
+ struct device_attribute *attr, char *buf);
+static ssize_t show_debug(struct device *d,
+ struct device_attribute *attr, char *buf);
+static ssize_t show_sim(struct device *d,
+ struct device_attribute *attr, char *buf);
+static ssize_t show_phoneactive(struct device *d,
+ struct device_attribute *attr, char *buf);
+
+static DEVICE_ATTR(control, 0664, show_control, store_control);
+static DEVICE_ATTR(status, 0664, show_status, NULL);
+static DEVICE_ATTR(debug, 0664, show_debug, NULL);
+static DEVICE_ATTR(sim, 0664, show_sim, NULL);
+static DEVICE_ATTR(phoneactive, 0664, show_phoneactive, NULL);
+
+static struct attribute *modemctl_attributes[] = {
+ &dev_attr_control.attr,
+ &dev_attr_status.attr,
+ &dev_attr_debug.attr,
+ &dev_attr_sim.attr,
+ &dev_attr_phoneactive.attr,
+ NULL
+};
+
+static const struct attribute_group modemctl_group = {
+ .attrs = modemctl_attributes,
+};
+
+/* declare mailbox init function for xmm */
+extern void onedram_init_mailbox(void);
+
+static void xmm_on(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_cp_reset)
+ return;
+
+ /* ensure pda active pin set to low */
+ gpio_set_value(mc->gpio_pda_active, 0);
+ /* call mailbox init : BA goes to high, AB goes to low */
+ onedram_init_mailbox();
+ /* ensure cp_reset pin set to low */
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ if(mc->gpio_reset_req_n)
+ gpio_direction_output(mc->gpio_reset_req_n, 0);
+
+ msleep(100);
+
+ //gpio_set_value(mc->gpio_cp_reset, 1);
+ if(mc->gpio_phone_on)
+ gpio_set_value(mc->gpio_phone_on, 1);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100); /* no spec, confirm later exactly how much time
+ needed to initialize CP with RESET_PMU_N */
+
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ /* Follow RESET timming delay not Power-On timming,
+ because CP_RST & PHONE_ON have been set high already. */
+ // msleep(30); /* > 26.6 + 2 msec */
+ //msleep(40); /* > 37.2 + 2 msec */
+ msleep(100); /*wait modem stable */
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+ if(mc->gpio_reset_req_n)
+ gpio_direction_input(mc->gpio_reset_req_n);
+}
+
+static void xmm_off(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_cp_reset)
+ return;
+
+ if(mc->gpio_phone_on)
+ gpio_set_value(mc->gpio_phone_on, 0);
+ gpio_set_value(mc->gpio_cp_reset, 0);
+}
+
+static void xmm_reset(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_cp_reset)
+ return;
+
+ /* To Do :
+ * hard_reset(RESET_PMU_N) and soft_reset(RESET_REQ_N)
+ * should be divided later.
+ * soft_reset is used for CORE_DUMP
+ */
+ gpio_set_value(mc->gpio_cp_reset, 0);
+ msleep(100); /* no spec, confirm later exactly how much time
+ needed to initialize CP with RESET_PMU_N */
+ gpio_set_value(mc->gpio_cp_reset, 1);
+ //msleep(40); /* > 37.2 + 2 msec */
+ msleep(100); /*wait modem stable */
+
+ /* leave it as reset state */
+// gpio_set_value(mc->gpio_phone_on, 0);
+// gpio_set_value(mc->gpio_cp_reset, 0);
+}
+
+static void xmm_boot(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+
+ if(mc->gpio_usim_boot)
+ gpio_set_value(mc->gpio_usim_boot, 1);
+
+ if(mc->gpio_flm_sel)
+ gpio_set_value(mc->gpio_flm_sel, 0);
+}
+
+
+static int modem_on(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->ops || !mc->ops->modem_on) {
+ //
+ return -ENXIO;
+ }
+
+ mc->ops->modem_on(mc);
+
+ return 0;
+}
+
+static int modem_off(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->ops || !mc->ops->modem_off) {
+ //
+ return -ENXIO;
+ }
+
+ mc->ops->modem_off(mc);
+
+ return 0;
+}
+
+static int modem_reset(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->ops || !mc->ops->modem_reset) {
+ //
+ return -ENXIO;
+ }
+
+ mc->ops->modem_reset(mc);
+
+ return 0;
+}
+
+static int modem_boot_on(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->ops || !mc->ops->modem_boot_on) {
+ //
+ return -ENXIO;
+ }
+
+ mc->ops->modem_boot_on(mc);
+
+ return 0;
+}
+
+static int modem_boot_off(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->ops || !mc->ops->modem_boot_off) {
+ //
+ return -ENXIO;
+ }
+
+ mc->ops->modem_boot_off(mc);
+
+ return 0;
+}
+
+static int pda_on(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_pda_active) {
+
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_pda_active, 1);
+
+ return 0;
+}
+
+static int pda_off(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_pda_active) {
+
+ return -ENXIO;
+ }
+
+ gpio_set_value(mc->gpio_pda_active, 0);
+
+ return 0;
+}
+
+static int modem_get_active(struct modemctl *mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_phone_active || !mc->gpio_cp_reset)
+ return -ENXIO;
+
+ dev_dbg(mc->dev, "cp %d phone %d\n",
+ gpio_get_value(mc->gpio_cp_reset),
+ gpio_get_value(mc->gpio_phone_active));
+
+ if(gpio_get_value(mc->gpio_cp_reset))
+ return !!gpio_get_value(mc->gpio_phone_active);
+
+ return 0;
+}
+
+static ssize_t show_control(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+ struct modemctl *mc = dev_get_drvdata(d);
+ struct modemctl_ops *ops = mc->ops;
+
+ if(ops) {
+ if(ops->modem_on)
+ p += sprintf(p, "on ");
+ if(ops->modem_off)
+ p += sprintf(p, "off ");
+ if(ops->modem_reset)
+ p += sprintf(p, "reset ");
+ if(ops->modem_boot_on)
+ p += sprintf(p, "boot_on ");
+
+ if(ops->modem_boot_off)
+ p += sprintf(p, "boot_off ");
+ } else {
+ p += sprintf(p, "(No ops)");
+ }
+
+ p += sprintf(p, "\n");
+ return p - buf;
+}
+
+static ssize_t store_control(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct modemctl *mc = dev_get_drvdata(d);
+
+ if(!strncmp(buf, "on", 2)) {
+ modem_on(mc);
+ return count;
+ }
+
+ if(!strncmp(buf, "off", 3)) {
+ modem_off(mc);
+ return count;
+ }
+
+ if(!strncmp(buf, "reset", 5)) {
+ modem_reset(mc);
+ return count;
+ }
+
+ if(!strncmp(buf, "boot_on", 7)) {
+ modem_boot_on(mc);
+ return count;
+ }
+
+ if(!strncmp(buf, "boot_off", 8)) {
+ modem_boot_off(mc);
+ return count;
+ }
+ // for compatibility
+ if(!strncmp(buf, "boot", 4)) {
+ modem_boot_on(mc);
+ return count;
+ }
+
+ return count;
+}
+
+static ssize_t show_status(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+ struct modemctl *mc = dev_get_drvdata(d);
+
+ p += sprintf(p, "%d\n", modem_get_active(mc));
+
+ return p - buf;
+}
+
+static ssize_t show_sim(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+ int level = 3;
+ struct modemctl *mc = dev_get_drvdata(d);
+
+ if (mc->gpio_sim_ndetect) {
+ level = gpio_get_value(mc->gpio_sim_ndetect);
+ }
+
+ p += sprintf(p, "%d\n", level);
+ return p - buf;
+}
+
+static ssize_t show_phoneactive(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+ int level = 3;
+ struct modemctl *mc = dev_get_drvdata(d);
+
+ if (mc->gpio_phone_active) {
+ level = gpio_get_value(mc->gpio_phone_active);
+ }
+
+ p += sprintf(p, "%d\n", level);
+ return p - buf;
+}
+
+static ssize_t show_debug(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ /*
+ char *p = buf;
+ int i;
+ struct modemctl *mc = dev_get_drvdata(d);
+
+ if(mc->irq_phone_active)
+ p += sprintf(p, "Irq Phone Active: %d\n", mc->irq_phone_active);
+ if(mc->irq_sim_ndetect)
+ p += sprintf(p, "Irq Sim nDetect: %d\n", mc->irq_sim_ndetect);
+
+ p += sprintf(p, "GPIO ---- \n");
+
+ if(mc->gpio_phone_on)
+ p += sprintf(p, "\t%3d %d : phone on\n", mc->gpio_phone_on,
+ gpio_get_value(mc->gpio_phone_on));
+ if(mc->gpio_phone_active)
+ p += sprintf(p, "\t%3d %d : phone active\n", mc->gpio_phone_active,
+ gpio_get_value(mc->gpio_phone_active));
+ if(mc->gpio_pda_active)
+ p += sprintf(p, "\t%3d %d : pda active\n", mc->gpio_pda_active,
+ gpio_get_value(mc->gpio_pda_active));
+ if(mc->gpio_cp_reset)
+ p += sprintf(p, "\t%3d %d : CP reset\n", mc->gpio_cp_reset,
+ gpio_get_value(mc->gpio_cp_reset));
+ if(mc->gpio_usim_boot)
+ p += sprintf(p, "\t%3d %d : USIM boot\n", mc->gpio_usim_boot,
+ gpio_get_value(mc->gpio_usim_boot));
+ if(mc->gpio_flm_sel)
+ p += sprintf(p, "\t%3d %d : FLM sel\n", mc->gpio_flm_sel,
+ gpio_get_value(mc->gpio_flm_sel));
+ if(mc->gpio_sim_ndetect)
+ p += sprintf(p, "\t%3d %d : Sim n Detect\n", mc->gpio_sim_ndetect,
+ gpio_get_value(mc->gpio_sim_ndetect));
+
+ p += sprintf(p, "Support types --- \n");
+ for(i=0;i<ARRAY_SIZE(mdmctl_info);i++) {
+ if(mc->ops == &mdmctl_info[i].ops) {
+ p += sprintf(p, "\t * ");
+ } else {
+ p += sprintf(p, "\t ");
+ }
+ p += sprintf(p, "%s\n", mdmctl_info[i].name);
+ }
+*/
+ return 0;
+}
+
+static void mc_work(struct work_struct *work)
+{
+ struct modemctl *mc = container_of(work, struct modemctl, work);
+ int r;
+
+ r = modem_get_active(mc);
+ if (r < 0) {
+ dev_err(mc->dev, "Not initialized\n");
+ return;
+ }
+
+ dev_info(mc->dev, "PHONE ACTIVE: %d\n", r);
+
+ if (r) {
+ if (mc->sim_change_reset == SIM_LEVEL_CHANGED) {
+ kobject_uevent(&mc->dev->kobj, KOBJ_CHANGE);
+ } else {
+ if (mc->sim_reference_level == SIM_LEVEL_NONE) {
+ sim_get_reference_status(mc);
+ }
+ kobject_uevent(&mc->dev->kobj, KOBJ_ONLINE);
+ }
+ }
+ else
+ kobject_uevent(&mc->dev->kobj, KOBJ_OFFLINE);
+}
+
+static irqreturn_t modemctl_irq_handler(int irq, void *dev_id)
+{
+ struct modemctl *mc = (struct modemctl *)dev_id;
+
+ if (!work_pending(&mc->work))
+ schedule_work(&mc->work);
+
+ return IRQ_HANDLED;
+}
+
+static int sim_get_reference_status(struct modemctl* mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_sim_ndetect)
+ return -ENXIO;
+
+ mc->sim_reference_level = gpio_get_value(mc->gpio_sim_ndetect);
+
+ return 0;
+}
+
+static int sim_check_status(struct modemctl* mc)
+{
+ dev_dbg(mc->dev, "%s\n", __func__);
+ if(!mc->gpio_sim_ndetect || mc->sim_reference_level == SIM_LEVEL_NONE) {
+ return -ENXIO;
+ }
+
+ if (mc->sim_reference_level != gpio_get_value(mc->gpio_sim_ndetect)) {
+ mc->sim_change_reset = SIM_LEVEL_CHANGED;
+ }
+ else
+ {
+ mc->sim_change_reset = SIM_LEVEL_STABLE;
+ }
+
+ return 0;
+}
+
+static void sim_irq_debounce_timer_func(unsigned aulong)
+{
+ struct modemctl *mc = (struct modemctl *)aulong;
+ int r;
+
+ r = sim_check_status(mc);
+ if (r < 0) {
+ dev_err(mc->dev, "Not initialized\n");
+ return;
+ }
+
+ if (mc->sim_change_reset == SIM_LEVEL_CHANGED) {
+ if (!work_pending(&mc->work))
+ schedule_work(&mc->work);
+
+ _wake_lock_timeout(mc);
+ }
+}
+
+static irqreturn_t simctl_irq_handler(int irq, void *dev_id)
+{
+ struct modemctl *mc = (struct modemctl *)dev_id;
+ int r;
+
+ if ( mc->sim_reference_level == SIM_LEVEL_NONE) {
+ return IRQ_HANDLED;
+ }
+
+ r = sim_check_status(mc);
+ if (r < 0) {
+ dev_err(mc->dev, "Not initialized\n");
+ return IRQ_HANDLED;
+ }
+
+ if (mc->sim_change_reset == SIM_LEVEL_CHANGED) {
+ mod_timer(&mc->sim_irq_debounce_timer, jiffies + SIM_DEBOUNCE_TIME_HZ);
+ _wake_lock_timeout(mc);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static struct modemctl_ops* _find_ops(const char *name)
+{
+ int i;
+ struct modemctl_ops *ops = NULL;
+
+ for(i=0;i<ARRAY_SIZE(mdmctl_info);i++) {
+ if(mdmctl_info[i].name && !strcmp(name, mdmctl_info[i].name))
+ ops = &mdmctl_info[i].ops;
+ }
+
+ return ops;
+}
+
+static void _free_all(struct modemctl *mc)
+{
+ if(mc) {
+ if(mc->ops)
+ mc->ops = NULL;
+
+ if(mc->group)
+ sysfs_remove_group(&mc->dev->kobj, mc->group);
+
+ if(mc->irq_phone_active)
+ free_irq(mc->irq_phone_active, mc);
+
+ if(mc->irq_sim_ndetect)
+ free_irq(mc->irq_sim_ndetect, mc);
+
+ if(mc->dev)
+ device_destroy(mc->class, mc->dev->devt);
+
+ if(mc->class)
+ class_destroy(mc->class);
+
+ _wake_lock_destroy(mc);
+
+ kfree(mc);
+ }
+}
+
+static int __devinit modemctl_probe(struct platform_device *pdev)
+{
+ struct modemctl *mc = NULL;
+ struct modemctl_platform_data *pdata;
+ struct resource *res;
+ int r = 0;
+ int irq_phone_active, irq_sim_ndetect;
+
+ printk("[%s]\n",__func__);
+
+ pdata = pdev->dev.platform_data;
+ if(!pdata || !pdata->cfg_gpio) {
+ dev_err(&pdev->dev, "No platform data\n");
+ r = -EINVAL;
+ goto err;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if(!res) {
+ dev_err(&pdev->dev, "failed to get irq number\n");
+ r = -EINVAL;
+ goto err;
+ }
+ irq_phone_active = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
+ if(!res) {
+ dev_err(&pdev->dev, "failed to get irq number\n");
+ r = -EINVAL;
+ goto err;
+ }
+ irq_sim_ndetect = res->start;
+
+ mc = kzalloc(sizeof(struct modemctl), GFP_KERNEL);
+ if(!mc) {
+ dev_err(&pdev->dev, "failed to allocate device\n");
+ r = -ENOMEM;
+ goto err;
+ }
+
+ mc->gpio_phone_on = pdata->gpio_phone_on;
+ mc->gpio_phone_active = pdata->gpio_phone_active;
+ mc->gpio_pda_active = pdata->gpio_pda_active;
+ mc->gpio_cp_reset = pdata->gpio_cp_reset;
+ mc->gpio_reset_req_n = pdata->gpio_reset_req_n;
+ mc->gpio_usim_boot = pdata->gpio_usim_boot;
+ mc->gpio_flm_sel = pdata->gpio_flm_sel;
+ mc->gpio_sim_ndetect = pdata->gpio_sim_ndetect;
+ mc->sim_change_reset = SIM_LEVEL_NONE;
+ mc->sim_reference_level = SIM_LEVEL_NONE;
+
+ mc->ops = _find_ops(pdata->name);
+ if(!mc->ops) {
+ dev_err(&pdev->dev, "can't find operations: %s\n", pdata->name);
+ goto err;
+ }
+
+ mc->class = class_create(THIS_MODULE, "modemctl");
+ if(IS_ERR(mc->class)) {
+ dev_err(&pdev->dev, "failed to create sysfs class\n");
+ r = PTR_ERR(mc->class);
+ mc->class = NULL;
+ goto err;
+ }
+
+ pdata->cfg_gpio();
+
+ mc->dev = device_create(mc->class, &pdev->dev, MKDEV(0, 0), NULL, "%s", pdata->name);
+ if(IS_ERR(mc->dev)) {
+ dev_err(&pdev->dev, "failed to create device\n");
+ r = PTR_ERR(mc->dev);
+ goto err;
+ }
+ dev_set_drvdata(mc->dev, mc);
+
+ r = sysfs_create_group(&mc->dev->kobj, &modemctl_group);
+ if(r) {
+ dev_err(&pdev->dev, "failed to create sysfs files\n");
+ goto err;
+ }
+ mc->group = &modemctl_group;
+
+ INIT_WORK(&mc->work, mc_work);
+
+ r = request_irq(irq_phone_active, modemctl_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "phone_active", mc);
+ if(r) {
+ dev_err(&pdev->dev, "failed to allocate an interrupt(%d)\n",
+ irq_phone_active);
+ goto err;
+ }
+ r = enable_irq_wake(irq_phone_active);
+ if(r) {
+ dev_err(&pdev->dev, "failed to set wakeup source(%d)\n",
+ irq_phone_active);
+ goto err;
+ }
+
+ mc->irq_phone_active = irq_phone_active;
+
+ setup_timer(&mc->sim_irq_debounce_timer, (void*)sim_irq_debounce_timer_func,(unsigned long)mc);
+
+ r = request_irq(irq_sim_ndetect, simctl_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "sim_ndetect", mc);
+ if(r) {
+ dev_err(&pdev->dev, "failed to allocate an interrupt(%d)\n",
+ irq_sim_ndetect);
+ goto err;
+ }
+
+ r = enable_irq_wake(irq_sim_ndetect);
+ if(r) {
+ dev_err(&pdev->dev, "failed to set wakeup source(%d)\n",
+ irq_sim_ndetect);
+ goto err;
+ }
+
+ mc->irq_sim_ndetect= irq_sim_ndetect;
+
+ _wake_lock_init(mc);
+
+ platform_set_drvdata(pdev, mc);
+
+ return 0;
+
+err:
+ _free_all(mc);
+ return r;
+}
+
+static int __devexit modemctl_remove(struct platform_device *pdev)
+{
+ struct modemctl *mc = platform_get_drvdata(pdev);
+
+ flush_work(&mc->work);
+ platform_set_drvdata(pdev, NULL);
+ _free_all(mc);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int modemctl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct modemctl *mc = platform_get_drvdata(pdev);
+
+ pda_off(mc);
+
+ return 0;
+}
+
+static int modemctl_resume(struct platform_device *pdev)
+{
+ struct modemctl *mc = platform_get_drvdata(pdev);
+
+ pda_on(mc);
+
+ return 0;
+}
+#else
+# define modemctl_suspend NULL
+# define modemctl_resume NULL
+#endif
+
+static struct platform_driver modemctl_driver = {
+ .probe = modemctl_probe,
+ .remove = __devexit_p(modemctl_remove),
+ .suspend = modemctl_suspend,
+ .resume = modemctl_resume,
+ .driver = {
+ .name = DRVNAME,
+ },
+};
+
+static int __init modemctl_init(void)
+{
+ printk("[%s]\n",__func__);
+ return platform_driver_register(&modemctl_driver);
+}
+
+static void __exit modemctl_exit(void)
+{
+ platform_driver_unregister(&modemctl_driver);
+}
+
+module_init(modemctl_init);
+module_exit(modemctl_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Suchang Woo <suchang.woo@samsung.com>");
+MODULE_DESCRIPTION("Modem control");
diff --git a/drivers/misc/samsung_modemctl/modemctl/modemctl.h b/drivers/misc/samsung_modemctl/modemctl/modemctl.h
new file mode 100644
index 0000000..65bf46f
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/modemctl/modemctl.h
@@ -0,0 +1,39 @@
+/**
+ * header for modem control
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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 __MODEM_CONTROL_H__
+#define __MODEM_CONTROL_H__
+
+struct modemctl_platform_data {
+ const char *name;
+
+ unsigned gpio_phone_on;
+ unsigned gpio_phone_active;
+ unsigned gpio_pda_active;
+ unsigned gpio_cp_reset;
+ unsigned gpio_reset_req_n;
+ unsigned gpio_usim_boot;
+ unsigned gpio_flm_sel;
+ unsigned gpio_sim_ndetect;
+
+ void (*cfg_gpio)(void);
+};
+
+#endif /* __MODEM_CONTROL_H__ */
diff --git a/drivers/misc/samsung_modemctl/onedram/Makefile b/drivers/misc/samsung_modemctl/onedram/Makefile
new file mode 100755
index 0000000..3f53c4a
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/onedram/Makefile
@@ -0,0 +1,2 @@
+obj-y += onedram.o
+
diff --git a/drivers/misc/samsung_modemctl/onedram/onedram.c b/drivers/misc/samsung_modemctl/onedram/onedram.c
new file mode 100644
index 0000000..15fe9b4
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/onedram/onedram.c
@@ -0,0 +1,949 @@
+/**
+ * Samsung Virtual Network driver using OneDram device
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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
+ */
+
+//#define DEBUG
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/spinlock.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <linux/wait.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include "onedram.h"
+
+#define DRVNAME "onedram"
+
+#define ONEDRAM_REG_OFFSET 0xFFF800
+#define ONEDRAM_REG_SIZE 0x800
+
+static DEFINE_MUTEX(onedram_mutex);
+
+struct onedram_reg_mapped {
+ u32 sem;
+ u32 reserved1[7];
+ u32 mailbox_AB; // CP write, AP read
+ u32 reserved2[7];
+ u32 mailbox_BA; // AP write, CP read
+ u32 reserved3[23];
+ u32 check_AB; // can't read
+ u32 reserved4[7];
+ u32 check_BA; // 0: CP read, 1: CP don't read
+};
+
+struct onedram_handler {
+ struct list_head list;
+ void *data;
+ void (*handler)(u32, void *);
+};
+
+struct onedram_handler_head {
+ struct list_head list;
+ u32 len;
+ spinlock_t lock;
+};
+static struct onedram_handler_head h_list;
+
+static struct resource onedram_resource = {
+ .name = DRVNAME,
+ .start = 0,
+ .end = -1,
+ .flags = IORESOURCE_MEM,
+};
+
+struct onedram {
+ struct class *class;
+ struct device *dev;
+ struct cdev cdev;
+ dev_t devid;
+
+ wait_queue_head_t waitq;
+ struct fasync_struct *async_queue;
+ u32 mailbox;
+
+ unsigned long base;
+ unsigned long size;
+ void __iomem *mmio;
+
+ int irq;
+
+ struct completion comp;
+ atomic_t ref_sem;
+ unsigned long flags;
+
+ const struct attribute_group *group;
+
+ struct onedram_reg_mapped *reg;
+};
+struct onedram *onedram;
+
+static DEFINE_SPINLOCK(onedram_lock);
+
+static unsigned long hw_tmp; /* for hardware */
+static inline int _read_sem(struct onedram *od);
+static inline void _write_sem(struct onedram *od, int v);
+
+static unsigned long recv_cnt;
+static unsigned long send_cnt;
+static ssize_t show_debug(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+ struct onedram *od = dev_get_drvdata(d);
+
+ if (!od)
+ return 0;
+
+ p += sprintf(p, "Semaphore: %d (%d)\n", _read_sem(od), (char)hw_tmp);
+ p += sprintf(p, "Mailbox: %x\n", od->reg->mailbox_AB);
+ p += sprintf(p, "Reference count: %d\n", atomic_read(&od->ref_sem));
+ p += sprintf(p, "Mailbox send: %lu\n", send_cnt);
+ p += sprintf(p, "Mailbox recv: %lu\n", recv_cnt);
+
+ return p - buf;
+}
+
+static DEVICE_ATTR(debug, 0664, show_debug, NULL);
+
+static struct attribute *onedram_attributes[] = {
+ &dev_attr_debug.attr,
+ NULL
+};
+
+static const struct attribute_group onedram_group = {
+ .attrs = onedram_attributes,
+};
+
+static inline void _write_sem(struct onedram *od, int v)
+{
+ od->reg->sem = v;
+ hw_tmp = od->reg->sem; /* for hardware */
+}
+
+static inline int _read_sem(struct onedram *od)
+{
+ return od->reg->sem;
+}
+
+static inline int _send_cmd(struct onedram *od, u32 cmd)
+{
+ if (!od) {
+ printk(KERN_ERR "[%s]onedram: Dev is NULL, but try to access\n",__func__);
+ return -EFAULT;
+ }
+
+ if (!od->reg) {
+ dev_err(od->dev, "Failed to send cmd, not initialized\n");
+ return -EFAULT;
+ }
+
+ dev_dbg(od->dev, "send %x\n", cmd);
+ send_cnt++;
+ od->reg->mailbox_BA = cmd;
+ return 0;
+}
+
+static inline int _recv_cmd(struct onedram *od, u32 *cmd)
+{
+ if (!cmd)
+ return -EINVAL;
+
+ if (!od) {
+ printk(KERN_ERR "[%s]onedram: Dev is NULL, but try to access\n",__func__);
+ return -EFAULT;
+ }
+
+ if (!od->reg) {
+ dev_err(od->dev, "Failed to read cmd, not initialized\n");
+ return -EFAULT;
+ }
+
+ recv_cnt++;
+ *cmd = od->reg->mailbox_AB;
+ return 0;
+}
+
+static inline int _get_auth(struct onedram *od, u32 cmd)
+{
+ unsigned long timeleft;
+ int retry = 0;
+
+ /* send cmd every 20m seconds */
+ while (1) {
+ _send_cmd(od, cmd);
+
+ timeleft = wait_for_completion_timeout(&od->comp, HZ/50);
+#if 0
+ if (timeleft)
+ break;
+#endif
+ if (_read_sem(od))
+ break;
+
+ retry++;
+ if (retry > 50 ) { /* time out after 1 seconds */
+ dev_err(od->dev, "get authority time out\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+static int get_auth(struct onedram *od, u32 cmd)
+{
+ int r;
+
+ if (!od) {
+ printk(KERN_ERR "[%s]onedram: Dev is NULL, but try to access\n",__func__);
+ return -EFAULT;
+ }
+
+ if (!od->reg) {
+ dev_err(od->dev, "Failed to get authority\n");
+ return -EFAULT;
+ }
+
+ atomic_inc(&od->ref_sem);
+
+ if (_read_sem(od))
+ return 0;
+
+ if (cmd)
+ r = _get_auth(od, cmd);
+ else
+ r = -EACCES;
+
+ if (r < 0)
+ atomic_dec(&od->ref_sem);
+
+ return r;
+}
+
+static int put_auth(struct onedram *od, int release)
+{
+ if (!od) {
+ printk(KERN_ERR "[%s]onedram: Dev is NULL, but try to access\n",__func__);
+ return -EFAULT;
+ }
+
+ if (!od->reg) {
+ dev_err(od->dev, "Failed to put authority\n");
+ return -EFAULT;
+ }
+
+ if (release)
+ set_bit(0, &od->flags);
+
+ if (atomic_dec_and_test(&od->ref_sem)
+ && test_and_clear_bit(0, &od->flags)) {
+ INIT_COMPLETION(od->comp);
+ _write_sem(od, 0);
+ dev_dbg(od->dev, "rel_sem: %d\n", _read_sem(od));
+ }
+
+ return 0;
+}
+
+static int rel_sem(struct onedram *od)
+{
+ if (!od) {
+ printk(KERN_ERR "[%s]onedram: Dev is NULL, but try to access\n",__func__);
+ return -EFAULT;
+ }
+
+ if (!od->reg) {
+ dev_err(od->dev, "Failed to put authority\n");
+ return -EFAULT;
+ }
+
+ if (atomic_read(&od->ref_sem))
+ return -EBUSY;
+
+ INIT_COMPLETION(od->comp);
+ clear_bit(0, &od->flags);
+ _write_sem(od, 0);
+ dev_dbg(od->dev, "rel_sem: %d\n", _read_sem(od));
+
+ return 0;
+}
+
+int onedram_read_mailbox(u32 *mb)
+{
+ return _recv_cmd(onedram, mb);
+}
+EXPORT_SYMBOL(onedram_read_mailbox);
+
+int onedram_write_mailbox(u32 mb)
+{
+ return _send_cmd(onedram, mb);
+}
+EXPORT_SYMBOL(onedram_write_mailbox);
+
+void onedram_init_mailbox(void)
+{
+ int r = 0;
+ /* flush mailbox before registering onedram irq */
+ r = onedram->reg->mailbox_AB;
+
+ /* Set nINT_ONEDRAM_CP to low */
+ onedram->reg->mailbox_BA=0x0;
+}
+EXPORT_SYMBOL(onedram_init_mailbox);
+
+int onedram_get_auth(u32 cmd)
+{
+ return get_auth(onedram, cmd);
+}
+EXPORT_SYMBOL(onedram_get_auth);
+
+int onedram_put_auth(int release)
+{
+ return put_auth(onedram, release);
+}
+EXPORT_SYMBOL(onedram_put_auth);
+
+int onedram_rel_sem(void)
+{
+ return rel_sem(onedram);
+}
+EXPORT_SYMBOL(onedram_rel_sem);
+
+int onedram_read_sem(void)
+{
+ return _read_sem(onedram);
+}
+EXPORT_SYMBOL(onedram_read_sem);
+
+void onedram_get_vbase(void** vbase)
+{
+ *vbase = (void*)onedram->mmio;
+}
+EXPORT_SYMBOL(onedram_get_vbase);
+
+static unsigned long long old_clock;
+static u32 old_mailbox;
+
+static irqreturn_t onedram_irq_handler(int irq, void *data)
+{
+ struct onedram *od = (struct onedram *)data;
+ struct list_head *l;
+ unsigned long flags;
+ int r;
+ u32 mailbox;
+
+ r = onedram_read_mailbox(&mailbox);
+ if (r)
+ return IRQ_HANDLED;
+
+// if (old_mailbox == mailbox &&
+// old_clock + 100000 > cpu_clock(smp_processor_id()))
+// return IRQ_HANDLED;
+
+ dev_dbg(od->dev, "[%d] recv %x\n", _read_sem(od), mailbox);
+ hw_tmp = _read_sem(od); /* for hardware */
+
+ if (h_list.len) {
+ spin_lock_irqsave(&h_list.lock, flags);
+ list_for_each(l, &h_list.list) {
+ struct onedram_handler *h =
+ list_entry(l, struct onedram_handler, list);
+
+ if (h->handler)
+ h->handler(mailbox, h->data);
+ }
+ spin_unlock_irqrestore(&h_list.lock, flags);
+
+ spin_lock(&onedram_lock);
+ od->mailbox = mailbox;
+ spin_unlock(&onedram_lock);
+ } else {
+ od->mailbox = mailbox;
+ }
+
+ if (_read_sem(od))
+ complete_all(&od->comp);
+
+ wake_up_interruptible(&od->waitq);
+ kill_fasync(&od->async_queue, SIGIO, POLL_IN);
+
+// old_clock = cpu_clock(smp_processor_id());
+// old_mailbox = mailbox;
+
+ return IRQ_HANDLED;
+}
+
+static void onedram_vm_close(struct vm_area_struct *vma)
+{
+ struct onedram *od = vma->vm_private_data;
+ unsigned long offset;
+ unsigned long size;
+
+ put_auth(od, 0);
+
+ offset = (vma->vm_pgoff << PAGE_SHIFT) - od->base;
+ size = vma->vm_end - vma->vm_start;
+ dev_dbg(od->dev, "Rel region: 0x%08lx 0x%08lx\n", offset, size);
+ onedram_release_region(offset, size);
+}
+
+static struct vm_operations_struct onedram_vm_ops = {
+ .close = onedram_vm_close,
+};
+
+static int onedram_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct onedram *od = container_of(cdev, struct onedram, cdev);
+
+ filp->private_data = od;
+ return 0;
+}
+
+static int onedram_release(struct inode *inode, struct file *filp)
+{
+ filp->private_data = NULL;
+ return 0;
+}
+
+static unsigned int onedram_poll(struct file *filp, poll_table *wait)
+{
+ struct onedram *od;
+ u32 data;
+
+ od = filp->private_data;
+
+ poll_wait(filp, &od->waitq, wait);
+
+ spin_lock_irq(&onedram_lock);
+ data = od->mailbox;
+ spin_unlock_irq(&onedram_lock);
+
+ if (data)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static ssize_t onedram_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ u32 data;
+ ssize_t retval;
+ struct onedram *od;
+
+ od = filp->private_data;
+
+ if (count < sizeof(u32))
+ return -EINVAL;
+
+ add_wait_queue(&od->waitq, &wait);
+
+ while (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ spin_lock_irq(&onedram_lock);
+ data = od->mailbox;
+ od->mailbox = 0;
+ spin_unlock_irq(&onedram_lock);
+
+ if (data)
+ break;
+ else if (filp->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto out;
+ } else if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ goto out;
+ }
+ schedule();
+ }
+
+ retval = put_user(data, (u32 __user *)buf);
+ if (!retval)
+ retval = sizeof(u32);
+out:
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&od->waitq, &wait);
+
+ return retval;
+}
+
+static ssize_t onedram_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct onedram *od;
+
+ od = filp->private_data;
+
+ if (count) {
+ u32 data;
+
+ if (get_user(data, (u32 __user *)buf))
+ return -EFAULT;
+
+ _send_cmd(od, data);
+ }
+
+ return count;
+}
+
+static int onedram_fasync(int fd, struct file *filp, int on)
+{
+ struct onedram *od;
+
+ od = filp->private_data;
+
+ return fasync_helper(fd, filp, on, &od->async_queue);
+}
+
+static int onedram_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int r;
+ struct onedram *od;
+ unsigned long size;
+ unsigned long pfn;
+ unsigned long offset;
+ struct resource *res;
+
+ od = filp->private_data;
+ if (!od || !vma)
+ return -EFAULT;
+
+ atomic_inc(&od->ref_sem);
+ if (!_read_sem(od)) {
+ atomic_dec(&od->ref_sem);
+ return -EPERM;
+ }
+
+ size = vma->vm_end - vma->vm_start;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ if (size > od->size - PAGE_ALIGN(ONEDRAM_REG_SIZE) - offset)
+ return -EINVAL;
+
+ dev_dbg(od->dev, "Req region: 0x%08lx 0x%08lx\n", offset, size);
+ res = onedram_request_region(offset, size, "mmap");
+ if (!res)
+ return -EBUSY;
+
+ pfn = __phys_to_pfn(od->base + offset);
+ r = remap_pfn_range(vma, vma->vm_start, pfn,
+ size,
+ vma->vm_page_prot);
+ if (r)
+ return -EAGAIN;
+
+ vma->vm_ops = &onedram_vm_ops;
+ vma->vm_private_data = od;
+ return 0;
+}
+
+static long onedram_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct cdev *cdev = filp->f_dentry->d_inode->i_cdev;
+ struct onedram *od = container_of(cdev, struct onedram, cdev);
+ int r;
+
+ mutex_lock(&onedram_mutex);
+
+ switch (cmd) {
+ case ONEDRAM_GET_AUTH:
+ r = get_auth(od, arg);
+ break;
+ case ONEDRAM_PUT_AUTH:
+ r = put_auth(od, 0);
+ break;
+ case ONEDRAM_REL_SEM:
+ r = rel_sem(od);
+ break;
+ default:
+ r = -ENOIOCTLCMD;
+ break;
+ }
+
+ mutex_unlock(&onedram_mutex);
+
+ return r;
+}
+
+static const struct file_operations onedram_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = onedram_read,
+ .write = onedram_write,
+ .poll = onedram_poll,
+ .fasync = onedram_fasync,
+ .open = onedram_open,
+ .release = onedram_release,
+ .mmap = onedram_mmap,
+ .unlocked_ioctl = onedram_ioctl,
+};
+
+static int _register_chrdev(struct onedram *od)
+{
+ int r;
+ dev_t devid;
+
+ od->class = class_create(THIS_MODULE, DRVNAME);
+ if (IS_ERR(od->class)) {
+ r = PTR_ERR(od->class);
+ od->class = NULL;
+ return r;
+ }
+
+ r = alloc_chrdev_region(&devid, 0, 1, DRVNAME);
+ if (r)
+ return r;
+
+ cdev_init(&od->cdev, &onedram_fops);
+
+ r = cdev_add(&od->cdev, devid, 1);
+ if (r) {
+ unregister_chrdev_region(devid, 1);
+ return r;
+ }
+ od->devid = devid;
+
+ od->dev = device_create(od->class, NULL, od->devid, od, DRVNAME);
+ if (IS_ERR(od->dev)) {
+ r = PTR_ERR(od->dev);
+ od->dev = NULL;
+ return r;
+ }
+ dev_set_drvdata(od->dev, od);
+
+ return 0;
+}
+
+static inline int _request_mem(struct onedram *od, struct platform_device *pdev)
+{
+ struct resource *reso;
+
+ reso = request_mem_region(od->base, od->size, DRVNAME);
+ if (!reso) {
+ dev_err(&pdev->dev, "Failed to request the mem region:"
+ " 0x%08lx (%lu)\n", od->base, od->size);
+ return -EBUSY;
+ }
+
+ od->mmio = ioremap_nocache(od->base, od->size);
+ if (!od->mmio) {
+ release_mem_region(od->base, od->size);
+ dev_err(&pdev->dev, "Failed to ioremap: 0x%08lx (%lu)\n",
+ od->base, od->size);
+ return -EBUSY;
+ }
+
+ od->reg = (struct onedram_reg_mapped *)(
+ (char *)od->mmio + ONEDRAM_REG_OFFSET);
+ dev_dbg(&pdev->dev, "Onedram semaphore: %d\n", _read_sem(od));
+
+ onedram_resource.start = (resource_size_t)od->mmio;
+ onedram_resource.end = (resource_size_t)od->mmio + od->size - 1;
+
+ return 0;
+}
+
+static void _release(struct onedram *od)
+{
+ if (!od)
+ return;
+
+ if (od->irq)
+ free_irq(od->irq, od);
+
+ if (od->group)
+ sysfs_remove_group(&od->dev->kobj, od->group);
+
+ if (od->dev)
+ device_destroy(od->class, od->devid);
+
+ if (od->devid) {
+ cdev_del(&od->cdev);
+ unregister_chrdev_region(od->devid, 1);
+ }
+
+ if (od->mmio) {
+ od->reg = NULL;
+ iounmap(od->mmio);
+ release_mem_region(od->base, od->size);
+ onedram_resource.start = 0;
+ onedram_resource.end = -1;
+ }
+
+ if (od->class)
+ class_destroy(od->class);
+
+ kfree(od);
+}
+
+struct resource* onedram_request_region(resource_size_t start,
+ resource_size_t n, const char *name)
+{
+ struct resource *res;
+
+ start += onedram_resource.start;
+ res = __request_region(&onedram_resource, start, n, name, 0);
+ if (!res)
+ return NULL;
+
+ return res;
+}
+EXPORT_SYMBOL(onedram_request_region);
+
+void onedram_release_region(resource_size_t start, resource_size_t n)
+{
+ start += onedram_resource.start;
+ __release_region(&onedram_resource, start, n);
+}
+EXPORT_SYMBOL(onedram_release_region);
+
+int onedram_register_handler(void (*handler)(u32, void *), void *data)
+{
+ unsigned long flags;
+ struct onedram_handler *hd;
+
+ if (!handler)
+ return -EINVAL;
+
+ hd = kzalloc(sizeof(struct onedram_handler), GFP_KERNEL);
+ if (!hd)
+ return -ENOMEM;
+
+ hd->data = data;
+ hd->handler = handler;
+
+ spin_lock_irqsave(&h_list.lock, flags);
+ list_add_tail(&hd->list, &h_list.list);
+ h_list.len++;
+ spin_unlock_irqrestore(&h_list.lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(onedram_register_handler);
+
+int onedram_unregister_handler(void (*handler)(u32, void *))
+{
+ unsigned long flags;
+ struct list_head *l, *tmp;
+
+ if (!handler)
+ return -EINVAL;
+
+ spin_lock_irqsave(&h_list.lock, flags);
+ list_for_each_safe(l, tmp, &h_list.list) {
+ struct onedram_handler *hd =
+ list_entry(l, struct onedram_handler, list);
+
+ if (hd->handler == handler) {
+ list_del(&hd->list);
+ h_list.len--;
+ kfree(hd);
+ }
+ }
+ spin_unlock_irqrestore(&h_list.lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(onedram_unregister_handler);
+
+static void _unregister_all_handlers(void)
+{
+ unsigned long flags;
+ struct list_head *l, *tmp;
+
+ spin_lock_irqsave(&h_list.lock, flags);
+ list_for_each_safe(l, tmp, &h_list.list) {
+ struct onedram_handler *hd =
+ list_entry(l, struct onedram_handler, list);
+
+ list_del(&hd->list);
+ h_list.len--;
+ kfree(hd);
+ }
+ spin_unlock_irqrestore(&h_list.lock, flags);
+}
+
+static void _init_data(struct onedram *od)
+{
+ init_completion(&od->comp);
+ atomic_set(&od->ref_sem, 0);
+ INIT_LIST_HEAD(&h_list.list);
+ spin_lock_init(&h_list.lock);
+ h_list.len = 0;
+ init_waitqueue_head(&od->waitq);
+}
+
+static int __devinit onedram_probe(struct platform_device *pdev)
+{
+ int r;
+ int irq;
+ struct onedram *od = NULL;
+ struct onedram_platform_data *pdata;
+ struct resource *res;
+
+ printk("[%s]\n",__func__);
+ pdata = pdev->dev.platform_data;
+ if (!pdata || !pdata->cfg_gpio) {
+ dev_err(&pdev->dev, "No platform data\n");
+ r = -EINVAL;
+ goto err;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to get irq number\n");
+ r = -EINVAL;
+ goto err;
+ }
+ irq = res->start;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to get mem region\n");
+ r = -EINVAL;
+ goto err;
+ }
+
+ od = kzalloc(sizeof(struct onedram), GFP_KERNEL);
+ if (!od) {
+ dev_err(&pdev->dev, "failed to allocate device\n");
+ r = -ENOMEM;
+ goto err;
+ }
+ onedram = od;
+
+ dev_dbg(&pdev->dev, "Onedram dev: %p\n", od);
+
+ od->base = res->start;
+ od->size = resource_size(res);
+ r = _request_mem(od, pdev);
+ if (r)
+ goto err;
+
+ /* init mailbox state before registering irq handler */
+ onedram_init_mailbox();
+
+ _init_data(od);
+
+ pdata->cfg_gpio();
+
+ r = request_irq(irq, onedram_irq_handler,
+ IRQF_TRIGGER_LOW, "onedram", od);
+ if (r) {
+ dev_err(&pdev->dev, "Failed to allocate an interrupt: %d\n",
+ irq);
+ goto err;
+ }
+ od->irq = irq;
+ enable_irq_wake(od->irq);
+
+ r = _register_chrdev(od);
+ if (r) {
+ dev_err(&pdev->dev, "Failed to register chrdev\n");
+ goto err;
+ }
+
+ r = sysfs_create_group(&od->dev->kobj, &onedram_group);
+ if (r) {
+ dev_err(&pdev->dev, "Failed to create sysfs files\n");
+ goto err;
+ }
+ od->group = &onedram_group;
+
+ platform_set_drvdata(pdev, od);
+
+ return 0;
+
+err:
+ _release(od);
+ return r;
+}
+
+static int __devexit onedram_remove(struct platform_device *pdev)
+{
+ struct onedram *od = platform_get_drvdata(pdev);
+
+ /* TODO: need onedram_resource clean? */
+ _unregister_all_handlers();
+ platform_set_drvdata(pdev, NULL);
+ onedram = NULL;
+ _release(od);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int onedram_suspend(struct platform_device *pdev, pm_message_t state)
+{
+// struct onedram *od = platform_get_drvdata(pdev);
+
+ return 0;
+}
+
+static int onedram_resume(struct platform_device *pdev)
+{
+// struct onedram *od = platform_get_drvdata(pdev);
+
+ return 0;
+}
+#else
+# define onedram_suspend NULL
+# define onedram_resume NULL
+#endif
+
+
+static struct platform_driver onedram_driver = {
+ .probe = onedram_probe,
+ .remove = __devexit_p(onedram_remove),
+ .suspend = onedram_suspend,
+ .resume = onedram_resume,
+ .driver = {
+ .name = DRVNAME,
+ },
+};
+
+static int __init onedram_init(void)
+{
+ printk("[%s]\n",__func__);
+ return platform_driver_register(&onedram_driver);
+}
+
+static void __exit onedram_exit(void)
+{
+ platform_driver_unregister(&onedram_driver);
+}
+
+module_init(onedram_init);
+module_exit(onedram_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Suchang Woo <suchang.woo@samsung.com>");
+MODULE_DESCRIPTION("Onedram driver");
diff --git a/drivers/misc/samsung_modemctl/onedram/onedram.h b/drivers/misc/samsung_modemctl/onedram/onedram.h
new file mode 100644
index 0000000..5fb50a9
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/onedram/onedram.h
@@ -0,0 +1,54 @@
+/**
+ * header for onedram driver
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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 __ONEDRAM_H__
+#define __ONEDRAM_H__
+
+#include <linux/ioport.h>
+#include <linux/types.h>
+
+struct onedram_platform_data {
+ void (*cfg_gpio)(void);
+};
+
+extern int onedram_register_handler(void (*handler)(u32, void *), void *data);
+extern int onedram_unregister_handler(void (*handler)(u32, void *));
+
+extern struct resource* onedram_request_region(resource_size_t start,
+ resource_size_t size, const char *name);
+extern void onedram_release_region(resource_size_t start,
+ resource_size_t size);
+
+extern int onedram_read_mailbox(u32 *);
+extern int onedram_write_mailbox(u32);
+
+extern int onedram_get_auth(u32 cmd);
+extern int onedram_put_auth(int release);
+
+extern int onedram_rel_sem(void);
+extern int onedram_read_sem(void);
+
+extern void onedram_get_vbase(void **);
+
+#define ONEDRAM_GET_AUTH _IOW('o', 0x20, u32)
+#define ONEDRAM_PUT_AUTH _IO('o', 0x21)
+#define ONEDRAM_REL_SEM _IO('o', 0x22)
+
+#endif /* __ONEDRAM_H__ */
diff --git a/drivers/misc/samsung_modemctl/svnet/Makefile b/drivers/misc/samsung_modemctl/svnet/Makefile
new file mode 100755
index 0000000..868232b
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/Makefile
@@ -0,0 +1,6 @@
+svnet-y := main.o pdp.o
+
+svnet-y += sipc4.o
+
+obj-y += svnet.o
+
diff --git a/drivers/misc/samsung_modemctl/svnet/main.c b/drivers/misc/samsung_modemctl/svnet/main.c
new file mode 100644
index 0000000..16829ad
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/main.c
@@ -0,0 +1,866 @@
+/**
+ * Samsung Virtual Network driver using OneDram device
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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
+ */
+
+//#define DEBUG
+
+#if defined(DEBUG)
+# define NOISY_DEBUG
+#endif
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/list.h>
+#include <linux/jiffies.h>
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+
+#include <linux/if_phonet.h>
+#include <linux/phonet.h>
+#include <net/phonet/phonet.h>
+
+#ifdef CONFIG_HAS_WAKELOCK
+#include <linux/wakelock.h>
+
+#define DEFAULT_RAW_WAKE_TIME (6*HZ)
+#define DEFAULT_FMT_WAKE_TIME (HZ/2)
+#endif
+
+#if defined(NOISY_DEBUG)
+# define _dbg(dev, format, arg...) dev_dbg(dev, format, ## arg)
+#else
+# define _dbg(dev, format, arg...) do { } while (0)
+#endif
+
+#include "sipc.h"
+#include "pdp.h"
+
+#define SVNET_DEV_ADDR 0xa0
+
+enum {
+ SVNET_NORMAL = 0,
+ SVNET_RESET,
+ SVNET_EXIT,
+ SVNET_MAX,
+};
+
+struct svnet_stat {
+ unsigned int st_wq_state;
+ unsigned long st_recv_evt;
+ unsigned long st_recv_pkt_ph;
+ unsigned long st_recv_pkt_pdp;
+ unsigned long st_do_write;
+ unsigned long st_do_read;
+ unsigned long st_do_rx;
+};
+static struct svnet_stat stat;
+
+struct svnet_evt {
+ struct list_head list;
+ u32 event;
+};
+
+struct svnet_evt_head {
+ struct list_head list;
+ u32 len;
+ spinlock_t lock;
+};
+
+struct svnet {
+ struct net_device *ndev;
+ const struct attribute_group *group;
+
+ struct workqueue_struct *wq;
+ struct work_struct work_read;
+ struct delayed_work work_write;
+ struct delayed_work work_rx;
+
+ struct work_struct work_exit;
+ int exit_flag;
+
+ struct sk_buff_head txq;
+ struct svnet_evt_head rxq;
+
+ struct sipc *si;
+#ifdef CONFIG_HAS_WAKELOCK
+ struct wake_lock wlock;
+ long wake_time; /* jiffies */ /* wake time for not fmt packet */
+ long wake_process_time; /* jiffies */ /* processing wake time */
+#endif
+};
+
+static struct svnet *svnet_dev;
+
+#ifdef CONFIG_HAS_WAKELOCK
+static inline void _wake_lock_init(struct svnet *sn)
+{
+ wake_lock_init(&sn->wlock, WAKE_LOCK_SUSPEND, "svnet");
+ sn->wake_time = DEFAULT_RAW_WAKE_TIME;
+ sn->wake_process_time = DEFAULT_FMT_WAKE_TIME;
+ sn->wlock.expires = jiffies;
+}
+
+static inline void _wake_lock_destroy(struct svnet *sn)
+{
+ wake_lock_destroy(&sn->wlock);
+}
+
+static inline void _wake_lock_timeout(struct svnet *sn)
+{
+ long exp_cnt = sn->wlock.expires - jiffies;
+ if (exp_cnt < sn->wake_time)
+ wake_lock_timeout(&sn->wlock, sn->wake_time);
+}
+
+void _non_fmt_wakelock_timeout() {
+ if (svnet_dev)
+ _wake_lock_timeout(svnet_dev);
+}
+
+static inline void _wake_process_lock_timeout(struct svnet *sn)
+{
+ long exp_cnt = sn->wlock.expires - jiffies;
+ if (exp_cnt < sn->wake_process_time)
+ wake_lock_timeout(&sn->wlock, sn->wake_process_time);
+}
+
+void _fmt_wakelock_timeout() {
+ if (svnet_dev)
+ _wake_process_lock_timeout(svnet_dev);
+}
+
+static inline void _wake_lock_settime(struct svnet *sn, long time)
+{
+ if (sn)
+ sn->wake_time = time;
+}
+
+static inline long _wake_lock_gettime(struct svnet *sn)
+{
+ return sn?sn->wake_time:DEFAULT_RAW_WAKE_TIME;
+}
+#else
+#define _wake_lock_init(sn) do { } while(0)
+#define _wake_lock_destroy(sn) do { } while(0)
+#define _wake_lock_timeout(sn) do { } while(0)
+#define _wake_process_lock_timeout(sn) do { } while(0)
+#define _non_fmt_wakelock_timeout() do { } while(0)
+#define _fmt_wakelock_timeout() do { } while(0)
+#define _wake_lock_settime(sn, time) do { } while(0)
+#define _wake_lock_gettime(sn) (0)
+#endif
+
+static unsigned long long tmp_itor;
+static unsigned long long tmp_xtow;
+static unsigned long long time_max_itor;
+static unsigned long long time_max_xtow;
+static unsigned long long time_max_read;
+static unsigned long long time_max_write;
+
+extern unsigned long long time_max_semlat;
+
+static int _queue_evt(struct svnet_evt_head *h, u32 event);
+
+static ssize_t show_version(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "Samsung IPC version %s\n", sipc_version);
+}
+
+static ssize_t show_waketime(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+ unsigned int msec;
+ unsigned long j;
+
+ if (!svnet_dev)
+ return 0;
+
+ j = _wake_lock_gettime(svnet_dev);
+ msec = jiffies_to_msecs(j);
+ p += sprintf(p, "%u\n", msec);
+
+ return p - buf;
+}
+
+static ssize_t store_waketime(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long msec;
+ unsigned long j;
+ int r;
+
+ if (!svnet_dev)
+ return count;
+
+ r = strict_strtoul(buf, 10, &msec);
+ if (r)
+ return count;
+
+ j = msecs_to_jiffies(msec);
+ _wake_lock_settime(svnet_dev, j);
+
+ return count;
+}
+
+static inline int _show_stat(char *buf)
+{
+ char *p = buf;
+
+ p += sprintf(p, "Stat -------- \n");
+ p += sprintf(p, "\twork state: %d\n", stat.st_wq_state);
+ p += sprintf(p, "\trecv mailbox: %lu\n", stat.st_recv_evt);
+ p += sprintf(p, "\trecv phonet: %lu\n", stat.st_recv_pkt_ph);
+ p += sprintf(p, "\trecv packet: %lu\n", stat.st_recv_pkt_pdp);
+ p += sprintf(p, "\twrite count: %lu\n", stat.st_do_write);
+ p += sprintf(p, "\tread count: %lu\n", stat.st_do_read);
+ p += sprintf(p, "\trx count: %lu\n", stat.st_do_rx);
+ p += sprintf(p, "\n");
+
+ return p - buf;
+}
+
+static ssize_t show_latency(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+
+ p += sprintf(p, "Max read latency: %12llu ns\n", time_max_itor);
+ p += sprintf(p, "Max read time: %12llu ns\n", time_max_read);
+ p += sprintf(p, "Max write latency: %12llu ns\n", time_max_xtow);
+ p += sprintf(p, "Max write time: %12llu ns\n", time_max_write);
+ p += sprintf(p, "Max sem. latency: %12llu ns\n", time_max_semlat);
+
+ return p - buf;
+}
+
+static ssize_t show_debug(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ char *p = buf;
+
+ if (!svnet_dev)
+ return 0;
+
+ p += _show_stat(p);
+
+ p += sprintf(p, "Event queue ----- \n");
+ p += sprintf(p, "\tTX queue\t%u\n", skb_queue_len(&svnet_dev->txq));
+ p += sprintf(p, "\tRX queue\t%u\n", svnet_dev->rxq.len);
+
+ p += sipc_debug_show(svnet_dev->si, p);
+
+ return p - buf;
+}
+
+static ssize_t store_debug(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ if (!svnet_dev)
+ return count;
+
+ switch (buf[0]) {
+ case 'R':
+ sipc_debug(svnet_dev->si, buf);
+ break;
+ default:
+
+ break;
+ }
+
+ return count;
+}
+
+static ssize_t store_whitelist(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ if (!svnet_dev)
+ return count;
+
+ switch (buf[0]) {
+ case 0x7F:
+ return sipc_whitelist(svnet_dev->si, buf, count);
+ break;
+ default:
+
+ break;
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(version, 0664, show_version, NULL);
+static DEVICE_ATTR(latency, 0664, show_latency, NULL);
+static DEVICE_ATTR(waketime, 0664, show_waketime, store_waketime);
+static DEVICE_ATTR(debug, 0664, show_debug, store_debug);
+static DEVICE_ATTR(whitelist, 0664, NULL, store_whitelist);
+
+static struct attribute *svnet_attributes[] = {
+ &dev_attr_version.attr,
+ &dev_attr_waketime.attr,
+ &dev_attr_debug.attr,
+ &dev_attr_latency.attr,
+ &dev_attr_whitelist.attr,
+ NULL
+};
+
+static const struct attribute_group svnet_group = {
+ .attrs = svnet_attributes,
+};
+
+
+int vnet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct svnet *sn;
+ struct pdp_priv *priv;
+
+ dev_dbg(&ndev->dev, "recv inet packet %p: %d bytes\n", skb, skb->len);
+ stat.st_recv_pkt_pdp++;
+
+ priv = netdev_priv(ndev);
+ if (!priv)
+ goto drop;
+
+ sn = netdev_priv(priv->parent);
+ if (!sn)
+ goto drop;
+
+ if (!tmp_xtow)
+ tmp_xtow = cpu_clock(smp_processor_id());
+
+ skb_queue_tail(&sn->txq, skb);
+
+ _wake_process_lock_timeout(sn);
+ queue_delayed_work(sn->wq, &sn->work_write, 0);
+
+ return NETDEV_TX_OK;
+
+drop:
+ ndev->stats.tx_dropped++;
+
+ return NETDEV_TX_OK;
+}
+
+static int svnet_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct svnet *sn;
+
+ if (skb->protocol != __constant_htons(ETH_P_PHONET)) {
+ dev_err(&ndev->dev, "recv not a phonet message\n");
+ goto drop;
+ }
+
+ stat.st_recv_pkt_ph++;
+ dev_dbg(&ndev->dev, "recv packet %p: %d bytes\n", skb, skb->len);
+
+ sn = netdev_priv(ndev);
+
+ if (sipc_check_skb(sn->si, skb)) {
+ sipc_do_cmd(sn->si, skb);
+ return NETDEV_TX_OK;
+ }
+
+ if (!tmp_xtow)
+ tmp_xtow = cpu_clock(smp_processor_id());
+
+ skb_queue_tail(&sn->txq, skb);
+
+ _wake_process_lock_timeout(sn);
+ queue_delayed_work(sn->wq, &sn->work_write, 0);
+
+ return NETDEV_TX_OK;
+
+drop:
+ dev_kfree_skb(skb);
+ ndev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+}
+
+static int _queue_evt(struct svnet_evt_head *h, u32 event)
+{
+ unsigned long flags;
+ struct svnet_evt *e;
+
+ e = kmalloc(sizeof(struct svnet_evt), GFP_ATOMIC);
+ if (!e)
+ return -ENOMEM;
+
+ e->event = event;
+
+ spin_lock_irqsave(&h->lock, flags);
+ list_add_tail(&e->list, &h->list);
+ h->len++;
+ spin_unlock_irqrestore(&h->lock, flags);
+
+ return 0;
+}
+
+static void _queue_purge(struct svnet_evt_head *h)
+{
+ unsigned long flags;
+ struct svnet_evt *e, *next;
+
+ spin_lock_irqsave(&h->lock, flags);
+ list_for_each_entry_safe(e, next, &h->list, list) {
+ list_del(&e->list);
+ h->len--;
+ kfree(e);
+ }
+ spin_unlock_irqrestore(&h->lock, flags);
+}
+
+static u32 _dequeue_evt(struct svnet_evt_head *h)
+{
+ unsigned long flags;
+ struct list_head *p;
+ struct svnet_evt *e;
+ u32 event;
+
+ spin_lock_irqsave(&h->lock, flags);
+ p = h->list.next;
+ if (p == &h->list) {
+ e = NULL;
+ event = 0;
+ } else {
+ e = list_entry(p, struct svnet_evt, list);
+ list_del(&e->list);
+ h->len--;
+ event = e->event;
+ }
+ spin_unlock_irqrestore(&h->lock, flags);
+
+ if (e)
+ kfree(e);
+
+ return event;
+}
+
+static int _proc_private_event(struct svnet *sn, u32 evt)
+{
+ switch(evt) {
+ case SIPC_EXIT_MB:
+ dev_err(&sn->ndev->dev, "Modem crash message received\n");
+ sn->exit_flag = SVNET_EXIT;
+ break;
+ case SIPC_RESET_MB:
+ dev_err(&sn->ndev->dev, "Modem reset message received\n");
+ sn->exit_flag = SVNET_RESET;
+ break;
+ default:
+ return 0;
+ }
+
+ queue_work(sn->wq, &sn->work_exit);
+
+ return 1;
+}
+
+static void svnet_queue_event(u32 evt, void *data)
+{
+ struct net_device *ndev = (struct net_device *)data;
+ struct svnet *sn;
+ int r;
+
+ if (!tmp_itor)
+ tmp_itor = cpu_clock(smp_processor_id());
+
+ stat.st_recv_evt++;
+
+ if (!ndev)
+ return;
+
+ sn = netdev_priv(ndev);
+ if (!sn)
+ return;
+
+ r = _proc_private_event(sn, evt);
+ if (r)
+ return;
+
+ r = _queue_evt(&sn->rxq, evt);
+ if (r) {
+ dev_err(&sn->ndev->dev, "Not enough memory: event skipped\n");
+ return;
+ }
+
+ _wake_process_lock_timeout(sn);
+ queue_work(sn->wq, &sn->work_read);
+}
+
+static int svnet_open(struct net_device *ndev)
+{
+ struct svnet *sn = netdev_priv(ndev);
+
+ dev_dbg(&ndev->dev, "%s\n", __func__);
+
+ /* TODO: check modem state */
+
+ if (!sn->si) {
+ sn->si = sipc_open(svnet_queue_event, ndev);
+ if (IS_ERR(sn->si)) {
+ dev_err(&ndev->dev, "IPC init error\n");
+ return PTR_ERR(sn->si);
+ }
+ sn->exit_flag = SVNET_NORMAL;
+ }
+
+ netif_wake_queue(ndev);
+ return 0;
+}
+
+static int svnet_close(struct net_device *ndev)
+{
+ struct svnet *sn = netdev_priv(ndev);
+
+ dev_dbg(&ndev->dev, "%s\n", __func__);
+
+ if (sn->wq)
+ flush_workqueue(sn->wq);
+ skb_queue_purge(&sn->txq);
+
+ if (sn->si)
+ sipc_close(&sn->si);
+
+ netif_stop_queue(ndev);
+
+ if (sn->wq)
+ flush_workqueue(sn->wq);
+ skb_queue_purge(&sn->txq);
+
+ return 0;
+}
+
+static int svnet_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd)
+{
+ struct if_phonet_req *req = (struct if_phonet_req *)ifr;
+
+ switch (cmd) {
+ case SIOCPNGAUTOCONF:
+ req->ifr_phonet_autoconf.device = SVNET_DEV_ADDR;
+ return 0;
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static const struct net_device_ops svnet_ops = {
+ .ndo_open = svnet_open,
+ .ndo_stop = svnet_close,
+ .ndo_start_xmit = svnet_xmit,
+ .ndo_do_ioctl = svnet_ioctl,
+};
+
+static void svnet_setup(struct net_device *ndev)
+{
+ ndev->features = 0;
+ ndev->netdev_ops = &svnet_ops;
+ ndev->header_ops = &phonet_header_ops;
+ ndev->type = ARPHRD_PHONET;
+ ndev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ ndev->mtu = PHONET_MAX_MTU;
+ ndev->hard_header_len = 1;
+ ndev->dev_addr[0] = SVNET_DEV_ADDR;
+ ndev->addr_len = 1;
+ ndev->tx_queue_len = 1000;
+
+// ndev->destructor = free_netdev;
+}
+
+static void svnet_read_wq(struct work_struct *work)
+{
+ struct svnet *sn = container_of(work,
+ struct svnet, work_read);
+ u32 event;
+ int r = 0;
+ int contd = 0;
+ unsigned long long t, d;
+
+ t = cpu_clock(smp_processor_id());
+ if (tmp_itor) {
+ d = t - tmp_itor;
+ _dbg(&sn->ndev->dev, "int_to_read %llu ns\n", d);
+ tmp_itor = 0;
+ if (time_max_itor < d)
+ time_max_itor = d;
+ }
+
+ dev_dbg(&sn->ndev->dev, "%s\n", __func__);
+ stat.st_do_read++;
+
+ stat.st_wq_state = 1;
+ event = _dequeue_evt(&sn->rxq);
+ while (event) {
+ // isn't it possible that merge the events?
+ dev_dbg(&sn->ndev->dev, "event %x\n", event);
+
+ if (sn->si) {
+ r = sipc_read(sn->si, event, &contd);
+ if (r < 0) {
+ dev_err(&sn->ndev->dev, "ret %d -> queue %x\n",
+ r, event);
+ _queue_evt(&sn->rxq, event);
+ break;
+ }
+ } else {
+ dev_err(&sn->ndev->dev,
+ "IPC not work, skip event %x\n", event);
+ }
+ event = _dequeue_evt(&sn->rxq);
+ }
+
+ if (contd > 0)
+ queue_delayed_work(sn->wq, &sn->work_rx, 0);
+
+ switch (r) {
+ case -EINVAL:
+ dev_err(&sn->ndev->dev, "Invalid argument\n");
+ break;
+ case -EBADMSG:
+ dev_err(&sn->ndev->dev, "Bad message, purge the buffer\n");
+ break;
+ case -ETIMEDOUT:
+ dev_err(&sn->ndev->dev, "Timed out\n");
+ break;
+ default:
+
+ break;
+ }
+
+ stat.st_wq_state = 2;
+
+ d = cpu_clock(smp_processor_id()) - t;
+ _dbg(&sn->ndev->dev, "read_time %llu ns\n", d);
+ if (d > time_max_read)
+ time_max_read = d;
+}
+
+static void svnet_write_wq(struct work_struct *work)
+{
+ struct svnet *sn = container_of(work,
+ struct svnet, work_write.work);
+ int r;
+ unsigned long long t, d;
+
+ t = cpu_clock(smp_processor_id());
+ if (tmp_xtow) {
+ d = t - tmp_xtow;
+ _dbg(&sn->ndev->dev, "xmit_to_write %llu ns\n", d);
+ tmp_xtow = 0;
+ if (d > time_max_xtow)
+ time_max_xtow = d;
+ }
+
+ dev_dbg(&sn->ndev->dev, "%s\n", __func__);
+ stat.st_do_write++;
+
+ stat.st_wq_state = 3;
+ if (sn->si)
+ r = sipc_write(sn->si, &sn->txq);
+ else {
+ skb_queue_purge(&sn->txq);
+ dev_err(&sn->ndev->dev, "IPC not work, drop packet\n");
+ r = 0;
+ }
+
+ switch (r) {
+ case -ENOSPC:
+ dev_err(&sn->ndev->dev, "buffer is full, wait...\n");
+ queue_delayed_work(sn->wq, &sn->work_write, HZ/10);
+ break;
+ case -EINVAL:
+ dev_err(&sn->ndev->dev, "Invalid arugment\n");
+ break;
+ case -ENXIO:
+ dev_err(&sn->ndev->dev, "IPC not work, purge the queue\n");
+ break;
+ case -ETIMEDOUT:
+ dev_err(&sn->ndev->dev, "Timed out\n");
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+
+ stat.st_wq_state = 4;
+ d = cpu_clock(smp_processor_id()) - t;
+ _dbg(&sn->ndev->dev, "write_time %llu ns\n", d);
+ if (d > time_max_write)
+ time_max_write = d;
+}
+
+static void svnet_rx_wq(struct work_struct *work)
+{
+ struct svnet *sn = container_of(work,
+ struct svnet, work_rx.work);
+ int r = 0;
+
+ dev_dbg(&sn->ndev->dev, "%s\n", __func__);
+ stat.st_do_rx++;
+
+ stat.st_wq_state = 5;
+ if (sn->si)
+ r = sipc_rx(sn->si);
+
+ if (r > 0)
+ queue_delayed_work(sn->wq, &sn->work_rx, HZ/10);
+
+ stat.st_wq_state = 6;
+}
+
+static char *uevent_envs[SVNET_MAX] = {
+ "",
+ "MAILBOX=cp_reset", /* reset */
+ "MAILBOX=cp_exit", /* exit */
+};
+static void svnet_exit_wq(struct work_struct *work)
+{
+ struct svnet *sn = container_of(work,
+ struct svnet, work_exit);
+ char *envs[2] = { NULL, NULL };
+
+ dev_dbg(&sn->ndev->dev, "%s: %d\n", __func__, sn->exit_flag);
+
+ if (sn->exit_flag == SVNET_NORMAL || sn->exit_flag >= SVNET_MAX)
+ return;
+
+ envs[0] = uevent_envs[sn->exit_flag];
+ kobject_uevent_env(&sn->ndev->dev.kobj, KOBJ_OFFLINE, envs);
+
+ _queue_purge(&sn->rxq);
+ skb_queue_purge(&sn->txq);
+
+ if (sn->exit_flag == SVNET_EXIT)
+ sipc_ramdump(sn->si);
+
+#if 0
+ rtnl_lock();
+ if (netif_running(sn->ndev))
+ dev_close(sn->ndev);
+ rtnl_unlock();
+#endif
+}
+
+static inline void _init_data(struct svnet *sn)
+{
+ INIT_WORK(&sn->work_read, svnet_read_wq);
+ INIT_DELAYED_WORK(&sn->work_write, svnet_write_wq);
+ INIT_DELAYED_WORK(&sn->work_rx, svnet_rx_wq);
+ INIT_WORK(&sn->work_exit, svnet_exit_wq);
+
+ INIT_LIST_HEAD(&sn->rxq.list);
+ spin_lock_init(&sn->rxq.lock);
+ sn->rxq.len = 0;
+ skb_queue_head_init(&sn->txq);
+}
+
+static void _free(struct svnet *sn)
+{
+ if (!sn)
+ return;
+
+ _wake_lock_destroy(sn);
+
+ if (sn->group)
+ sysfs_remove_group(&sn->ndev->dev.kobj, &svnet_group);
+
+ if (sn->wq) {
+ flush_workqueue(sn->wq);
+ destroy_workqueue(sn->wq);
+ }
+
+ if (sn->si) {
+ sipc_close(&sn->si);
+ sipc_exit();
+ }
+
+ if (sn->ndev)
+ unregister_netdev(sn->ndev);
+
+ // sn is ndev's priv
+ free_netdev(sn->ndev);
+}
+
+static int __init svnet_init(void)
+{
+ int r;
+ struct svnet *sn = NULL;
+ struct net_device *ndev;
+
+ printk("[%s]\n",__func__);
+ ndev = alloc_netdev(sizeof(struct svnet), "svnet%d", svnet_setup);
+ if (!ndev) {
+ r = -ENOMEM;
+ goto err;
+ }
+ netif_stop_queue(ndev);
+ sn = netdev_priv(ndev);
+
+ _wake_lock_init(sn);
+
+ r = register_netdev(ndev);
+ if (r) {
+ dev_err(&ndev->dev, "failed to register netdev\n");
+ goto err;
+ }
+ sn->ndev = ndev;
+
+ _init_data(sn);
+
+ sn->wq = create_workqueue("svnetd");
+ if (!sn->wq) {
+ dev_err(&ndev->dev, "failed to create a workqueue\n");
+ goto err;
+ }
+
+ r = sysfs_create_group(&sn->ndev->dev.kobj, &svnet_group);
+ if (r) {
+ dev_err(&ndev->dev, "failed to create sysfs group\n");
+ goto err;
+ }
+ sn->group = &svnet_group;
+
+ dev_dbg(&ndev->dev, "Svnet dev: %p\n", sn);
+ svnet_dev = sn;
+
+ return 0;
+
+err:
+ _free(sn);
+ return r;
+}
+
+static void __exit svnet_exit(void)
+{
+
+ _free(svnet_dev);
+ svnet_dev = NULL;
+}
+
+module_init(svnet_init);
+module_exit(svnet_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Suchang Woo <suchang.woo@samsung.com>");
+MODULE_DESCRIPTION("Samsung Virtual network interface");
diff --git a/drivers/misc/samsung_modemctl/svnet/main.h b/drivers/misc/samsung_modemctl/svnet/main.h
new file mode 100644
index 0000000..171115f
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/main.h
@@ -0,0 +1,7 @@
+/*add interface for sipc files*/
+
+void _non_fmt_wakelock_timeout(void);
+
+void _fmt_wakelock_timeout(void);
+
+
diff --git a/drivers/misc/samsung_modemctl/svnet/pdp.c b/drivers/misc/samsung_modemctl/svnet/pdp.c
new file mode 100644
index 0000000..3065871
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/pdp.c
@@ -0,0 +1,110 @@
+/**
+ * Samsung Virtual Network driver using OneDram device
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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
+ */
+
+//#define DEBUG
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+
+#include "pdp.h"
+
+extern int vnet_start_xmit(struct sk_buff *skb, struct net_device *ndev);
+
+static int vnet_open(struct net_device *ndev)
+{
+ netif_start_queue(ndev);
+ return 0;
+}
+
+static int vnet_stop(struct net_device *ndev)
+{
+ netif_stop_queue(ndev);
+ return 0;
+}
+
+static void vnet_tx_timeout(struct net_device *ndev)
+{
+ ndev->trans_start = jiffies;
+ ndev->stats.tx_errors++;
+ netif_wake_queue(ndev);
+}
+
+static struct net_device_ops vnet_ops = {
+ .ndo_open = vnet_open,
+ .ndo_stop = vnet_stop,
+// .ndo_tx_timeout = vnet_tx_timeout,
+// .ndo_start_xmit = vnet_start_xmit,
+};
+
+static void vnet_setup(struct net_device *ndev)
+{
+ vnet_ops.ndo_start_xmit = vnet_start_xmit;
+
+ ndev->netdev_ops = &vnet_ops;
+ ndev->type = ARPHRD_PPP;
+ ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+ ndev->hard_header_len = 0;
+ ndev->addr_len = 0;
+ ndev->tx_queue_len = 1000;
+ ndev->mtu = ETH_DATA_LEN;
+ ndev->watchdog_timeo = 5 * HZ;
+}
+
+struct net_device* create_pdp(int channel, struct net_device *parent)
+{
+ int r;
+ struct pdp_priv *priv;
+ struct net_device *ndev;
+ char devname[IFNAMSIZ];
+
+ if (!parent)
+ return ERR_PTR(-EINVAL);
+
+ sprintf(devname, "pdp%d", channel - 1);
+ ndev = alloc_netdev(sizeof(struct pdp_priv), devname, vnet_setup);
+ if (!ndev)
+ return ERR_PTR(-ENOMEM);
+
+ priv = netdev_priv(ndev);
+ priv->channel = channel;
+ priv->parent = parent;
+
+ r = register_netdev(ndev);
+ if (r) {
+ free_netdev(ndev);
+ return ERR_PTR(r);
+ }
+
+ return ndev;
+}
+
+void destroy_pdp(struct net_device **ndev)
+{
+ if (!ndev || !*ndev)
+ return;
+
+ unregister_netdev(*ndev);
+ free_netdev(*ndev);
+ *ndev = NULL;
+}
+
diff --git a/drivers/misc/samsung_modemctl/svnet/pdp.h b/drivers/misc/samsung_modemctl/svnet/pdp.h
new file mode 100644
index 0000000..77ec683
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/pdp.h
@@ -0,0 +1,34 @@
+/**
+ * SAMSUNG MODEM IPC header
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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 __PACKET_DATA_PROTOCOL_H__
+#define __PACKET_DATA_PROTOCOL_H__
+
+#include <linux/netdevice.h>
+
+struct pdp_priv {
+ int channel;
+ struct net_device *parent;
+};
+
+extern struct net_device* create_pdp(int channel, struct net_device *parent);
+extern void destroy_pdp(struct net_device **);
+
+#endif /* __PACKET_DATA_PROTOCOL_H__ */
diff --git a/drivers/misc/samsung_modemctl/svnet/sipc.h b/drivers/misc/samsung_modemctl/svnet/sipc.h
new file mode 100644
index 0000000..16e74c8
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/sipc.h
@@ -0,0 +1,64 @@
+/**
+ * SAMSUNG MODEM IPC header
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+*
+ * 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 __SAMSUNG_IPC_H__
+#define __SAMSUNG_IPC_H__
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+
+extern const char *sipc_version;
+
+/*
+#if 1
+# include "sipc4.h"
+#else
+# error "Unknown version"
+#endif
+*/
+
+#define SIPC_RESET_MB 0xFFFFFF7E /* -2 & ~(INT_VALID) */
+#define SIPC_EXIT_MB 0xFFFFFF7F /* -1 & ~(INT_VALID) */
+
+struct sipc;
+
+extern struct sipc* sipc_open(void (*queue)(u32 mailbox, void *data),
+ struct net_device *ndev);
+extern void sipc_close(struct sipc **);
+
+extern void sipc_exit(void);
+
+extern int sipc_write(struct sipc *, struct sk_buff_head *);
+extern int sipc_read(struct sipc *, u32 mailbox, int *cond);
+extern int sipc_rx(struct sipc *);
+
+
+/* TODO: use PN_CMD ?? */
+extern int sipc_check_skb(struct sipc *, struct sk_buff *skb);
+extern int sipc_do_cmd(struct sipc *, struct sk_buff *skb);
+
+extern ssize_t sipc_debug_show(struct sipc *, char *);
+extern int sipc_debug(struct sipc *, const char *);
+extern int sipc_whitelist(struct sipc *si, const char *buf, size_t count);
+
+extern void sipc_ramdump(struct sipc *);
+
+#endif /* __SAMSUNG_IPC_H__ */
diff --git a/drivers/misc/samsung_modemctl/svnet/sipc4.c b/drivers/misc/samsung_modemctl/svnet/sipc4.c
new file mode 100644
index 0000000..899bd69
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/sipc4.c
@@ -0,0 +1,2069 @@
+/**
+ * SAMSUNG MODEM IPC version 4
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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
+ */
+
+//#define DEBUG
+
+#if defined(DEBUG)
+//# define NOISY_DEBUG
+#endif
+
+#include "pdp.h"
+#include "sipc.h"
+#include "sipc4.h"
+#include "main.h"
+
+#include <linux/circ_buf.h>
+#include <linux/workqueue.h>
+#include <asm/errno.h>
+
+#include <net/sock.h>
+#include <linux/if_ether.h>
+#include <linux/phonet.h>
+#include <net/phonet/phonet.h>
+
+#include "../onedram/onedram.h"
+
+#if defined(CONFIG_KERNEL_DEBUG_SEC)
+#include <linux/kernel_sec_common.h>
+#define ERRMSG "Unknown CP Crash"
+static char cp_errmsg[65];
+static void _go_dump(struct sipc *si);
+#else
+#define _go_dump(si) do { } while(0)
+#endif
+
+#if defined(NOISY_DEBUG)
+static struct device *_dev;
+# define _dbg(format, arg...) \
+ dev_dbg(_dev, format, ## arg)
+#else
+# define _dbg(format, arg...) \
+ do { } while (0)
+#endif
+
+const char *sipc_version = "4.1";
+
+static const char hdlc_start[1] = { HDLC_START };
+static const char hdlc_end[1] = { HDLC_END };
+
+struct mailbox_data {
+ u16 mask_send;
+ u16 mask_req_ack;
+ u16 mask_res_ack;
+};
+
+static struct mailbox_data mb_data[IPCIDX_MAX] = {
+ {
+ .mask_send = MBD_SEND_FMT,
+ .mask_req_ack = MBD_REQ_ACK_FMT,
+ .mask_res_ack = MBD_RES_ACK_FMT,
+ },
+ {
+ .mask_send = MBD_SEND_RAW,
+ .mask_req_ack = MBD_REQ_ACK_RAW,
+ .mask_res_ack = MBD_RES_ACK_RAW,
+ },
+ {
+ .mask_send = MBD_SEND_RFS,
+ .mask_req_ack = MBD_REQ_ACK_RFS,
+ .mask_res_ack = MBD_RES_ACK_RFS,
+ },
+};
+
+/* semaphore latency */
+unsigned long long time_max_semlat;
+//static volatile unsigned long *TCNT = (unsigned long *)0xF520000C;
+
+struct sipc;
+struct ringbuf;
+
+struct ringbuf_info {
+ unsigned int out_off;
+ unsigned int in_off;
+ unsigned int size;
+ int (*read)(struct sipc *si, int inbuf, struct ringbuf *rb);
+};
+
+struct ringbuf {
+ unsigned char *out_base;
+ unsigned char *in_base;
+ struct ringbuf_cont *cont;
+ struct ringbuf_info *info;
+};
+#define rb_size info->size
+#define rb_read info->read
+#define rb_out_head cont->out_head
+#define rb_out_tail cont->out_tail
+#define rb_in_head cont->in_head
+#define rb_in_tail cont->in_tail
+
+
+static int _read_fmt(struct sipc *si, int inbuf, struct ringbuf *rb);
+static int _read_raw(struct sipc *si, int inbuf, struct ringbuf *rb);
+static int _read_rfs(struct sipc *si, int inbuf, struct ringbuf *rb);
+
+static struct ringbuf_info rb_info[IPCIDX_MAX] = {
+ {
+ .out_off = FMT_OUT,
+ .in_off = FMT_IN,
+ .size = FMT_SZ,
+ .read = _read_fmt,
+ },
+ {
+ .out_off = RAW_OUT,
+ .in_off = RAW_IN,
+ .size = RAW_SZ,
+ .read = _read_raw,
+ },
+ {
+ .out_off = RFS_OUT,
+ .in_off = RFS_IN,
+ .size = RFS_SZ,
+ .read = _read_rfs,
+ },
+};
+
+#define FRAG_BLOCK_MAX (PAGE_SIZE - sizeof(struct list_head) \
+ - sizeof(u32) - sizeof(char *))
+struct frag_block {
+ struct list_head list;
+ u32 len;
+ char *ptr;
+ char buf[FRAG_BLOCK_MAX];
+};
+
+struct frag_list {
+ struct list_head list;
+ u8 msg_id;
+ u32 len;
+ // timeout??
+ struct list_head block_head;
+};
+
+struct frag_head {
+ struct list_head head;
+ unsigned long bitmap[FMT_ID_SIZE/BITS_PER_LONG];
+};
+
+struct frag_info {
+ struct sk_buff *skb;
+ unsigned int offset;
+ u8 msg_id;
+};
+
+struct sipc {
+ struct sipc_mapped *map;
+ struct ringbuf rb[IPCIDX_MAX];
+
+ struct resource *res;
+
+ void (*queue)(u32, void *);
+ void *queue_data;
+
+ /* for fragmentation */
+ u8 msg_id;
+ char *frag_buf;
+ struct frag_info frag;
+
+ /* for merging */
+ struct frag_head frag_map;
+
+ int od_rel; /* onedram authority release */
+
+ struct net_device *svndev;
+
+ const struct attribute_group *group;
+
+ struct sk_buff_head rfs_rx;
+};
+
+/* sizeof(struct phonethdr) + NET_SKB_PAD > SMP_CACHE_BYTES */
+//#define RFS_MTU (PAGE_SIZE - sizeof(struct phonethdr) - NET_SKB_PAD)
+/* SMP_CACHE_BYTES > sizeof(struct phonethdr) + NET_SKB_PAD */
+#define RFS_MTU (PAGE_SIZE - SMP_CACHE_BYTES)
+#define RFS_TX_RATE 4
+
+/* set at storage device */
+unsigned int factory_test_force_sleep = 0;
+EXPORT_SYMBOL(factory_test_force_sleep);
+
+/* from mach-XX.c */
+extern unsigned int HWREV;
+
+/* TODO: move PDP related codes to other source file */
+static DEFINE_MUTEX(pdp_mutex);
+static struct net_device *pdp_devs[PDP_MAX];
+static int pdp_cnt;
+unsigned long pdp_bitmap[DIV_ROUND_UP(PDP_MAX, BITS_PER_LONG)];
+
+static void clear_pdp_wq(struct work_struct *work);
+static DECLARE_WORK(pdp_work, clear_pdp_wq);
+
+static ssize_t show_act(struct device *d,
+ struct device_attribute *attr, char *buf);
+static ssize_t show_deact(struct device *d,
+ struct device_attribute *attr, char *buf);
+static ssize_t store_act(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t store_deact(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count);
+
+static ssize_t show_suspend(struct device *d,
+ struct device_attribute *attr, char *buf);
+static ssize_t store_suspend(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count);
+static ssize_t store_resume(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count);
+
+static DEVICE_ATTR(activate, 0664, show_act, store_act);
+static DEVICE_ATTR(deactivate, 0664, show_deact, store_deact);
+static DEVICE_ATTR(suspend, 0664, show_suspend, store_suspend);
+static DEVICE_ATTR(resume, 0664, NULL, store_resume);
+
+static struct attribute *pdp_attributes[] = {
+ &dev_attr_activate.attr,
+ &dev_attr_deactivate.attr,
+ &dev_attr_suspend.attr,
+ &dev_attr_resume.attr,
+ NULL
+};
+
+static const struct attribute_group pdp_group = {
+ .name = "pdp",
+ .attrs = pdp_attributes,
+};
+
+
+#if defined(NOISY_DEBUG)
+#define DUMP_LIMIT 32
+static char dump_buf[64];
+void _dbg_dump(u8 *buf, int size)
+{
+ int i;
+ int len = 0;
+
+ if (!buf)
+ return;
+
+ if (size > DUMP_LIMIT)
+ size = DUMP_LIMIT;
+
+ for (i=0;i<32 && i<size;i++) {
+ len += sprintf(&dump_buf[len], "%02x ", buf[i]);
+ if ((i & 0xf) == 0xf) {
+ dump_buf[len] = '\0';
+ _dbg("dump %04x [ %s]\n", (i>>4), dump_buf);
+ len = 0;
+ }
+ }
+ if (len) {
+ dump_buf[len] = '\0';
+ _dbg("dump %04x [ %s]\n", i, dump_buf);
+ }
+}
+#else
+# define _dbg_dump(buf, size) do { } while(0)
+#endif
+
+static u8 _get_msg_id(struct sipc *si)
+{
+ if (!si)
+ return 0;
+
+ si->msg_id = (si->msg_id + 1) & FMT_ID_MASK;
+
+ return si->msg_id;
+}
+
+static int _get_auth(void)
+{
+ int r;
+ unsigned long long t, d;
+
+ t = cpu_clock(smp_processor_id());
+
+ r = onedram_get_auth(MB_CMD(MBC_REQ_SEM)); // wait for completion
+
+ d = cpu_clock(smp_processor_id()) - t;
+ if (d > time_max_semlat)
+ time_max_semlat = d;
+
+ return r;
+}
+
+static void _put_auth(struct sipc *si)
+{
+ if (!si)
+ return;
+
+ onedram_put_auth(0);
+
+ if (si->od_rel && !onedram_rel_sem()) {
+ onedram_write_mailbox(MB_CMD(MBC_RES_SEM));
+ si->od_rel = 0;
+ }
+}
+
+static inline void _req_rel_auth(struct sipc *si)
+{
+ si->od_rel = 1;
+}
+
+static int _get_auth_try(void)
+{
+ return onedram_get_auth(0);
+}
+
+static void _check_buffer(struct sipc *si)
+{
+ int i;
+ u32 mailbox;
+
+#if 0
+ i = onedram_read_sem();
+ if (i != 0x1)
+ return;
+#endif
+ i = _get_auth_try();
+ if (i)
+ return;
+
+ mailbox = 0;
+
+ for (i=0;i<IPCIDX_MAX;i++) {
+ int inbuf;
+ struct ringbuf *rb;
+
+ rb = &si->rb[i];
+ inbuf = CIRC_CNT(rb->rb_in_head, rb->rb_in_tail, rb->rb_size);
+ if (!inbuf)
+ continue;
+
+ mailbox |= mb_data[i].mask_send;
+ }
+ _put_auth(si);
+
+ if (mailbox)
+ si->queue(MB_DATA(mailbox), si->queue_data);
+}
+
+static void _do_command(struct sipc *si, u32 mailbox)
+{
+ int r;
+ u32 cmd = (mailbox & MBC_MASK) & ~(MB_CMD(0));
+
+// dev_dbg(&si->svndev->dev, "Command: %x\n", cmd);
+
+ switch(cmd) {
+ case MBC_REQ_SEM:
+ r = onedram_rel_sem();
+ if (r) {
+ dev_dbg(&si->svndev->dev, "onedram in use, "
+ "defer releasing semaphore\n");
+ _req_rel_auth(si);
+ }
+ else
+ onedram_write_mailbox(MB_CMD(MBC_RES_SEM));
+ break;
+ case MBC_RES_SEM:
+ /* do nothing */
+ break;
+ case MBC_PHONE_START:
+ onedram_write_mailbox(MB_CMD(MBC_INIT_END) | CP_BOOT_AIRPLANE
+ | AP_OS_ANDROID);
+ break;
+ case MBC_RESET:
+ printk("svnet reset mailbox msg : 0x%08x\n", mailbox);
+ si->queue(SIPC_RESET_MB, si->queue_data);
+ break;
+ case MBC_ERR_DISPLAY:
+ printk("svnet error display mailbox msg : 0x%08x\n", mailbox);
+ si->queue(SIPC_EXIT_MB, si->queue_data);
+ break;
+ /* TODO : impletment other commands... */
+ default:
+ /* do nothing */
+
+ break;
+ }
+}
+
+void sipc_handler(u32 mailbox, void *data)
+{
+ struct sipc *si = (struct sipc *)data;
+
+ if (!si || !si->queue)
+ return;
+
+ dev_dbg(&si->svndev->dev, "recv mailbox %x\n", mailbox);
+
+#if defined(CONFIG_KERNEL_DEBUG_SEC)
+ if (mailbox == KERNEL_SEC_DUMP_AP_DEAD_ACK) {
+ // deal ack for ap crash that indicated to cp
+ kernel_sec_set_cp_ack();
+ }
+#endif
+
+ if ((mailbox & MB_VALID) == 0) {
+ dev_err(&si->svndev->dev, "Invalid mailbox message: %x\n", mailbox);
+ return;
+ }
+
+ if (mailbox & MB_COMMAND) {
+ _check_buffer(si); // check buffer for missing interrupt
+ _do_command(si, mailbox);
+ return;
+ }
+
+ si->queue(mailbox, si->queue_data);
+}
+
+static inline void _init_data(struct sipc *si, unsigned char *base)
+{
+ int i;
+
+ si->map = (struct sipc_mapped *)base;
+ si->map->magic = 0x0;
+ si->map->access = 0x0;
+ si->map->hwrev = HWREV;
+
+ for (i=0;i<IPCIDX_MAX;i++) {
+ struct ringbuf *r = &si->rb[i];
+ struct ringbuf_info *info = &rb_info[i];
+ struct ringbuf_cont *cont = &si->map->rbcont[i];
+
+ r->out_base = base + info->out_off;
+ r->in_base = base + info->in_off;
+ r->info = info;
+ r->cont = cont;
+
+ cont->out_head = 0;
+ cont->out_tail = 0;
+ cont->in_head = 0;
+ cont->in_tail = 0;
+ }
+}
+
+static void _init_proc(struct sipc *si)
+{
+ u32 mailbox;
+ int r;
+
+ r = onedram_read_mailbox(&mailbox);
+ if (r)
+ return;
+
+ sipc_handler(mailbox, si);
+}
+
+struct sipc* sipc_open(void (*queue)(u32, void*), struct net_device *ndev)
+{
+ struct sipc *si;
+ struct resource *res;
+ int r;
+ void * onedram_vbase;
+
+ if (!queue || !ndev)
+ return ERR_PTR(-EINVAL);
+
+ si = kzalloc(sizeof(struct sipc), GFP_KERNEL);
+ if (!si)
+ return ERR_PTR(-ENOMEM);
+
+ /* If FMT_SZ grown up, MUST be changed!! */
+ si->frag_buf = kmalloc(FMT_SZ, GFP_KERNEL);
+ if (!si->frag_buf) {
+ sipc_close(&si);
+ return ERR_PTR(-ENOMEM);
+ }
+ INIT_LIST_HEAD(&si->frag_map.head);
+
+ res = onedram_request_region(0, SIPC_MAP_SIZE, SIPC_NAME);
+ if (!res) {
+ sipc_close(&si);
+ return ERR_PTR(-EBUSY);
+ }
+ si->res = res;
+
+ r = onedram_register_handler(sipc_handler, si);
+ if (r) {
+ sipc_close(&si);
+ return ERR_PTR(r);
+ }
+ si->queue = queue;
+ si->queue_data = ndev;
+ si->svndev = ndev;
+
+ /* TODO: need?? */
+ if (work_pending(&pdp_work))
+ flush_work(&pdp_work);
+
+ r = sysfs_create_group(&si->svndev->dev.kobj, &pdp_group);
+ if (r) {
+ sipc_close(&si);
+ return ERR_PTR(r);
+ }
+ si->group = &pdp_group;
+
+#if defined(NOISY_DEBUG)
+ _dev = &si->svndev->dev;
+#endif
+
+ onedram_get_vbase(&onedram_vbase);
+
+ // use io remapped address rather than request_region
+ if (onedram_vbase) {
+ _init_data(si, (unsigned char *)onedram_vbase);
+ }else {
+ _init_data(si, (unsigned char *)res->start);
+ }
+
+ skb_queue_head_init(&si->rfs_rx);
+
+ /* process init message */
+ _init_proc(si);
+
+ return si;
+}
+
+static void clear_pdp_wq(struct work_struct *work)
+{
+ int i;
+
+ mutex_lock(&pdp_mutex);
+
+ for (i=0;i<sizeof(pdp_devs)/sizeof(pdp_devs[0]);i++) {
+ if (pdp_devs[i]) {
+ destroy_pdp(&pdp_devs[i]);
+ clear_bit(i, pdp_bitmap);
+ }
+ }
+ pdp_cnt = 0;
+
+ mutex_unlock(&pdp_mutex);
+}
+
+void sipc_exit(void)
+{
+ if (work_pending(&pdp_work))
+ flush_work(&pdp_work);
+ else
+ clear_pdp_wq(NULL);
+}
+
+void sipc_close(struct sipc **psi)
+{
+ struct sipc *si;
+
+ if (!psi || !*psi)
+ return;
+
+ si = *psi;
+
+ if (si->group && si->svndev) {
+ int i;
+ sysfs_remove_group(&si->svndev->dev.kobj, si->group);
+
+ mutex_lock(&pdp_mutex);
+ for (i=0;i<sizeof(pdp_devs)/sizeof(pdp_devs[0]);i++) {
+ if (pdp_devs[i])
+ netif_stop_queue(pdp_devs[i]);
+ }
+ mutex_unlock(&pdp_mutex);
+ schedule_work(&pdp_work);
+ }
+
+ if (si->frag_buf)
+ kfree(si->frag_buf);
+
+ if (si->queue)
+ onedram_unregister_handler(sipc_handler);
+
+ if (si->res)
+ onedram_release_region(0, SIPC_MAP_SIZE);
+
+ kfree(si);
+ *psi = NULL;
+}
+
+static inline void _wake_queue(int idx)
+{
+ mutex_lock(&pdp_mutex);
+
+ if (pdp_devs[idx] && !test_bit(idx, pdp_bitmap))
+ netif_wake_queue(pdp_devs[idx]);
+
+ mutex_unlock(&pdp_mutex);
+}
+
+static int __write(struct ringbuf *rb, u8 *buf, unsigned int size)
+{
+ int c;
+ int len = 0;
+
+ // no check space
+
+ _dbg("%s b: size %u head %u tail %u\n", __func__,
+ size, rb->rb_out_head, rb->rb_out_tail);
+ _dbg_dump(buf, size);
+
+ while(1) {
+ c = CIRC_SPACE_TO_END(rb->rb_out_head, rb->rb_out_tail, rb->rb_size);
+ if(size < c)
+ c = size;
+ if(c <= 0)
+ break;
+ memcpy(rb->out_base + rb->rb_out_head, buf, c);
+ rb->rb_out_head = (rb->rb_out_head + c) & (rb->rb_size - 1);
+ buf += c;
+ size -= c;
+ len += c;
+ }
+
+ _dbg("%s a: size %u head %u tail %u\n", __func__,
+ len, rb->rb_out_head, rb->rb_out_tail);
+
+ return len;
+}
+
+static inline void _set_raw_hdr(struct raw_hdr *h, int res,
+ unsigned int len, int control)
+{
+ h->len = len;
+ h->channel = CHID(res);
+ h->control = 0;
+}
+
+static int _write_raw_buf(struct ringbuf *rb, int res, struct sk_buff *skb)
+{
+ int len;
+ struct raw_hdr h;
+
+ _dbg("%s: packet %p res 0x%02x\n", __func__, skb, res);
+
+ len = skb->len + sizeof(h);
+
+ _set_raw_hdr(&h, res, len, 0);
+
+ len = __write(rb, (u8 *)hdlc_start, sizeof(hdlc_start));
+ len += __write(rb, (u8 *)&h, sizeof(h));
+ len += __write(rb, skb->data, skb->len);
+ len += __write(rb, (u8 *)hdlc_end, sizeof(hdlc_end));
+
+ return len;
+}
+
+static int _write_raw_skb(struct ringbuf *rb, int res, struct sk_buff *skb)
+{
+ char *b;
+
+ _dbg("%s: packet %p res 0x%02x\n", __func__, skb, res);
+
+ b = skb_put(skb, sizeof(hdlc_end));
+ memcpy(b, hdlc_end, sizeof(hdlc_end));
+
+ b = skb_push(skb, sizeof(struct raw_hdr) + sizeof(hdlc_start));
+ memcpy(b, hdlc_start, sizeof(hdlc_start));
+
+ b += sizeof(hdlc_start);
+
+ _set_raw_hdr((struct raw_hdr *)b, res,
+ skb->len - sizeof(hdlc_start) - sizeof(hdlc_end), 0);
+
+ return __write(rb, skb->data, skb->len);
+}
+
+static int _write_raw(struct ringbuf *rb, struct sk_buff *skb, int res)
+{
+ int len;
+ int space;
+
+ space = CIRC_SPACE(rb->rb_out_head, rb->rb_out_tail, rb->rb_size);
+ if(space < skb->len + sizeof(struct raw_hdr)
+ + sizeof(hdlc_start) + sizeof(hdlc_end))
+ return -ENOSPC;
+
+ if(skb_headroom(skb) > (sizeof(struct raw_hdr) + sizeof(hdlc_start))
+ && skb_tailroom(skb) > sizeof(hdlc_end)) {
+ len = _write_raw_skb(rb, res, skb);
+ } else {
+ len = _write_raw_buf(rb, res, skb);
+ }
+
+ if (res >= PN_PDP_START && res <= PN_PDP_END)
+ _wake_queue(PDP_ID(res));
+ else
+ netif_wake_queue(skb->dev);
+ return len;
+}
+
+static int _write_rfs_buf(struct ringbuf *rb, struct sk_buff *skb)
+{
+ int len;
+
+ _dbg("%s: packet %p\n", __func__, skb);
+ len = __write(rb, (u8 *)hdlc_start, sizeof(hdlc_start));
+ len += __write(rb, skb->data, skb->len);
+ len += __write(rb, (u8 *)hdlc_end, sizeof(hdlc_end));
+
+ return len;
+}
+
+static int _write_rfs_skb(struct ringbuf *rb, struct sk_buff *skb)
+{
+ char *b;
+
+ _dbg("%s: packet %p\n", __func__, skb);
+ b = skb_put(skb, sizeof(hdlc_end));
+ memcpy(b, hdlc_end, sizeof(hdlc_end));
+
+ b = skb_push(skb, sizeof(hdlc_start));
+ memcpy(b, hdlc_start, sizeof(hdlc_start));
+
+ return __write(rb, skb->data, skb->len);
+}
+
+static int _write_rfs(struct ringbuf *rb, struct sk_buff *skb)
+{
+ int len;
+ int space;
+
+ space = CIRC_SPACE(rb->rb_out_head, rb->rb_out_tail, rb->rb_size);
+ if(space < skb->len + sizeof(hdlc_start) + sizeof(hdlc_end))
+ return -ENOSPC;
+
+ if(skb_headroom(skb) > sizeof(hdlc_start)
+ && skb_tailroom(skb) > sizeof(hdlc_end)) {
+ len = _write_rfs_skb(rb, skb);
+ } else {
+ len = _write_rfs_buf(rb, skb);
+ }
+
+ netif_wake_queue(skb->dev);
+ return len;
+}
+
+static int _write_fmt_buf(char *frag_buf, struct ringbuf *rb,
+ struct sk_buff *skb, struct frag_info *fi, int wlen,
+ u8 control)
+{
+ char *buf = frag_buf;
+ struct fmt_hdr *h;
+
+ memcpy(buf, hdlc_start, sizeof(hdlc_start));
+ buf += sizeof(hdlc_start);
+
+ h = (struct fmt_hdr *)buf;
+ h->len = sizeof(struct fmt_hdr) + wlen;
+ h->control = control;
+ buf += sizeof(struct fmt_hdr);
+
+ memcpy(buf, skb->data + fi->offset, wlen);
+ buf += wlen;
+
+ memcpy(buf, hdlc_end, sizeof(hdlc_end));
+ buf += sizeof(hdlc_end);
+
+ return __write(rb, frag_buf, buf - frag_buf);
+}
+
+static int _write_fmt(struct sipc *si, struct ringbuf *rb, struct sk_buff *skb)
+{
+ int len;
+ int space;
+ int remain;
+ struct frag_info *fi = &si->frag;
+
+ if (skb != fi->skb) {
+ /* new packet */
+ fi->skb = skb;
+// fi->msg_id = _get_msg_id(si);
+ fi->offset = 0;
+ }
+
+ len = 0;
+ remain = skb->len - fi->offset;
+
+ _dbg("%s: packet %p length %d sent %d\n", __func__, skb, skb->len, fi->offset);
+
+ while (remain > 0) {
+ int wlen;
+ u8 control;
+
+ space = CIRC_SPACE(rb->rb_out_head, rb->rb_out_tail, rb->rb_size);
+ space -= sizeof(struct fmt_hdr)
+ + sizeof(hdlc_start) + sizeof(hdlc_end);
+ if (space < FMT_TX_MIN)
+ return -ENOSPC;
+
+ if (remain > space) {
+ /* multiple frame */
+ wlen = space;
+ control = 0x1 | FMT_MB_MASK;
+ } else {
+ wlen = remain;
+ if (fi->offset == 0) {
+ /* single frame */
+ control = 0x0;
+ } else {
+ /* last frmae */
+ control = 0x1;
+ }
+ }
+
+ wlen = _write_fmt_buf(si->frag_buf, rb, skb, fi, wlen, control);
+ if (wlen < 0)
+ return wlen;
+
+ len += wlen;
+
+ wlen -= sizeof(hdlc_start) + sizeof(struct fmt_hdr)
+ + sizeof(hdlc_end);
+
+ fi->offset += wlen;
+ remain -= wlen;
+ }
+
+ if (len > 0) {
+ fi->skb = NULL;
+ fi->offset = 0;
+ }
+
+ netif_wake_queue(skb->dev);
+ return len; /* total write bytes */
+}
+
+static int _write(struct sipc *si, int res, struct sk_buff *skb, u32 *mailbox)
+{
+ int r;
+ int rid;
+
+ rid = res_to_ridx(res);
+ if(rid < 0 || rid >= IPCIDX_MAX)
+ return -EINVAL;
+
+ switch(rid) {
+ case IPCIDX_FMT:
+ r = _write_fmt(si, &si->rb[rid], skb);
+ break;
+ case IPCIDX_RAW:
+ r = _write_raw(&si->rb[rid], skb, res);
+ break;
+ case IPCIDX_RFS:
+ r = _write_rfs(&si->rb[rid], skb);
+ break;
+ default:
+ /* do nothing */
+ r = 0;
+ break;
+ }
+
+ if(r > 0)
+ *mailbox |= mb_data[rid].mask_send;
+
+ _dbg("%s: return %d\n", __func__, r);
+ return r;
+}
+
+static inline void _update_stat(struct net_device *ndev, unsigned int len)
+{
+ if(!ndev)
+ return;
+
+ ndev->stats.tx_bytes += len;
+ ndev->stats.tx_packets++;
+}
+
+static inline int _write_pn(struct sipc *si, struct sk_buff *skb, u32 *mb)
+{
+ int r;
+ struct phonethdr *ph;
+
+ ph = pn_hdr(skb);
+ skb_pull(skb, sizeof(struct phonethdr) + 1); // 1 is addr len
+
+ r = _write(si, ph->pn_res, skb, mb);
+ if (r < 0)
+ skb_push(skb, sizeof(struct phonethdr) + 1);
+
+ return r;
+}
+
+int sipc_write(struct sipc *si, struct sk_buff_head *sbh)
+{
+ int r;
+ u32 mailbox;
+ struct sk_buff *skb;
+
+ if (!sbh)
+ return -EINVAL;
+
+ if (!si) {
+ skb_queue_purge(sbh);
+ return -ENXIO;
+ }
+
+ r = _get_auth();
+ if (r) {
+ if (factory_test_force_sleep){
+ printk("tx ignored for factory force sleep\n");
+ skb_queue_purge(sbh);
+ return 0;
+ } else {
+ return r;
+ }
+ }
+
+ r = mailbox = 0;
+ skb = skb_dequeue(sbh);
+ while (skb) {
+ struct net_device *ndev = skb->dev;
+ int len = skb->len;
+
+ dev_dbg(&si->svndev->dev, "write packet %p\n", skb);
+
+ if (skb->protocol != __constant_htons(ETH_P_PHONET)) {
+ struct pdp_priv *priv;
+ priv = netdev_priv(ndev);
+ r = _write(si, PN_PDP(priv->channel), skb, &mailbox);
+ } else
+ r = _write_pn(si, skb, &mailbox);
+
+ if (r < 0)
+ break;
+
+ _update_stat(ndev, len);
+ dev_kfree_skb_any(skb);
+
+ skb = skb_dequeue(sbh);
+ }
+
+ _req_rel_auth(si);
+ _put_auth(si);
+
+ if(mailbox)
+ onedram_write_mailbox(MB_DATA(mailbox));
+
+ if (r < 0) {
+ if (r == -ENOSPC) {
+ dev_err(&si->svndev->dev,
+ "write nospc queue %p\n", skb);
+ skb_queue_head(sbh, skb);
+ netif_stop_queue(skb->dev);
+ } else {
+ dev_err(&si->svndev->dev,
+ "write err %d, drop %p\n", r, skb);
+ dev_kfree_skb_any(skb);
+ }
+ }
+
+ return r;
+}
+
+extern int __read(struct ringbuf *rb, unsigned char *buf, unsigned int size)
+{
+ int c;
+ int len = 0;
+ unsigned char *p = buf;
+
+ _dbg("%s b: size %u head %u tail %u\n", __func__,
+ size, rb->rb_in_head, rb->rb_in_tail);
+
+ while(1) {
+ c = CIRC_CNT_TO_END(rb->rb_in_head, rb->rb_in_tail, rb->rb_size);
+ if(size < c)
+ c = size;
+ if(c <= 0)
+ break;
+ if (p) {
+ memcpy(p, rb->in_base + rb->rb_in_tail, c);
+ p += c;
+ }
+ rb->rb_in_tail = (rb->rb_in_tail + c) & (rb->rb_size - 1);
+ size -= c;
+ len += c;
+ }
+
+ _dbg("%s a: size %u head %u tail %u\n", __func__,
+ len, rb->rb_in_head, rb->rb_in_tail);
+ _dbg_dump(buf, len);
+
+ return len;
+}
+
+static inline void _get_raw_hdr(struct raw_hdr *h, int *res,
+ unsigned int *len, int *control)
+{
+ if(res)
+ *res = PN_RAW(h->channel);
+ if(len)
+ *len = h->len;
+ if(control)
+ *control = h->control;
+}
+
+static inline void _phonet_rx(struct net_device *ndev,
+ struct sk_buff *skb, int res)
+{
+ int r;
+ struct phonethdr *ph;
+
+ skb->protocol = __constant_htons(ETH_P_PHONET);
+
+ ph = (struct phonethdr *)skb_push(skb, sizeof(struct phonethdr));
+ ph->pn_rdev = ndev->dev_addr[0];
+ ph->pn_sdev = 0;
+ ph->pn_res = res;
+ ph->pn_length = __cpu_to_be16(skb->len + 2 - sizeof(*ph));
+ ph->pn_robj = 0;
+ ph->pn_sobj = 0;
+
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += skb->len;
+
+ skb_reset_mac_header(skb);
+
+ r = netif_rx_ni(skb);
+ if (r != NET_RX_SUCCESS)
+ dev_err(&ndev->dev, "phonet rx error: %d\n", r);
+
+ _dbg("%s: res 0x%02x packet %p len %d\n", __func__, res, skb, skb->len);
+}
+
+static int _read_pn(struct net_device *ndev, struct ringbuf *rb, int len,
+ int res)
+{
+ int r;
+ struct sk_buff *skb;
+ char *p;
+ int read_len = len + sizeof(hdlc_end);
+
+ _dbg("%s: res 0x%02x data %d\n", __func__, res, len);
+
+ skb = netdev_alloc_skb(ndev, read_len + sizeof(struct phonethdr));
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ skb_reserve(skb, sizeof(struct phonethdr));
+
+ p = skb_put(skb, len);
+ r = __read(rb, p, read_len);
+ if (r != read_len) {
+ kfree_skb(skb);
+ return -EBADMSG;
+ }
+
+ _phonet_rx(ndev, skb, res);
+
+ return r;
+}
+
+static inline struct sk_buff* _alloc_phskb(struct net_device *ndev, int len)
+{
+ struct sk_buff *skb;
+
+ skb = netdev_alloc_skb(ndev, len + sizeof(struct phonethdr));
+ if (likely(skb))
+ skb_reserve(skb, sizeof(struct phonethdr));
+
+ return skb;
+}
+
+static inline int _alloc_rfs(struct net_device *ndev,
+ struct sk_buff_head *list, int len)
+{
+ int r = 0;
+ struct sk_buff *skb;
+
+ __skb_queue_head_init(list);
+
+ while (len > 0) {
+ skb = _alloc_phskb(ndev, RFS_MTU);
+ if (unlikely(!skb)) {
+ r = -ENOMEM;
+ break;
+ }
+ __skb_queue_tail(list, skb);
+ len -= RFS_MTU;
+ }
+
+ return r;
+}
+static void _free_rfs(struct sk_buff_head *list)
+{
+ struct sk_buff *skb;
+
+ skb = __skb_dequeue(list);
+ while (skb) {
+ __kfree_skb(skb);
+ skb = __skb_dequeue(list);
+ }
+}
+
+static inline int _read_rfs_rb(struct ringbuf *rb, int len,
+ struct sk_buff_head *list)
+{
+ int r;
+ int read_len;
+ struct sk_buff *skb;
+ char *p;
+
+ read_len = 0;
+ skb = list->next;
+ while (skb != (struct sk_buff *)list) {
+ int rd = RFS_MTU;
+
+ if (skb == list->next) /* first sk has header */
+ rd -= sizeof(struct rfs_hdr);
+
+ if (len < rd)
+ rd = len;
+
+ p = skb_put(skb, rd);
+ r = __read(rb, p, rd);
+ if (r != rd)
+ return -EBADMSG;
+
+ len -= r;
+ read_len += r;
+ skb = skb->next;
+ }
+
+ return read_len;
+}
+
+static int _read_rfs_data(struct sipc *si, struct ringbuf *rb, int len,
+ struct rfs_hdr *h)
+{
+ int r;
+ struct sk_buff_head list;
+ struct sk_buff *skb;
+ char *p;
+ int read_len;
+ struct net_device *ndev = si->svndev;
+
+ _dbg("%s: %d bytes\n", __func__, len);
+
+ /* alloc sk_buffs */
+ r = _alloc_rfs(ndev, &list, len + sizeof(struct rfs_hdr));
+ if (r)
+ goto free_skb;
+
+ skb = list.next;
+ p = skb_put(skb, sizeof(struct rfs_hdr));
+ memcpy(p, h, sizeof(struct rfs_hdr));
+
+ /* read data all */
+ r = _read_rfs_rb(rb, len, &list);
+ if (r < 0)
+ goto free_skb;
+
+ read_len = r;
+
+ /* move to rfs_rx queue */
+ skb = __skb_dequeue(&list);
+ while (skb) {
+// _phonet_rx(ndev, skb, PN_RFS);
+ skb_queue_tail(&si->rfs_rx, skb);
+ skb = __skb_dequeue(&list);
+ }
+
+ /* remove hdlc_end */
+ read_len += __read(rb, NULL, sizeof(hdlc_end));
+
+ return read_len;
+
+free_skb:
+ _free_rfs(&list);
+ return r;
+}
+
+static int _read_pdp(struct ringbuf *rb, int len,
+ int res)
+{
+ int r;
+ struct sk_buff *skb;
+ char *p;
+ int read_len = len + sizeof(hdlc_end);
+ struct net_device *ndev;
+
+ _dbg("%s: res 0x%02x data %d\n", __func__, res, len);
+
+ mutex_lock(&pdp_mutex);
+
+ ndev = pdp_devs[PDP_ID(res)];
+ if (!ndev) {
+ // drop data
+ r = __read(rb, NULL, read_len);
+ mutex_unlock(&pdp_mutex);
+ return r;
+ }
+
+ skb = netdev_alloc_skb(ndev, read_len);
+ if (unlikely(!skb)) {
+ mutex_unlock(&pdp_mutex);
+ return -ENOMEM;
+ }
+
+ p = skb_put(skb, len);
+ r = __read(rb, p, read_len);
+ if (r != read_len) {
+ mutex_unlock(&pdp_mutex);
+ kfree_skb(skb);
+ return -EBADMSG;
+ }
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += skb->len;
+
+ mutex_unlock(&pdp_mutex);
+
+ read_len = r;
+
+ skb->protocol = __constant_htons(ETH_P_IP);
+
+ skb_reset_mac_header(skb);
+
+ _dbg("%s: pdp packet %p len %d\n", __func__, skb, skb->len);
+
+ r = netif_rx_ni(skb);
+ if (r != NET_RX_SUCCESS)
+ dev_err(&ndev->dev, "pdp rx error: %d\n", r);
+
+ return read_len;
+}
+
+static int _read_raw(struct sipc *si, int inbuf, struct ringbuf *rb)
+{
+ int r;
+ char buf[sizeof(struct raw_hdr) + sizeof(hdlc_start)];
+ int res, data_len;
+ u32 tail;
+
+ while (inbuf > 0) {
+ tail = rb->rb_in_tail;
+
+ r = __read(rb, buf, sizeof(buf));
+ if (r < sizeof(buf) ||
+ strncmp(buf, hdlc_start, sizeof(hdlc_start))) {
+ dev_err(&si->svndev->dev, "Bad message: %c %d\n", buf[0], r);
+ return -EBADMSG;
+ }
+ inbuf -= r;
+
+ _get_raw_hdr((struct raw_hdr *)&buf[sizeof(hdlc_start)],
+ &res, &data_len, NULL);
+
+ data_len -= sizeof(struct raw_hdr);
+
+ if (res >= PN_PDP_START && res <= PN_PDP_END) {
+ r = _read_pdp(rb, data_len, res);
+ } else {
+ r = _read_pn(si->svndev, rb, data_len, res);
+ }
+
+ if (r < 0) {
+ if (r == -ENOMEM)
+ rb->rb_in_tail = tail;
+
+ return r;
+ }
+
+ inbuf -= r;
+ }
+
+ return 0;
+}
+
+static int _read_rfs(struct sipc *si, int inbuf, struct ringbuf *rb)
+{
+ int r;
+ char buf[sizeof(struct rfs_hdr) + sizeof(hdlc_start)];
+ int data_len;
+ u32 tail;
+ struct rfs_hdr *h;
+
+ h = (struct rfs_hdr *)&buf[sizeof(hdlc_start)];
+ while (inbuf > 0) {
+ tail = rb->rb_in_tail;
+
+ r = __read(rb, buf, sizeof(buf));
+ if (r < sizeof(buf) ||
+ strncmp(buf, hdlc_start, sizeof(hdlc_start))) {
+ dev_err(&si->svndev->dev, "Bad message: %c %d\n", buf[0], r);
+ return -EBADMSG;
+ }
+ inbuf -= r;
+
+ data_len = h->len - sizeof(struct rfs_hdr);
+
+ r = _read_rfs_data(si, rb, data_len, h);
+ if (r < 0) {
+ if (r == -ENOMEM)
+ rb->rb_in_tail = tail;
+
+ return r;
+ }
+
+ inbuf -= r;
+ }
+
+ return 0;
+}
+
+
+static struct frag_list* _find_frag_list(u8 control, struct frag_head *fh)
+{
+ struct frag_list *fl;
+ u8 msg_id = control & FMT_ID_MASK;
+
+ if (!test_bit(msg_id, fh->bitmap))
+ return NULL;
+
+ list_for_each_entry(fl, &fh->head, list) {
+ if (fl->msg_id == msg_id)
+ break;
+ }
+
+ return fl;
+}
+
+static int _fill_skb(struct sk_buff *skb, struct frag_list *fl)
+{
+ struct frag_block *fb, *n;
+ int offset = 0;
+ char *p;
+
+ list_for_each_entry_safe(fb, n, &fl->block_head, list) {
+ p = skb_put(skb, fb->len);
+ memcpy(p, fb->buf, fb->len);
+ offset += fb->len;
+ list_del(&fb->list);
+ kfree(fb);
+ }
+
+ return offset;
+}
+
+static void _destroy_frag_list(struct frag_list *fl, struct frag_head *fh)
+{
+ struct frag_block *fb, *n;
+
+ if (!fl || !fh)
+ return;
+
+ list_for_each_entry_safe(fb, n, &fl->block_head, list) {
+ kfree(fb);
+ }
+
+ clear_bit(fl->msg_id, fh->bitmap);
+ list_del(&fl->list);
+ kfree(fl);
+}
+
+static struct frag_list* _create_frag_list(u8 control, struct frag_head *fh)
+{
+ struct frag_list *fl;
+ u8 msg_id = control & FMT_ID_MASK;
+
+ if (test_bit(msg_id, fh->bitmap)) {
+ fl = _find_frag_list(control, fh);
+ _destroy_frag_list(fl, fh);
+ }
+
+ fl = kmalloc(sizeof(struct frag_list), GFP_KERNEL);
+ if (!fl)
+ return NULL;
+
+ INIT_LIST_HEAD(&fl->block_head);
+ fl->msg_id = msg_id;
+ fl->len = 0;
+ list_add(&fl->list, &fh->head);
+ set_bit(msg_id, fh->bitmap);
+
+ return fl;
+}
+
+static inline struct frag_block* _create_frag_block(struct frag_list *fl)
+{
+ struct frag_block *fb;
+
+ fb = kmalloc(sizeof(struct frag_block), GFP_KERNEL);
+ if (!fb)
+ return NULL;
+
+ fb->len = 0;
+ fb->ptr = fb->buf;
+ list_add_tail(&fb->list, &fl->block_head);
+
+ return fb;
+}
+
+static struct frag_block* _prepare_frag_block(struct frag_list *fl, int size)
+{
+ struct frag_block *fb;
+
+ if (size > FRAG_BLOCK_MAX)
+ BUG();
+
+ if (list_empty(&fl->block_head)) {
+ fb = _create_frag_block(fl);
+ } else {
+ fb = list_entry(fl->block_head.prev, struct frag_block, list);
+ if (size > FRAG_BLOCK_MAX - fb->len)
+ fb = _create_frag_block(fl);
+ }
+
+ return fb;
+}
+
+static int _read_fmt_frag(struct frag_head *fh, struct fmt_hdr *h,
+ struct ringbuf *rb)
+{
+ int r;
+ int data_len;
+ int read_len;
+ struct frag_list *fl;
+ struct frag_block *fb;
+
+ data_len = h->len - sizeof(struct fmt_hdr);
+ read_len = data_len + sizeof(hdlc_end);
+
+ _dbg("%s: data %d\n", __func__, data_len);
+
+ fl = _find_frag_list(h->control, fh);
+ if (!fl)
+ fl = _create_frag_list(h->control, fh);
+
+ if (!fl)
+ return -ENOMEM;
+
+ fb = _prepare_frag_block(fl, read_len);
+ if (!fb) {
+ if (fl->len == 0)
+ _destroy_frag_list(fl, fh);
+
+ return -ENOMEM;
+ }
+
+ r = __read(rb, fb->ptr, read_len);
+ if (r != read_len) {
+ _destroy_frag_list(fl, fh);
+ return -EBADMSG;
+ }
+
+ fb->ptr += data_len;
+ fb->len += data_len;
+ fl->len += data_len;
+
+ _dbg("%s: fl %p len %d fb %p ptr %p len %d\n", __func__,
+ fl, fl->len, fb, fb->ptr, fb->len);
+
+ return r;
+}
+
+static int _read_fmt_last(struct frag_head *fh, struct fmt_hdr *h,
+ struct ringbuf *rb, struct net_device *ndev)
+{
+ int r;
+ int data_len;
+ int read_len;
+ int total_len;
+ struct sk_buff *skb;
+ struct frag_list *fl;
+ char *p;
+
+ total_len = data_len = h->len - sizeof(struct fmt_hdr);
+ read_len = data_len + sizeof(hdlc_end);
+
+ fl = _find_frag_list(h->control & FMT_ID_MASK, fh);
+ if (fl)
+ total_len += fl->len;
+
+ _dbg("%s: total %d data %d\n", __func__, total_len, data_len);
+
+ skb = netdev_alloc_skb(ndev, total_len
+ + sizeof(struct phonethdr) + sizeof(hdlc_end));
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ skb_reserve(skb, sizeof(struct phonethdr));
+
+ if (fl)
+ _fill_skb(skb, fl);
+
+ _destroy_frag_list(fl, fh);
+
+ p = skb_put(skb, data_len);
+ r = __read(rb, p, read_len);
+ if (r != read_len) {
+ kfree_skb(skb);
+ return -EBADMSG;
+ }
+
+ _phonet_rx(ndev, skb, PN_FMT);
+
+ return r;
+}
+
+static int _read_fmt(struct sipc *si, int inbuf, struct ringbuf *rb)
+{
+ int r;
+ char buf[sizeof(struct fmt_hdr) + sizeof(hdlc_start)];
+ struct fmt_hdr *h;
+ u32 tail;
+ struct net_device *ndev = si->svndev;
+
+ h = (struct fmt_hdr *)&buf[sizeof(hdlc_start)];
+ while (inbuf > 0) {
+ tail = rb->rb_in_tail;
+
+ r = __read(rb, buf, sizeof(buf));
+ if (r < sizeof(buf) ||
+ strncmp(buf, hdlc_start, sizeof(hdlc_start))) {
+ dev_err(&ndev->dev, "Bad message: %c %d\n", buf[0], r);
+ return -EBADMSG;
+ }
+ inbuf -= r;
+
+ if (is_fmt_last(h->control))
+ r = _read_fmt_last(&si->frag_map, h, rb, ndev);
+ else
+ r = _read_fmt_frag(&si->frag_map, h, rb);
+
+ if (r < 0) {
+ if (r == -ENOMEM)
+ rb->rb_in_tail = tail;
+
+ return r;
+ }
+
+ inbuf -= r;
+ }
+
+ return 0;
+}
+
+static inline int check_mailbox(u32 mailbox, int idx)
+{
+ return mailbox & mb_data[idx].mask_send;
+}
+
+static inline void purge_buffer(struct ringbuf *rb)
+{
+ rb->rb_in_tail = rb->rb_in_head;
+}
+
+int sipc_read(struct sipc *si, u32 mailbox, int *cond)
+{
+ int r = 0;
+ int i;
+ u32 res = 0;
+
+ if (!si)
+ return -EINVAL;
+
+ r = _get_auth();
+ if (r)
+ return r;
+
+ for (i=0;i<IPCIDX_MAX;i++) {
+ int inbuf;
+ struct ringbuf *rb;
+
+// if (!check_mailbox(mailbox, i))
+// continue;
+
+ rb = &si->rb[i];
+ inbuf = CIRC_CNT(rb->rb_in_head, rb->rb_in_tail, rb->rb_size);
+ if (!inbuf)
+ continue;
+
+ if (i == IPCIDX_FMT)
+ _fmt_wakelock_timeout();
+ else
+ _non_fmt_wakelock_timeout();
+
+ _dbg("%s: %d bytes in %d\n", __func__, inbuf, i);
+
+ r = rb->rb_read(si, inbuf, rb);
+ if (r < 0) {
+ if (r == -EBADMSG)
+ purge_buffer(rb);
+
+ dev_err(&si->svndev->dev, "read err %d\n", r);
+ break;
+ }
+
+ if (mailbox & mb_data[i].mask_req_ack)
+ res = mb_data[i].mask_res_ack;
+ }
+
+#if !defined(CONFIG_ARIES_NTT)
+ _req_rel_auth(si);
+#endif
+
+ _put_auth(si);
+
+ if (res)
+ onedram_write_mailbox(MB_DATA(res));
+
+ *cond = skb_queue_len(&si->rfs_rx);
+
+ return r;
+}
+
+int sipc_rx(struct sipc *si)
+{
+ int tx_cnt;
+ struct sk_buff *skb;
+
+ if (!si)
+ return -EINVAL;
+
+ if (skb_queue_len(&si->rfs_rx) == 0)
+ return 0;
+
+ tx_cnt = 0;
+ skb = skb_dequeue(&si->rfs_rx);
+ while (skb) {
+ _phonet_rx(si->svndev, skb, PN_RFS);
+ tx_cnt++;
+ if (tx_cnt > RFS_TX_RATE)
+ break;
+ skb = skb_dequeue(&si->rfs_rx);
+ }
+
+ return skb_queue_len(&si->rfs_rx);
+}
+
+static inline ssize_t _debug_show_buf(struct sipc *si, char *buf)
+{
+ int i;
+ int r;
+ int inbuf, outbuf;
+ char *p = buf;
+
+ r = _get_auth();
+ if (r) {
+ p += sprintf(p, "\nGet authority: timed out!\n");
+ return p - buf;
+ }
+
+ p += sprintf(p, "\nHeader info ---------\n");
+
+ for (i=0;i<IPCIDX_MAX;i++) {
+ struct ringbuf *rb = &si->rb[i];
+ inbuf = CIRC_CNT(rb->rb_in_head, rb->rb_in_tail, rb->rb_size);
+ outbuf = CIRC_CNT(rb->rb_out_head, rb->rb_out_tail, rb->rb_size);
+ p += sprintf(p, "%d\tSize\t%8u\n\tIn\t%8u\t%8u\t%8u\n\tOut\t%8u\t%8u\t%8u\n",
+ i, rb->rb_size,
+ rb->rb_in_head, rb->rb_in_tail, inbuf,
+ rb->rb_out_head, rb->rb_out_tail, outbuf);
+ }
+ _put_auth(si);
+
+ return p - buf;
+}
+
+static inline ssize_t _debug_show_pdp(struct sipc *si, char *buf)
+{
+ int i;
+ char *p = buf;
+
+ p += sprintf(p, "\nPDP count: %d\n", pdp_cnt);
+
+ mutex_lock(&pdp_mutex);
+ for (i=0;i<sizeof(pdp_devs)/sizeof(pdp_devs[0]);i++) {
+ if (pdp_devs[i])
+ p += sprintf(p, "pdp%d: %d", i,
+ netif_queue_stopped(pdp_devs[i]));
+ }
+ mutex_unlock(&pdp_mutex);
+
+ return p - buf;
+}
+
+ssize_t sipc_debug_show(struct sipc *si, char *buf)
+{
+ char *p = buf;
+
+ if (!si || !buf)
+ return 0;
+
+ p += _debug_show_buf(si, p);
+
+ p += _debug_show_pdp(si, p);
+
+ p += sprintf(p, "\nDebug command -----------\n");
+ p += sprintf(p, "R0\tcopy FMT out to in\n");
+ p += sprintf(p, "R1\tcopy RAW out to in\n");
+ p += sprintf(p, "R2\tcopy RFS out to in\n");
+
+ return p - buf;
+}
+
+static void test_copy_buf(struct sipc *si, int idx)
+{
+ struct ringbuf *rb;
+
+ if(idx >= IPCIDX_MAX)
+ return;
+
+ rb = (struct ringbuf *)&si->rb[idx];
+
+ memcpy(rb->in_base, rb->out_base, rb->info->size);
+ rb->rb_in_head = rb->rb_out_head;
+ rb->rb_in_tail = rb->rb_out_tail;
+
+ rb->rb_out_tail = rb->rb_out_head;
+
+ if (si->queue)
+ si->queue(MB_DATA(mb_data[idx].mask_send), si->queue_data);
+}
+
+int sipc_debug(struct sipc *si, const char *buf)
+{
+ int r;
+
+ if (!si || !buf)
+ return -EINVAL;
+
+ r = _get_auth();
+ if (r)
+ return r;
+
+ switch (buf[0]) {
+ case 'R':
+ test_copy_buf(si, buf[1]-'0');
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ _put_auth(si);
+
+ return 0;
+}
+
+int sipc_whitelist(struct sipc *si, const char *buf, size_t count)
+{
+ int r;
+ struct ringbuf *rb;
+
+ printk("[%s]\n",__func__);
+
+ if (factory_test_force_sleep) {
+ printk("[%s]factory test\n",__func__);
+ return count;
+ }
+
+ if (!si || !buf)
+ return -EINVAL;
+
+ r = _get_auth();
+ if (r)
+ return r;
+
+ rb = (struct ringbuf *)&si->rb[IPCIDX_FMT];
+
+ //write direct full-established-packet to buf
+ r = __write(rb,(u8 *) buf, (unsigned int )count);
+
+ _req_rel_auth(si);
+ _put_auth(si);
+
+ onedram_write_mailbox(MB_DATA(mb_data[IPCIDX_FMT].mask_send));
+ return r;
+}
+
+int sipc_check_skb(struct sipc *si, struct sk_buff *skb)
+{
+ struct phonethdr *ph;
+
+ ph = pn_hdr(skb);
+
+ if (ph->pn_res == PN_CMD)
+ return 1;
+
+ return 0;
+}
+
+int sipc_do_cmd(struct sipc *si, struct sk_buff *skb)
+{
+ if (!si)
+ return -EINVAL;
+
+ skb_pull(skb, sizeof(struct phonethdr) + 1);
+
+ // TODO:
+ if (!strncmp("PHONE_ON", skb->data, sizeof("PHONE_ON"))) {
+
+ return 0;
+ }
+
+ return 0;
+}
+
+static int pdp_activate(struct net_device *svndev, int channel)
+{
+ int idx;
+ struct net_device *ndev;
+
+ if (!svndev || channel < 1 || channel > PDP_MAX)
+ return -EINVAL;
+
+ idx = channel - 1; /* start from 0 */
+
+ mutex_lock(&pdp_mutex);
+
+ if (pdp_devs[idx]) {
+ mutex_unlock(&pdp_mutex);
+ return -EBUSY;
+ }
+
+ ndev = create_pdp(channel, svndev);
+ if (IS_ERR(ndev)) {
+ mutex_unlock(&pdp_mutex);
+ return PTR_ERR(ndev);
+ }
+
+ pdp_devs[idx] = ndev;
+ pdp_cnt++;
+
+ mutex_unlock(&pdp_mutex);
+
+ return 0;
+}
+
+static int pdp_deactivate(int channel)
+{
+ int idx;
+
+ if (channel < 1 || channel > PDP_MAX)
+ return -EINVAL;
+
+ idx = channel - 1; /* start from 0 */
+
+ mutex_lock(&pdp_mutex);
+
+ if (!pdp_devs[idx]) {
+ mutex_unlock(&pdp_mutex);
+ return -EBUSY;
+ }
+
+ destroy_pdp(&pdp_devs[idx]);
+ clear_bit(idx, pdp_bitmap);
+ pdp_cnt--;
+
+ mutex_unlock(&pdp_mutex);
+
+ return 0;
+}
+
+static ssize_t show_act(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ int i;
+ char *p = buf;
+
+ mutex_lock(&pdp_mutex);
+
+ for (i=0;i<sizeof(pdp_devs)/sizeof(pdp_devs[0]);i++) {
+ if (pdp_devs[i])
+ p += sprintf(p, "%d\n", (i+1));
+ }
+
+ mutex_unlock(&pdp_mutex);
+
+ return p - buf;
+}
+
+static ssize_t show_deact(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ int i;
+ char *p = buf;
+
+ mutex_lock(&pdp_mutex);
+
+ for(i=0;i<sizeof(pdp_devs)/sizeof(pdp_devs[0]);i++) {
+ if (!pdp_devs[i])
+ p += sprintf(p, "%d\n", (i+1));
+ }
+
+ mutex_unlock(&pdp_mutex);
+
+ return p - buf;
+}
+
+static ssize_t store_act(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int r;
+ unsigned long chan;
+ struct net_device *ndev = to_net_dev(d);
+
+ if (!ndev)
+ return count;
+
+ r = strict_strtoul(buf, 10, &chan);
+ if (!r)
+ r = pdp_activate(ndev, chan);
+
+ if (r)
+ dev_err(&ndev->dev, "Failed to activate pdp "
+ " channel %lu: %d\n", chan, r);
+
+ return count;
+}
+
+static ssize_t store_deact(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int r;
+ unsigned long chan;
+ struct net_device *ndev = to_net_dev(d);
+
+ if (!ndev)
+ return count;
+
+ r = strict_strtoul(buf, 10, &chan);
+ if (!r)
+ r = pdp_deactivate(chan);
+
+ if (r)
+ dev_err(&ndev->dev, "Failed to deactivate pdp"
+ " channel %lu: %d\n", chan, r);
+
+ return count;
+}
+
+static ssize_t show_suspend(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ int i;
+ char *p = buf;
+
+ mutex_lock(&pdp_mutex);
+
+ for (i=0;i<sizeof(pdp_devs)/sizeof(pdp_devs[0]);i++) {
+ if (test_bit(i, pdp_bitmap))
+ p += sprintf(p, "%d\n", (i+1));
+ }
+
+ mutex_unlock(&pdp_mutex);
+
+ return p - buf;
+}
+
+static ssize_t store_suspend(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int r;
+ unsigned long chan;
+ int id;
+
+ r = strict_strtoul(buf, 10, &chan);
+ if (r)
+ return count;
+
+ if (chan < 1 || chan > PDP_MAX)
+ return count;
+
+ id = chan - 1;
+
+ mutex_lock(&pdp_mutex);
+
+ set_bit(id, pdp_bitmap);
+
+ if (pdp_devs[id])
+ netif_stop_queue(pdp_devs[id]);
+
+ mutex_unlock(&pdp_mutex);
+
+ return count;
+}
+
+static ssize_t store_resume(struct device *d,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int r;
+ unsigned long chan;
+ int id;
+
+ r = strict_strtoul(buf, 10, &chan);
+ if (r)
+ return count;
+
+ if (chan < 1 || chan > PDP_MAX)
+ return count;
+
+ id = chan - 1;
+
+ mutex_lock(&pdp_mutex);
+
+ clear_bit(id, pdp_bitmap);
+
+ if (pdp_devs[id])
+ netif_wake_queue(pdp_devs[id]);
+
+ mutex_unlock(&pdp_mutex);
+
+ return count;
+}
+
+void sipc_ramdump(struct sipc *si)
+{
+#if defined(CONFIG_KERNEL_DEBUG_SEC)
+ /* silent reset at debug level low */
+ if ( kernel_sec_get_debug_level() == KERNEL_SEC_DEBUG_LEVEL_LOW )
+ return;
+#endif
+ _go_dump(si);
+}
+
+#if defined(CONFIG_KERNEL_DEBUG_SEC)
+static void _go_dump(struct sipc *si)
+{
+ int r;
+ t_kernel_sec_mmu_info mmu_info;
+
+ memset(cp_errmsg, 0, sizeof(cp_errmsg));
+
+ r = _get_auth();
+ if (r)
+ strcpy(cp_errmsg, ERRMSG);
+ else {
+ char *p;
+ p = (char *)si->map + FATAL_DISP;
+ memcpy(cp_errmsg, p, sizeof(cp_errmsg));
+ }
+
+ printk("CP Dump Cause - %s\n", cp_errmsg);
+
+// kernel_sec_set_cause_strptr(cp_errmsg, sizeof(cp_errmsg));
+ kernel_sec_set_upload_magic_number();
+ kernel_sec_get_mmu_reg_dump(&mmu_info);
+ kernel_sec_set_upload_cause(UPLOAD_CAUSE_CP_ERROR_FATAL);
+ kernel_sec_hw_reset(false);
+
+ // Never Return!!!
+}
+#endif
diff --git a/drivers/misc/samsung_modemctl/svnet/sipc4.h b/drivers/misc/samsung_modemctl/svnet/sipc4.h
new file mode 100644
index 0000000..c156a4f
--- /dev/null
+++ b/drivers/misc/samsung_modemctl/svnet/sipc4.h
@@ -0,0 +1,252 @@
+/**
+ * SAMSUNG MODEM IPC header version 4
+ *
+ * Copyright (C) 2010 Samsung Electronics. All rights reserved.
+ *
+ * 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 __SAMSUNG_IPC_V4_H__
+#define __SAMSUNG_IPC_V4_H__
+
+/* IPC4.1 NEW PARTITION MAP
+ * This map is seen by AP side
+
+ 0x00_0000 ===========================================
+ MAGIC(4)| ACCESS(4) | RESERVED(8)
+ 0x00_0010 -------------------------------------------
+ FMT_OUT_PTR | FMT_IN_PTR
+ HEAD(4) | TAIL(4) | HEAD(4) | TAIL(4)
+ 0x00_0020 -------------------------------------------
+ RAW_OUT_PTR | RAW_IN_PTR
+ HEAD(4) | TAIL(4) | HEAD(4) | TAIL(4)
+ 0x00_0030 -------------------------------------------
+ RFS_OUT_PTR | RFS_IN_PTR
+ HEAD(4) | TAIL(4) | HEAD(4) | TAIL(4)
+ 0x00_0040 -------------------------------------------
+ RESERVED (4KB - 64B)
+ 0x00_1000 -------------------------------------------
+ CP Fatal Display (160B)
+ 0x00_10A0 -------------------------------------------
+ RESERVED (1MB - 4kb-4kb-4kb - 160B)
+ 0x0F_E000 -------------------------------------------
+ Formatted Out (4KB)
+ 0x0F_F000 -------------------------------------------
+ Formatted In (4KB)
+ 0x10_0000 ===========================================
+ Raw Out (1MB)
+ 0x20_0000 ===========================================
+ Raw In (1MB)
+ 0x30_0000 ===========================================
+ RemoteFS Out (1MB)
+ 0x40_0000 ===========================================
+ RemoteFS In (1MB)
+ 0x50_0000 ===========================================
+
+ 0xFF_FFFF ===========================================
+*/
+
+#define FMT_OUT 0x0FE000
+#define FMT_IN 0x0FF000
+#define FMT_SZ 0x1000 /* 4096 bytes */
+
+#define RAW_OUT 0x100000
+#define RAW_IN 0x200000
+#define RAW_SZ 0x100000 /* 1 MB */
+
+#define RFS_OUT 0x300000
+#define RFS_IN 0x400000
+#define RFS_SZ 0x100000 /* 1 MB */
+
+#define FATAL_DISP 0x001000
+#define FATAL_DISP_SZ 0xA0 /* 160 bytes */
+
+#define SIPC_MAP_SIZE (RFS_IN + RFS_SZ)
+#define SIPC_NAME "IPCv4.1"
+
+enum {
+ IPCIDX_FMT = 0,
+ IPCIDX_RAW,
+ IPCIDX_RFS,
+ IPCIDX_MAX
+};
+
+struct ringbuf_cont {
+ u32 out_head;
+ u32 out_tail;
+ u32 in_head;
+ u32 in_tail;
+};
+
+struct sipc_mapped { /* map to the onedram start addr */
+ u32 magic;
+ u32 access;
+ u32 hwrev;
+ u32 reserved;
+
+ struct ringbuf_cont rbcont[IPCIDX_MAX];
+};
+
+
+#define PN_CMD 0x00
+#define PN_FMT 0x01
+#define PN_RFS 0x41
+#define PN_RAW(chid) (0x20 | (chid))
+#define CHID(x) ((x) & 0x1F)
+
+#define res_to_ridx(x) ((x) >> 5)
+
+/*
+ * IPC Frame Format
+ */
+#define HDLC_START 0x7F
+#define HDLC_END 0x7E
+
+/* Formatted IPC Frame */
+struct fmt_hdr {
+ u16 len;
+ u8 control;
+} __attribute__ ((packed));
+
+#define FMT_ID_MASK 0x7F /* Information ID mask */
+#define FMT_ID_SIZE 0x80 /* = 128 ( 0 ~ 127 ) */
+#define FMT_MB_MASK 0x80 /* More bit mask */
+
+#define FMT_TX_MIN 5 /* ??? */
+
+#define is_fmt_last(x) (!((x) & FMT_MB_MASK))
+
+/* RAW IPC Frame */
+struct raw_hdr {
+ u32 len;
+ u8 channel;
+ u8 control;
+} __attribute__ ((packed));
+
+
+/* RFS IPC Frame */
+struct rfs_hdr {
+ u32 len;
+ u8 cmd;
+ u8 id;
+} __attribute__ ((packed));
+
+/*
+ * RAW frame channel ID
+ */
+enum {
+ CHID_0 = 0,
+ CHID_CSD_VT_DATA,
+ CHID_PDS_PVT_CONTROL,
+ CHID_PDS_VT_AUDIO,
+ CHID_PDS_VT_VIDEO,
+ CHID_5, /* 5 */
+ CHID_6,
+ CHID_CDMA_DATA,
+ CHID_PCM_DATA,
+ CHID_TRANSFER_SCREEN,
+ CHID_PSD_DATA1, /* 10 */
+ CHID_PSD_DATA2,
+ CHID_PSD_DATA3,
+ CHID_PSD_DATA4,
+ CHID_PSD_DATA5,
+ CHID_PSD_DATA6, /* 15 */
+ CHID_PSD_DATA7,
+ CHID_PSD_DATA8,
+ CHID_PSD_DATA9,
+ CHID_PSD_DATA10,
+ CHID_PSD_DATA11, /* 20 */
+ CHID_PSD_DATA12,
+ CHID_PSD_DATA13,
+ CHID_PSD_DATA14,
+ CHID_PSD_DATA15,
+ CHID_BT_DUN, /* 25 */
+ CHID_CIQ_BRIDGE_DATA,
+ CHID_27,
+ CHID_CP_LOG1,
+ CHID_CP_LOG2,
+ CHID_30, /* 30 */
+ CHID_31,
+ CHID_MAX
+};
+
+#define PDP_MAX 15
+#define PN_PDP_START PN_RAW(CHID_PSD_DATA1)
+#define PN_PDP_END PN_RAW(CHID_PSD_DATA15)
+
+#define PN_PDP(chid) (0x20 | ((chid) + CHID_PSD_DATA1 - 1))
+#define PDP_ID(res) ((res) - PN_PDP_START)
+
+
+/*
+ * IPC 4.0 Mailbox message definition
+ */
+#define MB_VALID 0x0080
+#define MB_COMMAND 0x0040
+
+#define MB_CMD(x) (MB_VALID | MB_COMMAND | x)
+#define MB_DATA(x) (MB_VALID | x)
+
+/*
+ * If not command
+ */
+#define MBD_SEND_FMT 0x0002
+#define MBD_SEND_RAW 0x0001
+#define MBD_SEND_RFS 0x0100
+#define MBD_REQ_ACK_FMT 0x0020
+#define MBD_REQ_ACK_RAW 0x0010
+#define MBD_REQ_ACK_RFS 0x0400
+#define MBD_RES_ACK_FMT 0x0008
+#define MBD_RES_ACK_RAW 0x0004
+#define MBD_RES_ACK_RFS 0x0200
+
+/*
+ * If command
+ */
+enum {
+ MBC_NONE = 0,
+ MBC_INIT_START, // 0x0001
+ MBC_INIT_END, // 0x0002
+ MBC_REQ_ACTIVE, // 0x0003
+ MBC_RES_ACTIVE, // 0x0004
+ MBC_TIME_SYNC, // 0x0005
+ MBC_POWER_OFF, // 0x0006
+ MBC_RESET, // 0x0007
+ MBC_PHONE_START, // 0x0008
+ MBC_ERR_DISPLAY, // 0x0009
+ MBC_POWER_SAVE, // 0x000A
+ MBC_NV_REBUILD, // 0x000B
+ MBC_EMER_DOWN, // 0x000C
+ MBC_REQ_SEM, // 0x000D
+ MBC_RES_SEM, // 0x000E
+ MBC_MAX // 0x000F
+};
+#define MBC_MASK 0xFF
+
+/* CMD_INIT_END extended bit */
+#define CP_BOOT_ONLINE 0x0000
+#define CP_BOOT_AIRPLANE 0x1000
+#define AP_OS_ANDROID 0x0100
+#define AP_OS_WINMOBILE 0x0200
+#define AP_OS_LINUX 0x0300
+#define AP_OS_SYMBIAN 0x0400
+
+/* CMD_PHONE_START extended bit */
+#define CP_QUALCOMM 0x0100
+#define CP_INFINEON 0x0200
+#define CP_BROADCOM 0x0300
+
+#endif /* __SAMSUNG_IPC_V4_H__ */
+
diff --git a/drivers/misc/sec_jack.c b/drivers/misc/sec_jack.c
new file mode 100755
index 0000000..319542d
--- /dev/null
+++ b/drivers/misc/sec_jack.c
@@ -0,0 +1,545 @@
+/* drivers/misc/sec_jack.c
+ *
+ * Copyright (C) 2010 Samsung Electronics Co.Ltd
+ *
+ * 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.
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/switch.h>
+#include <linux/input.h>
+#include <linux/timer.h>
+#include <linux/wakelock.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/gpio_event.h>
+#include <linux/sec_jack.h>
+
+#undef pr_debug
+#define pr_debug pr_info
+
+#define MAX_ZONE_LIMIT 10
+#define SEND_KEY_CHECK_TIME_MS 30 /* 30ms */
+#define DET_CHECK_TIME_MS 200 /* 200ms */
+#define WAKE_LOCK_TIME (HZ * 5) /* 5 sec */
+
+struct sec_jack_info {
+ struct sec_jack_platform_data *pdata;
+ struct delayed_work jack_detect_work;
+ struct work_struct buttons_work;
+ struct workqueue_struct *queue;
+ struct input_dev *input_dev;
+ struct wake_lock det_wake_lock;
+ struct sec_jack_zone *zone;
+ struct input_handler handler;
+ struct input_handle handle;
+ struct input_device_id ids;
+ int det_irq;
+ int dev_id;
+ int pressed;
+ int pressed_code;
+ struct platform_device *send_key_dev;
+ unsigned int cur_jack_type;
+};
+
+/* with some modifications like moving all the gpio structs inside
+ * the platform data and getting the name for the switch and
+ * gpio_event from the platform data, the driver could support more than
+ * one headset jack, but currently user space is looking only for
+ * one key file and switch for a headset so it'd be overkill and
+ * untestable so we limit to one instantiation for now.
+ */
+static atomic_t instantiated = ATOMIC_INIT(0);
+
+/* sysfs name HeadsetObserver.java looks for to track headset state
+ */
+struct switch_dev switch_jack_detection = {
+ .name = "h2w",
+};
+
+static struct gpio_event_direct_entry sec_jack_key_map[] = {
+ {
+ .code = KEY_UNKNOWN,
+ },
+};
+
+static struct gpio_event_input_info sec_jack_key_info = {
+ .info.func = gpio_event_input_func,
+ .info.no_suspend = true,
+ .type = EV_KEY,
+ .debounce_time.tv64 = SEND_KEY_CHECK_TIME_MS * NSEC_PER_MSEC,
+ .keymap = sec_jack_key_map,
+ .keymap_size = ARRAY_SIZE(sec_jack_key_map)
+};
+
+static struct gpio_event_info *sec_jack_input_info[] = {
+ &sec_jack_key_info.info,
+};
+
+static struct gpio_event_platform_data sec_jack_input_data = {
+ .name = "sec_jack",
+ .info = sec_jack_input_info,
+ .info_count = ARRAY_SIZE(sec_jack_input_info),
+};
+
+/* gpio_input driver does not support to read adc value.
+ * We use input filter to support 3-buttons of headset
+ * without changing gpio_input driver.
+ */
+static bool sec_jack_buttons_filter(struct input_handle *handle,
+ unsigned int type, unsigned int code,
+ int value)
+{
+ struct sec_jack_info *hi = handle->handler->private;
+
+ pr_debug("%s: type=%d, code=%d, value=%d\n", __func__, type, code, value);
+ if (type != EV_KEY || code != KEY_UNKNOWN)
+ return false;
+
+ pr_debug("%s: queueing type=%d, code=%d, value=%d\n", __func__, type, code, value);
+ hi->pressed = value;
+
+ /* This is called in timer handler of gpio_input driver.
+ * We use workqueue to read adc value.
+ */
+ queue_work(hi->queue, &hi->buttons_work);
+
+ return true;
+}
+
+static int sec_jack_buttons_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct sec_jack_info *hi;
+ struct sec_jack_platform_data *pdata;
+ struct sec_jack_buttons_zone *btn_zones;
+ int err;
+ int i;
+
+ /* bind input_handler to input device related to only sec_jack */
+ if (dev->name != sec_jack_input_data.name)
+ return -ENODEV;
+
+ pr_debug("%s\n", __func__);
+ hi = handler->private;
+ pdata = hi->pdata;
+ btn_zones = pdata->buttons_zones;
+
+ hi->input_dev = dev;
+ hi->handle.dev = dev;
+ hi->handle.handler = handler;
+ hi->handle.open = 0;
+ hi->handle.name = "sec_jack_buttons";
+
+ err = input_register_handle(&hi->handle);
+ if (err) {
+ pr_err("%s: Failed to register sec_jack buttons handle, "
+ "error %d\n", __func__, err);
+ goto err_register_handle;
+ }
+
+ err = input_open_device(&hi->handle);
+ if (err) {
+ pr_err("%s: Failed to open input device, error %d\n",
+ __func__, err);
+ goto err_open_device;
+ }
+
+ for (i = 0; i < pdata->num_buttons_zones; i++)
+ input_set_capability(dev, EV_KEY, btn_zones[i].code);
+
+ return 0;
+
+ err_open_device:
+ input_unregister_handle(&hi->handle);
+ err_register_handle:
+
+ return err;
+}
+
+static void sec_jack_buttons_disconnect(struct input_handle *handle)
+{
+ pr_debug("%s\n", __func__);
+ input_close_device(handle);
+ input_unregister_handle(handle);
+}
+
+static void sec_jack_set_type(struct sec_jack_info *hi, int jack_type)
+{
+ struct sec_jack_platform_data *pdata = hi->pdata;
+
+ /* this can happen during slow inserts where we think we identified
+ * the type but then we get another interrupt and do it again
+ */
+ if (jack_type == hi->cur_jack_type) {
+ if (jack_type != SEC_HEADSET_4POLE)
+ pdata->set_micbias_state(false);
+ return;
+ }
+
+ if (jack_type == SEC_HEADSET_4POLE) {
+ /* for a 4 pole headset, enable detection of send/end key */
+ if (hi->send_key_dev == NULL)
+ /* enable to get events again */
+ hi->send_key_dev = platform_device_register_data(NULL,
+ GPIO_EVENT_DEV_NAME,
+ hi->dev_id,
+ &sec_jack_input_data,
+ sizeof(sec_jack_input_data));
+ } else {
+ /* for all other jacks, disable send/end key detection */
+ if (hi->send_key_dev != NULL) {
+ /* disable to prevent false events on next insert */
+ platform_device_unregister(hi->send_key_dev);
+ hi->send_key_dev = NULL;
+ }
+ /* micbias is left enabled for 4pole and disabled otherwise */
+ pdata->set_micbias_state(false);
+ }
+
+ hi->cur_jack_type = jack_type;
+ pr_info("%s : jack_type = %d\n", __func__, jack_type);
+
+ /* prevent suspend to allow user space to respond to switch */
+ wake_lock_timeout(&hi->det_wake_lock, WAKE_LOCK_TIME);
+
+ switch_set_state(&switch_jack_detection, jack_type);
+}
+
+static void handle_jack_not_inserted(struct sec_jack_info *hi)
+{
+ pr_debug("%s\n", __func__);
+ sec_jack_set_type(hi, SEC_JACK_NO_DEVICE);
+ hi->pdata->set_micbias_state(false);
+}
+
+static void determine_jack_type(struct sec_jack_info *hi)
+{
+ struct sec_jack_zone *zones = hi->pdata->zones;
+ int size = hi->pdata->num_zones;
+ int count[MAX_ZONE_LIMIT] = {0};
+ int adc;
+ int i;
+ unsigned npolarity = !hi->pdata->det_active_high;
+
+ pr_debug("%s\n", __func__);
+ while (gpio_get_value(hi->pdata->det_gpio) ^ npolarity) {
+ adc = hi->pdata->get_adc_value();
+ pr_debug("%s: adc = %d\n", __func__, adc);
+
+ /* determine the type of headset based on the
+ * adc value. An adc value can fall in various
+ * ranges or zones. Within some ranges, the type
+ * can be returned immediately. Within others, the
+ * value is considered unstable and we need to sample
+ * a few more types (up to the limit determined by
+ * the range) before we return the type for that range.
+ */
+ for (i = 0; i < size; i++) {
+ if (adc <= zones[i].adc_high) {
+ if (++count[i] > zones[i].check_count) {
+ sec_jack_set_type(hi,
+ zones[i].jack_type);
+ return;
+ }
+ msleep(zones[i].delay_ms);
+ break;
+ }
+ }
+ }
+ /* jack removed before detection complete */
+ pr_debug("%s : jack removed before detection complete\n", __func__);
+ handle_jack_not_inserted(hi);
+}
+
+/* thread run whenever the headset detect state changes (either insertion
+ * or removal).
+ */
+static irqreturn_t sec_jack_detect_irq_thread(int irq, void *dev_id)
+{
+ struct sec_jack_info *hi = dev_id;
+ struct sec_jack_platform_data *pdata = hi->pdata;
+ int time_left_ms = DET_CHECK_TIME_MS;
+ unsigned npolarity = !hi->pdata->det_active_high;
+
+ pr_debug("%s", __func__);
+
+ /* set mic bias to enable adc */
+ pdata->set_micbias_state(true);
+
+ /* debounce headset jack. don't try to determine the type of
+ * headset until the detect state is true for a while.
+ */
+ while (time_left_ms > 0) {
+ if (!(gpio_get_value(hi->pdata->det_gpio) ^ npolarity)) {
+ /* jack not detected. */
+ handle_jack_not_inserted(hi);
+ return IRQ_HANDLED;
+ }
+ msleep(10);
+ time_left_ms -= 10;
+ }
+ /* jack presence was detected the whole time, figure out which type */
+ determine_jack_type(hi);
+ return IRQ_HANDLED;
+}
+
+static void sec_jack_init_jack_state(struct sec_jack_info *hi)
+{
+ struct sec_jack_platform_data *pdata = hi->pdata;
+ int time_left_ms = DET_CHECK_TIME_MS;
+ unsigned npolarity = !hi->pdata->det_active_high;
+
+ pr_debug("%s", __func__);
+
+ /* set mic bias to enable adc */
+ pdata->set_micbias_state(true);
+
+ /* debounce headset jack. don't try to determine the type of
+ * headset until the detect state is true for a while.
+ */
+ while (time_left_ms > 0) {
+ if (!(gpio_get_value(hi->pdata->det_gpio) ^ npolarity)) {
+ /* jack not detected. */
+ handle_jack_not_inserted(hi);
+ return;
+ }
+ msleep(10);
+ time_left_ms -= 10;
+ }
+ /* jack presence was detected the whole time, figure out which type */
+ determine_jack_type(hi);
+}
+
+/* thread run whenever the button of headset is pressed or released */
+void sec_jack_buttons_work(struct work_struct *work)
+{
+ struct sec_jack_info *hi =
+ container_of(work, struct sec_jack_info, buttons_work);
+ struct sec_jack_platform_data *pdata = hi->pdata;
+ struct sec_jack_buttons_zone *btn_zones = pdata->buttons_zones;
+ int adc;
+ int i;
+
+ /* when button is released */
+ if (hi->pressed == 0) {
+ input_report_key(hi->input_dev, hi->pressed_code, 0);
+ input_sync(hi->input_dev);
+ pr_debug("%s: keycode=%d, is released\n", __func__,
+ hi->pressed_code);
+ return;
+ }
+
+ /* when button is pressed */
+ adc = pdata->get_adc_value();
+ pr_debug("%s: adc=%d\n", __func__, adc);
+ for (i = 0; i < pdata->num_buttons_zones; i++)
+ if (adc >= btn_zones[i].adc_low &&
+ adc <= btn_zones[i].adc_high) {
+ hi->pressed_code = btn_zones[i].code;
+ input_report_key(hi->input_dev, btn_zones[i].code, 1);
+ input_sync(hi->input_dev);
+ pr_debug("%s: keycode=%d, is pressed\n", __func__,
+ btn_zones[i].code);
+ return;
+ }
+
+ pr_warn("%s: key is skipped. ADC value is %d\n", __func__, adc);
+}
+
+static int sec_jack_probe(struct platform_device *pdev)
+{
+ struct sec_jack_info *hi;
+ struct sec_jack_platform_data *pdata = pdev->dev.platform_data;
+ int ret;
+
+ pr_info("%s : Registering jack driver\n", __func__);
+ if (!pdata) {
+ pr_err("%s : pdata is NULL.\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!pdata->get_adc_value || !pdata->zones ||
+ !pdata->set_micbias_state || pdata->num_zones > MAX_ZONE_LIMIT) {
+ pr_err("%s : need to check pdata\n", __func__);
+ return -ENODEV;
+ }
+
+ if (atomic_xchg(&instantiated, 1)) {
+ pr_err("%s : already instantiated, can only have one\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ sec_jack_key_map[0].gpio = pdata->send_end_gpio;
+
+ /* If no other keys in pdata, make all keys default to KEY_MEDIA */
+ if (pdata->num_buttons_zones == 0)
+ sec_jack_key_map[0].code = KEY_MEDIA;
+
+ hi = kzalloc(sizeof(struct sec_jack_info), GFP_KERNEL);
+ if (hi == NULL) {
+ pr_err("%s : Failed to allocate memory.\n", __func__);
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ hi->pdata = pdata;
+
+ /* make the id of our gpi_event device the same as our platform device,
+ * which makes it the responsiblity of the board file to make sure
+ * it is unique relative to other gpio_event devices
+ */
+ hi->dev_id = pdev->id;
+
+ ret = gpio_request(pdata->det_gpio, "ear_jack_detect");
+ if (ret) {
+ pr_err("%s : gpio_request failed for %d\n",
+ __func__, pdata->det_gpio);
+ goto err_gpio_request;
+ }
+
+ ret = switch_dev_register(&switch_jack_detection);
+ if (ret < 0) {
+ pr_err("%s : Failed to register switch device\n", __func__);
+ goto err_switch_dev_register;
+ }
+
+ wake_lock_init(&hi->det_wake_lock, WAKE_LOCK_SUSPEND, "sec_jack_det");
+
+ INIT_WORK(&hi->buttons_work, sec_jack_buttons_work);
+ hi->queue = create_singlethread_workqueue("sec_jack_wq");
+ if (hi->queue == NULL) {
+ ret = -ENOMEM;
+ pr_err("%s: Failed to create workqueue\n", __func__);
+ goto err_create_wq_failed;
+ }
+
+ hi->det_irq = gpio_to_irq(pdata->det_gpio);
+
+ set_bit(EV_KEY, hi->ids.evbit);
+ hi->ids.flags = INPUT_DEVICE_ID_MATCH_EVBIT;
+ hi->handler.filter = sec_jack_buttons_filter;
+ hi->handler.connect = sec_jack_buttons_connect;
+ hi->handler.disconnect = sec_jack_buttons_disconnect;
+ hi->handler.name = "sec_jack_buttons";
+ hi->handler.id_table = &hi->ids;
+ hi->handler.private = hi;
+
+ ret = input_register_handler(&hi->handler);
+ if (ret) {
+ pr_err("%s : Failed to register_handler\n", __func__);
+ goto err_register_input_handler;
+ }
+ ret = request_threaded_irq(hi->det_irq, NULL,
+ sec_jack_detect_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "sec_headset_detect", hi);
+ if (ret) {
+ pr_err("%s : Failed to request_irq.\n", __func__);
+ goto err_request_detect_irq;
+ }
+
+ /* to handle insert/removal when we're sleeping in a call */
+ ret = enable_irq_wake(hi->det_irq);
+ if (ret) {
+ pr_err("%s : Failed to enable_irq_wake.\n", __func__);
+ goto err_enable_irq_wake;
+ }
+
+ dev_set_drvdata(&pdev->dev, hi);
+
+#if defined (CONFIG_SAMSUNG_CAPTIVATE) || defined(CONFIG_SAMSUNG_VIBRANT)
+ pdata->det_active_high = 1;
+#else
+ pdata->det_active_high = 0;
+#endif
+
+ /* initialize headset jack state */
+ sec_jack_init_jack_state(hi);
+
+ return 0;
+
+err_enable_irq_wake:
+ free_irq(hi->det_irq, hi);
+err_request_detect_irq:
+ input_unregister_handler(&hi->handler);
+err_register_input_handler:
+ destroy_workqueue(hi->queue);
+err_create_wq_failed:
+ wake_lock_destroy(&hi->det_wake_lock);
+ switch_dev_unregister(&switch_jack_detection);
+err_switch_dev_register:
+ gpio_free(pdata->det_gpio);
+err_gpio_request:
+ kfree(hi);
+err_kzalloc:
+ atomic_set(&instantiated, 0);
+
+ return ret;
+}
+
+static int sec_jack_remove(struct platform_device *pdev)
+{
+
+ struct sec_jack_info *hi = dev_get_drvdata(&pdev->dev);
+
+ pr_info("%s :\n", __func__);
+ disable_irq_wake(hi->det_irq);
+ free_irq(hi->det_irq, hi);
+ destroy_workqueue(hi->queue);
+ if (hi->send_key_dev) {
+ platform_device_unregister(hi->send_key_dev);
+ hi->send_key_dev = NULL;
+ }
+ input_unregister_handler(&hi->handler);
+ wake_lock_destroy(&hi->det_wake_lock);
+ switch_dev_unregister(&switch_jack_detection);
+ gpio_free(hi->pdata->det_gpio);
+ kfree(hi);
+ atomic_set(&instantiated, 0);
+
+ return 0;
+}
+
+static struct platform_driver sec_jack_driver = {
+ .probe = sec_jack_probe,
+ .remove = sec_jack_remove,
+ .driver = {
+ .name = "sec_jack",
+ .owner = THIS_MODULE,
+ },
+};
+static int __init sec_jack_init(void)
+{
+ return platform_driver_register(&sec_jack_driver);
+}
+
+static void __exit sec_jack_exit(void)
+{
+ platform_driver_unregister(&sec_jack_driver);
+}
+
+module_init(sec_jack_init);
+module_exit(sec_jack_exit);
+
+MODULE_AUTHOR("ms17.kim@samsung.com");
+MODULE_DESCRIPTION("Samsung Electronics Corp Ear-Jack detection driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/uid_stat.c b/drivers/misc/uid_stat.c
new file mode 100644
index 0000000..2141124
--- /dev/null
+++ b/drivers/misc/uid_stat.c
@@ -0,0 +1,156 @@
+/* drivers/misc/uid_stat.c
+ *
+ * Copyright (C) 2008 - 2009 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <asm/atomic.h>
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+#include <linux/uid_stat.h>
+#include <net/activity_stats.h>
+
+static DEFINE_SPINLOCK(uid_lock);
+static LIST_HEAD(uid_list);
+static struct proc_dir_entry *parent;
+
+struct uid_stat {
+ struct list_head link;
+ uid_t uid;
+ atomic_t tcp_rcv;
+ atomic_t tcp_snd;
+};
+
+static struct uid_stat *find_uid_stat(uid_t uid) {
+ unsigned long flags;
+ struct uid_stat *entry;
+
+ spin_lock_irqsave(&uid_lock, flags);
+ list_for_each_entry(entry, &uid_list, link) {
+ if (entry->uid == uid) {
+ spin_unlock_irqrestore(&uid_lock, flags);
+ return entry;
+ }
+ }
+ spin_unlock_irqrestore(&uid_lock, flags);
+ return NULL;
+}
+
+static int tcp_snd_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len;
+ unsigned int bytes;
+ char *p = page;
+ struct uid_stat *uid_entry = (struct uid_stat *) data;
+ if (!data)
+ return 0;
+
+ bytes = (unsigned int) (atomic_read(&uid_entry->tcp_snd) + INT_MIN);
+ p += sprintf(p, "%u\n", bytes);
+ len = (p - page) - off;
+ *eof = (len <= count) ? 1 : 0;
+ *start = page + off;
+ return len;
+}
+
+static int tcp_rcv_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len;
+ unsigned int bytes;
+ char *p = page;
+ struct uid_stat *uid_entry = (struct uid_stat *) data;
+ if (!data)
+ return 0;
+
+ bytes = (unsigned int) (atomic_read(&uid_entry->tcp_rcv) + INT_MIN);
+ p += sprintf(p, "%u\n", bytes);
+ len = (p - page) - off;
+ *eof = (len <= count) ? 1 : 0;
+ *start = page + off;
+ return len;
+}
+
+/* Create a new entry for tracking the specified uid. */
+static struct uid_stat *create_stat(uid_t uid) {
+ unsigned long flags;
+ char uid_s[32];
+ struct uid_stat *new_uid;
+ struct proc_dir_entry *entry;
+
+ /* Create the uid stat struct and append it to the list. */
+ if ((new_uid = kmalloc(sizeof(struct uid_stat), GFP_KERNEL)) == NULL)
+ return NULL;
+
+ new_uid->uid = uid;
+ /* Counters start at INT_MIN, so we can track 4GB of network traffic. */
+ atomic_set(&new_uid->tcp_rcv, INT_MIN);
+ atomic_set(&new_uid->tcp_snd, INT_MIN);
+
+ spin_lock_irqsave(&uid_lock, flags);
+ list_add_tail(&new_uid->link, &uid_list);
+ spin_unlock_irqrestore(&uid_lock, flags);
+
+ sprintf(uid_s, "%d", uid);
+ entry = proc_mkdir(uid_s, parent);
+
+ /* Keep reference to uid_stat so we know what uid to read stats from. */
+ create_proc_read_entry("tcp_snd", S_IRUGO, entry , tcp_snd_read_proc,
+ (void *) new_uid);
+
+ create_proc_read_entry("tcp_rcv", S_IRUGO, entry, tcp_rcv_read_proc,
+ (void *) new_uid);
+
+ return new_uid;
+}
+
+int uid_stat_tcp_snd(uid_t uid, int size) {
+ struct uid_stat *entry;
+ activity_stats_update();
+ if ((entry = find_uid_stat(uid)) == NULL &&
+ ((entry = create_stat(uid)) == NULL)) {
+ return -1;
+ }
+ atomic_add(size, &entry->tcp_snd);
+ return 0;
+}
+
+int uid_stat_tcp_rcv(uid_t uid, int size) {
+ struct uid_stat *entry;
+ activity_stats_update();
+ if ((entry = find_uid_stat(uid)) == NULL &&
+ ((entry = create_stat(uid)) == NULL)) {
+ return -1;
+ }
+ atomic_add(size, &entry->tcp_rcv);
+ return 0;
+}
+
+static int __init uid_stat_init(void)
+{
+ parent = proc_mkdir("uid_stat", NULL);
+ if (!parent) {
+ pr_err("uid_stat: failed to create proc entry\n");
+ return -1;
+ }
+ return 0;
+}
+
+__initcall(uid_stat_init);
diff --git a/drivers/misc/wl127x-rfkill.c b/drivers/misc/wl127x-rfkill.c
new file mode 100644
index 0000000..f5b9515
--- /dev/null
+++ b/drivers/misc/wl127x-rfkill.c
@@ -0,0 +1,121 @@
+/*
+ * Bluetooth TI wl127x rfkill power control via GPIO
+ *
+ * Copyright (C) 2009 Motorola, Inc.
+ * Copyright (C) 2008 Texas Instruments
+ * Initial code: Pavan Savoy <pavan.savoy@gmail.com> (wl127x_power.c)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/rfkill.h>
+#include <linux/platform_device.h>
+#include <linux/wl127x-rfkill.h>
+
+static int wl127x_rfkill_set_power(void *data, enum rfkill_state state)
+{
+ int nshutdown_gpio = (int) data;
+
+ switch (state) {
+ case RFKILL_STATE_UNBLOCKED:
+ gpio_set_value(nshutdown_gpio, 1);
+ break;
+ case RFKILL_STATE_SOFT_BLOCKED:
+ gpio_set_value(nshutdown_gpio, 0);
+ break;
+ default:
+ printk(KERN_ERR "invalid bluetooth rfkill state %d\n", state);
+ }
+ return 0;
+}
+
+static int wl127x_rfkill_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ struct wl127x_rfkill_platform_data *pdata = pdev->dev.platform_data;
+ enum rfkill_state default_state = RFKILL_STATE_SOFT_BLOCKED; /* off */
+
+ rc = gpio_request(pdata->nshutdown_gpio, "wl127x_nshutdown_gpio");
+ if (unlikely(rc))
+ return rc;
+
+ rc = gpio_direction_output(pdata->nshutdown_gpio, 0);
+ if (unlikely(rc))
+ return rc;
+
+ rfkill_set_default(RFKILL_TYPE_BLUETOOTH, default_state);
+ wl127x_rfkill_set_power(NULL, default_state);
+
+ pdata->rfkill = rfkill_allocate(&pdev->dev, RFKILL_TYPE_BLUETOOTH);
+ if (unlikely(!pdata->rfkill))
+ return -ENOMEM;
+
+ pdata->rfkill->name = "wl127x";
+ pdata->rfkill->state = default_state;
+ /* userspace cannot take exclusive control */
+ pdata->rfkill->user_claim_unsupported = 1;
+ pdata->rfkill->user_claim = 0;
+ pdata->rfkill->data = (void *) pdata->nshutdown_gpio;
+ pdata->rfkill->toggle_radio = wl127x_rfkill_set_power;
+
+ rc = rfkill_register(pdata->rfkill);
+
+ if (unlikely(rc))
+ rfkill_free(pdata->rfkill);
+
+ return 0;
+}
+
+static int wl127x_rfkill_remove(struct platform_device *pdev)
+{
+ struct wl127x_rfkill_platform_data *pdata = pdev->dev.platform_data;
+
+ rfkill_unregister(pdata->rfkill);
+ rfkill_free(pdata->rfkill);
+ gpio_free(pdata->nshutdown_gpio);
+
+ return 0;
+}
+
+static struct platform_driver wl127x_rfkill_platform_driver = {
+ .probe = wl127x_rfkill_probe,
+ .remove = wl127x_rfkill_remove,
+ .driver = {
+ .name = "wl127x-rfkill",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init wl127x_rfkill_init(void)
+{
+ return platform_driver_register(&wl127x_rfkill_platform_driver);
+}
+
+static void __exit wl127x_rfkill_exit(void)
+{
+ platform_driver_unregister(&wl127x_rfkill_platform_driver);
+}
+
+module_init(wl127x_rfkill_init);
+module_exit(wl127x_rfkill_exit);
+
+MODULE_ALIAS("platform:wl127x");
+MODULE_DESCRIPTION("wl127x-rfkill");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");