diff options
Diffstat (limited to 'drivers/misc')
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 *)¶m, (void *)arg, sizeof(param)); + if (dpram_extra_mem_rw(¶m) < 0) { + printk(KERN_ERR "[OneDRAM] external memory access fail..\n"); + ret = -1; + } else if (!param.rw) { //read + ret = copy_to_user((unsigned long *)arg, ¶m, 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"); |