summaryrefslogtreecommitdiff
path: root/drivers/platform
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/platform
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/Kconfig9
-rw-r--r--drivers/platform/Makefile8
-rw-r--r--drivers/platform/goldfish/Kconfig5
-rw-r--r--drivers/platform/goldfish/Makefile5
-rw-r--r--drivers/platform/goldfish/goldfish_pipe.c612
-rw-r--r--drivers/platform/goldfish/pdev_bus.c240
-rw-r--r--drivers/platform/olpc/Makefile4
-rw-r--r--drivers/platform/olpc/olpc-ec.c336
-rw-r--r--drivers/platform/sprd/Kconfig266
-rw-r--r--drivers/platform/sprd/Makefile63
-rw-r--r--drivers/platform/sprd/adc.c1046
-rw-r--r--drivers/platform/sprd/adi.c380
-rw-r--r--drivers/platform/sprd/adie_irq.c229
-rw-r--r--drivers/platform/sprd/arch_init.c42
-rw-r--r--drivers/platform/sprd/arch_lock.c101
-rw-r--r--drivers/platform/sprd/arch_misc.c525
-rwxr-xr-xdrivers/platform/sprd/aux_debug.c148
-rwxr-xr-xdrivers/platform/sprd/avs_debug.c139
-rw-r--r--drivers/platform/sprd/boot_mode.c46
-rw-r--r--drivers/platform/sprd/busmonitor.c2111
-rw-r--r--drivers/platform/sprd/clock-sc8830_dt.c29
-rw-r--r--drivers/platform/sprd/clock.h148
-rw-r--r--drivers/platform/sprd/clock_dt.h131
-rw-r--r--drivers/platform/sprd/common.c72
-rw-r--r--drivers/platform/sprd/cpufreq-scx35.c1272
-rw-r--r--drivers/platform/sprd/cpuidle-scx35.c445
-rw-r--r--drivers/platform/sprd/dcdc_cal.c417
-rw-r--r--drivers/platform/sprd/dcdc_debug.c126
-rw-r--r--drivers/platform/sprd/devices.h169
-rw-r--r--drivers/platform/sprd/dhd_adapter.c497
-rw-r--r--drivers/platform/sprd/dma_r4p0.c890
-rw-r--r--drivers/platform/sprd/dmc_dfs_test.c112
-rwxr-xr-xdrivers/platform/sprd/dmc_freq.c816
-rw-r--r--drivers/platform/sprd/dmc_freq_28nm.c657
-rw-r--r--drivers/platform/sprd/dmc_freq_ap.c838
-rwxr-xr-xdrivers/platform/sprd/dmc_freq_ap_28nm.c551
-rw-r--r--drivers/platform/sprd/fix_v7_tag_ram_bug.S183
-rw-r--r--drivers/platform/sprd/glb.c85
-rw-r--r--drivers/platform/sprd/headsmp.S84
-rw-r--r--drivers/platform/sprd/hotplug.c167
-rw-r--r--drivers/platform/sprd/io-sc8830.c67
-rw-r--r--drivers/platform/sprd/iomap.c111
-rw-r--r--drivers/platform/sprd/irq.c319
-rw-r--r--drivers/platform/sprd/mailbox.c205
-rwxr-xr-xdrivers/platform/sprd/pin_switch.c559
-rw-r--r--drivers/platform/sprd/platsmp.c382
-rw-r--r--drivers/platform/sprd/pm-scx35.c1829
-rw-r--r--drivers/platform/sprd/pm.c171
-rw-r--r--drivers/platform/sprd/pm_debug.c723
-rw-r--r--drivers/platform/sprd/pm_debug_scx35.c982
-rwxr-xr-xdrivers/platform/sprd/reserve.c182
-rwxr-xr-xdrivers/platform/sprd/sec_gps_s5n6420.c140
-rw-r--r--drivers/platform/sprd/sprd_debug.c249
-rw-r--r--drivers/platform/sprd/sprd_mem_pool.c470
-rw-r--r--drivers/platform/sprd/sprd_vibrator_2723.c141
-rw-r--r--drivers/platform/sprd/standby-scx35.S848
-rwxr-xr-xdrivers/platform/sprd/sys_reset.c75
-rw-r--r--drivers/platform/sprd/sysdump64.c741
-rw-r--r--drivers/platform/sprd/sysdump64.h21
-rwxr-xr-xdrivers/platform/sprd/timer_sc8830.c515
-rwxr-xr-xdrivers/platform/sprd/timer_sharkl64.c281
-rw-r--r--drivers/platform/sprd/vibrator.c250
-rw-r--r--drivers/platform/x86/Kconfig792
-rw-r--r--drivers/platform/x86/Makefile55
-rw-r--r--drivers/platform/x86/acer-wmi.c2336
-rw-r--r--drivers/platform/x86/acerhdf.c739
-rw-r--r--drivers/platform/x86/amilo-rfkill.c176
-rw-r--r--drivers/platform/x86/apple-gmux.c633
-rw-r--r--drivers/platform/x86/asus-laptop.c2011
-rw-r--r--drivers/platform/x86/asus-nb-wmi.c284
-rw-r--r--drivers/platform/x86/asus-wmi.c2002
-rw-r--r--drivers/platform/x86/asus-wmi.h84
-rw-r--r--drivers/platform/x86/chromeos_laptop.c408
-rw-r--r--drivers/platform/x86/classmate-laptop.c1172
-rw-r--r--drivers/platform/x86/compal-laptop.c1100
-rw-r--r--drivers/platform/x86/dell-laptop.c636
-rw-r--r--drivers/platform/x86/dell-wmi-aio.c215
-rw-r--r--drivers/platform/x86/dell-wmi.c323
-rw-r--r--drivers/platform/x86/eeepc-laptop.c1573
-rw-r--r--drivers/platform/x86/eeepc-wmi.c278
-rw-r--r--drivers/platform/x86/fujitsu-laptop.c1250
-rw-r--r--drivers/platform/x86/fujitsu-tablet.c489
-rw-r--r--drivers/platform/x86/hdaps.c642
-rw-r--r--drivers/platform/x86/hp-wmi.c988
-rw-r--r--drivers/platform/x86/hp_accel.c391
-rw-r--r--drivers/platform/x86/ibm_rtl.c328
-rw-r--r--drivers/platform/x86/ideapad-laptop.c927
-rw-r--r--drivers/platform/x86/intel_ips.c1749
-rw-r--r--drivers/platform/x86/intel_ips.h21
-rw-r--r--drivers/platform/x86/intel_menlow.c543
-rw-r--r--drivers/platform/x86/intel_mid_powerbtn.c150
-rw-r--r--drivers/platform/x86/intel_mid_thermal.c574
-rw-r--r--drivers/platform/x86/intel_oaktrail.c397
-rw-r--r--drivers/platform/x86/intel_pmic_gpio.c328
-rw-r--r--drivers/platform/x86/intel_scu_ipc.c598
-rw-r--r--drivers/platform/x86/intel_scu_ipcutil.c121
-rw-r--r--drivers/platform/x86/msi-laptop.c1205
-rw-r--r--drivers/platform/x86/msi-wmi.c362
-rw-r--r--drivers/platform/x86/mxm-wmi.c111
-rw-r--r--drivers/platform/x86/panasonic-laptop.c691
-rw-r--r--drivers/platform/x86/pvpanic.c124
-rw-r--r--drivers/platform/x86/samsung-laptop.c1654
-rw-r--r--drivers/platform/x86/samsung-q10.c193
-rw-r--r--drivers/platform/x86/sony-laptop.c4530
-rw-r--r--drivers/platform/x86/tc1100-wmi.c283
-rw-r--r--drivers/platform/x86/thinkpad_acpi.c9163
-rw-r--r--drivers/platform/x86/topstar-laptop.c193
-rw-r--r--drivers/platform/x86/toshiba_acpi.c1373
-rw-r--r--drivers/platform/x86/toshiba_bluetooth.c131
-rw-r--r--drivers/platform/x86/wmi.c986
-rw-r--r--drivers/platform/x86/xo1-rfkill.c84
-rw-r--r--drivers/platform/x86/xo15-ebook.c173
112 files changed, 68834 insertions, 0 deletions
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
new file mode 100644
index 00000000..cadf57d2
--- /dev/null
+++ b/drivers/platform/Kconfig
@@ -0,0 +1,9 @@
+if X86
+source "drivers/platform/x86/Kconfig"
+endif
+if GOLDFISH
+source "drivers/platform/goldfish/Kconfig"
+endif
+if ARCH_SCX35
+source "drivers/platform/sprd/Kconfig"
+endif
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
new file mode 100644
index 00000000..3dadcc27
--- /dev/null
+++ b/drivers/platform/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for linux/drivers/platform
+#
+
+obj-$(CONFIG_X86) += x86/
+obj-$(CONFIG_OLPC) += olpc/
+obj-$(CONFIG_GOLDFISH) += goldfish/
+obj-$(CONFIG_ARCH_SCX35) += sprd/
diff --git a/drivers/platform/goldfish/Kconfig b/drivers/platform/goldfish/Kconfig
new file mode 100644
index 00000000..635ef25c
--- /dev/null
+++ b/drivers/platform/goldfish/Kconfig
@@ -0,0 +1,5 @@
+config GOLDFISH_PIPE
+ tristate "Goldfish virtual device for QEMU pipes"
+ ---help---
+ This is a virtual device to drive the QEMU pipe interface used by
+ the Goldfish Android Virtual Device.
diff --git a/drivers/platform/goldfish/Makefile b/drivers/platform/goldfish/Makefile
new file mode 100644
index 00000000..a0022395
--- /dev/null
+++ b/drivers/platform/goldfish/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for Goldfish platform specific drivers
+#
+obj-$(CONFIG_GOLDFISH) += pdev_bus.o
+obj-$(CONFIG_GOLDFISH_PIPE) += goldfish_pipe.o
diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c
new file mode 100644
index 00000000..4f5aa831
--- /dev/null
+++ b/drivers/platform/goldfish/goldfish_pipe.c
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2012 Intel, Inc.
+ * Copyright (C) 2013 Intel, 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.
+ *
+ */
+
+/* This source file contains the implementation of a special device driver
+ * that intends to provide a *very* fast communication channel between the
+ * guest system and the QEMU emulator.
+ *
+ * Usage from the guest is simply the following (error handling simplified):
+ *
+ * int fd = open("/dev/qemu_pipe",O_RDWR);
+ * .... write() or read() through the pipe.
+ *
+ * This driver doesn't deal with the exact protocol used during the session.
+ * It is intended to be as simple as something like:
+ *
+ * // do this _just_ after opening the fd to connect to a specific
+ * // emulator service.
+ * const char* msg = "<pipename>";
+ * if (write(fd, msg, strlen(msg)+1) < 0) {
+ * ... could not connect to <pipename> service
+ * close(fd);
+ * }
+ *
+ * // after this, simply read() and write() to communicate with the
+ * // service. Exact protocol details left as an exercise to the reader.
+ *
+ * This driver is very fast because it doesn't copy any data through
+ * intermediate buffers, since the emulator is capable of translating
+ * guest user addresses into host ones.
+ *
+ * Note that we must however ensure that each user page involved in the
+ * exchange is properly mapped during a transfer.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+/*
+ * IMPORTANT: The following constants must match the ones used and defined
+ * in external/qemu/hw/goldfish_pipe.c in the Android source tree.
+ */
+
+/* pipe device registers */
+#define PIPE_REG_COMMAND 0x00 /* write: value = command */
+#define PIPE_REG_STATUS 0x04 /* read */
+#define PIPE_REG_CHANNEL 0x08 /* read/write: channel id */
+#define PIPE_REG_SIZE 0x0c /* read/write: buffer size */
+#define PIPE_REG_ADDRESS 0x10 /* write: physical address */
+#define PIPE_REG_WAKES 0x14 /* read: wake flags */
+#define PIPE_REG_PARAMS_ADDR_LOW 0x18 /* read/write: batch data address */
+#define PIPE_REG_PARAMS_ADDR_HIGH 0x1c /* read/write: batch data address */
+#define PIPE_REG_ACCESS_PARAMS 0x20 /* write: batch access */
+
+/* list of commands for PIPE_REG_COMMAND */
+#define CMD_OPEN 1 /* open new channel */
+#define CMD_CLOSE 2 /* close channel (from guest) */
+#define CMD_POLL 3 /* poll read/write status */
+
+/* List of bitflags returned in status of CMD_POLL command */
+#define PIPE_POLL_IN (1 << 0)
+#define PIPE_POLL_OUT (1 << 1)
+#define PIPE_POLL_HUP (1 << 2)
+
+/* The following commands are related to write operations */
+#define CMD_WRITE_BUFFER 4 /* send a user buffer to the emulator */
+#define CMD_WAKE_ON_WRITE 5 /* tell the emulator to wake us when writing
+ is possible */
+
+/* The following commands are related to read operations, they must be
+ * listed in the same order than the corresponding write ones, since we
+ * will use (CMD_READ_BUFFER - CMD_WRITE_BUFFER) as a special offset
+ * in goldfish_pipe_read_write() below.
+ */
+#define CMD_READ_BUFFER 6 /* receive a user buffer from the emulator */
+#define CMD_WAKE_ON_READ 7 /* tell the emulator to wake us when reading
+ * is possible */
+
+/* Possible status values used to signal errors - see goldfish_pipe_error_convert */
+#define PIPE_ERROR_INVAL -1
+#define PIPE_ERROR_AGAIN -2
+#define PIPE_ERROR_NOMEM -3
+#define PIPE_ERROR_IO -4
+
+/* Bit-flags used to signal events from the emulator */
+#define PIPE_WAKE_CLOSED (1 << 0) /* emulator closed pipe */
+#define PIPE_WAKE_READ (1 << 1) /* pipe can now be read from */
+#define PIPE_WAKE_WRITE (1 << 2) /* pipe can now be written to */
+
+struct access_params {
+ u32 channel;
+ u32 size;
+ u32 address;
+ u32 cmd;
+ u32 result;
+ /* reserved for future extension */
+ u32 flags;
+};
+
+/* The global driver data. Holds a reference to the i/o page used to
+ * communicate with the emulator, and a wake queue for blocked tasks
+ * waiting to be awoken.
+ */
+struct goldfish_pipe_dev {
+ spinlock_t lock;
+ unsigned char __iomem *base;
+ struct access_params *aps;
+ int irq;
+};
+
+static struct goldfish_pipe_dev pipe_dev[1];
+
+/* This data type models a given pipe instance */
+struct goldfish_pipe {
+ struct goldfish_pipe_dev *dev;
+ struct mutex lock;
+ unsigned long flags;
+ wait_queue_head_t wake_queue;
+};
+
+
+/* Bit flags for the 'flags' field */
+enum {
+ BIT_CLOSED_ON_HOST = 0, /* pipe closed by host */
+ BIT_WAKE_ON_WRITE = 1, /* want to be woken on writes */
+ BIT_WAKE_ON_READ = 2, /* want to be woken on reads */
+};
+
+
+static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd)
+{
+ unsigned long flags;
+ u32 status;
+ struct goldfish_pipe_dev *dev = pipe->dev;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ writel((u32)pipe, dev->base + PIPE_REG_CHANNEL);
+ writel(cmd, dev->base + PIPE_REG_COMMAND);
+ status = readl(dev->base + PIPE_REG_STATUS);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return status;
+}
+
+static void goldfish_cmd(struct goldfish_pipe *pipe, u32 cmd)
+{
+ unsigned long flags;
+ struct goldfish_pipe_dev *dev = pipe->dev;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ writel((u32)pipe, dev->base + PIPE_REG_CHANNEL);
+ writel(cmd, dev->base + PIPE_REG_COMMAND);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* This function converts an error code returned by the emulator through
+ * the PIPE_REG_STATUS i/o register into a valid negative errno value.
+ */
+static int goldfish_pipe_error_convert(int status)
+{
+ switch (status) {
+ case PIPE_ERROR_AGAIN:
+ return -EAGAIN;
+ case PIPE_ERROR_NOMEM:
+ return -ENOMEM;
+ case PIPE_ERROR_IO:
+ return -EIO;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * Notice: QEMU will return 0 for un-known register access, indicating
+ * param_acess is supported or not
+ */
+static int valid_batchbuffer_addr(struct goldfish_pipe_dev *dev,
+ struct access_params *aps)
+{
+ u32 aph, apl;
+ u64 paddr;
+ aph = readl(dev->base + PIPE_REG_PARAMS_ADDR_HIGH);
+ apl = readl(dev->base + PIPE_REG_PARAMS_ADDR_LOW);
+
+ paddr = ((u64)aph << 32) | apl;
+ if (paddr != (__pa(aps)))
+ return 0;
+ return 1;
+}
+
+/* 0 on success */
+static int setup_access_params_addr(struct platform_device *pdev,
+ struct goldfish_pipe_dev *dev)
+{
+ u64 paddr;
+ struct access_params *aps;
+
+ aps = devm_kzalloc(&pdev->dev, sizeof(struct access_params), GFP_KERNEL);
+ if (!aps)
+ return -1;
+
+ /* FIXME */
+ paddr = __pa(aps);
+ writel((u32)(paddr >> 32), dev->base + PIPE_REG_PARAMS_ADDR_HIGH);
+ writel((u32)paddr, dev->base + PIPE_REG_PARAMS_ADDR_LOW);
+
+ if (valid_batchbuffer_addr(dev, aps)) {
+ dev->aps = aps;
+ return 0;
+ } else
+ return -1;
+}
+
+/* A value that will not be set by qemu emulator */
+#define INITIAL_BATCH_RESULT (0xdeadbeaf)
+static int access_with_param(struct goldfish_pipe_dev *dev, const int cmd,
+ unsigned long address, unsigned long avail,
+ struct goldfish_pipe *pipe, int *status)
+{
+ struct access_params *aps = dev->aps;
+
+ if (aps == NULL)
+ return -1;
+
+ aps->result = INITIAL_BATCH_RESULT;
+ aps->channel = (unsigned long)pipe;
+ aps->size = avail;
+ aps->address = address;
+ aps->cmd = cmd;
+ writel(cmd, dev->base + PIPE_REG_ACCESS_PARAMS);
+ /*
+ * If the aps->result has not changed, that means
+ * that the batch command failed
+ */
+ if (aps->result == INITIAL_BATCH_RESULT)
+ return -1;
+ *status = aps->result;
+ return 0;
+}
+
+/* This function is used for both reading from and writing to a given
+ * pipe.
+ */
+static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer,
+ size_t bufflen, int is_write)
+{
+ unsigned long irq_flags;
+ struct goldfish_pipe *pipe = filp->private_data;
+ struct goldfish_pipe_dev *dev = pipe->dev;
+ const int cmd_offset = is_write ? 0
+ : (CMD_READ_BUFFER - CMD_WRITE_BUFFER);
+ unsigned long address, address_end;
+ int ret = 0;
+
+ /* If the emulator already closed the pipe, no need to go further */
+ if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
+ return -EIO;
+
+ /* Null reads or writes succeeds */
+ if (unlikely(bufflen) == 0)
+ return 0;
+
+ /* Check the buffer range for access */
+ if (!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ,
+ buffer, bufflen))
+ return -EFAULT;
+
+ /* Serialize access to the pipe */
+ if (mutex_lock_interruptible(&pipe->lock))
+ return -ERESTARTSYS;
+
+ address = (unsigned long)(void *)buffer;
+ address_end = address + bufflen;
+
+ while (address < address_end) {
+ unsigned long page_end = (address & PAGE_MASK) + PAGE_SIZE;
+ unsigned long next = page_end < address_end ? page_end
+ : address_end;
+ unsigned long avail = next - address;
+ int status, wakeBit;
+
+ /* Ensure that the corresponding page is properly mapped */
+ /* FIXME: this isn't safe or sufficient - use get_user_pages */
+ if (is_write) {
+ char c;
+ /* Ensure that the page is mapped and readable */
+ if (__get_user(c, (char __user *)address)) {
+ if (!ret)
+ ret = -EFAULT;
+ break;
+ }
+ } else {
+ /* Ensure that the page is mapped and writable */
+ if (__put_user(0, (char __user *)address)) {
+ if (!ret)
+ ret = -EFAULT;
+ break;
+ }
+ }
+
+ /* Now, try to transfer the bytes in the current page */
+ spin_lock_irqsave(&dev->lock, irq_flags);
+ if (access_with_param(dev, CMD_WRITE_BUFFER + cmd_offset,
+ address, avail, pipe, &status)) {
+ writel((u32)pipe, dev->base + PIPE_REG_CHANNEL);
+ writel(avail, dev->base + PIPE_REG_SIZE);
+ writel(address, dev->base + PIPE_REG_ADDRESS);
+ writel(CMD_WRITE_BUFFER + cmd_offset,
+ dev->base + PIPE_REG_COMMAND);
+ status = readl(dev->base + PIPE_REG_STATUS);
+ }
+ spin_unlock_irqrestore(&dev->lock, irq_flags);
+
+ if (status > 0) { /* Correct transfer */
+ ret += status;
+ address += status;
+ continue;
+ }
+
+ if (status == 0) /* EOF */
+ break;
+
+ /* An error occured. If we already transfered stuff, just
+ * return with its count. We expect the next call to return
+ * an error code */
+ if (ret > 0)
+ break;
+
+ /* If the error is not PIPE_ERROR_AGAIN, or if we are not in
+ * non-blocking mode, just return the error code.
+ */
+ if (status != PIPE_ERROR_AGAIN ||
+ (filp->f_flags & O_NONBLOCK) != 0) {
+ ret = goldfish_pipe_error_convert(status);
+ break;
+ }
+
+ /* We will have to wait until more data/space is available.
+ * First, mark the pipe as waiting for a specific wake signal.
+ */
+ wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ;
+ set_bit(wakeBit, &pipe->flags);
+
+ /* Tell the emulator we're going to wait for a wake event */
+ goldfish_cmd(pipe, CMD_WAKE_ON_WRITE + cmd_offset);
+
+ /* Unlock the pipe, then wait for the wake signal */
+ mutex_unlock(&pipe->lock);
+
+ while (test_bit(wakeBit, &pipe->flags)) {
+ if (wait_event_interruptible(
+ pipe->wake_queue,
+ !test_bit(wakeBit, &pipe->flags)))
+ return -ERESTARTSYS;
+
+ if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
+ return -EIO;
+ }
+
+ /* Try to re-acquire the lock */
+ if (mutex_lock_interruptible(&pipe->lock))
+ return -ERESTARTSYS;
+
+ /* Try the transfer again */
+ continue;
+ }
+ mutex_unlock(&pipe->lock);
+ return ret;
+}
+
+static ssize_t goldfish_pipe_read(struct file *filp, char __user *buffer,
+ size_t bufflen, loff_t *ppos)
+{
+ return goldfish_pipe_read_write(filp, buffer, bufflen, 0);
+}
+
+static ssize_t goldfish_pipe_write(struct file *filp,
+ const char __user *buffer, size_t bufflen,
+ loff_t *ppos)
+{
+ return goldfish_pipe_read_write(filp, (char __user *)buffer,
+ bufflen, 1);
+}
+
+
+static unsigned int goldfish_pipe_poll(struct file *filp, poll_table *wait)
+{
+ struct goldfish_pipe *pipe = filp->private_data;
+ unsigned int mask = 0;
+ int status;
+
+ mutex_lock(&pipe->lock);
+
+ poll_wait(filp, &pipe->wake_queue, wait);
+
+ status = goldfish_cmd_status(pipe, CMD_POLL);
+
+ mutex_unlock(&pipe->lock);
+
+ if (status & PIPE_POLL_IN)
+ mask |= POLLIN | POLLRDNORM;
+
+ if (status & PIPE_POLL_OUT)
+ mask |= POLLOUT | POLLWRNORM;
+
+ if (status & PIPE_POLL_HUP)
+ mask |= POLLHUP;
+
+ if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
+ mask |= POLLERR;
+
+ return mask;
+}
+
+static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id)
+{
+ struct goldfish_pipe_dev *dev = dev_id;
+ unsigned long irq_flags;
+ int count = 0;
+
+ /* We're going to read from the emulator a list of (channel,flags)
+ * pairs corresponding to the wake events that occured on each
+ * blocked pipe (i.e. channel).
+ */
+ spin_lock_irqsave(&dev->lock, irq_flags);
+ for (;;) {
+ /* First read the channel, 0 means the end of the list */
+ struct goldfish_pipe *pipe;
+ unsigned long wakes;
+ unsigned long channel = readl(dev->base + PIPE_REG_CHANNEL);
+
+ if (channel == 0)
+ break;
+
+ /* Convert channel to struct pipe pointer + read wake flags */
+ wakes = readl(dev->base + PIPE_REG_WAKES);
+ pipe = (struct goldfish_pipe *)(ptrdiff_t)channel;
+
+ /* Did the emulator just closed a pipe? */
+ if (wakes & PIPE_WAKE_CLOSED) {
+ set_bit(BIT_CLOSED_ON_HOST, &pipe->flags);
+ wakes |= PIPE_WAKE_READ | PIPE_WAKE_WRITE;
+ }
+ if (wakes & PIPE_WAKE_READ)
+ clear_bit(BIT_WAKE_ON_READ, &pipe->flags);
+ if (wakes & PIPE_WAKE_WRITE)
+ clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags);
+
+ wake_up_interruptible(&pipe->wake_queue);
+ count++;
+ }
+ spin_unlock_irqrestore(&dev->lock, irq_flags);
+
+ return (count == 0) ? IRQ_NONE : IRQ_HANDLED;
+}
+
+/**
+ * goldfish_pipe_open - open a channel to the AVD
+ * @inode: inode of device
+ * @file: file struct of opener
+ *
+ * Create a new pipe link between the emulator and the use application.
+ * Each new request produces a new pipe.
+ *
+ * Note: we use the pipe ID as a mux. All goldfish emulations are 32bit
+ * right now so this is fine. A move to 64bit will need this addressing
+ */
+static int goldfish_pipe_open(struct inode *inode, struct file *file)
+{
+ struct goldfish_pipe *pipe;
+ struct goldfish_pipe_dev *dev = pipe_dev;
+ int32_t status;
+
+ /* Allocate new pipe kernel object */
+ pipe = kzalloc(sizeof(*pipe), GFP_KERNEL);
+ if (pipe == NULL)
+ return -ENOMEM;
+
+ pipe->dev = dev;
+ mutex_init(&pipe->lock);
+ init_waitqueue_head(&pipe->wake_queue);
+
+ /*
+ * Now, tell the emulator we're opening a new pipe. We use the
+ * pipe object's address as the channel identifier for simplicity.
+ */
+
+ status = goldfish_cmd_status(pipe, CMD_OPEN);
+ if (status < 0) {
+ kfree(pipe);
+ return status;
+ }
+
+ /* All is done, save the pipe into the file's private data field */
+ file->private_data = pipe;
+ return 0;
+}
+
+static int goldfish_pipe_release(struct inode *inode, struct file *filp)
+{
+ struct goldfish_pipe *pipe = filp->private_data;
+
+ /* The guest is closing the channel, so tell the emulator right now */
+ goldfish_cmd(pipe, CMD_CLOSE);
+ kfree(pipe);
+ filp->private_data = NULL;
+ return 0;
+}
+
+static const struct file_operations goldfish_pipe_fops = {
+ .owner = THIS_MODULE,
+ .read = goldfish_pipe_read,
+ .write = goldfish_pipe_write,
+ .poll = goldfish_pipe_poll,
+ .open = goldfish_pipe_open,
+ .release = goldfish_pipe_release,
+};
+
+static struct miscdevice goldfish_pipe_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "goldfish_pipe",
+ .fops = &goldfish_pipe_fops,
+};
+
+static int goldfish_pipe_probe(struct platform_device *pdev)
+{
+ int err;
+ struct resource *r;
+ struct goldfish_pipe_dev *dev = pipe_dev;
+
+ /* not thread safe, but this should not happen */
+ WARN_ON(dev->base != NULL);
+
+ spin_lock_init(&dev->lock);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL || resource_size(r) < PAGE_SIZE) {
+ dev_err(&pdev->dev, "can't allocate i/o page\n");
+ return -EINVAL;
+ }
+ dev->base = devm_ioremap(&pdev->dev, r->start, PAGE_SIZE);
+ if (dev->base == NULL) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ return -EINVAL;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (r == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+ dev->irq = r->start;
+
+ err = devm_request_irq(&pdev->dev, dev->irq, goldfish_pipe_interrupt,
+ IRQF_SHARED, "goldfish_pipe", dev);
+ if (err) {
+ dev_err(&pdev->dev, "unable to allocate IRQ\n");
+ goto error;
+ }
+
+ err = misc_register(&goldfish_pipe_device);
+ if (err) {
+ dev_err(&pdev->dev, "unable to register device\n");
+ goto error;
+ }
+ setup_access_params_addr(pdev, dev);
+ return 0;
+
+error:
+ dev->base = NULL;
+ return err;
+}
+
+static int goldfish_pipe_remove(struct platform_device *pdev)
+{
+ struct goldfish_pipe_dev *dev = pipe_dev;
+ misc_deregister(&goldfish_pipe_device);
+ dev->base = NULL;
+ return 0;
+}
+
+static struct platform_driver goldfish_pipe = {
+ .probe = goldfish_pipe_probe,
+ .remove = goldfish_pipe_remove,
+ .driver = {
+ .name = "goldfish_pipe"
+ }
+};
+
+module_platform_driver(goldfish_pipe);
+MODULE_AUTHOR("David Turner <digit@google.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/goldfish/pdev_bus.c b/drivers/platform/goldfish/pdev_bus.c
new file mode 100644
index 00000000..92cc4cfa
--- /dev/null
+++ b/drivers/platform/goldfish/pdev_bus.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (C) 2011 Intel, Inc.
+ * Copyright (C) 2013 Intel, 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#define PDEV_BUS_OP_DONE (0x00)
+#define PDEV_BUS_OP_REMOVE_DEV (0x04)
+#define PDEV_BUS_OP_ADD_DEV (0x08)
+
+#define PDEV_BUS_OP_INIT (0x00)
+
+#define PDEV_BUS_OP (0x00)
+#define PDEV_BUS_GET_NAME (0x04)
+#define PDEV_BUS_NAME_LEN (0x08)
+#define PDEV_BUS_ID (0x0c)
+#define PDEV_BUS_IO_BASE (0x10)
+#define PDEV_BUS_IO_SIZE (0x14)
+#define PDEV_BUS_IRQ (0x18)
+#define PDEV_BUS_IRQ_COUNT (0x1c)
+
+struct pdev_bus_dev {
+ struct list_head list;
+ struct platform_device pdev;
+ struct resource resources[0];
+};
+
+static void goldfish_pdev_worker(struct work_struct *work);
+
+static void __iomem *pdev_bus_base;
+static unsigned long pdev_bus_addr;
+static unsigned long pdev_bus_len;
+static u32 pdev_bus_irq;
+static LIST_HEAD(pdev_bus_new_devices);
+static LIST_HEAD(pdev_bus_registered_devices);
+static LIST_HEAD(pdev_bus_removed_devices);
+static DECLARE_WORK(pdev_bus_worker, goldfish_pdev_worker);
+
+
+static void goldfish_pdev_worker(struct work_struct *work)
+{
+ int ret;
+ struct pdev_bus_dev *pos, *n;
+
+ list_for_each_entry_safe(pos, n, &pdev_bus_removed_devices, list) {
+ list_del(&pos->list);
+ platform_device_unregister(&pos->pdev);
+ kfree(pos);
+ }
+ list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) {
+ list_del(&pos->list);
+ ret = platform_device_register(&pos->pdev);
+ if (ret)
+ pr_err("goldfish_pdev_worker failed to register device, %s\n",
+ pos->pdev.name);
+ list_add_tail(&pos->list, &pdev_bus_registered_devices);
+ }
+}
+
+static void goldfish_pdev_remove(void)
+{
+ struct pdev_bus_dev *pos, *n;
+ u32 base;
+
+ base = readl(pdev_bus_base + PDEV_BUS_IO_BASE);
+
+ list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) {
+ if (pos->resources[0].start == base) {
+ list_del(&pos->list);
+ kfree(pos);
+ return;
+ }
+ }
+ list_for_each_entry_safe(pos, n, &pdev_bus_registered_devices, list) {
+ if (pos->resources[0].start == base) {
+ list_del(&pos->list);
+ list_add_tail(&pos->list, &pdev_bus_removed_devices);
+ schedule_work(&pdev_bus_worker);
+ return;
+ }
+ };
+ pr_err("goldfish_pdev_remove could not find device at %x\n", base);
+}
+
+static int goldfish_new_pdev(void)
+{
+ struct pdev_bus_dev *dev;
+ u32 name_len;
+ u32 irq = -1, irq_count;
+ int resource_count = 2;
+ u32 base;
+ char *name;
+
+ base = readl(pdev_bus_base + PDEV_BUS_IO_BASE);
+
+ irq_count = readl(pdev_bus_base + PDEV_BUS_IRQ_COUNT);
+ name_len = readl(pdev_bus_base + PDEV_BUS_NAME_LEN);
+ if (irq_count)
+ resource_count++;
+
+ dev = kzalloc(sizeof(*dev) +
+ sizeof(struct resource) * resource_count +
+ name_len + 1 + sizeof(*dev->pdev.dev.dma_mask), GFP_ATOMIC);
+ if (dev == NULL)
+ return -ENOMEM;
+
+ dev->pdev.num_resources = resource_count;
+ dev->pdev.resource = (struct resource *)(dev + 1);
+ dev->pdev.name = name = (char *)(dev->pdev.resource + resource_count);
+ dev->pdev.dev.coherent_dma_mask = ~0;
+ dev->pdev.dev.dma_mask = (void *)(dev->pdev.name + name_len + 1);
+ *dev->pdev.dev.dma_mask = ~0;
+
+ writel((unsigned long)name, pdev_bus_base + PDEV_BUS_GET_NAME);
+ name[name_len] = '\0';
+ dev->pdev.id = readl(pdev_bus_base + PDEV_BUS_ID);
+ dev->pdev.resource[0].start = base;
+ dev->pdev.resource[0].end = base +
+ readl(pdev_bus_base + PDEV_BUS_IO_SIZE) - 1;
+ dev->pdev.resource[0].flags = IORESOURCE_MEM;
+ if (irq_count) {
+ irq = readl(pdev_bus_base + PDEV_BUS_IRQ);
+ dev->pdev.resource[1].start = irq;
+ dev->pdev.resource[1].end = irq + irq_count - 1;
+ dev->pdev.resource[1].flags = IORESOURCE_IRQ;
+ }
+
+ pr_debug("goldfish_new_pdev %s at %x irq %d\n", name, base, irq);
+ list_add_tail(&dev->list, &pdev_bus_new_devices);
+ schedule_work(&pdev_bus_worker);
+
+ return 0;
+}
+
+static irqreturn_t goldfish_pdev_bus_interrupt(int irq, void *dev_id)
+{
+ irqreturn_t ret = IRQ_NONE;
+ while (1) {
+ u32 op = readl(pdev_bus_base + PDEV_BUS_OP);
+ switch (op) {
+ case PDEV_BUS_OP_DONE:
+ return IRQ_NONE;
+
+ case PDEV_BUS_OP_REMOVE_DEV:
+ goldfish_pdev_remove();
+ break;
+
+ case PDEV_BUS_OP_ADD_DEV:
+ goldfish_new_pdev();
+ break;
+ }
+ ret = IRQ_HANDLED;
+ }
+ return ret;
+}
+
+static int goldfish_pdev_bus_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *r;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL)
+ return -EINVAL;
+
+ pdev_bus_addr = r->start;
+ pdev_bus_len = resource_size(r);
+
+ if (request_mem_region(pdev_bus_addr, pdev_bus_len, "goldfish")) {
+ dev_err(&pdev->dev, "unable to reserve Goldfish MMIO.\n");
+ return -EBUSY;
+ }
+
+ pdev_bus_base = ioremap(pdev_bus_addr, pdev_bus_len);
+ if (pdev_bus_base == NULL) {
+ ret = -ENOMEM;
+ dev_err(&pdev->dev, "unable to map Goldfish MMIO.\n");
+ goto free_resources;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (r == NULL) {
+ ret = -ENOENT;
+ goto free_map;
+ }
+
+ pdev_bus_irq = r->start;
+
+ ret = request_irq(pdev_bus_irq, goldfish_pdev_bus_interrupt,
+ IRQF_SHARED, "goldfish_pdev_bus", pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to request Goldfish IRQ\n");
+ goto free_map;
+ }
+
+ writel(PDEV_BUS_OP_INIT, pdev_bus_base + PDEV_BUS_OP);
+ return 0;
+
+free_map:
+ iounmap(pdev_bus_base);
+free_resources:
+ release_mem_region(pdev_bus_addr, pdev_bus_len);
+ return ret;
+}
+
+static int goldfish_pdev_bus_remove(struct platform_device *pdev)
+{
+ iounmap(pdev_bus_base);
+ free_irq(pdev_bus_irq, pdev);
+ release_mem_region(pdev_bus_addr, pdev_bus_len);
+ return 0;
+}
+
+static struct platform_driver goldfish_pdev_bus_driver = {
+ .probe = goldfish_pdev_bus_probe,
+ .remove = goldfish_pdev_bus_remove,
+ .driver = {
+ .name = "goldfish_pdev_bus"
+ }
+};
+
+module_platform_driver(goldfish_pdev_bus_driver);
diff --git a/drivers/platform/olpc/Makefile b/drivers/platform/olpc/Makefile
new file mode 100644
index 00000000..dc8b26bc
--- /dev/null
+++ b/drivers/platform/olpc/Makefile
@@ -0,0 +1,4 @@
+#
+# OLPC XO platform-specific drivers
+#
+obj-$(CONFIG_OLPC) += olpc-ec.o
diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c
new file mode 100644
index 00000000..f9119525
--- /dev/null
+++ b/drivers/platform/olpc/olpc-ec.c
@@ -0,0 +1,336 @@
+/*
+ * Generic driver for the OLPC Embedded Controller.
+ *
+ * Copyright (C) 2011-2012 One Laptop per Child Foundation.
+ *
+ * Licensed under the GPL v2 or later.
+ */
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/olpc-ec.h>
+#include <asm/olpc.h>
+
+struct ec_cmd_desc {
+ u8 cmd;
+ u8 *inbuf, *outbuf;
+ size_t inlen, outlen;
+
+ int err;
+ struct completion finished;
+ struct list_head node;
+
+ void *priv;
+};
+
+struct olpc_ec_priv {
+ struct olpc_ec_driver *drv;
+ struct work_struct worker;
+ struct mutex cmd_lock;
+
+ /* Pending EC commands */
+ struct list_head cmd_q;
+ spinlock_t cmd_q_lock;
+
+ struct dentry *dbgfs_dir;
+
+ /*
+ * Running an EC command while suspending means we don't always finish
+ * the command before the machine suspends. This means that the EC
+ * is expecting the command protocol to finish, but we after a period
+ * of time (while the OS is asleep) the EC times out and restarts its
+ * idle loop. Meanwhile, the OS wakes up, thinks it's still in the
+ * middle of the command protocol, starts throwing random things at
+ * the EC... and everyone's uphappy.
+ */
+ bool suspended;
+};
+
+static struct olpc_ec_driver *ec_driver;
+static struct olpc_ec_priv *ec_priv;
+static void *ec_cb_arg;
+
+void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg)
+{
+ ec_driver = drv;
+ ec_cb_arg = arg;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_driver_register);
+
+static void olpc_ec_worker(struct work_struct *w)
+{
+ struct olpc_ec_priv *ec = container_of(w, struct olpc_ec_priv, worker);
+ struct ec_cmd_desc *desc = NULL;
+ unsigned long flags;
+
+ /* Grab the first pending command from the queue */
+ spin_lock_irqsave(&ec->cmd_q_lock, flags);
+ if (!list_empty(&ec->cmd_q)) {
+ desc = list_first_entry(&ec->cmd_q, struct ec_cmd_desc, node);
+ list_del(&desc->node);
+ }
+ spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
+
+ /* Do we actually have anything to do? */
+ if (!desc)
+ return;
+
+ /* Protect the EC hw with a mutex; only run one cmd at a time */
+ mutex_lock(&ec->cmd_lock);
+ desc->err = ec_driver->ec_cmd(desc->cmd, desc->inbuf, desc->inlen,
+ desc->outbuf, desc->outlen, ec_cb_arg);
+ mutex_unlock(&ec->cmd_lock);
+
+ /* Finished, wake up olpc_ec_cmd() */
+ complete(&desc->finished);
+
+ /* Run the worker thread again in case there are more cmds pending */
+ schedule_work(&ec->worker);
+}
+
+/*
+ * Throw a cmd descripter onto the list. We now have SMP OLPC machines, so
+ * locking is pretty critical.
+ */
+static void queue_ec_descriptor(struct ec_cmd_desc *desc,
+ struct olpc_ec_priv *ec)
+{
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&desc->node);
+
+ spin_lock_irqsave(&ec->cmd_q_lock, flags);
+ list_add_tail(&desc->node, &ec->cmd_q);
+ spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
+
+ schedule_work(&ec->worker);
+}
+
+int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
+{
+ struct olpc_ec_priv *ec = ec_priv;
+ struct ec_cmd_desc desc;
+
+ /* Ensure a driver and ec hook have been registered */
+ if (WARN_ON(!ec_driver || !ec_driver->ec_cmd))
+ return -ENODEV;
+
+ if (!ec)
+ return -ENOMEM;
+
+ /* Suspending in the middle of a command hoses things really badly */
+ if (WARN_ON(ec->suspended))
+ return -EBUSY;
+
+ might_sleep();
+
+ desc.cmd = cmd;
+ desc.inbuf = inbuf;
+ desc.outbuf = outbuf;
+ desc.inlen = inlen;
+ desc.outlen = outlen;
+ desc.err = 0;
+ init_completion(&desc.finished);
+
+ queue_ec_descriptor(&desc, ec);
+
+ /* Timeouts must be handled in the platform-specific EC hook */
+ wait_for_completion(&desc.finished);
+
+ /* The worker thread dequeues the cmd; no need to do anything here */
+ return desc.err;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_cmd);
+
+#ifdef CONFIG_DEBUG_FS
+
+/*
+ * debugfs support for "generic commands", to allow sending
+ * arbitrary EC commands from userspace.
+ */
+
+#define EC_MAX_CMD_ARGS (5 + 1) /* cmd byte + 5 args */
+#define EC_MAX_CMD_REPLY (8)
+
+static DEFINE_MUTEX(ec_dbgfs_lock);
+static unsigned char ec_dbgfs_resp[EC_MAX_CMD_REPLY];
+static unsigned int ec_dbgfs_resp_bytes;
+
+static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ int i, m;
+ unsigned char ec_cmd[EC_MAX_CMD_ARGS];
+ unsigned int ec_cmd_int[EC_MAX_CMD_ARGS];
+ char cmdbuf[64];
+ int ec_cmd_bytes;
+
+ mutex_lock(&ec_dbgfs_lock);
+
+ size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf), ppos, buf, size);
+
+ m = sscanf(cmdbuf, "%x:%u %x %x %x %x %x", &ec_cmd_int[0],
+ &ec_dbgfs_resp_bytes, &ec_cmd_int[1], &ec_cmd_int[2],
+ &ec_cmd_int[3], &ec_cmd_int[4], &ec_cmd_int[5]);
+ if (m < 2 || ec_dbgfs_resp_bytes > EC_MAX_CMD_REPLY) {
+ /* reset to prevent overflow on read */
+ ec_dbgfs_resp_bytes = 0;
+
+ pr_debug("olpc-ec: bad ec cmd: cmd:response-count [arg1 [arg2 ...]]\n");
+ size = -EINVAL;
+ goto out;
+ }
+
+ /* convert scanf'd ints to char */
+ ec_cmd_bytes = m - 2;
+ for (i = 0; i <= ec_cmd_bytes; i++)
+ ec_cmd[i] = ec_cmd_int[i];
+
+ pr_debug("olpc-ec: debugfs cmd 0x%02x with %d args %02x %02x %02x %02x %02x, want %d returns\n",
+ ec_cmd[0], ec_cmd_bytes, ec_cmd[1], ec_cmd[2],
+ ec_cmd[3], ec_cmd[4], ec_cmd[5], ec_dbgfs_resp_bytes);
+
+ olpc_ec_cmd(ec_cmd[0], (ec_cmd_bytes == 0) ? NULL : &ec_cmd[1],
+ ec_cmd_bytes, ec_dbgfs_resp, ec_dbgfs_resp_bytes);
+
+ pr_debug("olpc-ec: response %02x %02x %02x %02x %02x %02x %02x %02x (%d bytes expected)\n",
+ ec_dbgfs_resp[0], ec_dbgfs_resp[1], ec_dbgfs_resp[2],
+ ec_dbgfs_resp[3], ec_dbgfs_resp[4], ec_dbgfs_resp[5],
+ ec_dbgfs_resp[6], ec_dbgfs_resp[7],
+ ec_dbgfs_resp_bytes);
+
+out:
+ mutex_unlock(&ec_dbgfs_lock);
+ return size;
+}
+
+static ssize_t ec_dbgfs_cmd_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ unsigned int i, r;
+ char *rp;
+ char respbuf[64];
+
+ mutex_lock(&ec_dbgfs_lock);
+ rp = respbuf;
+ rp += sprintf(rp, "%02x", ec_dbgfs_resp[0]);
+ for (i = 1; i < ec_dbgfs_resp_bytes; i++)
+ rp += sprintf(rp, ", %02x", ec_dbgfs_resp[i]);
+ mutex_unlock(&ec_dbgfs_lock);
+ rp += sprintf(rp, "\n");
+
+ r = rp - respbuf;
+ return simple_read_from_buffer(buf, size, ppos, respbuf, r);
+}
+
+static const struct file_operations ec_dbgfs_ops = {
+ .write = ec_dbgfs_cmd_write,
+ .read = ec_dbgfs_cmd_read,
+};
+
+static struct dentry *olpc_ec_setup_debugfs(void)
+{
+ struct dentry *dbgfs_dir;
+
+ dbgfs_dir = debugfs_create_dir("olpc-ec", NULL);
+ if (IS_ERR_OR_NULL(dbgfs_dir))
+ return NULL;
+
+ debugfs_create_file("cmd", 0600, dbgfs_dir, NULL, &ec_dbgfs_ops);
+
+ return dbgfs_dir;
+}
+
+#else
+
+static struct dentry *olpc_ec_setup_debugfs(void)
+{
+ return NULL;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int olpc_ec_probe(struct platform_device *pdev)
+{
+ struct olpc_ec_priv *ec;
+ int err;
+
+ if (!ec_driver)
+ return -ENODEV;
+
+ ec = kzalloc(sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ ec->drv = ec_driver;
+ INIT_WORK(&ec->worker, olpc_ec_worker);
+ mutex_init(&ec->cmd_lock);
+
+ INIT_LIST_HEAD(&ec->cmd_q);
+ spin_lock_init(&ec->cmd_q_lock);
+
+ ec_priv = ec;
+ platform_set_drvdata(pdev, ec);
+
+ err = ec_driver->probe ? ec_driver->probe(pdev) : 0;
+ if (err) {
+ ec_priv = NULL;
+ kfree(ec);
+ } else {
+ ec->dbgfs_dir = olpc_ec_setup_debugfs();
+ }
+
+ return err;
+}
+
+static int olpc_ec_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
+ int err = 0;
+
+ if (ec_driver->suspend)
+ err = ec_driver->suspend(pdev);
+ if (!err)
+ ec->suspended = true;
+
+ return err;
+}
+
+static int olpc_ec_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
+
+ ec->suspended = false;
+ return ec_driver->resume ? ec_driver->resume(pdev) : 0;
+}
+
+static const struct dev_pm_ops olpc_ec_pm_ops = {
+ .suspend_late = olpc_ec_suspend,
+ .resume_early = olpc_ec_resume,
+};
+
+static struct platform_driver olpc_ec_plat_driver = {
+ .probe = olpc_ec_probe,
+ .driver = {
+ .name = "olpc-ec",
+ .pm = &olpc_ec_pm_ops,
+ },
+};
+
+static int __init olpc_ec_init_module(void)
+{
+ return platform_driver_register(&olpc_ec_plat_driver);
+}
+
+arch_initcall(olpc_ec_init_module);
+
+MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/sprd/Kconfig b/drivers/platform/sprd/Kconfig
new file mode 100644
index 00000000..67f52cd9
--- /dev/null
+++ b/drivers/platform/sprd/Kconfig
@@ -0,0 +1,266 @@
+#
+# SPREADTRUM Platform Specific Drivers
+#
+menu "Spreadtrum Platform Specific Drivers"
+
+menu "Spreadtrum Adie Type"
+config ADIE_SC2713
+ bool "Spredtrum A-die SC2713 Chip Support"
+ default n
+ help
+ Say Y here to support for Spreadtrum a-die sc2713 chip.
+
+config ADIE_SC2713S
+ bool "Spredtrum A-die SC2713S Chip Support"
+ default n
+ help
+ Say Y here to support for Spreadtrum a-die sc2713s chip.
+
+config ADIE_SC2711
+ bool "Spredtrum A-die SC2711 Chip Support"
+ default n
+ help
+ Say Y here to support for Spreadtrum a-die sc2711 chip.
+
+config ADIE_SC2723
+ bool "Spredtrum A-die SC2723 Chip Support"
+ default n
+ help
+ Say Y here to support for Spreadtrum a-die sc2723 chip.
+
+config ADIE_SC2723S
+ bool "Spredtrum A-die SC2723S Chip Support"
+ default n
+ help
+ Say Y here to support for Spreadtrum a-die sc2723S chip.
+endmenu
+
+config BUS_MONITOR_DEBUG
+ bool "Spreadtrum BM Support"
+ default y
+ help
+ Say Y here to support debug busmonitor
+
+config SC_FPGA
+ bool "Spreadtrum FPGA support"
+ default n
+ help
+ This is used for FPGA verification
+
+config SCX35L64BIT_FPGA
+ bool "Spreadtrum FPGA platform"
+ depends on ARCH_SCX35L64
+ select SC_FPGA
+ default n
+ help
+ SCX35L64BIT_FPGA fpga platform
+
+config SC_VIBRATOR
+ bool "vibrator for SC serials"
+ select ANDROID_TIMED_OUTPUT
+ default n
+
+config SPRD_VIBRATOR_2723
+ bool "vibrator for sprd_vibrator_2723"
+ select ANDROID_TIMED_OUTPUT
+ default n
+
+config SC_VIBRATOR_GPIO
+ bool "vibrator for SC serials by gpio"
+ default n
+ depends on SC_VIBRATOR
+
+config SC_VIBRATOR_POWER
+ bool "vibrator for SC serials by power"
+ default n
+ depends on SC_VIBRATOR
+
+config SPRD_ION_MM_SIZE
+ int "size for ion"
+ range 1 64
+ default 1
+ depends on ION
+
+config SPRD_ION_OVERLAY_SIZE
+ int "size for ion overlay"
+ range 1 64
+ default 1
+ depends on ION
+
+config SPRD_VIBRATOR_2723
+ bool "vibrator for sprd_vibrator_2723"
+ select ANDROID_TIMED_OUTPUT
+ default n
+
+config EIRQ_NUM
+ int "number for external irq region desc request"
+ range 0 1024
+ default 0
+
+config PIN_POWER_DOMAIN_SWITCH
+ bool "pin power domain switch for 28nm chip"
+ default n
+
+comment "SC8830 ADIE type"
+
+config SC_INTERNAL_ADI
+ bool "spreadtrum sc internal adi"
+ default y
+ depends on ARCH_SCX35
+
+config SC_INTERNAL_ADC
+ bool "spreadtrum sc internal adc"
+ default y
+ depends on ARCH_SCX35
+
+config SC_INTERNAL_WATCHDOG
+ bool "spreadtrum sc internal watchdog"
+ default y
+ depends on ARCH_SCX35
+
+config FIX_V7TAGRAM_BUG
+ bool "fix arm-v7 scu tag ram bug"
+ default n
+ depends on ARCH_SCX35
+ depends on SMP
+
+config DDR_VALIDITY_TEST
+ bool "test ddr validity before go into ddr"
+ default n
+
+config SPRD_CPU_DYNAMIC_HOTPLUG
+ bool "cpu dynamic hotplug with DVFS"
+ default n
+ depends on HOTPLUG_CPU
+ depends on ARCH_SCX35
+
+config SPRD_MEM_POOL
+ default y
+ bool "Using a memory pool to alloc 8k to 64k"
+ depends on !COMPACTION
+ help
+ This option enables memmory pool for allocating 8k to 64k.
+
+config SPRD_DCDC_DEBUG
+ tristate "Enable dcdc debug module"
+ default n
+ help
+ This option select dcdc debug module.
+
+config SPRD_AVS_DEBUG
+ tristate "Enable avs debug module"
+ default n
+ help
+ This option select avs debug module.
+
+config SPRD_AUX_DEBUG
+ tristate "Enable aux debug moduel"
+ default n
+ help
+ This option select aux debug module.
+
+config SPRD_SIMCTRL
+ bool "Enable simctrl module"
+ default n
+ help
+ This option select simctrl module.
+config SPRD_MODEM_TD
+ bool "SPRD TD modem"
+ default n
+ help
+ This option select modem type.
+
+config MODEM_W_MEMCUT
+ bool "SPRD w modem memory cut"
+ default n
+ help
+ This option select for w modem memory cut.
+
+config SIPC_TD
+ bool "SIPC instance of TD modem"
+ select SIPC
+ default n
+
+config SIPC_WCDMA
+ bool "SIPC instance of WCDMA modem"
+ select SIPC
+ default n
+
+config SIPC_WCN
+ bool "SIPC instance of Wireless Connection"
+ select SIPC
+ default n
+
+config SIPC_PMIC
+ bool "SIPC instance of PMIC arm7 modem"
+ select SIPC
+ default n
+
+
+config SIPC_GGE
+ bool "SIPC instance of GGE modem"
+ select SIPC
+ default n
+
+config SIPC_LTE
+ bool "SIPC instance of LTE modem"
+ select SIPC
+ default n
+
+config VOIP_CPT
+ bool "VOIP process in modem T,it is processed in modem W by default"
+ default n
+ depends on SIPC_TD
+
+config VOIP_LTE
+ bool "VOIP process in modem LTE,it is processed in modem W by default"
+ default n
+ depends on SIPC_LTE
+
+config CP_SWITCH
+ bool "cp switch"
+ default n
+ help
+ This option select cp switch
+
+config EAR_LOW_LEVEL_DETECT
+ bool "ear detecting trigger is low level"
+ default n
+config SS_FUNCTION
+ bool "only for samsung function"
+ default n
+config TSP_0_1_A
+ bool "For corsica_ve 3131 0.1A board"
+ default n
+
+config SPRD_MAILBOX
+ bool "Spreadtrum Mailbox IPI Arch"
+ default n
+
+config MUIC_CABLE_DETECT
+ bool "usb, jig-box and so on cables are detected by external MUIC chip"
+ default n
+ depends on MFD_SM5504 || MFD_RT8973
+
+config SPRD_SYSDUMP
+ bool "Enable SPRD sysdump handler"
+ depends on ARM64
+ help
+ This option enables Spreadtrum sysdump which prepare debug info
+ for u-boot to write into mmc storage after kernel panics.
+
+config SPRD_DEBUG
+ bool "Enable SPRD DEBUG"
+
+config SPRD_DEBUG_SCHED_LOG
+ bool "SPRD Scheduler Logging Feature"
+ depends on SPRD_DEBUG
+ default n
+ help
+ SPRD Scheduler Logging Feature for Debug use.
+
+config GPS_LSI_S5N6420
+bool "LSI_GPS_CHIPSET_HARRIER"
+default n
+
+endmenu
diff --git a/drivers/platform/sprd/Makefile b/drivers/platform/sprd/Makefile
new file mode 100644
index 00000000..a9ec1b88
--- /dev/null
+++ b/drivers/platform/sprd/Makefile
@@ -0,0 +1,63 @@
+obj-$(CONFIG_64BIT) += iomap.o
+obj-$(CONFIG_ARCH_SCX35) += arch_init.o arch_misc.o adi.o adc.o glb.o boot_mode.o pin_switch.o
+obj-$(CONFIG_SPRD_MAILBOX) += mailbox.o
+obj-$(CONFIG_SC_INTERNAL_WATCHDOG) += sys_reset.o
+obj-$(CONFIG_BUS_MONITOR_DEBUG) += busmonitor.o
+obj-$(CONFIG_SC_VIBRATOR) += vibrator.o
+obj-$(CONFIG_GPS_LSI_S5N6420) += sec_gps_s5n6420.o
+ifneq ($(CONFIG_64BIT),y)
+obj-y += timer_sc8830.o
+else
+obj-y += timer_sharkl64.o
+endif
+ifneq ($(CONFIG_DMA_SPRD),y)
+ obj-$(CONFIG_ARCH_SCX35) +=dma_r4p0.o
+endif
+obj-$(CONFIG_SC_INTERNAL_ADI) += adie_irq.o
+obj-$(CONFIG_SPRD_DEBUG) += sprd_debug.o
+obj-$(CONFIG_PM) += pm.o
+ifeq ($(CONFIG_PM),y)
+ obj-$(CONFIG_ARCH_SCX35) += pm-scx35.o standby-scx35.o pm_debug_scx35.o
+endif
+obj-$(CONFIG_CPU_FREQ) += cpufreq-scx35.o
+obj-$(CONFIG_SPRD_VIBRATOR_2723) += sprd_vibrator_2723.o
+ifeq ($(CONFIG_64BIT),y)
+obj-$(CONFIG_SPRD_SYSDUMP) += sysdump64.o
+endif
+
+
+ifneq ($(CONFIG_64BIT),y)
+
+ifeq ($(CONFIG_CPU_IDLE),y)
+ obj-$(CONFIG_ARCH_SCX35) += cpuidle-scx35.o
+endif
+obj-y += irq.o reserve.o common.o
+obj-$(CONFIG_ARCH_SCX35) += clock-sc8830_dt.o
+obj-$(CONFIG_ARCH_SCX35) += io-sc8830.o pin_switch.o
+
+ifeq ($(CONFIG_ARCH_SCX30G),y)
+obj-$(CONFIG_PM_DEVFREQ) += dmc_freq_28nm.o dmc_dfs_test.o
+obj-$(CONFIG_SCX35_DMC_FREQ_AP) += dmc_freq_ap_28nm.o
+ CFLAGS_dmc_freq_ap_28nm.o += -fPIC
+else
+ifeq ($(CONFIG_ARCH_SCX35L),y)
+obj-y += dmc_freq_28nm.o
+else
+obj-$(CONFIG_PM_DEVFREQ) += dmc_freq.o
+obj-$(CONFIG_SCX35_DMC_FREQ_AP) += dmc_freq_ap.o
+endif
+endif
+
+obj-$(CONFIG_FIX_V7TAGRAM_BUG) += fix_v7_tag_ram_bug.o
+obj-$(CONFIG_SPRD_DCDC_DEBUG) += dcdc_debug.o
+obj-$(CONFIG_SMP) += platsmp.o headsmp.o
+obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
+ifeq ($(CONFIG_BCMDHD),m)
+obj-y += dhd_adapter.o
+endif
+
+obj-$(CONFIG_SPRD_MEM_POOL) += sprd_mem_pool.o
+obj-$(CONFIG_SPRD_AVS_DEBUG) += avs_debug.o
+obj-$(CONFIG_SPRD_AUX_DEBUG) += aux_debug.o
+
+endif
diff --git a/drivers/platform/sprd/adc.c b/drivers/platform/sprd/adc.c
new file mode 100644
index 00000000..f10bb8f7
--- /dev/null
+++ b/drivers/platform/sprd/adc.c
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ * Author: steve.zhan <steve.zhan@spreadtrum.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/kernel.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/irqflags.h>
+#include <linux/delay.h>
+#include <linux/hwspinlock.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/miscdevice.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/arch_lock.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/adc.h>
+
+static void __iomem *io_base; /* Mapped base address */
+
+#define adc_write(val, reg) \
+do { \
+ sci_adi_raw_write((unsigned long)reg, val); \
+} while (0)
+
+static unsigned adc_read(unsigned long addr)
+{
+ return (unsigned)sci_adi_read(addr);
+}
+
+/*FIXME: If we have not hwspinlock , we need use spinlock to do it */
+#define sci_adc_lock() \
+do { \
+ WARN_ON(IS_ERR_VALUE(hwspin_lock_timeout_irqsave(arch_get_hwlock(HWLOCK_ADC), -1, &flags))); \
+} while(0)
+
+#define sci_adc_unlock() \
+do { \
+ hwspin_unlock_irqrestore(arch_get_hwlock(HWLOCK_ADC), &flags); \
+} while(0)
+
+
+#define ADC_CTL (0x00)
+#define ADC_SW_CH_CFG (0x04)
+#define ADC_FAST_HW_CHX_CFG(_X_) ((_X_) * 0x4 + 0x8)
+#define ADC_SLOW_HW_CHX_CFG(_X_) ((_X_) * 0x4 + 0x28)
+#define ADC_HW_CH_DELAY (0x48)
+
+#define ADC_DAT (0x4c)
+#define adc_get_data(_SAMPLE_BITS_) (adc_read((unsigned long)(io_base + ADC_DAT)) & (_SAMPLE_BITS_))
+
+#define ADC_IRQ_EN (0x50)
+#define adc_enable_irq(_X_) \
+do { \
+ adc_write(((_X_) & 0x1), (unsigned long)(io_base + ADC_IRQ_EN)); \
+} while(0)
+
+#define ADC_IRQ_CLR (0x54)
+#define adc_clear_irq() \
+do { \
+ adc_write(0x1, (unsigned long)(io_base + ADC_IRQ_CLR)); \
+} while (0)
+
+#define ADC_IRQ_STS (0x58)
+#define adc_mask_irqstatus() adc_read((unsigned long)(io_base + ADC_IRQ_STS))
+
+#define ADC_IRQ_RAW (0x5c)
+#define adc_raw_irqstatus() adc_read((unsigned long)(io_base + ADC_IRQ_RAW))
+
+#define ADC_DEBUG (0x60)
+
+/*ADC_CTL */
+#define ADC_MAX_SAMPLE_NUM (0x10)
+#define BIT_SW_CH_RUN_NUM(_X_) ((((_X_) - 1) & 0xf ) << 4)
+#define BIT_ADC_BIT_MODE(_X_) (((_X_) & 0x1) << 2) /*0: adc in 10bits mode, 1: adc in 12bits mode */
+#define BIT_ADC_BIT_MODE_MASK (BIT_ADC_BIT_MODE(1))
+#define BIT_SW_CH_ON ( BIT(1) ) /*WO*/
+#define BIT_ADC_EN ( BIT(0) )
+
+/*ADC_SW_CH_CFG && ADC_FAST(SLOW)_HW_CHX_CFG*/
+#define BIT_CH_IN_MODE(_X_) (((_X_) & 0x1) << 8) /*0: resistance path, 1: capacitance path */
+#define BIT_CH_SLOW(_X_) (((_X_) & 0x1) << 6) /*0: quick mode, 1: slow mode */
+#define BIT_CH_SCALE(_X_) (((_X_) & 0x1) << 5) /*0: little scale, 1: big scale */
+#define BIT_CH_ID(_X_) ((_X_) & 0x1f)
+
+/*ADC_FAST(SLOW)_HW_CHX_CFG*/
+#define BIT_CH_DLY_EN(_X_) (((_X_) & 0x1) << 7) /*0:disable, 1:enable */
+
+/*ADC_HW_CH_DELAY*/
+#define BIT_HW_CH_DELAY(_X_) ((_X_) & 0xff) /*its unit is ADC clock */
+
+#if defined(CONFIG_ARCH_SC8825)
+#define BIT_ADC_EB ( BIT(5) )
+#endif
+
+#define SPRDBAT_AUXADC_CAL_NO 0
+#define SPRDBAT_AUXADC_CAL_NV 1
+#define SPRDBAT_AUXADC_CAL_CHIP 2
+int cal_type = SPRDBAT_AUXADC_CAL_NO;
+static short adc_cali_data[2][2] = {
+ { 4200 , 3310 },
+ { 3600 , 2832 },
+};
+
+struct sprd_adc_data{
+ struct miscdevice misc_dev;
+ struct mutex lock;
+ unsigned short channel;
+ unsigned char scale;
+ unsigned int voltage_ratio;
+};
+
+#ifdef CONFIG_OTP_SPRD
+extern int sci_efuse_calibration_get(unsigned int *p_cal_data);
+#endif
+
+static int __init adc_cali_fuse_proc(void);
+
+static ssize_t sprd_adc_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count);
+static ssize_t sprd_adc_show(struct device *dev,
+ struct device_attribute *dev_attr,
+ char *buf);
+static ssize_t sprd_cal_type_show(struct device *dev,
+ struct device_attribute *dev_attr,
+ char *buf);
+
+static struct device_attribute sprd_adc_attr[] = {
+ __ATTR(adc_channel, S_IRUGO | S_IWUSR | S_IWGRP, sprd_adc_show, sprd_adc_store),
+ __ATTR(adc_scale, S_IRUGO | S_IWUSR | S_IWGRP, sprd_adc_show, sprd_adc_store),
+ __ATTR(adc_voltage_ratio, S_IRUGO, sprd_adc_show, NULL),
+ __ATTR(adc_data_raw, S_IRUGO, sprd_adc_show, NULL),
+ __ATTR(adc_cal_type, S_IRUGO, sprd_cal_type_show, NULL),
+};
+
+#define SPRD_MODULE_NAME "sprd-adc"
+
+#define DIV_ROUND(n, d) (((n) + ((d)/2)) / (d))
+#define MEASURE_TIMES ( 15 )
+#define ADC_DROP_CNT ( DIV_ROUND(MEASURE_TIMES, 5) )
+
+static int average_int(int a[], int len)
+{
+ int i, sum = 0;
+ for (i = 0; i < len; i++)
+ sum += a[i];
+ return DIV_ROUND(sum, len);
+}
+
+static int compare_val(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
+static void dump_adc_data(uint32_t* p_adc_val, int len)
+{
+ int i = 0;
+
+ printk("dump adc data: ");
+ for (i = 0; i < len; i++) {
+ printk("%d ", p_adc_val[i]);
+ }
+ printk("\n");
+}
+
+/*
+* Get the raw data of adc channel
+* @ channel: adc channel id
+* @ scale: adc scale
+*
+* return 0 if failed
+*
+*/
+int sci_get_adc_data(unsigned int channel, unsigned int scale)
+{
+ uint32_t adc_val[MEASURE_TIMES] = {0}, adc_res = 0;
+ struct adc_sample_data adc_sample = {
+ .channel_id = channel,
+ .channel_type = 0, /* sw */
+ .hw_channel_delay = 0,
+ .scale = scale, /* 1: big scale, 0: small scale */
+ .pbuf = &adc_val[0],
+ .sample_num = MEASURE_TIMES,
+ .sample_bits = 1, /* 12 bit*/
+ .sample_speed = 0, /* quick mode */
+ .signal_mode = 0, /* resistance path */
+ };
+
+ if (0 != sci_adc_get_values(&adc_sample)) {
+ pr_err("sprd_adc error occured in function %s\n", __func__);
+ sci_adc_dump_register();
+ //BUG_ON();
+ return 0;
+ }
+
+ dump_adc_data(adc_val, ARRAY_SIZE(adc_val));
+
+ sort(adc_val, ARRAY_SIZE(adc_val), sizeof(uint32_t), compare_val, 0);
+
+ adc_res = average_int(&adc_val[ADC_DROP_CNT], MEASURE_TIMES - ADC_DROP_CNT * 2);
+
+ return (int)adc_res;
+}
+
+EXPORT_SYMBOL(sci_get_adc_data);
+
+/*
+* The value of adc device node is set to kernel space
+*
+* Example:
+* $ adb root && adb shell
+* $ echo 5 > /sys/class/misc/adc_channel # set current adc channel to 5 (vbat channel)
+* $ echo 1 > /sys/class/misc/adc_scale #set adc to big scale
+*/
+static ssize_t sprd_adc_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ struct sprd_adc_data *adc_data = dev_get_drvdata(dev);
+ //const ptrdiff_t offset = dev_attr - sprd_adc_attr;
+ //unsigned long tmp = simple_strtoul(buf, NULL, 10);
+ unsigned int len = 0, tmp_data = 0;
+
+ if ((NULL == adc_data) || (NULL == buf))
+ return -EIO;
+
+ len = sscanf(buf, "%d", &tmp_data);
+
+ if (0 == len)
+ return -EIO;
+
+ pr_info("%s line: %d, attr name(%s), tmp_data %d, count %d, len %d\n", __func__, __LINE__,
+ dev_attr->attr.name, tmp_data, (int)count, len);
+
+ mutex_lock(&adc_data->lock);
+
+ if (0 == strcmp(dev_attr->attr.name, "adc_channel")) {
+ WARN_ON(tmp_data > BIT_CH_ID(-1));
+
+ adc_data->channel = (unsigned short)tmp_data;
+ } else if (0 == strcmp(dev_attr->attr.name, "adc_scale")) {
+ adc_data->scale = (unsigned char)tmp_data;
+ }
+
+ mutex_unlock(&adc_data->lock);
+
+ return count;
+}
+
+/*
+* The value of adc device node is showed to user space
+*
+* Example:
+* $ adb root && adb shell
+* $ cat /sys/class/misc/adc_channel # show current adc channel number
+* $ cat /sys/class/misc/adc_scale #show adc scale value
+* $ cat /sys/class/misc/adc_data_raw #show adc raw data
+*/
+static ssize_t sprd_adc_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct sprd_adc_data *adc_data = dev_get_drvdata(dev);
+ int len = 0, adc_value = 0;
+
+ if ((NULL == adc_data) || (NULL == buf))
+ return -EIO;
+
+ pr_info("%s line: %d, attr name(%s), channel(%d), scale(%d)\n", __func__, __LINE__,
+ dev_attr->attr.name, adc_data->channel, adc_data->scale);
+
+ mutex_lock(&adc_data->lock);
+
+ if (0 == strcmp(dev_attr->attr.name, "adc_channel")) {
+ len += sprintf(buf, "%d\n", adc_data->channel);
+ } else if (0 == strcmp(dev_attr->attr.name, "adc_scale")) {
+ len += sprintf(buf, "%d\n", adc_data->scale);
+ } else if (0 == strcmp(dev_attr->attr.name, "adc_voltage_ratio")) {
+ unsigned int div_num = 0, div_den = 0;
+
+ sci_adc_get_vol_ratio(adc_data->channel, adc_data->scale, &div_num, &div_den);
+ adc_data->voltage_ratio = (div_num << 16) | (div_den & 0xFFFF);
+ len += sprintf(buf, "%d\n", adc_data->voltage_ratio);
+ } else if (0 == strcmp(dev_attr->attr.name, "adc_data_raw")) {
+ adc_value = sci_get_adc_data(adc_data->channel, adc_data->scale);
+ if (adc_value < 0)
+ adc_value = 0;
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%d\n", adc_value);
+
+ pr_info("value: %d, len: %d\n", adc_value, len);
+ }
+
+ mutex_unlock(&adc_data->lock);
+
+ return len;
+}
+static ssize_t sprd_cal_type_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ return sprintf(buf, "%d", cal_type);
+}
+
+static int sprd_device_delete_attributes(struct device *dev,
+ struct device_attribute *dev_attrs, int len)
+{
+ int i = 0;
+
+ for (i = 0; i < len; i++) {
+ device_remove_file(dev, &dev_attrs[i]);
+ }
+
+ return 0;
+}
+
+static int sprd_device_create_attributes(struct device *dev,
+ struct device_attribute *dev_attrs, int len)
+{
+ int i = 0, rc = 0;
+
+ for (i = 0; i < len; i++) {
+ rc = device_create_file(dev, &dev_attrs[i]);
+ if (rc)
+ break;
+ }
+
+ if (rc) {
+ for (; i >= 0 ; --i)
+ device_remove_file(dev, &dev_attrs[i]);
+ }
+
+ return rc;
+}
+
+#ifndef CONFIG_OF
+static int __init sprd_adc_dev_register(void)
+{
+ static struct platform_device sprdadc_device = {
+ .name = SPRD_MODULE_NAME,
+ .id = -1,
+ };
+
+ return platform_device_register(&sprdadc_device);
+}
+#endif
+
+static int sprd_adc_probe(struct platform_device *pdev)
+{
+ struct sprd_adc_data *adc_data = NULL;
+ int rc = 0;
+
+ pr_info("%s()->Line: %d\n", __func__, __LINE__);
+
+ adc_data = devm_kzalloc(&pdev->dev, sizeof(struct sprd_adc_data), GFP_KERNEL);
+ if (!adc_data) {
+ pr_err("%s failed to kzalloc sprd_adc_data!\n", __func__);
+ return -ENOMEM;
+ }
+
+ adc_data->channel = 5;
+ adc_data->misc_dev.minor = MISC_DYNAMIC_MINOR;
+ adc_data->misc_dev.name = SPRD_MODULE_NAME; //pdev->dev.driver->name
+ adc_data->misc_dev.fops = NULL;
+ adc_data->misc_dev.parent = NULL;
+
+ dev_set_drvdata(&pdev->dev, adc_data);
+
+ mutex_init(&adc_data->lock);
+
+ rc = misc_register(&adc_data->misc_dev);
+ if (rc) {
+ pr_err("%s failed to register misc device.\n", __func__);
+ return rc;
+ }
+
+ rc = sprd_device_create_attributes(adc_data->misc_dev.this_device, sprd_adc_attr, ARRAY_SIZE(sprd_adc_attr));
+ if (rc) {
+ pr_err("%s failed to create device attributes.\n", __func__);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int sprd_adc_remove(struct platform_device *pdev)
+{
+ struct sprd_adc_data *adc_data = platform_get_drvdata(pdev);
+ int rc = 0;
+
+ sprd_device_delete_attributes(adc_data->misc_dev.this_device, sprd_adc_attr, ARRAY_SIZE(sprd_adc_attr));
+
+ rc = misc_deregister(&adc_data->misc_dev);
+ if (rc) {
+ pr_err("%s failed to unregister misc device.\n", __func__);
+ }
+
+ kfree(adc_data);
+
+ return rc;
+}
+
+
+#ifdef CONFIG_OF
+static struct of_device_id sprd_adc_of_match[] = {
+ { .compatible = "sprd,sprd-adc", },
+ { }
+};
+#endif
+
+static struct platform_driver sprd_adc_driver = {
+ .probe = sprd_adc_probe,
+ .remove = sprd_adc_remove,
+ .driver = {
+ .name = SPRD_MODULE_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sprd_adc_of_match),
+ },
+};
+
+static int __init sprd_adc_driver_init(void)
+{
+ return platform_driver_register(&sprd_adc_driver);
+}
+
+static void __exit sprd_adc_driver_exit(void)
+{
+ platform_driver_unregister(&sprd_adc_driver);
+}
+
+module_init(sprd_adc_driver_init);
+module_exit(sprd_adc_driver_exit);
+
+
+static void sci_adc_enable(void)
+{
+#if defined(CONFIG_ARCH_SC8825)
+ /* enable adc */
+ sci_adi_set(ANA_REG_GLB_ANA_APB_CLK_EN,
+ BIT_ANA_ADC_EB | BIT_ANA_CLK_AUXADC_EN |
+ BIT_ANA_CLK_AUXAD_EN);
+#elif defined(CONFIG_ARCH_SCX35)
+ sci_adi_set(ANA_REG_GLB_ARM_MODULE_EN,
+ BIT_ANA_ADC_EN);
+ sci_adi_set(ANA_REG_GLB_ARM_CLK_EN,
+ BIT_CLK_AUXADC_EN | BIT_CLK_AUXAD_EN);
+ sci_adi_set(ANA_REG_GLB_XTL_WAIT_CTRL,
+ BIT_XTL_EN);
+ sci_glb_set(REG_AON_APB_SINDRV_CTRL, BIT_SINDRV_ENA); //maybe need to modify it in later
+#endif
+}
+
+void sci_adc_dump_register()
+{
+ unsigned long _base = (unsigned long)(io_base);
+ unsigned long _end = _base + 0x64;
+
+ printk("sci_adc_dump_register begin\n");
+ for (; _base < _end; _base += 4) {
+ printk("_base = 0x%lx, value = 0x%x\n", _base, adc_read(_base));
+ }
+ printk("sci_adc_dump_register end\n");
+}
+
+EXPORT_SYMBOL(sci_adc_dump_register);
+
+void sci_adc_init(void)
+{
+ io_base = (void __iomem *)REGS_ADC_BASE;
+
+#ifndef CONFIG_OF
+ sprd_adc_dev_register();
+#endif
+
+ adc_enable_irq(0);
+ adc_clear_irq();
+ sci_adc_enable();
+ if (cal_type == SPRDBAT_AUXADC_CAL_NO) {
+ if (!adc_cali_fuse_proc())
+ cal_type = SPRDBAT_AUXADC_CAL_CHIP;
+ }
+}
+EXPORT_SYMBOL(sci_adc_init);
+
+/*
+* Notes: for hw channel, config its hardware configuration register and using sw channel to read it.
+*/
+static int sci_adc_config(struct adc_sample_data *adc)
+{
+ unsigned long addr = 0;
+ unsigned val = 0;
+ int ret = 0;
+
+ BUG_ON(!adc);
+ BUG_ON(adc->channel_id > ADC_MAX);
+
+ val = BIT_CH_IN_MODE(adc->signal_mode);
+ val |= BIT_CH_SLOW(adc->sample_speed);
+ val |= BIT_CH_SCALE(adc->scale);
+ val |= BIT_CH_ID(adc->channel_id);
+ val |= BIT_CH_DLY_EN(adc->hw_channel_delay ? 1 : 0);
+
+ adc_write(val, (unsigned long)(io_base + ADC_SW_CH_CFG));
+
+ if (adc->channel_type > 0) { /*hardware */
+ adc_write(BIT_HW_CH_DELAY(adc->hw_channel_delay),
+ (unsigned long)(io_base + ADC_HW_CH_DELAY));
+
+ if (adc->channel_type == 1) { /*slow */
+ addr = (unsigned long)(io_base + ADC_SLOW_HW_CHX_CFG(adc->channel_id));
+ } else {
+ addr = (unsigned long)(io_base + ADC_FAST_HW_CHX_CFG(adc->channel_id));
+ }
+ adc_write(val, addr);
+ }
+
+ return ret;
+}
+
+#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S)
+#define RATIO(_n_, _d_) (_n_ << 16 | _d_)
+static int sci_adc_ratio(int channel, int scale, int mux)
+{
+ switch (channel) {
+ case ADC_CHANNEL_0:
+ return RATIO(1, 1);
+ case ADC_CHANNEL_1:
+ case ADC_CHANNEL_2:
+ case ADC_CHANNEL_3:
+ return (scale ? RATIO(400, 1025) : RATIO(1, 1));
+ case ADC_CHANNEL_VBAT: //Vbat
+ case ADC_CHANNEL_ISENSE:
+ return RATIO(7, 29);
+ case ADC_CHANNEL_VCHGSEN:
+ return RATIO(77, 1024);
+ case ADC_CHANNEL_DCDCCORE: //dcdc core/arm/mem/gen/rf/con/wpa
+ mux = mux >> 13;
+ switch (mux) {
+ case 1: //dcdcarm
+ case 2: //dcdccore
+ return (scale ? RATIO(36, 55) : RATIO(9, 11));
+ case 3: //dcdcmem
+ case 5: //dcdcrf
+ return (scale ? RATIO(12, 25) : RATIO(3, 5));
+ case 4: //dcdcgen
+ return (scale ? RATIO(3, 10) : RATIO(3, 8));
+ case 6: //dcdccon
+ return (scale ? RATIO(9, 20) : RATIO(9, 16));
+ case 7: //dcdwpa
+ return (scale ? RATIO(12, 55) : RATIO(3, 11));
+ default:
+ return RATIO(1, 1);
+ }
+ case 0xE: //LP dcxo
+ return (scale ? RATIO(4, 5) : RATIO(1, 1));
+ case 0x14: //headmic
+ return RATIO(1, 3);
+
+ case ADC_CHANNEL_LDO0: //dcdc supply LDO, vdd18/vddcamd/vddcamio/vddrf0/vddgen1/vddgen0
+ return RATIO(1, 2);
+ case ADC_CHANNEL_VBATBK: //VbatBK
+ case ADC_CHANNEL_LDO1: //VbatD Domain LDO, vdd25/vddcama/vddsim2/vddsim1/vddsim0
+ case ADC_CHANNEL_LDO2: //VbatA Domain LDO, vddwifipa/vddcammot/vddemmccore/vdddcxo/vddsdcore/vdd28
+ case ADC_CHANNEL_WHTLED: //kpled/vibr
+ case ADC_CHANNEL_WHTLED_VFB://vddsdio/vddusb/vddfgu
+ case ADC_CHANNEL_USBDP: //DP from terminal
+ case ADC_CHANNEL_USBDM: //DM from terminal
+ return RATIO(1, 3);
+
+ default:
+ return RATIO(1, 1);
+ }
+ return RATIO(1, 1);
+}
+
+void sci_adc_get_vol_ratio(unsigned int channel_id, int scale, unsigned int *div_numerators,
+ unsigned int *div_denominators)
+{
+ unsigned int ratio = sci_adc_ratio(channel_id, scale, 0);
+ *div_numerators = ratio >> 16;
+ *div_denominators = ratio << 16 >> 16;
+}
+
+unsigned int sci_adc_get_ratio(unsigned int channel_id, int scale, int mux)
+{
+ unsigned int ratio = (unsigned int)sci_adc_ratio(channel_id, scale, mux);
+
+ return ratio;
+}
+
+#elif (defined(CONFIG_ARCH_SCX15) || defined(CONFIG_ADIE_SC2711))
+#define RATIO(_n_, _d_) (_n_ << 16 | _d_)
+static int sci_adc_ratio(int channel, int scale, int mux)
+{
+ switch (channel) {
+ case ADC_CHANNEL_0:
+ case ADC_CHANNEL_1:
+ case ADC_CHANNEL_2:
+ case ADC_CHANNEL_3:
+ return (scale ? RATIO(400, 1025) : RATIO(1, 1));
+ case ADC_CHANNEL_VBAT: //vbat
+ case ADC_CHANNEL_ISENSE:
+ return RATIO(7, 29);
+ case ADC_CHANNEL_VCHGSEN:
+ return RATIO(77, 1024);
+ case ADC_CHANNEL_DCDCCORE: //dcdccore
+ case ADC_CHANNEL_DCDCARM: //dcdcarm
+ return (scale ? RATIO(4, 5) : RATIO(1, 1));
+ case ADC_CHANNEL_DCDCMEM: //dcdcmem
+ return (scale ? RATIO(3, 5) : RATIO(4, 5));
+ case ADC_CHANNEL_DCDCLDO: //dcdcgen
+ return RATIO(4, 9);
+ case 0x14 /* ADC_CHANNEL_HEADMIC */: //DCDCHEADMIC
+ return RATIO(1, 3);
+ case ADC_CHANNEL_LDO0: //DCDC Supply LDO, VDD18/CAMIO/CAMD/EMMCIO
+ return RATIO(1, 2);
+ case ADC_CHANNEL_VBATBK: //DCDCVBATBK
+ case ADC_CHANNEL_LDO1: //VBATD Domain LDO, VDD25/SD/USB/SIM0/SIM1/SIM2
+ case ADC_CHANNEL_LDO2: //VBATA Domain LDO, VDD28/CAMA/CAMMOT/EMMCCORE/CON/DCXO/RF0
+ case ADC_CHANNEL_USBDP: //DP from terminal
+ case ADC_CHANNEL_USBDM: //DM from terminal
+ return RATIO(1, 3);
+
+ default:
+ return RATIO(1, 1);
+ }
+ return RATIO(1, 1);
+}
+
+void sci_adc_get_vol_ratio(unsigned int channel_id, int scale, unsigned int *div_numerators,
+ unsigned int *div_denominators)
+{
+ unsigned int ratio = sci_adc_ratio(channel_id, scale, 0);
+ *div_numerators = ratio >> 16;
+ *div_denominators = ratio << 16 >> 16;
+}
+
+#else
+void sci_adc_get_vol_ratio(unsigned int channel_id, int scale, unsigned int *div_numerators,
+ unsigned int *div_denominators)
+{
+ unsigned int chip_id = 0;
+
+ switch (channel_id) {
+
+ case ADC_CHANNEL_0:
+ case ADC_CHANNEL_1:
+ case ADC_CHANNEL_2:
+ case ADC_CHANNEL_3:
+ if (scale) {
+ *div_numerators = 16;
+ *div_denominators = 41;
+ } else {
+ *div_numerators = 1;
+ *div_denominators = 1;
+ }
+ return;
+ case ADC_CHANNEL_PROG: //channel 4
+ case ADC_CHANNEL_VCHGBG: //channel 7
+ case ADC_CHANNEL_HEADMIC: //18
+ *div_numerators = 1;
+ *div_denominators = 1;
+ return;
+ case ADC_CHANNEL_VBAT: //channel 5
+ case ADC_CHANNEL_ISENSE: //channel 8
+#if defined(CONFIG_ARCH_SCX35)
+ *div_numerators = 7;
+ *div_denominators = 29;
+#else
+ chip_id = sci_adi_read(CHIP_ID_LOW_REG);
+#ifdef CHIP_ID_HIGH_REG
+ chip_id |= (sci_adi_read(CHIP_ID_HIGH_REG) << 16);
+#endif
+ if (chip_id == 0x8820A001) { //metalfix
+ *div_numerators = 247;
+ *div_denominators = 1024;
+ } else {
+ *div_numerators = 266;
+ *div_denominators = 1000;
+ }
+#endif
+ return;
+ case ADC_CHANNEL_VCHGSEN: //channel 6
+ *div_numerators = 77;
+ *div_denominators = 1024;
+ return;
+ case ADC_CHANNEL_TPYD: //channel 9
+ case ADC_CHANNEL_TPYU: //channel 10
+ case ADC_CHANNEL_TPXR: //channel 11
+ case ADC_CHANNEL_TPXL: //channel 12
+ if (scale) { //larger
+ *div_numerators = 2;
+ *div_denominators = 5;
+ } else {
+ *div_numerators = 3;
+ *div_denominators = 5;
+ }
+ return;
+ case ADC_CHANNEL_DCDCCORE: //channel 13
+ case ADC_CHANNEL_DCDCARM: //channel 14
+ if (scale) { //lager
+ *div_numerators = 4;
+ *div_denominators = 5;
+ } else {
+ *div_numerators = 1;
+ *div_denominators = 1;
+ }
+ return;
+ case ADC_CHANNEL_DCDCMEM: //channel 15
+ if (scale) { //lager
+ *div_numerators = 3;
+ *div_denominators = 5;
+ } else {
+ *div_numerators = 4;
+ *div_denominators = 5;
+ }
+ return;
+ case ADC_CHANNEL_DCDCLDO: //16
+ *div_numerators = 4;
+ *div_denominators = 9;
+ return;
+
+#if defined(CONFIG_ARCH_SCX35)
+ case ADC_CHANNEL_VBATBK:
+ case ADC_CHANNEL_LDO0:
+ case ADC_CHANNEL_LDO2:
+ case ADC_CHANNEL_USBDP:
+ case ADC_CHANNEL_USBDM:
+ *div_numerators = 1;
+ *div_denominators = 3;
+ return;
+ case ADC_CHANNEL_LDO1:
+ *div_numerators = 1;
+ *div_denominators = 2;
+ return;
+ case ADC_CHANNEL_DCDCGPU:
+ if (scale) {
+ *div_numerators = 4;
+ *div_denominators = 5;
+ } else {
+ *div_numerators = 1;
+ *div_denominators = 1;
+ }
+ return;
+ case ADC_CHANNEL_DCDCWRF:
+ if (scale) {
+ *div_numerators = 1;
+ *div_denominators = 3;
+ } else {
+ *div_numerators = 4;
+ *div_denominators = 5;
+ }
+ return;
+#else
+ case ADC_CHANNEL_VBATBK: //channel 17
+ case ADC_CHANNEL_LDO0: //channel 19,20
+ case ADC_CHANNEL_LDO1:
+ *div_numerators = 1;
+ *div_denominators = 3;
+ return;
+ case ADC_CHANNEL_LDO2: //channel 21
+ *div_numerators = 1;
+ *div_denominators = 2;
+ return;
+#endif
+ default:
+ *div_numerators = 1;
+ *div_denominators = 1;
+ break;
+ }
+}
+#endif
+
+int sci_adc_get_values(struct adc_sample_data *adc)
+{
+ unsigned long flags;
+ int cnt = 12;
+ unsigned long addr = 0;
+ unsigned val = 0;
+ int ret = 0;
+ int num = 0;
+ int sample_bits_msk = 0;
+ int *pbuf = 0;
+
+ if (!adc || adc->channel_id > ADC_MAX)
+ return -EINVAL;
+
+ pbuf = adc->pbuf;
+ if (!pbuf)
+ return -EINVAL;
+
+ num = adc->sample_num;
+ if (num > ADC_MAX_SAMPLE_NUM)
+ return -EINVAL;
+
+ sci_adc_lock();
+
+ sci_adc_config(adc); //configs adc sample.
+
+ addr = (unsigned long)(io_base + ADC_CTL);
+ val = adc_read((unsigned long)addr);
+ val &= ~(BIT_ADC_EN | BIT_SW_CH_ON | BIT_ADC_BIT_MODE_MASK);
+ adc_write(val, addr);
+
+ adc_clear_irq();
+
+ val = BIT_SW_CH_RUN_NUM(num);
+ val |= BIT_ADC_EN;
+ val |= BIT_ADC_BIT_MODE(adc->sample_bits);
+ val |= BIT_SW_CH_ON;
+
+ adc_write(val, addr);
+
+ while ((!adc_raw_irqstatus()) && cnt--) {
+ udelay(50);
+ }
+
+ if (!cnt) {
+ ret = -1;
+ WARN_ON(1);
+ goto Exit;
+ }
+
+ if (adc->sample_bits)
+ sample_bits_msk = ((1 << 12) - 1); //12
+ else
+ sample_bits_msk = ((1 << 10) - 1); //10
+ while (num--)
+ *pbuf++ = adc_get_data(sample_bits_msk);
+
+Exit:
+ val = adc_read((unsigned long)addr);
+ val &= ~BIT_ADC_EN;
+ adc_write(val, addr);
+
+ sci_adc_unlock();
+
+ return ret;
+}
+
+EXPORT_SYMBOL_GPL(sci_adc_get_values);
+
+static int __average(int a[], int N)
+{
+#define __DIV_ROUND(n, d) (((n) + ((d)/2)) / (d))
+ int i, sum = 0;
+
+ for (i = 0; i < N; i++)
+ sum += a[i];
+ return __DIV_ROUND(sum, N);
+}
+
+static int sci_adc_set_current(u8 enable, int isen)
+{
+ if(enable) {
+ /* BITS_AUXAD_CURRENT_IBS = (isen * 100 / 125 -1) */
+ isen = (isen * 100 / 125 -1);
+ if(isen > BITS_AUXAD_CURRENT_IBS(-1))
+ isen = BITS_AUXAD_CURRENT_IBS(-1);
+ sci_adi_write(ANA_REG_GLB_AUXAD_CTL, (BIT_AUXAD_CURRENTSEN_EN | BITS_AUXAD_CURRENT_IBS(isen)),
+ BIT_AUXAD_CURRENTSEN_EN | BITS_AUXAD_CURRENT_IBS(-1));
+ } else {
+ sci_adi_clr(ANA_REG_GLB_AUXAD_CTL, BIT_AUXAD_CURRENTSEN_EN);
+ }
+
+ return 0;
+}
+
+/*
+ * sci_adc_get_value_by_isen - read adc value by current sense
+ * @channel: adc software channel id;
+ * @scale: adc sample scale, 0:little scale, 1:big scale;
+ * @current: adc current isense(uA), 1.25uA/step, max 40uA;
+ *
+ * returns: adc value
+ */
+int sci_adc_get_value_by_isen(unsigned int channel, int scale, int isen)
+{
+#define ADC_MESURE_NUMBER 15
+
+ struct adc_sample_data adc;
+ int results[ADC_MESURE_NUMBER + 1] = {0};
+ int ret = 0, i = 0;
+
+ /* Fixme: only external adc channel used */
+ BUG_ON(channel > 3);
+
+ WARN_ON(isen > 40);
+
+ adc.channel_id = channel;
+ adc.channel_type = 0;
+ adc.hw_channel_delay = 0;
+ adc.pbuf = &results[0];
+ adc.sample_bits = 1;
+ adc.sample_num = ADC_MESURE_NUMBER;
+ adc.sample_speed = 0;
+ adc.scale = scale;
+ adc.signal_mode = 0;
+
+ sci_adc_set_current(1, isen);
+ if(0 == sci_adc_get_values(&adc)) {
+ ret = __average(&results[ADC_MESURE_NUMBER/5],
+ (ADC_MESURE_NUMBER - ADC_MESURE_NUMBER * 2 /5));
+ }
+ sci_adc_set_current(0, 0);
+
+ for(i = 0; i < ARRAY_SIZE(results); i++) {
+ printk("%d\t", results[i]);
+ }
+ printk("\n%s() adc[%d] value: %d\n", __func__, channel, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sci_adc_get_value_by_isen);
+
+static int is_valid_adc_cal(void)
+{
+ return !!(0 != adc_cali_data[0][0]);
+}
+
+static int __init adc_cali_fuse_proc(void)
+{
+ if (cal_type == SPRDBAT_AUXADC_CAL_NO) {
+ unsigned int efuse_cal_data[2] = { 0 };
+ #ifdef CONFIG_OTP_SPRD
+ if (sci_efuse_calibration_get(efuse_cal_data)) {
+ adc_cali_data[0][0] =
+ efuse_cal_data[0] & 0xffff;
+ adc_cali_data[0][1] =
+ (efuse_cal_data[0] >> 16) & 0xffff;
+ adc_cali_data[1][0] =
+ efuse_cal_data[1] & 0xffff;
+ adc_cali_data[1][1] =
+ (efuse_cal_data[1] >> 16) & 0xffff;
+ printk("auxadc cal from efuse ok!!!\n");
+ printk("efuse_cal_data[0]: 0x%x, efuse_cal_data[1]:0x%x\n",
+ efuse_cal_data[0], efuse_cal_data[1]);
+ return 0;
+ }
+ #endif
+ }
+ return -1;
+}
+
+static int __init adc_cali_proc(char *str)
+{
+ unsigned int adc_data[2] = { 0 };
+ memset(adc_cali_data, 0, ARRAY_SIZE(adc_cali_data));
+
+ if (str) {
+ char *cali_data = &str[1];
+ sscanf(cali_data, "%d,%d", &adc_data[0], &adc_data[1]);
+
+ adc_cali_data[0][0] = adc_data[0] & 0xFFFF;
+ adc_cali_data[0][1] = (adc_data[0] >> 16) & 0xFFFF;
+ adc_cali_data[1][0] = adc_data[1] & 0xFFFF;
+ adc_cali_data[1][1] = (adc_data[1] >> 16) & 0xFFFF;
+
+ pr_info("%s adc cali data (%d : %d -- %d : %d)\n", __func__,
+ (int)adc_cali_data[0][0], (int)adc_cali_data[0][1],
+ (int)adc_cali_data[1][0], (int)adc_cali_data[1][1]);
+ cal_type = SPRDBAT_AUXADC_CAL_NV;
+ }
+ return 1;
+}
+__setup("adc_cal", adc_cali_proc);
+
+
+static int adc2vbat(int adc_res, int scale)
+{
+ int t = 0;
+
+ if(!is_valid_adc_cal())
+ return 0;
+
+ if (1 || scale) {
+ t = adc_cali_data[0][0] - adc_cali_data[1][0];
+ t *= (adc_res - adc_cali_data[0][1]);
+ t /= (adc_cali_data[0][1] - adc_cali_data[1][1]);
+ t += adc_cali_data[0][0];
+ } else {
+ t = adc_cali_data[2][0] - adc_cali_data[3][0];
+ t *= (adc_res - adc_cali_data[2][1]);
+ t /= (adc_cali_data[2][1] - adc_cali_data[3][1]);
+ t += adc_cali_data[2][0];
+ }
+
+ return t;
+}
+
+#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S)
+/*
+* Get adc channel voltage
+* @ channel: adc channel id
+* @ scale: adc scale
+* @ mux: adc calibration
+* @ adc_data: adc channel raw data
+*
+* return 0 if failed
+*
+*/
+int sci_adc_vol_request(unsigned int channel, int scale, int mux, int* adc_data)
+{
+ unsigned int bat_numerators;
+ unsigned int bat_denominators;
+ unsigned int chan_numerators;
+ unsigned int chan_denominators;
+ u32 res;
+ int chan_adc = 0, chan_vol = 0;
+
+ res = (u32)sci_adc_get_ratio(ADC_CHANNEL_VBAT, 0, 0);
+ bat_numerators = (res >> 16) & 0xFFFF;
+ bat_denominators = res & 0xFFFF;
+
+ chan_adc = sci_get_adc_data(channel, scale);
+ if (chan_adc) {
+ if(adc_data)
+ *adc_data = chan_adc;
+
+ chan_vol = adc2vbat(chan_adc, scale);
+ if(scale) {
+ res = (u32) sci_adc_get_ratio(channel, scale, mux);
+ chan_numerators = (res >> 16) & 0xFFFF;
+ chan_denominators = res & 0xFFFF;
+
+ chan_vol *= (bat_numerators * chan_denominators);
+ chan_vol /= (bat_denominators * chan_numerators);
+ }
+ }
+
+ return chan_vol;
+}
+EXPORT_SYMBOL_GPL(sci_adc_vol_request);
+#endif
diff --git a/drivers/platform/sprd/adi.c b/drivers/platform/sprd/adi.c
new file mode 100644
index 00000000..9fc9f30e
--- /dev/null
+++ b/drivers/platform/sprd/adi.c
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ * Copyright (C) 2012 steve zhan
+ *
+ * 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.
+ */
+
+/**
+*For arm read ,delay about 1us when clk_adi runs at 76.8M
+*The interface timing is compatible with spi timing
+*/
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/irqflags.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/hwspinlock.h>
+#include <asm/delay.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/arch_lock.h>
+#include <soc/sprd/adi.h>
+
+unsigned long sprd_adi_base;
+unsigned long sprd_adi_phys;
+
+/* soc defined begin*/
+#define CTL_ADI_BASE (SPRD_ADI_BASE)
+
+/* registers definitions for controller CTL_ADI */
+#define REG_ADI_CTRL0 (CTL_ADI_BASE + 0x04)
+#ifdef CONFIG_ARCH_SCX35LT8
+#define REG_ADI_CHNL_PRIL (CTL_ADI_BASE + 0x08)
+#define REG_ADI_CHNL_PRIH (CTL_ADI_BASE + 0x0C)
+#define REG_ADI_INT_RAW (CTL_ADI_BASE + 0x14)
+#define REG_ADI_RD_CMD (CTL_ADI_BASE + 0x28)
+#define REG_ADI_RD_DATA (CTL_ADI_BASE + 0x2C)
+#define REG_ADI_FIFO_STS (CTL_ADI_BASE + 0x30)
+#define REG_ADI_GSSI_CFG0 (CTL_ADI_BASE + 0x20)
+#define REG_ADI_GSSI_CFG1 (CTL_ADI_BASE + 0x24)
+#else
+#define REG_ADI_CHNL_PRI (CTL_ADI_BASE + 0x08)
+#define REG_ADI_INT_RAW (CTL_ADI_BASE + 0x10)
+#define REG_ADI_RD_CMD (CTL_ADI_BASE + 0x24)
+#define REG_ADI_RD_DATA (CTL_ADI_BASE + 0x28)
+#define REG_ADI_FIFO_STS (CTL_ADI_BASE + 0x2c)
+#define REG_ADI_GSSI_CFG0 (CTL_ADI_BASE + 0x1C)
+#define REG_ADI_GSSI_CFG1 (CTL_ADI_BASE + 0x20)
+#endif
+/* bits definitions for register REG_ADI_CTRL0 */
+#define BIT_ARM_SCLK_EN ( BIT(1) )
+#define BITS_CMMB_WR_PRI ( (1) << 4 & (BIT(4)|BIT(5)) )
+
+/* bits definitions for register REG_ADI_CHNL_PRI */
+#define BITS_PD_WR_PRI ( (1) << 14 & (BIT(14)|BIT(15)) )
+#define BITS_RFT_WR_PRI ( (1) << 12 & (BIT(12)|BIT(13)) )
+#define BITS_DSP_RD_PRI ( (2) << 10 & (BIT(10)|BIT(11)) )
+#define BITS_DSP_WR_PRI ( (2) << 8 & (BIT(8)|BIT(9)) )
+#define BITS_ARM_RD_PRI ( (3) << 6 & (BIT(6)|BIT(7)) )
+#define BITS_ARM_WR_PRI ( (3) << 4 & (BIT(4)|BIT(5)) )
+#define BITS_STC_WR_PRI ( (1) << 2 & (BIT(2)|BIT(3)) )
+#define BITS_INT_STEAL_PRI ( (3) << 0 & (BIT(0)|BIT(1)) )
+
+/* bits definitions for register REG_ADI_RD_DATA */
+#define BIT_RD_CMD_BUSY ( BIT(31) )
+#define SHIFT_RD_ADDR ( 16 )
+
+#define SHIFT_RD_VALU ( 0 )
+#define MASK_RD_VALU ( 0xFFFF )
+
+/* bits definitions for register REG_ADI_FIFO_STS */
+#define BIT_FIFO_FULL ( BIT(11) )
+#define FIFO_IS_FULL() (__raw_readl((void __iomem *)REG_ADI_FIFO_STS) & BIT_FIFO_FULL)
+#define BIT_FIFO_EMPTY ( BIT(10) )
+
+/* special V1 (sc8830 soc) defined */
+/* bits definitions for register REG_ADI_CTRL0 */
+#define BIT_ADI_WR(_X_) ( (_X_) << 2 )
+#define BITS_ADDR_BYTE_SEL(_X_) ( (_X_) << 0 & (BIT(0)|BIT(1)) )
+
+/* bits definitions for register REG_ADI_CHNL_PRI */
+#define VALUE_CH_PRI (0x0)
+
+
+#if defined(CONFIG_ARCH_SCX35)
+#define ANA_VIRT_BASE ( REGS_ADISLAVE_BASE )
+#define ANA_PHYS_BASE ( REGS_ADISLAVE_PHYS )
+#else
+#define ANA_VIRT_BASE ( REGS_MISC_BASE )
+#define ANA_PHYS_BASE ( REGS_MISC_PHYS )
+#endif
+
+#define ANA_ADDR_SIZE (SZ_4K)
+
+#define TO_ADDR(_x_) ( ((_x_) >> SHIFT_RD_ADDR) & readback_addr_mak )
+/* soc defined end*/
+
+/*This value have a bit of depending on real hardware fifo size*/
+#define CACHE_SIZE (16)
+#define HEAD_ADD (1)
+#define TAIL_ADD (0)
+
+static struct __data {
+ unsigned long reg;
+ u16 val;
+} __data_array[CACHE_SIZE];
+
+static struct __data *head_p = &__data_array[0];
+static struct __data *tail_p = &__data_array[0];
+static u32 data_in_cache = 0;
+static u32 readback_addr_mak __read_mostly = 0;
+
+/*FIXME: Now define adi IP version, sc8825 is zero, sc8830 is one,
+* Adi need init early that than read soc id, now using this ARCH dependency.
+*/
+static inline int __adi_ver(void)
+{
+#ifdef CONFIG_ARCH_SC8825
+ return 0;
+#else
+ return 1;
+#endif
+}
+
+static inline int __adi_fifo_drain(void)
+{
+ int cnt = 1000;
+ while (!(__raw_readl((void __iomem *)REG_ADI_FIFO_STS) & BIT_FIFO_EMPTY) && cnt--) {
+ udelay(1);
+ }
+ WARN(cnt == 0, "ADI WAIT timeout!!!");
+ return 0;
+}
+
+int sci_is_adi_vaddr(unsigned long vaddr)
+{
+ return (vaddr >= ANA_VIRT_BASE && vaddr <= (ANA_VIRT_BASE + ANA_ADDR_SIZE));
+}
+EXPORT_SYMBOL(sci_is_adi_vaddr);
+
+int sci_adi_p2v(unsigned long paddr, unsigned long *vaddr)
+{
+ if(paddr < ANA_PHYS_BASE || paddr > (ANA_PHYS_BASE + ANA_ADDR_SIZE)) {
+ return -1;
+ } else {
+ *vaddr = paddr - ANA_PHYS_BASE + ANA_VIRT_BASE;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(sci_adi_p2v);
+
+static inline int __adi_addr_check(unsigned long vaddr)
+{
+ if(!sci_is_adi_vaddr(vaddr)) {
+ WARN_ONCE(1, "Maybe ADI vaddr is wrong?!!");
+ return -1;
+ }
+ return 0;
+}
+
+static inline unsigned long __adi_translate_addr(unsigned long regvddr)
+{
+ regvddr = regvddr - ANA_VIRT_BASE + ANA_PHYS_BASE;
+ return regvddr;
+}
+
+static inline int __adi_read(unsigned long regPddr, unsigned int *v)
+{
+ unsigned long val;
+ int cnt = 2000;
+ int retry_cnt = 0;
+
+ /*
+ * We don't wait write fifo empty in here,
+ * Because if normal write is SYNC, that will
+ * wait fifo empty at the end of the write.
+ */
+re_try:
+ __raw_writel((u32)regPddr,(void __iomem *)REG_ADI_RD_CMD);
+
+ /*
+ * wait read operation complete, RD_data[31]
+ * is set simulaneously when writing read command.
+ * RD_data[31] will be cleared after the read operation complete,
+ */
+ do {
+ val = __raw_readl((void __iomem *)REG_ADI_RD_DATA);
+ } while ((val & BIT_RD_CMD_BUSY) && cnt--);
+
+ WARN(cnt == 0, "ADI READ timeout!!!");
+ /* val high part should be the address of the last read operation */
+ if ((!v) || TO_ADDR(val) != (regPddr & readback_addr_mak)) {
+ printk("Warning:val = 0x%lx, regPaddr = 0x%lx, readback_addr_mak = 0x%x\n",
+ val,regPddr,readback_addr_mak);
+ if(retry_cnt++ < 5)
+ goto re_try;
+ else
+ return -1;
+ }
+
+ *v = val & MASK_RD_VALU;
+ return 0;
+}
+
+int sci_adi_read(unsigned long reg)
+{
+ int val = 0;
+ if (!__adi_addr_check(reg)) {
+ unsigned long flags;
+ int ret = 0;
+ reg = __adi_translate_addr(reg);
+ __arch_default_lock(HWLOCK_ADI, &flags);
+ ret = __adi_read(reg, &val);
+ __arch_default_unlock(HWLOCK_ADI, &flags);
+ if (ret) {
+ printk("read error: reg = 0x%lx\n",reg);
+ BUG_ON(1);
+ }
+ }
+ return val;
+}
+EXPORT_SYMBOL(sci_adi_read);
+
+static inline void __p_add(struct __data **p, u32 isHead)
+{
+ if (++(*p) > &__data_array[CACHE_SIZE - 1])
+ (*p) = &__data_array[0];
+ if (head_p == tail_p) {
+ if (isHead == HEAD_ADD) {
+ data_in_cache = 0;
+ } else {
+ data_in_cache = CACHE_SIZE;
+ }
+ } else {
+ data_in_cache = 2;
+ }
+}
+
+static inline int __adi_write(unsigned long reg, u16 val, u32 sync)
+{
+ tail_p->reg = reg;
+ tail_p->val = val;
+ __p_add(&tail_p, TAIL_ADD);
+ while (!FIFO_IS_FULL() && (data_in_cache != 0)) {
+ __raw_writel(head_p->val,(void __iomem *)head_p->reg);
+ __p_add(&head_p, HEAD_ADD);
+ }
+
+ if (sync || data_in_cache == CACHE_SIZE) {
+ __adi_fifo_drain();
+ while (data_in_cache != 0) {
+ while (FIFO_IS_FULL()) {
+ cpu_relax();
+ }
+ __raw_writel(head_p->val,(void __iomem *)head_p->reg);
+ __p_add(&head_p, HEAD_ADD);
+ }
+ __adi_fifo_drain();
+ }
+
+ return 0;
+}
+
+int sci_adi_write_fast(unsigned long reg, u16 val, u32 sync)
+{
+ if (!__adi_addr_check(reg)) {
+ unsigned long flags;
+ __arch_default_lock(HWLOCK_ADI, &flags);
+ __adi_write(reg, val, sync);
+ __arch_default_unlock(HWLOCK_ADI, &flags);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(sci_adi_write_fast);
+
+int sci_adi_write(unsigned long reg, u16 or_val, u16 clear_msk)
+{
+ if (!__adi_addr_check(reg)) {
+ unsigned long flags;
+ int ret = 0, val = 0;
+ __arch_default_lock(HWLOCK_ADI, &flags);
+ ret = __adi_read(__adi_translate_addr(reg), &val);
+ if (!ret)
+ __adi_write(reg, (val & ~clear_msk) | or_val, 1);
+ __arch_default_unlock(HWLOCK_ADI, &flags);
+ if (ret)
+ BUG_ON(1);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(sci_adi_write);
+
+static void __init __adi_init(void)
+{
+ uint32_t value;
+ value = __raw_readl((void __iomem *)REG_ADI_CTRL0);
+
+ if (__adi_ver() == 0) {
+ value &= ~BIT_ARM_SCLK_EN;
+ value |= BITS_CMMB_WR_PRI;
+ __raw_writel(value, (void __iomem *)REG_ADI_CTRL0);
+
+#ifdef CONFIG_ARCH_SCX35LT8
+ value = __raw_readl((void __iomem *)REG_ADI_CHNL_PRIL);
+#else
+ value = __raw_readl((void __iomem *)REG_ADI_CHNL_PRI);
+#endif
+ value |= BITS_PD_WR_PRI | BITS_RFT_WR_PRI |
+ BITS_DSP_RD_PRI | BITS_DSP_WR_PRI |
+ BITS_ARM_RD_PRI | BITS_ARM_WR_PRI |
+ BITS_STC_WR_PRI | BITS_INT_STEAL_PRI;
+#ifdef CONFIG_ARCH_SCX35LT8
+ __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRIL);
+#else
+ __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRI);
+#endif
+ readback_addr_mak = 0x7ff;
+ } else if (__adi_ver() == 1) {
+ if (value)
+ WARN_ON(1);
+
+ value = VALUE_CH_PRI;
+#ifdef CONFIG_ARCH_SCX35LT8
+ __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRIL);
+ __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRIH);
+#else
+ __raw_writel(value, (void __iomem *)REG_ADI_CHNL_PRI);
+#endif
+ value = __raw_readl((void __iomem *)REG_ADI_GSSI_CFG0);
+ readback_addr_mak = (value & 0x3f) - ((value >> 11) & 0x1f) - 1;
+ readback_addr_mak = (1<<(readback_addr_mak + 2)) - 1;
+ }
+}
+
+int __init sci_adi_init(void)
+{
+ struct resource res;
+ struct device_node *np;
+
+
+ np = of_find_compatible_node(NULL, NULL, "sprd,adi");
+ if (of_can_translate_address(np)) {
+ of_address_to_resource(np, 0, &res);
+ sprd_adi_phys = res.start;
+ sprd_adi_base = ioremap_nocache(res.start,
+ resource_size(&res));
+ if (!sprd_adi_base) {
+ printk("%s: no such memory!\n", __func__);
+ return -ENOMEM;
+ }
+ printk("%s: sprd_adi_base=%lx\n", __func__, sprd_adi_base);
+ }
+
+#if defined(CONFIG_ARCH_SC8825)
+ /* enable adi in global regs */
+ sci_glb_set((unsigned long )REG_GLB_GEN0, BIT_ADI_EB);
+ /* reset adi */
+ sci_glb_set((unsigned long )REG_GLB_SOFT_RST, BIT_ADI_RST);
+ udelay(2);
+ sci_glb_clr((unsigned long )REG_GLB_SOFT_RST, BIT_ADI_RST);
+
+#elif defined(CONFIG_ARCH_SCX35)
+ /*enable adi in global regs*/
+ sci_glb_set((unsigned long )REG_AON_APB_APB_EB0, BIT_ADI_EB);
+#endif
+ __adi_init();
+
+ return 0;
+}
+
diff --git a/drivers/platform/sprd/adie_irq.c b/drivers/platform/sprd/adie_irq.c
new file mode 100644
index 00000000..df4ac0ca
--- /dev/null
+++ b/drivers/platform/sprd/adie_irq.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ * Author: steve.zhan <steve.zhan@spreadtrum.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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/irqflags.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#ifdef CONFIG_OF
+#include <linux/irqdomain.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#endif
+
+#include <asm/delay.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/globalregs.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/irqs.h>
+#include <soc/sprd/sci_glb_regs.h>
+
+//#include <mach/irqs.h>
+
+/* registers definitions for controller ANA_CTL_INT */
+#define ANA_REG_INT_MASK_STATUS (ANA_CTL_INT_BASE + 0x0000)
+#define ANA_REG_INT_RAW_STATUS (ANA_CTL_INT_BASE + 0x0004)
+#define ANA_REG_INT_EN (ANA_CTL_INT_BASE + 0x0008)
+#define ANA_REG_INT_MASK_STATUS_SYNC (ANA_CTL_INT_BASE + 0x000c)
+
+/* bits definitions for register REG_INT_MASK_STATUS */
+#if defined(CONFIG_ARCH_SC8825)
+/* vars definitions for controller ANA_CTL_INT */
+#define MASK_ANA_INT ( 0xFF )
+#elif defined(CONFIG_ARCH_SCX15)
+/* vars definitions for controller ANA_CTL_INT */
+#define MASK_ANA_INT ( 0x3FF )
+#elif defined(CONFIG_ARCH_SCX35)
+/* vars definitions for controller ANA_CTL_INT */
+#define MASK_ANA_INT ( 0x7FF )
+#else
+#error "pls define interrupt number mask value"
+#endif
+
+/* used to define the start irq in xlate fun */
+#define ADIE_SUB_IRQ_START 32
+
+/* used to save vitual irq num */
+static int *virq_table = NULL;
+
+/* the total irq num of adie domain */
+static int irqnums = 0;
+
+static struct irq_domain *irq_domain;
+void sprd_ack_ana_irq(struct irq_data *data)
+{
+ /* nothing to do... */
+}
+
+static void sprd_mask_ana_irq(struct irq_data *data)
+{
+ int offset;
+#ifdef CONFIG_OF
+ int i;
+
+ for (i = 0; i < irqnums; i++) {
+ if (virq_table[i] == data->irq) {
+ offset = i;
+ break;
+ }
+ }
+ if (i == irqnums)
+ return;
+#else
+ offset = data->irq - IRQ_ANA_INT_START;
+#endif
+ pr_debug("%s %d\n", __FUNCTION__, data->irq);
+ sci_adi_write(ANA_REG_INT_EN, 0, BIT(offset) & MASK_ANA_INT);
+}
+
+static void sprd_unmask_ana_irq(struct irq_data *data)
+{
+ int offset;
+#ifdef CONFIG_OF
+ int i;
+
+ for (i = 0; i < irqnums; i++) {
+ if (virq_table[i] == data->irq) {
+ offset = i;
+ break;
+ }
+ }
+ if (i == irqnums)
+ return;
+#else
+ offset = data->irq - IRQ_ANA_INT_START;
+#endif
+ pr_debug("%s %d\n", __FUNCTION__, data->irq);
+ sci_adi_write(ANA_REG_INT_EN, BIT(offset) & MASK_ANA_INT, 0);
+}
+
+static struct irq_chip sprd_muxed_ana_chip = {
+ .name = "irq-ANA",
+ .irq_ack = sprd_ack_ana_irq,
+ .irq_mask = sprd_mask_ana_irq,
+ .irq_unmask = sprd_unmask_ana_irq,
+};
+
+static irqreturn_t sprd_muxed_ana_handler(int irq, void *dev_id)
+{
+ uint32_t irq_ana, status;
+ int i;
+
+ status = sci_adi_read(ANA_REG_INT_MASK_STATUS) & MASK_ANA_INT;
+ pr_debug("%s %d 0x%08x\n", __FUNCTION__, irq, status);
+ while (status) {
+ i = __ffs(status);
+ status &= ~(1 << i);
+#ifndef CONFIG_OF
+ irq_ana = IRQ_ANA_INT_START + i;
+#else
+ irq_ana = virq_table[i];
+#endif
+ pr_debug("%s generic_handle_irq %d\n", __FUNCTION__, irq_ana);
+ generic_handle_irq(irq_ana);
+ }
+ return IRQ_HANDLED;
+}
+
+static struct irqaction __adie_mux_irq = {
+ .name = "adie_mux",
+ .flags = IRQF_DISABLED | IRQF_NO_SUSPEND,
+ .handler = sprd_muxed_ana_handler,
+};
+
+void __init ana_init_irq(void)
+{
+ int n;
+
+ for (n = IRQ_ANA_INT_START; n < IRQ_ANA_INT_START + NR_ANA_IRQS; n++) {
+ irq_set_chip_and_handler(n, &sprd_muxed_ana_chip,
+ handle_level_irq);
+ set_irq_flags(n, IRQF_VALID);
+ }
+ setup_irq(IRQ_ANA_INT, &__adie_mux_irq);
+
+}
+
+#ifdef CONFIG_OF
+#define IRQCHIP_DECLARE(name,compstr,fn) \
+ static const struct of_device_id irqchip_of_match_##name \
+ __used __section(__irqchip_of_table) \
+ = { .compatible = compstr, .data = fn }
+
+static int adie_irq_domain_xlate(struct irq_domain *d,
+ struct device_node *ctrlr,
+ const u32 *intspec, unsigned int intsize,
+ unsigned long *out_hwirq, unsigned int *out_type)
+{
+ if (WARN_ON(intsize < 1))
+ return -EINVAL;
+ *out_hwirq = intspec[0] + ADIE_SUB_IRQ_START;
+ *out_type = (intsize > 1) ? intspec[1] : IRQ_TYPE_NONE;
+ return 0;
+}
+
+static int adie_irq_domain_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ virq_table[hw-ADIE_SUB_IRQ_START] = virq;
+ irq_set_chip_and_handler(virq, &sprd_muxed_ana_chip,
+ handle_level_irq);
+ set_irq_flags(virq, IRQF_VALID);
+
+ return 0;
+}
+
+const struct irq_domain_ops adie_irq_domain_ops = {
+ .xlate = adie_irq_domain_xlate,
+ .map = adie_irq_domain_map,
+};
+int __init adi_of_init(struct device_node *node, struct device_node *parent)
+{
+ struct resource res;
+ int irq;
+
+ if (WARN_ON(!node))
+ return -ENODEV;
+
+ if(of_address_to_resource(node, 0, &res)){
+ pr_err("no reg of property specified for adi\n");
+ return -ENODEV;
+ }
+
+ if (of_property_read_u32(node, "sprd,irqnums", &irqnums)){
+ pr_err("no sprd,irqnums of property specified");
+ BUG();
+ }
+
+ irq_domain = irq_domain_add_linear(node, irqnums + ADIE_SUB_IRQ_START,
+ &adie_irq_domain_ops, NULL);
+ BUG_ON(!irq_domain);
+
+ virq_table = kmalloc(sizeof(int)*irqnums, GFP_KERNEL);
+ BUG_ON(!virq_table);
+
+ if (parent) {
+ irq = irq_of_parse_and_map(node, 0);
+ setup_irq(irq, &__adie_mux_irq);
+ }
+
+ return 0;
+}
+IRQCHIP_DECLARE(sprd_adi, "sprd,adi-bus", adi_of_init);
+
+#endif
diff --git a/drivers/platform/sprd/arch_init.c b/drivers/platform/sprd/arch_init.c
new file mode 100644
index 00000000..68674b2a
--- /dev/null
+++ b/drivers/platform/sprd/arch_init.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 Spreadtrum Communications 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 <linux/module.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/adc.h>
+#include <soc/sprd/arch_lock.h>
+#include <linux/of_platform.h>
+
+static int __init arch_init(void)
+{
+ early_hwspinlocks_init();
+ sci_adi_init();
+ sci_adc_init();
+
+ return 0;
+}
+
+postcore_initcall_sync(arch_init);
+
+//jian.chen temporary modification
+#ifdef CONFIG_64BIT
+const struct of_device_id of_sprd_late_bus_match_table[] = {
+ { .compatible = "sprd,sound", },
+ {}
+};
+static void __init sc8830_init_late(void)
+{
+ of_platform_populate(of_find_node_by_path("/sprd-audio-devices"),
+ of_sprd_late_bus_match_table, NULL, NULL);
+}
+core_initcall(sc8830_init_late);
+#endif
diff --git a/drivers/platform/sprd/arch_lock.c b/drivers/platform/sprd/arch_lock.c
new file mode 100644
index 00000000..b4a7d66a
--- /dev/null
+++ b/drivers/platform/sprd/arch_lock.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ * Author: steve.zhan <steve.zhan@spreadtrum.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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/hwspinlock.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <soc/sprd/sci.h>
+#include <soc/sprd/arch_lock.h>
+#include <linux/of_irq.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include "devices.h"
+
+/**
+* Each physical lock must have a system-wide id number
+* that is agreed upon, otherwise remote processors can't possibly assume
+* they're using the same hardware lock.
+*/
+struct hwspinlock *hwlocks[HWSPINLOCK_ID_TOTAL_NUMS];
+unsigned char __initdata hwlocks_implemented[HWSPINLOCK_ID_TOTAL_NUMS];
+unsigned int hwspinlock_vid;
+unsigned char local_hwlocks_status[HWSPINLOCK_ID_TOTAL_NUMS];/*array[0] standfor lock0*/
+
+int __init early_init_hwlocks(void)
+{
+ int i;
+ struct hwspinlock **plock;
+ arch_hwlocks_implemented();
+
+ for (i = 0; i < HWSPINLOCK_ID_TOTAL_NUMS; ++i) {
+ if (hwlocks_implemented[i]) {
+ plock = &hwlocks[i];
+ *plock = hwspin_lock_request_specific(i);
+ if (WARN_ON(IS_ERR_OR_NULL(*plock)))
+ *plock = NULL;
+ else
+ pr_info("early alloc hwspinlock id %d\n",
+ hwspin_lock_get_id(*plock));
+ }
+ }
+ return 0;
+}
+
+static int remove_node(struct device_node *np)
+{
+ struct property *ps;
+
+ ps = kzalloc(sizeof(*ps), GFP_KERNEL);
+ if (!ps)
+ return -1;
+
+ ps->value = kstrdup("no", GFP_KERNEL);
+ ps->length = strlen(ps->value);
+ ps->name = kstrdup("status", GFP_KERNEL);
+ of_update_property(np, ps);
+ printk("After register,hwspinlock1 node available=%d\n",of_device_is_available(np));
+
+ return 0;
+}
+
+int __init hwspinlocks_init(void)
+{
+ struct device_node *np;
+ struct platform_device *pdev;
+ int ret;
+
+ np = of_find_node_by_name(NULL, "hwspinlock1");
+ if (!np) {
+ pr_warn("Can't get the hwspinlock1 node!\n");
+ return -ENODEV;
+ }
+
+ pdev = of_platform_device_create(np, 0, NULL);
+ if (!pdev) {
+ pr_warn("register hwspinlock1 failed!\n");
+ return -ENODEV;
+ }
+ pr_info("SPRD register hwspinlock1 ok,hwspinlock1's name is %s!\n",pdev->name);
+ ret = remove_node(np);
+
+ return ret;
+}
+
diff --git a/drivers/platform/sprd/arch_misc.c b/drivers/platform/sprd/arch_misc.c
new file mode 100644
index 00000000..4804d2b2
--- /dev/null
+++ b/drivers/platform/sprd/arch_misc.c
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ * Copyright (C) 2013 steve zhan
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/irqflags.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/hwspinlock.h>
+#include <linux/mm.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+
+#include <asm/delay.h>
+#include <asm/memory.h>
+#ifndef CONFIG_64BIT
+#include <asm/highmem.h>
+#include <mach/irqs.h>
+#endif
+#include <asm/pgtable-hwdef.h>
+#include <asm/tlbflush.h>
+#include <asm/fixmap.h>
+#include <asm/pgalloc.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/arch_misc.h>
+
+#ifndef CONFIG_64BIT
+#include "../../../arch/arm/mm/mm.h"
+#else
+#include "../../../arch/arm64/mm/mm.h"
+#endif
+/*#define DEBUG */
+#ifdef DEBUG
+#define DEBUGSEE printk
+#else
+#define DEBUGSEE(...)
+#endif
+
+static u32 chip_id __read_mostly;
+u32 sci_get_chip_id(void)
+{
+ return chip_id;
+}
+
+u32 sci_get_ana_chip_id(void)
+{
+#if defined(CONFIG_ARCH_SCX35)
+ return (u32)sci_adi_read(ANA_REG_GLB_CHIP_ID_HIGH) << 16 |
+ (u32)(sci_adi_read(ANA_REG_GLB_CHIP_ID_LOW) & ~MASK_ANA_VER);
+#else
+ return 0;
+#endif
+}
+
+int sci_get_ana_chip_ver(void)
+{
+#if defined(CONFIG_ARCH_SCX35)
+ return ((u32)sci_adi_read(ANA_REG_GLB_CHIP_ID_LOW) & MASK_ANA_VER);
+#else
+ return 0;
+#endif
+}
+
+void set_section_ro(unsigned long virt, unsigned long numsections)
+{
+ pmd_t *pmd;
+ pgd_t *pgd;
+ pud_t *pud;
+ unsigned long start = virt;
+ unsigned long end = virt + (numsections << SECTION_SHIFT);
+ unsigned long pmd_end;
+
+ while (virt < end) {
+ //pmd = pmd_off_k(virt);
+ pgd = pgd_offset_k(virt);
+ BUG_ON(pgd_none(*pgd) || pgd_bad(*pgd));
+
+ pud = pud_offset(pgd, virt);
+ BUG_ON(pud_none(*pud) || pud_bad(*pud));
+ pmd = pmd_offset(pud,virt);
+ if ((pmd_val(*pmd) & PMD_TYPE_MASK) != PMD_TYPE_SECT) {
+ pr_err("%s: pmd %p=%08lx for %08lx not section map\n",
+ __func__, pmd, pmd_val(*pmd), virt);
+ return;
+ }
+
+ /*
+ * We set section page table entry APX:1 AP:01
+ * Privileged : READ, Unprivileged : No Access
+ */
+ *pmd |= PAGE_READONLY;
+ pr_debug("%s: pmd %p=%08lx for %08lx ok\n",
+ __func__, pmd, pmd_val(*pmd), virt);
+ virt += SECTION_SIZE;
+ }
+
+ flush_tlb_kernel_range(start, end);
+}
+
+void __iomap_page(unsigned long virt, unsigned long size, int enable)
+{
+ unsigned long addr = virt, end = virt + (size & ~(SZ_4K - 1));
+ u32 *pgd;
+ u32 *pte;
+
+ pgd = (u32 *)init_mm.pgd + (addr >> 20);
+ pte = (u32 *)__va(*pgd & 0xfffffc00);
+ pte += (addr >> 12) & 0xff;
+
+ pr_debug("%s (%d) pgd %p, pte %p, *pte %x\n",
+ __FUNCTION__, enable, pgd, pte, *pte);
+
+ if (enable)
+ *pte |= 0x3;
+ else
+ *pte &= ~0x3;
+
+ flush_tlb_kernel_range(virt, end);
+}
+#if 0
+#define sci_mm_glb_set(reg, bit) __raw_writel((bit), (void *)REG_GLB_SET(reg))
+#define sci_mm_glb_clr(reg, bit) __raw_writel((bit), (void *)REG_GLB_CLR(reg))
+#define sci_mm_reg_set(reg, bit) __raw_writel(__raw_readl((void *)(reg)) | (bit), (void *)(reg))
+#define sci_mm_reg_clr(reg, bit) __raw_writel((__raw_readl((void *)(reg)) & ~(bit)), (void *)(reg))
+#ifdef CONFIG_OF
+int local_mm_enable(void *c, int enable)
+#else
+int sci_mm_enable(void *c, int enable, unsigned long *pflags)
+#endif
+{
+ if (enable) {
+ int to = 2000;
+ /* FIXME: mm domain power on at first
+ */
+ sci_mm_glb_clr(REG_PMU_APB_PD_MM_TOP_CFG, BIT_PD_MM_TOP_FORCE_SHUTDOWN);
+
+ __iomap_page(REGS_MM_AHB_BASE, SZ_4K, enable);
+ __iomap_page(REGS_MM_CLK_BASE, SZ_4K, enable);
+ __iomap_page(SPRD_DCAM_BASE, SZ_4K, enable);
+ __iomap_page(SPRD_VSP_BASE, SZ_4K, enable);
+
+#if defined(CONFIG_ARCH_SCX15)
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT_MM_EB);
+#else
+ sci_mm_glb_clr(REG_PMU_APB_PD_MM_TOP_CFG, BIT_PD_MM_TOP_FORCE_SHUTDOWN);
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT_MM_EB);
+ /* FIXME: wait a moment for mm domain stable
+ */
+ while ((__raw_readl((void *)REG_PMU_APB_PWR_STATUS0_DBG) & BITS_PD_MM_TOP_STATE(-1)) && to--) {
+ udelay(50);
+ }
+
+ WARN(0 || 0 != sci_glb_read(REG_PMU_APB_PWR_STATUS0_DBG, BITS_PD_MM_TOP_STATE(-1)),
+ "MM TOP CFG 0x%08x, TIMEOUT %d\n", __raw_readl((void *)REG_PMU_APB_PD_MM_TOP_CFG),
+ (2000 - to) * 50);
+
+ WARN(0 == sci_glb_read(REG_AON_APB_APB_EB0, BIT_MM_EB),
+ "AON APB EB0 0x%08x\n", __raw_readl((void *)REG_AON_APB_APB_EB0));
+
+ /* FIXME: force mm clock enable
+ */
+ sci_mm_glb_set(REG_AON_APB_APB_EB0, BIT_MM_EB);
+ sci_mm_glb_set(REG_MM_AHB_AHB_EB, BIT_MM_CKG_EB);
+ sci_mm_reg_set(REG_MM_AHB_GEN_CKG_CFG, BIT_MM_MTX_AXI_CKG_EN | BIT_MM_AXI_CKG_EN);
+ sci_mm_reg_set(REG_MM_CLK_MM_AHB_CFG, 0x3);/* default set mm ahb 153.6MHz */
+#endif
+ } else {
+#if defined(CONFIG_ARCH_SCX15)
+ sci_glb_clr(REG_AON_APB_APB_EB0, BIT_MM_EB);
+#else
+ sci_mm_reg_clr(REG_MM_AHB_GEN_CKG_CFG, BIT_MM_MTX_AXI_CKG_EN | BIT_MM_AXI_CKG_EN);
+ sci_mm_glb_clr(REG_MM_AHB_AHB_EB, BIT_MM_CKG_EB);
+
+ sci_glb_clr(REG_AON_APB_APB_EB0, BIT_MM_EB);
+ /* FIXME: do not force mm domain power off before retention
+ */
+ sci_mm_glb_set(REG_PMU_APB_PD_MM_TOP_CFG, BIT_PD_MM_TOP_FORCE_SHUTDOWN);
+#endif
+ /* FIXME: clean mm io map,
+ * do not access any reg in mm domain after mm clock disabled
+ */
+ __iomap_page(REGS_MM_AHB_BASE, SZ_4K, enable);
+ __iomap_page(REGS_MM_CLK_BASE, SZ_4K, enable);
+ __iomap_page(SPRD_DCAM_BASE, SZ_4K, enable);
+ __iomap_page(SPRD_VSP_BASE, SZ_4K, enable);
+ }
+ return 0;
+}
+#endif
+void __init sc_init_chip_id(void)
+{
+#if defined(CONFIG_ARCH_SC8825)
+ chip_id = __raw_readl(REG_AHB_CHIP_ID);
+#elif defined(CONFIG_ARCH_SCX35LT8)
+ chip_id = __raw_readl((void *)REG_AON_APB_CHIP_ID0);
+ if (chip_id == 0x6B4C5438) { //kLT8
+ chip_id = __raw_readl((void *)REG_AON_APB_CHIP_ID1);
+ if (chip_id == 0x53686172) { //shar
+ chip_id = 0x9838;
+ } else {
+ chip_id = 0;
+ printk(KERN_WARNING"Chip id is wrong!\n");
+ }
+ } else {
+ chip_id = 0;
+ printk(KERN_WARNING"Chip id is wrong!\n");
+ }
+#elif defined(CONFIG_ARCH_SCX35)
+ chip_id = __raw_readl((void *)REG_AON_APB_CHIP_ID);
+ if (chip_id == 0)
+ chip_id = SCX35_ALPHA_TAPOUT; //alpha Tapout.
+#else
+ #error "Is chip_id ..?"
+#endif
+}
+
+static inline int __chip_raw_ddie_write(ulong vreg, ulong or_val, u32 clear_msk)
+{
+ ulong val = __raw_readl((void *)vreg);
+ writel((val & ~clear_msk) | or_val, (void *)vreg);
+ return 0;
+}
+
+int sci_read_va(ulong vreg, ulong * val)
+{
+ DEBUGSEE("sci_read_va vreg = 0x%x type:", vreg);
+ if (!val)
+ return -1;
+
+ if (unlikely(sci_is_adi_vaddr(vreg))) {
+ DEBUGSEE("adie\n");
+ *val = sci_adi_read(vreg);
+ } else {
+ DEBUGSEE("ddie\n");
+ *val = __raw_readl((void *)vreg);
+ }
+
+ return 0;
+}
+
+int sci_write_va(ulong vreg, const ulong or_val, const u32 clear_msk)
+{
+ DEBUGSEE("sci_write_va\n");
+
+ if (unlikely(sci_is_adi_vaddr(vreg))) {
+ DEBUGSEE("adie\n");
+ sci_adi_write(vreg, or_val, clear_msk);
+ } else { /*FIXME: is not safe */
+ DEBUGSEE("ddie\n");
+ __chip_raw_ddie_write(vreg, or_val, clear_msk);
+ }
+ return 0;
+}
+
+int sci_read_pa(ulong preg, ulong * val)
+{
+ void __iomem *addr;
+ int ret = 0;
+ ulong vaddr = 0;
+
+ if (!val)
+ return -1;
+
+ DEBUGSEE("sci_read_pa 0x%x\n", preg);
+ addr = __va(preg);
+ if (!virt_addr_valid(addr)) {
+ if (!sci_adi_p2v(preg, &vaddr)) {
+ *val = sci_adi_read((long) vaddr);
+ } else {
+ addr = ioremap_nocache(preg, PAGE_SIZE);
+ if (!addr) {
+ printk(KERN_WARNING
+ "unable to map i/o region\n");
+ ret = -ENOMEM;
+ goto Exit;
+ }
+ *val = __raw_readl((void *) addr);
+ iounmap(addr);
+ }
+ } else {
+ *val = __raw_readl((void *) addr);
+ }
+Exit:
+ return ret;
+}
+
+int sci_write_pa(ulong paddr, const ulong or_val, const u32 clear_msk)
+{
+ int ret = 0;
+ ulong vaddr = 0;
+
+ DEBUGSEE("sci_write_pa 0x%x, 0x%x, 0x%x\n", paddr, or_val, clear_msk);
+ vaddr = (ulong) __va(paddr);
+ if (!virt_addr_valid(vaddr)) {
+ if (!sci_adi_p2v(paddr, &vaddr)) {
+ sci_adi_write(vaddr, or_val, clear_msk);
+ } else {
+ vaddr = (ulong) ioremap_nocache(paddr, PAGE_SIZE);
+ if (!vaddr) {
+ printk(KERN_WARNING
+ "unable to map i/o region\n");
+ ret = -ENOMEM;
+ goto Exit;
+ }
+ DEBUGSEE("vaddr = 0x%x\n", vaddr);
+ __chip_raw_ddie_write((ulong) vaddr, or_val, clear_msk);
+ iounmap((void __iomem *)vaddr);
+ }
+ } else {
+ __chip_raw_ddie_write((ulong) vaddr, or_val, clear_msk);
+ }
+Exit:
+ return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+struct sci_lookat {
+ const char *name;
+
+#define _LOOKAT_RWPV_ (1<<0)
+#define _LOOKAT_INPUT_OUTPUT_DATA_ (1<<1)
+ int entry_type;
+};
+static struct sci_lookat __lookat_array[] = {
+ {"addr_rwpv", _LOOKAT_RWPV_},
+ {"data", _LOOKAT_INPUT_OUTPUT_DATA_},
+};
+
+struct lookat_request {
+#define __READ__ (0<<1)
+#define __WRITE__ (1<<1)
+#define __P__ (0<<0)
+#define __V__ (1<<0)
+ u32 cmd;
+ ulong addr;
+ ulong value; /*if is read, save result to value, if is write, value is need to write */
+};
+static struct lookat_request __request[5];
+static int index = 0;
+static int queue_add_element(u32 raw_cmd)
+{
+ __request[index].cmd = raw_cmd & (0x3);
+ __request[index].addr = raw_cmd & (~0x3);
+ if (index == ARRAY_SIZE(__request) - 1)
+ index = 0;
+ else
+ index++;
+ return 0;
+}
+
+static struct lookat_request *__return_last_eliment(void)
+{
+ if (!index)
+ return &__request[ARRAY_SIZE(__request) - 1];
+ else
+ return &__request[index - 1];
+}
+
+static inline int queue_modify_value(ulong value)
+{
+ struct lookat_request *p = __return_last_eliment();
+
+ if (!(p->cmd & __WRITE__)) {
+ return -1;
+ } else { /*write, save the write data that need to send */
+ p->value = value;
+ return 0;
+ }
+}
+
+static int do_request(void)
+{
+ struct lookat_request *p;
+ int ret = 0;
+
+ p = __return_last_eliment();
+ DEBUGSEE("p->cmd = 0x%x, p->addr = 0x%x ", p->cmd, p->addr);
+
+ if ((p->cmd & __WRITE__) == __WRITE__) {
+ if ((p->cmd & __V__) == __V__)
+ ret = sci_write_va(p->addr, p->value, ~0);
+ else
+ ret = sci_write_pa(p->addr, p->value, ~0);
+ } else {
+ if ((p->cmd & __V__) == __V__)
+ ret = sci_read_va(p->addr, &p->value);
+ else
+ ret = sci_read_pa(p->addr, &p->value);
+ }
+ DEBUGSEE("p->value = 0x%x\n", p->value);
+
+ return ret;
+}
+
+static int __debug_set(void *data, u64 val)
+{
+ struct sci_lookat *p = data;
+ int ret = 0;
+ struct lookat_request *r;
+
+ if (p->entry_type == _LOOKAT_INPUT_OUTPUT_DATA_) {
+ if (queue_modify_value((ulong) val) || do_request()) {
+ ret = -1;
+ goto Exit;
+ }
+ } else if (p->entry_type == _LOOKAT_RWPV_) {
+ ret = queue_add_element((ulong) val);
+ if (!ret) {
+ r = __return_last_eliment();
+ if (!((r->cmd & __WRITE__) == __WRITE__))
+ ret = do_request();
+ goto Exit;
+ }
+ } else {
+ BUG_ON(1);
+ }
+
+Exit:
+ return ret;
+}
+
+static int __debug_get(void *data, u64 * val)
+{
+ struct sci_lookat *p = data;
+
+ if (p->entry_type == _LOOKAT_INPUT_OUTPUT_DATA_) {
+ struct lookat_request *r = __return_last_eliment();
+ *val = r->value;
+ DEBUGSEE("__debug_get *val = 0x%08x ", r->value);
+ } else if (p->entry_type == _LOOKAT_RWPV_) {
+ printk("echo addr | b00 to read Paddr's value\n");
+ printk("echo addr | b01 to read Vaddr's value\n");
+ printk
+ ("echo addr | b10 to write Paddr's value,Pls echo value to data file\n");
+ printk
+ ("echo addr | b11 to write Vaddr's value,Pls echo value to data file\n");
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(lookat_fops, __debug_get, __debug_set, "0x%08llx");
+
+static struct dentry *lookat_base;
+static int __init __debug_add(struct sci_lookat *lookat)
+{
+ if (!debugfs_create_file(lookat->name, 0644, lookat_base,
+ lookat, &lookat_fops))
+ return -ENOMEM;
+
+ return 0;
+}
+
+int __init lookat_debug_init(void)
+{
+ int i;
+ lookat_base = debugfs_create_dir("lookat", NULL);
+ if (!lookat_base)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(__lookat_array); ++i) {
+ if (__debug_add(&__lookat_array[i]))
+ goto err;
+ }
+
+ return 0;
+err:
+ debugfs_remove(lookat_base);
+ return -1;
+}
+
+module_init(lookat_debug_init);
+#endif
+
+#if defined(CONFIG_ARCH_SCX35LT8)
+void __clock_init_early(void)
+{
+ sci_glb_set(REG_AON_APB_APB_EB1,
+ BIT_CODEC_EB
+ );
+
+ sci_glb_set(REG_AON_APB_APB_EB0,
+ BIT_GPU_EB
+ );
+}
+
+static void sprd_ap_machine_start(void)
+{
+ __clock_init_early();
+}
+arch_initcall(sprd_ap_machine_start);
+
+void __clock_init_after(void)
+{
+ sci_glb_clr(REG_AON_APB_APB_EB1,
+ BIT_CODEC_EB
+ );
+
+ sci_glb_clr(REG_AON_APB_APB_EB0,
+ BIT_GPU_EB
+ );
+}
+
+static void sprd_ap_machine_exit(void)
+{
+ __clock_init_after();
+}
+subsys_initcall(sprd_ap_machine_exit);
+#endif
diff --git a/drivers/platform/sprd/aux_debug.c b/drivers/platform/sprd/aux_debug.c
new file mode 100755
index 00000000..ac65a71e
--- /dev/null
+++ b/drivers/platform/sprd/aux_debug.c
@@ -0,0 +1,148 @@
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <mach/common.h>
+#include <mach/hardware.h>
+#include <mach/sci.h>
+#include <linux/earlysuspend.h>
+#include <mach/sci_glb_regs.h>
+#include <mach/adi.h>
+#include <linux/random.h>
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/cacheflush.h>
+#include <asm/suspend.h>
+#include <linux/vmalloc.h>
+#include <linux/printk.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+
+enum aux_clk_src_e
+{
+ aux_clk_32k = 0,
+ aux_clk_26m_rf0 = 1,
+ aux_clk_26m_rf1 = 2,
+ aux_clk_48m = 3,
+ aux_clk_52m = 4,
+ aux_clk_51_2m = 5,
+ aux_clk_37_5m = 6,
+ aux_clk_40m_wifipll1 = 7,
+ aux_clk_66m = 8,
+ aux_clk_40m_wifipll2 = 9,
+ aux_clk_max = 10,
+};
+
+static char clk_menu[10][16]=
+{
+ "32k",
+ "26m rf0",
+ "26m rf1",
+ "48m tdpll",
+ "52m cpll",
+ "51_2m wpll",
+ "37_5m mpll",
+ "40m wifipll1",
+ "66m dpll",
+ "40m wifipll2"
+};
+
+struct aux_cfg_info
+{
+ struct dentry *debug_aux_dir;
+ char (*ptr_menu)[16];
+ volatile u32 aux_ena;
+ volatile u32 aux_sel;
+ volatile u32 aux_pin;
+ volatile u32 magic_header;
+ volatile u32 is_init_cfg;
+ volatile u32 cur_sel;
+ volatile u32 magic_ender;
+};
+
+static struct aux_cfg_info clk_cfg_info =
+{
+ 0x00000000, /*debug_aux_dir*/
+ clk_menu, /*clk_meum*/
+ 0x00000000, /*aux_ena*/
+ 0x00000000, /*aux_sel*/
+ 0x00000000, /*aux_pin*/
+ 0x5a5aa5a5, /*magic_header*/
+ 0x00000000, /*is_init_cfg*/
+ 0x6c657300, /*cur_sel*/
+ 0xa5a55a5a /*magic_ender*/
+};
+
+static void aux_clk_config(u32 clk_sel)
+{
+ u32 reg_val;
+
+ if(clk_sel < aux_clk_max){
+ /*select pin function to func 0*/
+ reg_val = sci_glb_read(clk_cfg_info.aux_pin,-1UL);
+ reg_val &= ~(0x3 << 4);
+ sci_glb_write(clk_cfg_info.aux_pin,reg_val,-1UL);
+ /*enable aux clock*/
+ sci_glb_set(clk_cfg_info.aux_ena,0x1 << 2);
+ /*select aux clock source*/
+ sci_glb_set(clk_cfg_info.aux_sel,clk_sel);
+ }
+}
+
+static int debugfs_aux_clk_sel_get(void *data, u64 * val)
+{
+ *val = clk_cfg_info.cur_sel & 0xf;
+
+ return 0;
+}
+static int debugfs_aux_clk_sel_set(void *data, u64 val)
+{
+ clk_cfg_info.cur_sel &= ~0xf;
+ clk_cfg_info.cur_sel |= val;
+
+ aux_clk_config(clk_cfg_info.cur_sel & 0xf);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fops_aux_clk_ops,
+ debugfs_aux_clk_sel_get, debugfs_aux_clk_sel_set, "%llu\n");
+
+static void aux_debugfs_create(void)
+{
+ clk_cfg_info.debug_aux_dir = debugfs_create_dir("aux_dir", NULL);
+ if (IS_ERR_OR_NULL(clk_cfg_info.debug_aux_dir)) {
+ pr_err("%s return %p\n", __FUNCTION__, clk_cfg_info.debug_aux_dir);
+ }
+ debugfs_create_file("aux", S_IRUGO | S_IWUGO,
+ clk_cfg_info.debug_aux_dir, &clk_cfg_info.cur_sel, &fops_aux_clk_ops);
+}
+
+static int __init aux_debug_init(void)
+{
+ clk_cfg_info.aux_sel = REG_AON_APB_AON_CGM_CFG;
+ clk_cfg_info.aux_ena = REG_AON_APB_APB_EB1;
+ clk_cfg_info.aux_pin = SPRD_PIN_BASE + 0x2ec;
+
+ aux_debugfs_create();
+
+ if(clk_cfg_info.is_init_cfg)
+ aux_clk_config(clk_cfg_info.cur_sel & 0xf);
+
+ return 0;
+}
+
+static void __exit aux_debug_exit(void)
+{
+ debugfs_remove_recursive(clk_cfg_info.debug_aux_dir);
+}
+module_init(aux_debug_init);
+module_exit(aux_debug_exit);
diff --git a/drivers/platform/sprd/avs_debug.c b/drivers/platform/sprd/avs_debug.c
new file mode 100755
index 00000000..01b1dbbb
--- /dev/null
+++ b/drivers/platform/sprd/avs_debug.c
@@ -0,0 +1,139 @@
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <mach/common.h>
+#include <mach/hardware.h>
+#include <mach/sci.h>
+#include <linux/earlysuspend.h>
+#include <mach/sci_glb_regs.h>
+#include <mach/adi.h>
+#include <linux/random.h>
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/cacheflush.h>
+#include <asm/suspend.h>
+#include <linux/vmalloc.h>
+#include <linux/printk.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+
+struct avs_hw_ctrl
+{
+ volatile u32 avs_tune_lmt_cfg;
+ volatile u32 avs_tune_status;
+ volatile u32 avs_cfg;
+ volatile u32 avs_tune_step_cfg;
+ volatile u32 avs_wait_cfg;
+ volatile u32 avs_int_cfg;
+ volatile u32 avs_start_single_act;
+ volatile u32 avs_start_repeat_act;
+ volatile u32 avs_hpm_en;
+ volatile u32 avs_hpm_rpt_anls;
+ volatile u32 avs_hpm_rpt_vld_status[4];
+ volatile u32 avs_hpm_dly_wind_sel;
+ volatile u32 avs_fsm_sts;
+};
+
+struct sprd_avs_info
+{
+ struct avs_hw_ctrl *chip;
+ struct timer_list avs_timer;
+ struct dentry *debug_avs_dir;
+ volatile u32 printfreq;
+ u32 window;
+};
+
+static struct sprd_avs_info sai;
+
+static void avs_init(void)
+{
+ /* avs enable */
+ sci_glb_set(REG_AON_APB_APB_EB1,1 << 6);
+}
+
+static void avs_trigger(void)
+{
+ /* avs hpm enable */
+ sai.chip->avs_hpm_en |= (1 << 1);
+ /* avs tune limit cfg */
+ sai.chip->avs_tune_lmt_cfg = 0xff80;
+ /* avs hpm rpt anls */
+ sai.chip->avs_hpm_rpt_anls = 0x3FF94;
+ /* start avs */
+ sai.chip->avs_start_repeat_act = 0x1;
+ /* delay for something */
+ udelay(5);
+}
+
+static void avs_timer_func(unsigned long data)
+{
+ static u32 arm_voltage_int[]={1100,700,800,900,1000,650,1200,1300};
+ u32 reg_val = sci_adi_read(ANA_REG_GLB_DCDC_ARM_ADI);
+ u32 int_part,frc_part;
+ int_part = arm_voltage_int[(reg_val & 0x7) >> 5];
+ frc_part = (reg_val & 0x1F)*3;
+
+ avs_trigger();
+
+ printk("****************************avs debug**********************************\r\n");
+ sai.window = sai.chip->avs_hpm_rpt_vld_status[0];
+ printk("avs window 0x%x arm voltage is %d mv\r\n",sai.window,(int_part + frc_part));
+ printk("****************************avs debug**********************************\r\n");
+ mod_timer(&sai.avs_timer,jiffies + msecs_to_jiffies(sai.printfreq + 10000));
+}
+
+static int debugfs_avs_printfreq_get(void *data, u64 * val)
+{
+ *(u32 *) data = *val = sai.printfreq;
+ return 0;
+}
+static int debugfs_avs_printfreq__set(void *data, u64 val)
+{
+ sai.printfreq = *(u32 *) data = val;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_avs_printfreq,
+ debugfs_avs_printfreq_get, debugfs_avs_printfreq__set, "%llu\n");
+
+static void avs_debugfs_create(void)
+{
+ sai.debug_avs_dir = debugfs_create_dir("avs_dir", NULL);
+ if (IS_ERR_OR_NULL(sai.debug_avs_dir)) {
+ pr_err("%s return %p\n", __FUNCTION__, sai.debug_avs_dir);
+ }
+ debugfs_create_file("avs", S_IRUGO | S_IWUGO,
+ sai.debug_avs_dir, &sai.printfreq, &fops_avs_printfreq);
+}
+
+static int __init avs_debug_init(void)
+{
+ sai.chip = (struct avs_hw_ctrl*)SPRD_AVSCA7_BASE;
+
+ init_timer(&sai.avs_timer);
+ sai.avs_timer.function = avs_timer_func;
+ sai.avs_timer.expires = jiffies + msecs_to_jiffies(2000);
+ sai.avs_timer.data = (u32)&sai;
+
+ avs_init();
+
+ avs_debugfs_create();
+
+ add_timer(&sai.avs_timer);
+ return 0;
+}
+
+static void __exit avs_debug_exit(void)
+{
+ del_timer_sync(&sai.avs_timer);
+}
+module_init(avs_debug_init);
+module_exit(avs_debug_exit);
diff --git a/drivers/platform/sprd/boot_mode.c b/drivers/platform/sprd/boot_mode.c
new file mode 100644
index 00000000..b206dc4d
--- /dev/null
+++ b/drivers/platform/sprd/boot_mode.c
@@ -0,0 +1,46 @@
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+
+
+static int calibration_mode = false;
+static int __init calibration_start(char *str)
+{
+ int calibration_device =0;
+ int mode=0,freq=0,device=0;
+ if(str){
+ pr_info("modem calibartion:%s\n", str);
+ sscanf(str, "%d,%d,%d", &mode,&freq,&device);
+ }
+ if(device & 0x80){
+ calibration_device = device & 0xf0;
+ calibration_mode = true;
+ pr_info("calibration device = 0x%x\n",calibration_device);
+ }
+ return 1;
+}
+__setup("calibration=", calibration_start);
+
+int in_calibration(void)
+{
+ return (int)(calibration_mode == true);
+}
+
+EXPORT_SYMBOL(in_calibration);
+
+static int autotest_mode = false;
+static int __init autotest_start(char *str)
+{
+ autotest_mode = true;
+ return 1;
+}
+__setup("autotest=", autotest_start);
+
+int in_autotest(void)
+{
+ return (int)(autotest_mode == true);
+}
+
+EXPORT_SYMBOL(in_autotest);
diff --git a/drivers/platform/sprd/busmonitor.c b/drivers/platform/sprd/busmonitor.c
new file mode 100644
index 00000000..10bd2148
--- /dev/null
+++ b/drivers/platform/sprd/busmonitor.c
@@ -0,0 +1,2111 @@
+/*copyright (C) 2014 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/semaphore.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/sysfs.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/timer.h>
+#include <linux/timex.h>
+#include <linux/rtc.h>
+
+#include <soc/sprd/sci.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/irqs.h>
+#include <soc/sprd/busmonitor.h>
+#include <soc/sprd/iomap.h>
+#include <soc/sprd/pm_debug.h>
+#include <soc/sprd/sprd_persistent_clock.h>
+
+
+#ifdef CONFIG_OF
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#endif
+
+ulong axi_bm_base = 0;
+ulong ahb_bm_base = 0;
+
+#define SPRD_AXI_BM_OFFSET 0x10000
+#define SPRD_AHB_BM_OFFSET 0x100000
+#define SPRD_AXIBM0_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM0)
+#define SPRD_AXIBM1_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM1)
+#define SPRD_AXIBM2_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM2)
+#define SPRD_AXIBM3_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM3)
+#define SPRD_AXIBM4_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM4)
+#define SPRD_AXIBM5_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM5)
+#define SPRD_AXIBM6_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM6)
+#define SPRD_AXIBM7_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM7)
+#define SPRD_AXIBM8_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM8)
+#define SPRD_AXIBM9_BASE (axi_bm_base + SPRD_AXI_BM_OFFSET * AXI_BM9)
+
+#define SPRD_BM0_BASE (ahb_bm_base + CHN0 * SPRD_AHB_BM_OFFSET)
+#define SPRD_BM1_BASE (ahb_bm_base + CHN1 * SPRD_AHB_BM_OFFSET)
+#define SPRD_BM2_BASE (ahb_bm_base + CHN2 * SPRD_AHB_BM_OFFSET)
+
+#define PUB_APB_BM_CNT_START REG_PUB_APB_BUSMON_CNT_START
+#define PUB_APB_BM_CFG REG_PUB_APB_BUSMON_CFG
+
+#define AP_AHB_AHB_EB REG_AP_AHB_AHB_EB
+#define AP_AHB_AHB_RST REG_AP_AHB_AHB_RST
+#define AP_AHB_MISC_CFG REG_AP_AHB_MISC_CFG
+
+struct memory_layout{
+ u32 dram_start;
+ u32 dram_end;
+
+ u32 ap_start1;
+ u32 ap_end1;
+
+ u32 shm_start;
+ u32 shm_end;
+
+ u32 ap_start2;
+ u32 ap_end2;
+
+ u32 cpgge_start;
+ u32 cpgge_end;
+
+ u32 cpw_start;
+ u32 cpw_end;
+
+ u32 cpwcn_start;
+ u32 cpwcn_end;
+
+ u32 cptl_start;
+ u32 cptl_end;
+
+ u32 ap_start3;
+ u32 ap_end3;
+
+ u32 fb_start;
+ u32 fb_end;
+
+ u32 ion_start;
+ u32 ion_end;
+};
+
+static struct memory_layout memory_layout;
+
+static u32 bm_count = 0;
+
+static DEFINE_SPINLOCK(bm_lock);
+
+static ulong __sci_get_bm_base(u32 bm_index)
+{
+ switch (bm_index) {
+ case AXI_BM0:
+ return SPRD_AXIBM0_BASE;
+ case AXI_BM1:
+ return SPRD_AXIBM1_BASE;
+ case AXI_BM2:
+ return SPRD_AXIBM2_BASE;
+ case AXI_BM3:
+ return SPRD_AXIBM3_BASE;
+ case AXI_BM4:
+ return SPRD_AXIBM4_BASE;
+ case AXI_BM5:
+ return SPRD_AXIBM5_BASE;
+ case AXI_BM6:
+ return SPRD_AXIBM6_BASE;
+ case AXI_BM7:
+ return SPRD_AXIBM7_BASE;
+ case AXI_BM8:
+ return SPRD_AXIBM8_BASE;
+ case AXI_BM9:
+ return SPRD_AXIBM9_BASE;
+ case AHB_BM0:
+ return SPRD_BM0_BASE;
+ case AHB_BM1:
+ return SPRD_BM1_BASE;
+ case AHB_BM2:
+ return SPRD_BM2_BASE;
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+static void __sci_bm_init(void)
+{
+ u32 bm_index;
+ volatile struct sci_bm_reg *bm_reg;
+
+ for (bm_index = AXI_BM0; bm_index < BM_SIZE; bm_index++) {
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_index);
+ memset((void *)bm_reg, 0x0, sizeof(struct sci_bm_reg));
+ }
+}
+
+static inline void __sci_axi_bm_chn_en(int chn)
+{
+ ulong val, base_reg;
+
+ base_reg = __sci_get_bm_base(chn);
+ val = __raw_readl((volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val |= (BM_CHN_EN);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+}
+
+static inline void __sci_axi_bm_chn_int_clr(int chn)
+{
+ ulong base_reg, val;
+
+ base_reg = __sci_get_bm_base(chn);
+ val = __raw_readl((volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val |= (BM_INT_CLR);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+}
+
+static inline void __sci_axi_bm_chn_int_disable(int chn)
+{
+ ulong base_reg, val;
+
+ base_reg = __sci_get_bm_base(chn);
+ val = __raw_readl((volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val &= ~(BM_INT_EN);
+ val |= (BM_INT_CLR);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+}
+
+static inline void __sci_axi_bm_chn_int_enable(int chn)
+{
+ ulong base_reg, val;
+
+ base_reg = __sci_get_bm_base(chn);
+ val = __raw_readl((volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val |= (BM_INT_CLR | BM_INT_EN);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+}
+
+static inline ulong __sci_axi_bm_chn_cnt_bw(int chn)
+{
+ ulong base_reg, rbw, wbw;
+
+ base_reg = __sci_get_bm_base(chn);
+ rbw = __raw_readl((volatile void *)(base_reg + AXI_BM_RBW_IN_WIN_REG));
+ wbw = __raw_readl((volatile void *)(base_reg + AXI_BM_WBW_IN_WIN_REG));
+ if(rbw || wbw)
+ BM_INFO(" chn:%d, rbw:%lu, wbw:%lu \n", chn, rbw, wbw );
+
+ return (rbw+wbw);
+}
+
+static void __sci_axi_bm_cnt_start(void)
+{
+ ulong bm_index, base_reg, val;
+
+ for (bm_index = AXI_BM0; bm_index <= AXI_BM9; bm_index++) {
+ base_reg = __sci_get_bm_base(bm_index);
+ val = __raw_readl((volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val |= (BM_CHN_EN | BM_CNT_EN | BM_CNT_START | BM_INT_EN);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+ }
+ return;
+}
+
+static void __sci_axi_bm_cnt_stop(void)
+{
+ ulong bm_index, base_reg, val;
+
+ for (bm_index = AXI_BM0; bm_index <= AXI_BM9; bm_index++) {
+ base_reg = __sci_get_bm_base(bm_index);
+ val = __raw_readl((volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val &= ~(BM_CNT_START | BM_INT_EN);//disable cnt and disable int
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+ }
+ return;
+}
+
+static void __sci_bm_int_clr(void)
+{
+ int bm_index;
+
+ for (bm_index = AXI_BM0; bm_index < BM_SIZE; bm_index++) {
+ __sci_axi_bm_chn_int_clr(bm_index);
+ }
+}
+
+static void __sci_axi_bm_int_disable(void)
+{
+ int bm_index;
+
+ for (bm_index = AXI_BM0; bm_index <= AXI_BM9; bm_index++) {
+ __sci_axi_bm_chn_int_disable(bm_index);
+ }
+}
+
+static void __sci_axi_bm_int_enable(void)
+{
+ int bm_index;
+
+ for (bm_index = AXI_BM0; bm_index <= AXI_BM9; bm_index++) {
+ __sci_axi_bm_chn_int_enable(bm_index);
+ }
+}
+
+/* performance count clear */
+static void __sci_axi_bm_cnt_clr(void)
+{
+ ulong bm_index, base_reg, val;
+
+ for (bm_index = AXI_BM0; bm_index <= AXI_BM9; bm_index++) {
+ base_reg = __sci_get_bm_base(bm_index);
+ val = __raw_readl((volatile void *)base_reg);
+ val |= (BM_CHN_EN | BM_CNT_EN);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val &= ~(BM_CNT_START);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+ val |= (BM_CNT_CLR);
+ __raw_writel(val, (volatile void *)(base_reg + AXI_BM_INTC_REG));
+ }
+ return;
+}
+
+static void __sci_axi_bm_set_winlen(void)
+{
+ ulong bm_index, base_reg, axi_clk, win_len;
+ /*the win len is 10ms*/
+#ifdef CONFIG_ARCH_SCX30G
+ axi_clk = 640;//emc_clk_get();
+#else
+ //axi_clk = __raw_readl(REG_AON_APB_DPLL_CFG) & 0x7ff;
+ //axi_clk = axi_clk << 2;
+ axi_clk = 640;
+#endif
+ /*the win_len = (axi_clk / 1000) * 10 */
+ win_len = axi_clk * 10000;
+ for (bm_index = AXI_BM0; bm_index <= AXI_BM9; bm_index++) {
+ base_reg = __sci_get_bm_base(bm_index);
+ __raw_writel(win_len, (volatile void *)(base_reg + AXI_BM_CNT_WIN_LEN_REG));
+ }
+}
+
+static int __sci_bm_glb_reset_and_enable(u32 bm_index, bool is_enable)
+{
+ ulong reg_en, reg_rst, bit_en, bit_rst;
+
+ switch (bm_index) {
+ case AXI_BM0:
+ case AXI_BM1:
+ case AXI_BM2:
+ case AXI_BM3:
+ case AXI_BM4:
+ case AXI_BM5:
+ case AXI_BM6:
+ case AXI_BM7:
+ case AXI_BM8:
+ case AXI_BM9:
+ reg_en = PUB_APB_BM_CFG;//REG_PUB_APB_BUSMON_CFG;
+ reg_rst = PUB_APB_BM_CFG;
+ bit_en = BIT(16 + bm_index);
+ bit_rst = BIT(bm_index);
+ break;
+ case AHB_BM0:
+ case AHB_BM1:
+ case AHB_BM2:
+ reg_en = AP_AHB_AHB_EB;// REG_AP_AHB_AHB_EB;
+ reg_rst = AP_AHB_AHB_RST; //REG_AP_AHB_AHB_RST;
+ bit_en = BIT(14 + bm_index - AHB_BM0);
+ bit_rst = BIT(17 + bm_index - AHB_BM0);
+ break;
+ default:
+ return -EINVAL;
+ }
+ sci_glb_set(reg_rst, bit_rst);
+ sci_glb_clr(reg_rst, bit_rst);
+
+ if (is_enable)
+ sci_glb_set(reg_en, bit_en);
+ else
+ sci_glb_clr(reg_en, bit_en);
+ return SPRD_BM_SUCCESS;
+}
+
+static void __sci_bm_glb_count_enable(bool is_enable)
+{
+ ulong reg_val = 0;
+
+ reg_val = sci_glb_read(PUB_APB_BM_CNT_START, 0x1);
+ if (is_enable) {
+ if (!reg_val)
+ sci_glb_set(PUB_APB_BM_CNT_START, BIT(0));
+ } else {
+ sci_glb_clr(PUB_APB_BM_CNT_START, BIT(0));
+ }
+}
+
+static void __sci_bm_glb_ahb_disable(void)
+{
+ int bm_index;
+
+ for (bm_index = AHB_BM0; bm_index < BM_SIZE; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, false);
+}
+
+static int __sci_bm_get_chn_sel(u32 bm_index)
+{
+ ulong reg_val, chn_sel;
+
+ reg_val = sci_glb_read(AP_AHB_MISC_CFG, 0xFFFFFFFF);
+
+ switch (bm_index) {
+ case AHB_BM0:
+ reg_val &= ((ulong)0x3 << 4);
+ chn_sel = (reg_val >> 4) + AHB_BM0_CHN0;
+ break;
+ case AHB_BM1:
+ reg_val &= ((ulong)0x3 << 8);
+ chn_sel = (reg_val >> 8) + AHB_BM1_CHN0;
+ break;
+ case AHB_BM2:
+ reg_val &= ((ulong)0x3 << 10);
+ chn_sel = (reg_val >> 10) + AHB_BM2_CHN0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return chn_sel;
+}
+
+static int __sci_bm_chn_sel(u32 bm_index, u32 chn_id)
+{
+ ulong reg_val;
+
+ reg_val = sci_glb_read(AP_AHB_MISC_CFG, 0xffffffff);
+
+ switch (bm_index) {
+ case AHB_BM0:
+ reg_val &= ~(0x3 << 4);
+ reg_val |= (chn_id & 0x3) << 4;
+ break;
+ case AHB_BM1:
+ reg_val &= ~(0x3 << 8);
+ reg_val |= (chn_id & 0x3) << 8;
+ break;
+ case AHB_BM2:
+ reg_val &= ~(0x3 << 10);
+ reg_val |= (chn_id & 0x3) << 10;
+ break;
+ default:
+ return -EINVAL;
+ }
+ sci_glb_write(AP_AHB_MISC_CFG, reg_val, 0xff0);
+
+ return SPRD_BM_SUCCESS;
+}
+
+static void __sci_bm_store_int_info(u32 bm_index)
+{
+ ulong reg_addr, mask_id;
+
+ reg_addr = __sci_get_bm_base(bm_index);
+ if(bm_ctn_dbg.bm_continue_dbg == false){
+ debug_bm_int_info[bm_index].bm_index = bm_index;
+ debug_bm_int_info[bm_index].msk_addr =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_ADDR_REG));
+ debug_bm_int_info[bm_index].msk_cmd =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_CMD_REG));
+ debug_bm_int_info[bm_index].msk_data_l =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_DATA_L_REG));
+ debug_bm_int_info[bm_index].msk_data_h =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_DATA_H_REG));
+ if(bm_index <= AXI_BM9){
+ mask_id = __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_ID_REG));
+ debug_bm_int_info[bm_index].msk_id = mask_id >> 2 ? 0 : mask_id;
+ }else{
+ mask_id = sci_glb_read(REG_AP_AHB_MISC_CFG, 0xFFFFFFFF);
+ debug_bm_int_info[bm_index].msk_id = 0;
+ }
+ BM_ERR("bm int info:\nBM CHN: %d\nOverlap ADDR: 0x%X\nOverlap CMD: 0x%X\nOverlap DATA: 0x%X 0x%X\nOverlap ID: %s--%ld\n",
+ debug_bm_int_info[bm_index].bm_index,
+ debug_bm_int_info[bm_index].msk_addr,
+ debug_bm_int_info[bm_index].msk_cmd,
+ debug_bm_int_info[bm_index].msk_data_l,
+ debug_bm_int_info[bm_index].msk_data_h,
+ bm_chn_name[bm_index].chn_name,
+ mask_id);
+ }else{
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].bm_index = bm_index;
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_addr =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_ADDR_REG));
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_cmd =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_CMD_REG));
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_data_l =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_DATA_L_REG));
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_data_h =
+ __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_DATA_H_REG));
+ if(bm_index <= AXI_BM9){
+ mask_id = __raw_readl((volatile void *)(reg_addr + AXI_BM_MATCH_ID_REG));
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_id = mask_id >> 2 ? 0 : mask_id;
+ }else{
+ mask_id = sci_glb_read(REG_AP_AHB_MISC_CFG, 0xFFFFFFFF);
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_id = 0;
+ }
+
+ BM_ERR("bm ctn info:\nBM CHN: %d\nOverlap ADDR: 0x%X\nOverlap CMD: 0x%X\nOverlap DATA: 0x%X 0x%X\nOverlap ID: %s--%ld\n",
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].bm_index,
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_addr,
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_cmd,
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_data_l,
+ bm_ctn_dbg.bm_ctn_info[bm_ctn_dbg.current_cnt].msk_data_h,
+ bm_chn_name[bm_index].chn_name,
+ mask_id);
+ bm_ctn_dbg.current_cnt++;
+ }
+}
+
+static irqreturn_t __sci_bm_isr(int irq_num, void *dev)
+{
+ ulong bm_index, rwbw_cnt, bm_chn, bm_reg, bm_int;
+ struct bm_per_info *bm_info;
+ struct timeval tv;
+ struct rtc_time tm;
+
+ bm_info = (struct bm_per_info *)per_buf;
+ spin_lock(&bm_lock);
+
+ /*only one precess handle the bm's irq is this case */
+ if (!bm_irq_in_process) {
+ bm_irq_in_process = true;
+ } else {
+ spin_unlock(&bm_lock);
+ return IRQ_NONE;
+ }
+
+ bm_reg = __sci_get_bm_base(AXI_BM0);
+ if(__raw_readl((volatile void *)(bm_reg + AXI_BM_INTC_REG)) & BM_CNT_EN)
+ {
+ if(bm_info == NULL){
+ BM_ERR("BM irq ERR, trigger info: int 0x%x, min addr 0x%x, max addr 0x%x, cnt len 0x%x\n",
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_INTC_REG)),
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_ADDR_MIN_REG)),
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_ADDR_MAX_REG)),
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_CNT_WIN_LEN_REG)));
+ return IRQ_NONE;
+ }
+ __sci_axi_bm_cnt_stop();
+
+ rwbw_cnt = 0x0;
+ /*count stop time stamp */
+ bm_info[buf_write_index].t_stop = get_sys_cnt();//__raw_readl((const volatile void *)(syscnt + 0xc));//((const volatile void *)(SPRD_SYSCNT_BASE + 0xc));
+ bm_info[buf_write_index].count = bm_count;
+
+ for (bm_chn = AXI_BM0; bm_chn <= AXI_BM9; bm_chn++) {
+ bm_reg = __sci_get_bm_base(bm_chn);
+ bm_info[buf_write_index].per_data[bm_chn][0] =
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_RTRANS_IN_WIN_REG));
+ bm_info[buf_write_index].per_data[bm_chn][1] =
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_RBW_IN_WIN_REG));
+ bm_info[buf_write_index].per_data[bm_chn][2] =
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_RLATENCY_IN_WIN_REG));
+
+ bm_info[buf_write_index].per_data[bm_chn][3] =
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_WTRANS_IN_WIN_REG));
+ bm_info[buf_write_index].per_data[bm_chn][4] =
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_WBW_IN_WIN_REG));
+ bm_info[buf_write_index].per_data[bm_chn][5] =
+ __raw_readl((volatile void *)(bm_reg + AXI_BM_WLATENCY_IN_WIN_REG));
+
+ rwbw_cnt += bm_info[buf_write_index].per_data[bm_chn][1];
+ rwbw_cnt += bm_info[buf_write_index].per_data[bm_chn][4];
+ }
+ if (++buf_write_index == PER_COUNT_RECORD_SIZE) {
+ buf_write_index = 0;
+ buf_skip_cnt++;
+ }
+
+ /*wake up the thread to output log per 4 second*/
+ if ((buf_write_index == 0) ||
+ buf_write_index == (PER_COUNT_RECORD_SIZE >> 1) ) {
+ /*need to skip the star buf, because it includes a lot of usefull info,skip 8s buf*/
+ if(buf_skip_cnt > 1)
+ up(&bm_seam);
+ }
+ __sci_bm_int_clr();
+ __sci_axi_bm_cnt_clr();
+ __sci_axi_bm_set_winlen();
+
+ /*count start time stamp */
+ do_gettimeofday(&tv);
+ rtc_time_to_tm(tv.tv_sec,&tm);
+ tm.tm_hour = tm.tm_hour < 16 ? (tm.tm_hour + 8) : (tm.tm_hour - 16);
+ bm_info[buf_write_index].t_start = get_sys_cnt();//__raw_readl((const volatile void *)(syscnt + 0xc));//((const volatile void *)(SPRD_SYSCNT_BASE + 0xc));
+ bm_info[buf_write_index].tmp1 = (tm.tm_hour * 10000) + (tm.tm_min * 100) + tm.tm_sec;//emc_clk_get();
+ bm_info[buf_write_index].tmp2 = 640;//__raw_readl(REG_AON_APB_DPLL_CFG);
+
+ __sci_axi_bm_cnt_start();
+
+ bm_irq_in_process = false;
+ spin_unlock(&bm_lock);
+
+ return IRQ_HANDLED;
+ }
+
+ for(bm_index = AXI_BM0; bm_index < BM_SIZE; bm_index++)
+ {
+ bm_reg = __sci_get_bm_base(bm_index);
+ if(bm_index < AHB_BM0)
+ bm_int = __raw_readl((volatile void *)(bm_reg + AXI_BM_INTC_REG));
+ else
+ bm_int = __raw_readl((volatile void *)(bm_reg + AHB_BM_INTC_REG));
+ if (bm_int & BM_INT_MSK_STS) {
+ __sci_bm_store_int_info(bm_index);
+ if((bm_ctn_dbg.bm_continue_dbg == true) && (bm_ctn_dbg.current_cnt <= BM_CONTINUE_DEBUG_SIZE)){
+ bm_int &= ~BM_INT_EN;
+ bm_int |= (BM_INT_CLR | BM_INT_EN);
+ }else{
+ bm_int &= ~BM_INT_EN;
+ }
+ __raw_writel(bm_int, (volatile void *)bm_reg);
+ if (bm_callback_set[bm_index].fun)
+ bm_callback_set[bm_index].fun(bm_callback_set[bm_index].data);
+ if(bm_st_info.bm_stack_st == true){
+ BM_ERR("Bus Monitor output stack!\n");
+ dump_stack();
+ }
+ if(true == bm_st_info.bm_panic_st){
+ BM_ERR("Bus Monitor enter panic!\n");
+ BUG();
+ }
+ }
+ }
+ bm_irq_in_process = false;
+ spin_unlock(&bm_lock);
+
+ return IRQ_HANDLED;
+}
+
+unsigned int dmc_mon_cnt_bw(void)
+{
+ int chn;
+ ulong cnt = 0;
+
+ if(bm_st_info.bm_dbg_st != true){
+ if(true == bm_st_info.bm_dfs_off_st)
+ return 0xFFFFFFFF;
+ for (chn = AXI_BM0; chn <= AXI_BM9; chn++)
+ cnt += __sci_axi_bm_chn_cnt_bw(chn);
+ return cnt;
+ }else
+ return 0xFFFFFFFF;
+}
+
+void dmc_mon_cnt_clr(void)
+{
+ if(bm_st_info.bm_dbg_st != true)
+ __sci_axi_bm_cnt_clr();
+ return;
+}
+
+void dmc_mon_cnt_start(void)
+{
+ if(bm_st_info.bm_dbg_st != true){
+ __sci_axi_bm_cnt_start();
+ __sci_bm_glb_count_enable(true);
+ __sci_axi_bm_cnt_start();
+ }
+ return;
+}
+
+void dmc_mon_cnt_stop(void)
+{
+ if(bm_st_info.bm_dbg_st != true){
+ __sci_bm_glb_count_enable(false);
+ __sci_axi_bm_cnt_stop();
+ }
+ return;
+}
+
+void dmc_mon_resume(void)
+{
+ return;
+}
+
+int sci_bm_set_point(enum sci_bm_index bm_index, enum sci_bm_chn chn,
+ const struct sci_bm_cfg *cfg, void(*call_back)(void *), void *data)
+{
+ int ret;
+ u32 ddr_size = 0;
+ struct device_node *np = NULL;
+ struct resource res;
+ volatile struct sci_bm_reg *bm_reg;
+
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_index);
+ /*clean the irq status*/
+ bm_reg->intc |= BM_INT_CLR;
+ bm_reg->intc = 0x0;
+ memset((void *)bm_reg, 0x0, sizeof(*bm_reg));
+
+#ifdef CONFIG_OF
+ np = of_find_node_by_name(NULL, "memory");
+ if(np){
+ ret = of_address_to_resource(np, 0, &res);
+ if(!ret)
+ ddr_size = res.end - res.start;
+ }
+ /* if monitor ddr addr, it needs addr wrap*/
+ if(cfg->addr_min >= 0x80000000 && "ERROR_NAME" != bm_chn_name[bm_index].chn_name){
+ if(ddr_size == 0x1fffffff)//512M
+ bm_store_vale[bm_index].bm_mask = 0xE0000000;
+ else if(ddr_size == 0x3fffffff)//1G
+ bm_store_vale[bm_index].bm_mask = 0xC0000000;
+ else// >= 4G, do not need mask
+ bm_store_vale[bm_index].bm_mask = 0x00000000;
+ }else
+ bm_store_vale[bm_index].bm_mask = 0x00000000;
+#endif
+
+ if ("ERROR_NAME" != bm_chn_name[bm_index].chn_name){
+ if (bm_index < AHB_BM0) {
+ bm_reg->addr_min = cfg->addr_min & (~bm_store_vale[bm_index].bm_mask);
+ bm_reg->addr_max = cfg->addr_max & (~bm_store_vale[bm_index].bm_mask);
+ bm_reg->addr_msk = bm_store_vale[bm_index].bm_mask;
+
+ /* the interrupt just trigger by addr range for axi
+ * busmonitor, so set the data range difficult to
+ * access.
+ */
+ if (bm_chn_name[bm_index].chn_type) {
+ bm_reg->data_min_l = 0x0fffffff;
+ bm_reg->data_min_h = 0x0fffffff;
+ bm_reg->data_max_l = 0x0;
+ bm_reg->data_max_h = 0x0;
+ bm_reg->data_msk_l = 0x0;
+ bm_reg->data_msk_h = 0x0;
+ } else {
+ bm_reg->data_min_l = 0x0;
+ bm_reg->data_min_h = 0x0;
+ bm_reg->data_max_l = 0xffffffff;
+ bm_reg->data_max_h = 0xffffffff;
+ bm_reg->data_msk_l = 0x0;
+ bm_reg->data_msk_h = 0x0;
+ }
+
+ switch (cfg->bm_mode) {
+ case R_MODE:
+ bm_reg->cfg = 0x1;
+ break;
+ case W_MODE:
+ bm_reg->cfg = 0x3;
+ break;
+ case RW_MODE:
+ bm_reg->cfg = 0x0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ bm_reg->intc |= BM_INT_EN | BM_CHN_EN;
+
+ } else {
+ bm_reg->addr_min = cfg->addr_min & (~bm_store_vale[bm_index].bm_mask);
+ bm_reg->addr_max = cfg->addr_max & (~bm_store_vale[bm_index].bm_mask);
+ bm_reg->addr_msk = bm_store_vale[bm_index].bm_mask;
+ bm_reg->data_min_l = cfg->data_min_l;
+ bm_reg->data_min_h = cfg->data_min_h;
+ bm_reg->data_max_l = cfg->data_max_l;
+ bm_reg->data_max_h = cfg->data_max_h;
+ bm_reg->data_msk_l = 0x0;
+ bm_reg->data_msk_h = 0x0;
+
+ switch (cfg->bm_mode) {
+ case R_MODE:
+ bm_reg->cfg = 0x1;
+ break;
+ case W_MODE:
+ bm_reg->cfg = 0x3;
+ break;
+ case RW_MODE:
+ bm_reg->cfg = 0x0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = __sci_bm_chn_sel(bm_index, chn);
+ if (ret < 0)
+ return ret;
+
+ bm_reg->intc |= BM_INT_EN | BM_CHN_EN;
+ }
+ }
+
+ bm_callback_set[bm_index].fun = call_back;
+ bm_callback_set[bm_index].data = data;
+
+ return SPRD_BM_SUCCESS;
+}
+
+void sci_bm_unset_point(enum sci_bm_index bm_index)
+{
+ ulong reg_addr;
+
+ reg_addr = __sci_get_bm_base(bm_index);
+
+ __raw_writel(BM_INT_CLR | BM_CHN_EN, (volatile void *)reg_addr);
+
+ __raw_writel(0x0, (volatile void *)reg_addr);
+
+ bm_callback_set[bm_index].fun = NULL;
+}
+
+void sci_bm_set_perform_point(void)
+{
+ __sci_bm_init();
+ __sci_axi_bm_cnt_clr();
+ __sci_bm_int_clr();
+
+ __sci_axi_bm_set_winlen();
+ __sci_bm_glb_count_enable(false);
+ __sci_axi_bm_cnt_start();
+ __sci_bm_glb_count_enable(true);
+}
+
+static void sci_bm_def_val_set_by_dts(void)
+{
+ u32 bm_chn, ret;
+ struct sci_bm_cfg bm_cfg;
+ //default set is monitor CP write AP memory.
+ for(bm_chn = AXI_BM0; bm_chn <= AXI_BM9; bm_chn++){
+ if(!strcmp(bm_chn_name[bm_chn].chn_name, "CPU") ||
+ !strcmp(bm_chn_name[bm_chn].chn_name, "DISP") ||
+ !strcmp(bm_chn_name[bm_chn].chn_name, "GPU") ||
+ !strcmp(bm_chn_name[bm_chn].chn_name, "AP/ZIP") ||
+ !strcmp(bm_chn_name[bm_chn].chn_name, "ZIP") ||
+ !strcmp(bm_chn_name[bm_chn].chn_name, "AP") ||
+ !strcmp(bm_chn_name[bm_chn].chn_name, "MM") ||
+ !strcmp(bm_chn_name[bm_chn].chn_name, "ERROR_NAME"))
+ continue;
+ bm_cfg.addr_min = memory_layout.ap_start3;
+ bm_cfg.addr_max = memory_layout.ap_end3;
+ bm_cfg.bm_mode = W_MODE;
+ ret = sci_bm_set_point(bm_chn, CHN0, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return;
+ }
+}
+
+static void sci_bm_def_val_set(void)
+{
+ u32 bm_chn, ret;
+ struct sci_bm_cfg bm_cfg;
+
+ for (bm_chn = AXI_BM0; bm_chn < BM_SIZE; bm_chn++){
+ if((0x00000000 == bm_def_value[bm_chn].str_addr) && (0x00000000 == bm_def_value[bm_chn].end_addr))
+ continue;
+ bm_cfg.addr_min = bm_def_value[bm_chn].str_addr;
+ bm_cfg.addr_max = bm_def_value[bm_chn].end_addr;
+ bm_cfg.bm_mode = bm_def_value[bm_chn].mode;
+ ret = sci_bm_set_point(bm_chn, bm_def_value[bm_chn].chn_sel, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return;
+ }
+}
+
+static int sci_bm_output_log(void *p)
+{
+ mm_segment_t old_fs;
+ int ret;
+
+ while (1) {
+ down(&bm_seam);
+
+ if (!log_file) {
+ log_file = filp_open(LOG_FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (IS_ERR(log_file) || !log_file || !log_file->f_dentry) {
+ BM_ERR("file_open(%s) for create failed\n", LOG_FILE_PATH);
+ return -ENODEV;
+ }
+ }
+ switch (buf_write_index) {
+ case 0:
+ buf_read_index = PER_COUNT_RECORD_SIZE >> 1;
+ break;
+ case (PER_COUNT_RECORD_SIZE >> 1):
+ buf_read_index = 0x0;
+ break;
+ default:
+ BM_ERR("get buf_read_indiex failed!\n");
+ }
+ old_fs = get_fs();
+ set_fs(get_ds());
+ ret = vfs_write(log_file,
+ (const char *)(per_buf + buf_read_index * sizeof(struct bm_per_info)),
+ sizeof(struct bm_per_info) *(PER_COUNT_RECORD_SIZE >> 1),
+ &log_file->f_pos);
+
+ set_fs(old_fs);
+
+ /*raw back file write*/
+ if (log_file->f_pos >= (sizeof(struct bm_per_info) * LOG_FILE_MAX_RECORDS)) {
+ log_file->f_pos = 0x0;
+ }
+ }
+
+ filp_close(log_file, NULL);
+
+ return 0;
+}
+
+static ssize_t sci_bm_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if(bm_st_info.bm_dbg_st == true)
+ return sprintf(buf, "Bus Monitor in debug mode!\n");
+ else
+ return sprintf(buf, "Bus Monitor in bandwidth mode!\n");
+}
+
+static ssize_t sci_bm_chn_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 bm_index;
+ char occ_info[30] = {};
+ char chn_info[30*BM_CHANNEL_SIZE] = {};
+
+ for(bm_index = AXI_BM0; bm_index < BM_CHANNEL_SIZE; bm_index++){
+ sprintf(occ_info, "%s\n", bm_chn_name[bm_index].chn_name);
+ strcat(chn_info, occ_info);
+ }
+ return sprintf(buf, "%s\n", chn_info);
+}
+
+static ssize_t sci_bm_axi_dbg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 bm_index;
+ ulong reg_addr, str_addr, end_addr;
+ char chn_info[58];
+ char info_buf[58*10] = {};
+
+ for(bm_index = AXI_BM0; bm_index < AHB_BM0; bm_index++){
+ reg_addr = __sci_get_bm_base(bm_index);
+ str_addr = __raw_readl((volatile void *)(reg_addr + AXI_BM_ADDR_MIN_REG)) + (0x80000000 & bm_store_vale[bm_index].bm_mask);
+ end_addr = __raw_readl((volatile void *)(reg_addr + AXI_BM_ADDR_MAX_REG)) + (0x80000000 & bm_store_vale[bm_index].bm_mask);
+ sprintf(chn_info, "%d 0x%08lX 0x%08lX %s\n", bm_index, str_addr, end_addr, bm_chn_name[bm_index].chn_name);
+ strcat(info_buf, chn_info);
+ }
+ BM_INFO("%s\n", info_buf);
+ return sprintf(buf, "%s\n", info_buf);
+}
+
+static ssize_t sci_bm_axi_dbg_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ unsigned char start[12], end[12], chn[6], mod[3];
+ unsigned long start_addr, end_addr, channel, bm_index;
+ struct sci_bm_cfg bm_cfg;
+ u32 rd_wt;
+ int ret,i;
+
+ sscanf(buf, "%s %s %s %s",chn, start, end, mod);
+
+ ret = strict_strtoul(start, 0, &start_addr);
+ if (ret)
+ BM_ERR("start %s is not in hex or decimal form.\n", buf);
+ ret = strict_strtoul(end, 0, &end_addr);
+ if (ret)
+ BM_ERR("end %s is not in hex or decimal form.\n", buf);
+ if((chn[0] >= '0') && (chn[0] <= '9'))
+ channel = chn[0] - '0';
+ else if(strcmp(chn, "all") == 0)
+ channel = BM_DEBUG_ALL_CHANNEL;
+ else{
+ BM_ERR("please input a legal channel number! e.g: 0-9,all.");
+ return EINVAL;
+ }
+ if((strcmp(mod, "r") == 0)||(strcmp(mod, "R") == 0))
+ rd_wt = R_MODE;
+ else if((strcmp(mod, "w") == 0)||(strcmp(mod, "W") == 0))
+ rd_wt = W_MODE;
+ else if((strcmp(mod, "rw") == 0)||(strcmp(mod, "RW") == 0))
+ rd_wt = RW_MODE;
+ else{
+ BM_ERR("please input a legal channel mode! e.g: r,w,rw");
+ return EINVAL;
+ }
+
+ BM_INFO("str addr 0x%lx end addr 0x%lx chn %ld rw %d\n", start_addr, end_addr, channel, rd_wt);
+ if(((channel > AXI_BM9) && (channel != BM_DEBUG_ALL_CHANNEL)) || (rd_wt > RW_MODE) || (start_addr > end_addr))
+ return -EINVAL;
+
+ /*for (bm_index = AXI_BM0; bm_index <= AXI_BM9; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, true);*/
+ bm_st_info.bm_dbg_st = true;
+ bm_st_info.bm_dfs_off_st = true;
+
+ if(channel != BM_DEBUG_ALL_CHANNEL){
+ bm_cfg.addr_min = (u32)start_addr;
+ bm_cfg.addr_max = (u32)end_addr;
+ bm_cfg.bm_mode = rd_wt;
+ ret = sci_bm_set_point(channel, CHN0, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return ret;
+ }else{
+ bm_cfg.addr_min = (u32)start_addr;
+ bm_cfg.addr_max = (u32)end_addr;
+ bm_cfg.bm_mode = rd_wt;
+ for(i = AXI_BM0; i < AHB_BM0; i++){
+ ret = sci_bm_set_point(i, CHN0, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return ret;
+ }
+ }
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_ahb_dbg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 bm_index, chn_sel;
+ ulong reg_addr, str_addr, end_addr, str_data, end_data;
+ char chn_info[84];
+ char info_buf[84*3] = {};
+
+ for(bm_index = AHB_BM0; bm_index < BM_SIZE; bm_index++){
+ reg_addr = __sci_get_bm_base(bm_index);
+ str_addr = __raw_readl((volatile void *)(reg_addr + AXI_BM_ADDR_MIN_REG)) + (0x80000000 & bm_store_vale[bm_index].bm_mask);
+ end_addr = __raw_readl((volatile void *)(reg_addr + AXI_BM_ADDR_MAX_REG)) + (0x80000000 & bm_store_vale[bm_index].bm_mask);
+ str_data = __raw_readl((volatile void *)(reg_addr + AXI_BM_DATA_MIN_L_REG));
+ end_data = __raw_readl((volatile void *)(reg_addr + AXI_BM_DATA_MAX_L_REG));
+ chn_sel = __sci_bm_get_chn_sel(bm_index);
+ sprintf(chn_info, "%d 0x%08lX 0x%08lX 0x%08lX 0x%08lX %s\n", bm_index-10, str_addr, end_addr,
+ str_data, end_data, bm_chn_name[chn_sel].chn_name);
+ strcat(info_buf, chn_info);
+ }
+ BM_INFO("%s\n", info_buf);
+ return sprintf(buf, "%s\n", info_buf);
+}
+
+static ssize_t sci_bm_ahb_dbg_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ unsigned char addr_start[12], data_min[12];
+ unsigned char addr_end[12], data_max[12];
+ unsigned long start_addr, end_addr, min_data, max_data, channel, chn_sel, bm_index;
+ struct sci_bm_cfg bm_cfg;
+ int ret;
+
+ sscanf(buf, "%ld %ld %s %s %s %s", &channel, &chn_sel, addr_start, addr_end, data_min, data_max);
+
+ ret = strict_strtoul(addr_start, 0, &start_addr);
+ if (ret)
+ BM_ERR("start addr %s is not in hex or decimal form.\n", buf);
+ ret = strict_strtoul(addr_end, 0, &end_addr);
+ if (ret)
+ BM_ERR("end addr %s is not in hex or decimal form.\n", buf);
+ ret = strict_strtoul(data_min, 0, &min_data);
+ if (ret)
+ BM_ERR("start data %s is not in hex or decimal form.\n", buf);
+ ret = strict_strtoul(data_max, 0, &max_data);
+ if (ret)
+ BM_ERR("end data %s is not in hex or decimal form.\n", buf);
+
+ BM_INFO("str addr 0x%lX end addr 0x%lX min data 0x%lX max data 0x%lX\n", start_addr, end_addr, min_data, max_data);
+ if((channel > 3) || (chn_sel > 4) || (start_addr > end_addr) || (min_data > max_data))
+ return -EINVAL;
+
+ __sci_bm_glb_reset_and_enable(channel + AHB_BM0, true);
+ bm_st_info.bm_dbg_st = true;
+ bm_st_info.bm_dfs_off_st = true;
+
+ bm_cfg.addr_min = (u32)start_addr;
+ bm_cfg.addr_max = (u32)end_addr;
+ bm_cfg.data_min_l = (u32)min_data;
+ bm_cfg.data_min_h = 0x0;
+ bm_cfg.data_max_l = (u32)max_data;
+ bm_cfg.data_max_h = 0x0;
+ bm_cfg.bm_mode = W_MODE;
+ ret = sci_bm_set_point(channel + AHB_BM0, chn_sel, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return ret;
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_bandwidth_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u32 bw_en, bm_index;
+ struct task_struct *t;
+
+ bm_st_info.bm_dfs_off_st = false;
+ sscanf(buf, "%d", &bw_en);
+ if(bw_en){
+ BM_INFO("bm bandwidth mode enable!!!\n");
+ bm_st_info.bm_dbg_st = false;
+ if(per_buf == NULL){
+ per_buf = kmalloc(PER_COUNT_BUF_SIZE, GFP_KERNEL);
+ if (!per_buf)
+ BM_ERR("kmalloc failed!\n");
+ sema_init(&bm_seam, 0);
+ t = kthread_run(sci_bm_output_log, NULL, "%s", "bm_per_log");
+ if (IS_ERR(t)) {
+ BM_ERR("bm probe: Failed to run thread bm_per_log\n");
+ kthread_stop(t);
+ return 0;
+ }
+ }
+ for (bm_index = AXI_BM0; bm_index <= AHB_BM2; bm_index++) {
+ __sci_bm_glb_reset_and_enable(bm_index, true);
+ bm_store_vale[bm_index].bm_mask= 0;
+ }
+ sci_bm_set_perform_point();
+ msleep(100);
+ }else{
+ BM_INFO("bm bandwidth mode disable!!!\n");
+ bm_st_info.bm_dbg_st = true;
+ if(per_buf != NULL){
+ kfree(per_buf);
+ per_buf = NULL;
+ if(log_file != NULL){
+ filp_close(log_file, NULL);
+ log_file = NULL;
+ buf_write_index = 0;
+ bm_count = 0;
+ }
+ }
+ __sci_axi_bm_cnt_clr();
+ __sci_bm_int_clr();
+ __sci_bm_glb_count_enable(false);
+ for (bm_index = AXI_BM0; bm_index <= AHB_BM2; bm_index++) {
+ __sci_bm_glb_reset_and_enable(bm_index, false);
+ bm_store_vale[bm_index].bm_mask= 0;
+ }
+ __sci_bm_glb_count_enable(false);
+ }
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_occur_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 bm_index;
+ char occ_info[116] = {};
+ char chn_info[116*21] = {};
+
+ for(bm_index = AXI_BM0; bm_index < BM_SIZE; bm_index++){
+ if(debug_bm_int_info[bm_index].msk_addr != 0){
+ sprintf(occ_info, " %s\n addr: 0x%X\n CMD: 0x%X\n msk_data_l: 0x%X\n msk_data_h: 0x%X\n id 0x%X\n",
+ bm_chn_name[bm_index].chn_name,
+ debug_bm_int_info[bm_index].msk_addr,
+ debug_bm_int_info[bm_index].msk_cmd,
+ debug_bm_int_info[bm_index].msk_data_l,
+ debug_bm_int_info[bm_index].msk_data_h,
+ debug_bm_int_info[bm_index].msk_id);
+ strcat(chn_info, occ_info);
+ }
+ }
+ if(chn_info[1] == 0)
+ sprintf(chn_info, ":-) ! No action was monitored by BM!!!\n");
+
+ return sprintf(buf, "%s\n", chn_info);
+}
+
+static ssize_t sci_bm_continue_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 bm_index;
+ char occ_info[96] = {};
+ char chn_info[96*50] = {};
+
+ for(bm_index = 0; bm_index < bm_ctn_dbg.loop_cnt; bm_index++){
+ if(bm_ctn_dbg.bm_ctn_info[bm_index].msk_addr != 0){
+ sprintf(occ_info, " %d\n addr: 0x%X\n CMD: 0x%X\n msk_data_l: 0x%X\n msk_data_h: 0x%X\n id 0x%X\n",
+ bm_ctn_dbg.bm_ctn_info[bm_index].bm_index,
+ bm_ctn_dbg.bm_ctn_info[bm_index].msk_addr,
+ bm_ctn_dbg.bm_ctn_info[bm_index].msk_cmd,
+ bm_ctn_dbg.bm_ctn_info[bm_index].msk_data_l,
+ bm_ctn_dbg.bm_ctn_info[bm_index].msk_data_h,
+ bm_ctn_dbg.bm_ctn_info[bm_index].msk_id);
+ strcat(chn_info, occ_info);
+ }
+ }
+ if(chn_info[1] == 0)
+ sprintf(chn_info, ":-) ! No action was monitored by BM!!!\n");
+
+ return sprintf(buf, "%s\n", chn_info);
+}
+
+static ssize_t sci_bm_continue_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u32 ctn_num;
+ sscanf(buf, "%d", &ctn_num);
+ if(ctn_num){
+ BM_INFO("Set BM support continue debug!!!\n");
+ bm_ctn_dbg.bm_continue_dbg = true;
+ if(ctn_num > BM_CONTINUE_DEBUG_SIZE)
+ bm_ctn_dbg.loop_cnt = BM_CONTINUE_DEBUG_SIZE;
+ else
+ bm_ctn_dbg.loop_cnt = ctn_num;
+ }else{
+ BM_INFO("Set BM do not support continue debug!!!\n");
+ bm_ctn_dbg.bm_continue_dbg = false;
+ bm_ctn_dbg.loop_cnt = 0;
+ }
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_dfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if(bm_st_info.bm_dfs_off_st == true)
+ return sprintf(buf, "The BM DFS is closed.\n");
+ else
+ return sprintf(buf, "The BM DFS is open.\n");
+}
+
+static ssize_t sci_bm_dfs_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u32 dfs_flg;
+ sscanf(buf, "%d", &dfs_flg);
+ if(dfs_flg){
+ BM_INFO("Disable BM DFS.\n");
+ bm_st_info.bm_dfs_off_st = true;
+ }else{
+ BM_INFO("Reopen BM DFS.\n");
+ bm_st_info.bm_dfs_off_st = false;
+ }
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_panic_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if(bm_st_info.bm_panic_st == true)
+ return sprintf(buf, "The BM panic is open.\n");
+ else
+ return sprintf(buf, "The BM panic is close.\n");
+}
+
+static ssize_t sci_bm_panic_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u32 panic_flg;
+ sscanf(buf, "%d", &panic_flg);
+ if(panic_flg){
+ BM_INFO("Reopen BM panic.\n");
+ bm_st_info.bm_panic_st = true;
+ }else{
+ BM_INFO("Disable BM panic.\n");
+ bm_st_info.bm_panic_st = false;
+ }
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_stack_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if(bm_st_info.bm_stack_st == true)
+ return sprintf(buf, "The BM stack is open.\n");
+ else
+ return sprintf(buf, "The BM stack is close.\n");
+}
+
+static ssize_t sci_bm_stack_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u32 panic_flg;
+ sscanf(buf, "%d", &panic_flg);
+ if(panic_flg){
+ BM_INFO("Reopen BM DFS.\n");
+ bm_st_info.bm_stack_st = true;
+ }else{
+ BM_INFO("Disable BM DFS.\n");
+ bm_st_info.bm_stack_st = false;
+ }
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_disable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u32 dis_val;
+ sscanf(buf, "%d", &dis_val);
+ if(dis_val){
+ BM_INFO("Set BM disable count %d\n", bm_count);
+ __raw_writel(0x0, (volatile void *)(REG_PUB_APB_BUSMON_CFG));
+ }else{
+ bm_count++;
+ BM_INFO("Set BM enable count %d\n", bm_count);
+ __raw_writel(BM_CHANNEL_ENABLE_FLAGE, (volatile void *)(REG_PUB_APB_BUSMON_CFG));
+ }
+ return strnlen(buf, count);
+}
+
+static ssize_t sci_bm_bus_status_show(struct device * dev,
+ struct device_attribute * attr,const char * buf)
+{
+ u32 bm_index;
+ ulong reg_addr, axi_val;
+ char axi_info[20] = {0}, axi_buf[10*20] = {0};
+
+ for(bm_index = AXI_BM0; bm_index < AHB_BM0; bm_index++){
+ reg_addr = __sci_get_bm_base(bm_index);
+ axi_val = __raw_readl((volatile void *)(reg_addr + AXI_BM_BUS_STATUS_REG));
+ sprintf(axi_info, "0x%08lX\n", axi_val);
+ strcat(axi_buf, axi_info);
+ }
+ BM_INFO("%s\n", axi_buf);
+ return sprintf(buf, "%s\n", axi_buf);
+}
+
+static DEVICE_ATTR(state, S_IRUGO | S_IWUSR,
+ sci_bm_state_show, NULL);
+
+static DEVICE_ATTR(chn, S_IRUGO | S_IWUSR,
+ sci_bm_chn_show, NULL);
+
+static DEVICE_ATTR(axi_dbg, S_IRUGO | S_IWUSR,
+ sci_bm_axi_dbg_show, sci_bm_axi_dbg_store);
+
+static DEVICE_ATTR(ahb_dbg, S_IRUGO | S_IWUSR,
+ sci_bm_ahb_dbg_show, sci_bm_ahb_dbg_store);
+
+static DEVICE_ATTR(bandwidth, S_IRUGO | S_IWUSR,
+ NULL, sci_bm_bandwidth_store);
+
+static DEVICE_ATTR(occur, S_IRUGO | S_IWUSR,
+ sci_bm_occur_show, NULL);
+
+static DEVICE_ATTR(continue, S_IRUGO | S_IWUSR,
+ sci_bm_continue_show, sci_bm_continue_store);
+
+static DEVICE_ATTR(dfs_off, S_IRUGO | S_IWUSR,
+ sci_bm_dfs_show, sci_bm_dfs_store);
+
+static DEVICE_ATTR(panic, S_IRUGO | S_IWUSR,
+ sci_bm_panic_show, sci_bm_panic_store);
+
+static DEVICE_ATTR(stack, S_IRUGO | S_IWUSR,
+ sci_bm_stack_show, sci_bm_stack_store);
+
+static DEVICE_ATTR(disable, S_IRUGO | S_IWUSR,
+ NULL, sci_bm_disable_store);
+
+static DEVICE_ATTR(bus_status, S_IRUGO | S_IWUSR,
+ sci_bm_bus_status_show, NULL);
+
+
+static struct attribute *bm_attrs[] = {
+ &dev_attr_state.attr,
+ &dev_attr_chn.attr,
+ &dev_attr_axi_dbg.attr,
+ &dev_attr_ahb_dbg.attr,
+ &dev_attr_bandwidth.attr,
+ &dev_attr_occur.attr,
+ &dev_attr_continue.attr,
+ &dev_attr_dfs_off.attr,
+ &dev_attr_panic.attr,
+ &dev_attr_stack.attr,
+ &dev_attr_disable.attr,
+ &dev_attr_bus_status.attr,
+ NULL,
+};
+
+static struct attribute_group bm_attr_group = {
+ .attrs = bm_attrs,
+};
+
+#define BM_MAX(x, y) (x > y ? x : y)
+static int sci_bm_get_mem_layout(void)
+{
+ struct device_node *np = NULL;
+ struct resource res;
+ int ret;
+ char *devname;
+
+ np = of_find_node_by_name(NULL, "memory");
+ if(np){
+ ret = of_address_to_resource(np, 0, &res);
+ if(!ret){
+ memory_layout.dram_start = res.start;
+ memory_layout.dram_end = res.end;
+ }
+ }
+
+ np = of_find_compatible_node(NULL, NULL, "sprd,sipc");
+ if(np){
+ ret = of_address_to_resource(np, 0, &res);
+ if(!ret){
+ memory_layout.shm_start = res.start;
+ memory_layout.shm_end = res.end;
+ }
+ }
+
+ for_each_compatible_node(np, NULL, "sprd,scproc"){
+ if(np){
+ of_property_read_string(np, "sprd,name", (const char **)&devname);
+ if(!strcmp(devname, "cpw")){
+ ret = of_address_to_resource(np, 0, &res);
+ if(!ret){
+ memory_layout.cpw_start = res.start;
+ memory_layout.cpw_end = res.end;
+ }
+ }else if(!strcmp(devname, "cpgge")){
+ ret = of_address_to_resource(np, 0, &res);
+ if(!ret){
+ memory_layout.cpgge_start = res.start;
+ memory_layout.cpgge_end = res.end;
+ }
+ }else if(!strcmp(devname, "cpwcn")){
+ ret = of_address_to_resource(np, 0, &res);
+ if(!ret){
+ memory_layout.cpwcn_start = res.start;
+ memory_layout.cpwcn_end = res.end;
+ }
+ }else if(!strcmp(devname, "cptl")){
+ ret = of_address_to_resource(np, 0, &res);
+ if(!ret){
+ memory_layout.cptl_start = res.start;
+ memory_layout.cptl_end = res.end;
+ }
+ }
+ }
+ }
+
+ np = of_find_compatible_node(NULL, NULL, "sprd,sprdfb");
+ if(np){
+ u32 val[2] = { 0 };
+ ret = of_property_read_u32_array(np, "sprd,fb_mem", val, 2);
+ if(!ret){
+ memory_layout.fb_start = val[0];
+ memory_layout.fb_end = val[0] + val[1] - 1;
+ }
+ }
+
+ np = of_find_compatible_node(NULL, NULL, "sprd,ion-sprd");
+ if(np){
+ struct device_node *child = NULL;
+ char *reg_name = NULL;
+ u32 val[2] = { 0 };
+
+ for_each_child_of_node(np, child){
+ of_property_read_string(child, "reg-names", (const char **)&reg_name);
+ if(!strcmp("ion_heap_carveout_overlay", reg_name)){
+ ret = of_property_read_u32_array(child, "sprd,ion-heap-mem", val, 2);
+ if(!ret){
+ memory_layout.ion_start = val[0];
+ memory_layout.ion_end= val[0] + val[1] - 1;
+ }
+ }
+ }
+ }
+ memory_layout.ap_start3 = BM_MAX(memory_layout.cpw_end, memory_layout.cpgge_end);
+ memory_layout.ap_start3 = BM_MAX(memory_layout.ap_start3, memory_layout.cpwcn_end);
+ memory_layout.ap_start3 = BM_MAX(memory_layout.ap_start3, memory_layout.cptl_end) + 1;
+
+ memory_layout.ap_start1 = memory_layout.dram_start;
+ memory_layout.ap_end1 = memory_layout.shm_start - 1;
+ memory_layout.ap_start2 = memory_layout.shm_end + 1;
+ memory_layout.ap_end2 = memory_layout.cpgge_start -1;
+ //memory_layout.ap_start3 = memory_layout.cptl_end + 1;
+ memory_layout.ap_end3 = memory_layout.fb_start -1;
+
+ BM_ERR("%s: dram: 0x%08X ~ 0x%08X\n", __func__, memory_layout.dram_start, memory_layout.dram_end);
+ BM_ERR("%s: ap_1: 0x%08X ~ 0x%08X\n", __func__, memory_layout.ap_start1, memory_layout.ap_end1);
+ BM_ERR("%s: shm : 0x%08X ~ 0x%08X\n", __func__, memory_layout.shm_start, memory_layout.shm_end);
+ BM_ERR("%s: ap_2: 0x%08X ~ 0x%08X\n", __func__, memory_layout.ap_start2, memory_layout.ap_end2);
+ BM_ERR("%s: cpge: 0x%08X ~ 0x%08X\n", __func__, memory_layout.cpgge_start, memory_layout.cpgge_end);
+ BM_ERR("%s: cpw : 0x%08X ~ 0x%08X\n", __func__, memory_layout.cpw_start, memory_layout.cpw_end);
+ BM_ERR("%s: cpwcn: 0x%08X ~ 0x%08X\n", __func__, memory_layout.cpwcn_start, memory_layout.cpwcn_end);
+ BM_ERR("%s: cptl: 0x%08X ~ 0x%08X\n", __func__, memory_layout.cptl_start, memory_layout.cptl_end);
+ BM_ERR("%s: ap_3: 0x%08X ~ 0x%08X\n", __func__, memory_layout.ap_start3, memory_layout.ap_end3);
+ BM_ERR("%s: fb : 0x%08X ~ 0x%08X\n", __func__, memory_layout.fb_start, memory_layout.fb_end);
+ BM_ERR("%s: ion : 0x%08X ~ 0x%08X\n", __func__, memory_layout.ion_start, memory_layout.ion_end);
+
+ return 0;
+}
+
+static void sci_bm_init_name_info(void)
+{
+ u32 bm_index, bm_chn, bm_no_name = 0;
+ u32 axi_chn_nr, axi_chn_type;
+ //init axi bm name info.
+ for(bm_chn = 0; bm_chn < bm_st_info.axi_bm_cnt; bm_chn++){
+ for(bm_index = 3; bm_index < BM_AXI_TOTAL_CHN_NAME + 3; bm_index++){
+ axi_chn_nr = (*(&bm_st_info.bm_def_mode + bm_index)) & 0xff;
+ axi_chn_type = ((*(&bm_st_info.bm_def_mode + bm_index)) >> 31) & 0x01;
+ if(bm_chn == axi_chn_nr){
+ bm_chn_name[bm_chn].chn_name = bm_name_list[bm_index - 3].chn_name;
+ bm_chn_name[bm_chn].chn_type = axi_chn_type;
+ break;
+ }
+ }
+ if (NULL == bm_chn_name[bm_chn].chn_name)
+ bm_chn_name[bm_chn].chn_name = "ERROR_NAME";
+ }
+ //init ahb bm name info.
+ for(bm_chn = bm_st_info.axi_bm_cnt; bm_chn < BM_AXI_TOTAL_CHN_NAME + BM_AHB_TOTAL_CHN_NAME + 3; bm_chn++){
+ for(bm_index = BM_AXI_TOTAL_CHN_NAME + 3; bm_index < BM_AXI_TOTAL_CHN_NAME + BM_AHB_TOTAL_CHN_NAME + 3; bm_index++){
+ if(bm_chn == ((*(&bm_st_info.bm_def_mode + bm_index)) + bm_st_info.axi_bm_cnt)){
+ bm_chn_name[bm_chn - bm_no_name].chn_name = bm_name_list[bm_index - 3].chn_name;
+ break;
+ }
+ }
+ if("ERROR_NAME" == bm_chn_name[bm_chn - bm_no_name].chn_name){
+ //del the no name chn. to get a continue name list.
+ bm_no_name++;
+ }
+ }
+}
+
+static void sci_bm_get_hw_info(void)
+{
+ struct device_node *np = NULL;
+ u32 val[2];
+
+ np = of_find_compatible_node(NULL, NULL, "sprd,sprd_bm");
+ if(np){
+ //Get the bus monitor default mode.
+ if(of_property_read_u32(np, "sprd,bm_status", &val[0])){
+ BM_WRN("it must be sure that the bus monitor is in pref mode!\n");
+ bm_st_info.bm_def_mode = 0;
+ }else{
+ bm_st_info.bm_def_mode = val[0];
+ BM_INFO("Bus Monitor mode: 0 - Pref; 1 - Debug. Current mode %d\n", bm_st_info.bm_def_mode);
+ }
+ if(of_property_read_u32_array(np, "sprd,bm_count", &val[0], 2)){
+ BM_WRN("it must be sure that the there is no count para!\n");
+ bm_st_info.axi_bm_cnt = 10;
+ bm_st_info.ahb_bm_chn = 11;
+ }else{
+ bm_st_info.axi_bm_cnt = val[0];
+ bm_st_info.ahb_bm_chn = val[1];
+ }
+ //Get AXI bus monitor arch.
+ if(of_property_read_u32_array(np, "sprd,cpu_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CPU chn!\n");
+ bm_st_info.cpu_chn = 0xff;
+ }else{
+ bm_st_info.cpu_chn = val[0];
+ bm_st_info.cpu_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,disp_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not DISP chn!\n");
+ bm_st_info.disp_chn = 0xff;
+ }else{
+ bm_st_info.disp_chn = val[0];
+ bm_st_info.disp_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,gpu_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not GPU chn!\n");
+ bm_st_info.gpu_chn = 0xff;
+ }else{
+ bm_st_info.gpu_chn = val[0];
+ bm_st_info.gpu_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,ap_zip_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP/ZIP chn!\n");
+ bm_st_info.ap_zip_chn = 0xff;
+ }else{
+ bm_st_info.ap_zip_chn = val[0];
+ bm_st_info.ap_zip_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,zip_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not ZIP chn!\n");
+ bm_st_info.zip_chn = 0xff;
+ }else{
+ bm_st_info.zip_chn = val[0];
+ bm_st_info.zip_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,ap_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP chn!\n");
+ bm_st_info.ap_chn = 0xff;
+ }else{
+ bm_st_info.ap_chn = val[0];
+ bm_st_info.ap_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,mm_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not MM chn!\n");
+ bm_st_info.mm_chn = 0xff;
+ }else{
+ bm_st_info.mm_chn = val[0];
+ bm_st_info.mm_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp0_arm0_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP0 ARM0 chn!\n");
+ bm_st_info.cp0_arm0_chn = 0xff;
+ }else{
+ bm_st_info.cp0_arm0_chn = val[0];
+ bm_st_info.cp0_arm0_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp0_arm1_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP0 ARM1 chn!\n");
+ bm_st_info.cp0_arm1_chn = 0xff;
+ }else{
+ bm_st_info.cp0_arm1_chn = val[0];
+ bm_st_info.cp0_arm1_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp0_dsp_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP0 DSP chn!\n");
+ bm_st_info.cp0_dsp_chn = 0xff;
+ }else{
+ bm_st_info.cp0_dsp_chn = val[0];
+ bm_st_info.cp0_dsp_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp0_arm0_1_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP0 ARM0/1 chn!\n");
+ bm_st_info.cp0_arm0_1_chn = 0xff;
+ }else{
+ bm_st_info.cp0_arm0_1_chn = val[0];
+ bm_st_info.cp0_arm0_1_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp1_lte_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP1 LTE chn!\n");
+ bm_st_info.cp1_lte_chn = 0xff;
+ }else{
+ bm_st_info.cp1_lte_chn = val[0];
+ bm_st_info.cp1_lte_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp1_dsp_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP1 DSP chn!\n");
+ bm_st_info.cp1_dsp_chn = 0xff;
+ }else{
+ bm_st_info.cp1_dsp_chn = val[0];
+ bm_st_info.cp1_dsp_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp1_arm_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP1 ARM chn!\n");
+ bm_st_info.cp1_arm_chn = 0xff;
+ }else{
+ bm_st_info.cp1_arm_chn = val[0];
+ bm_st_info.cp1_arm_chn |= (val[1] << 31);
+ }
+ if(of_property_read_u32_array(np, "sprd,cp2_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not CP2 chn!\n");
+ bm_st_info.cp2_chn = 0xff;
+ }else{
+ bm_st_info.cp2_chn = val[0];
+ bm_st_info.cp2_chn |= (val[1] << 31);
+ }
+
+ //Get the AHB bus monitor arch
+ if(of_property_read_u32_array(np, "sprd,ap_dap_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP DAP chn!\n");
+ bm_st_info.ap_dap = 0xff;
+ }else
+ bm_st_info.ap_dap = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_cpu_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP CPU chn!\n");
+ bm_st_info.ap_cpu = 0xff;
+ }else
+ bm_st_info.ap_cpu = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_dma_r_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP DMA READ chn!\n");
+ bm_st_info.ap_dma_r = 0xff;
+ }else
+ bm_st_info.ap_dma_r = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_dma_w_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP DMA WRITE chn!\n");
+ bm_st_info.ap_dma_w = 0xff;
+ }else
+ bm_st_info.ap_dma_w = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_sdio_0_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP SDIO 0 chn!\n");
+ bm_st_info.ap_sdio_0 = 0xff;
+ }else
+ bm_st_info.ap_sdio_0 = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_sdio_1_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP SDIO 1 chn!\n");
+ bm_st_info.ap_sdio_1 = 0xff;
+ }else
+ bm_st_info.ap_sdio_1 = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_sdio_2_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP SDIO 2 chn!\n");
+ bm_st_info.ap_sdio_2 = 0xff;
+ }else
+ bm_st_info.ap_sdio_2 = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_emmc_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP EMMC chn!\n");
+ bm_st_info.ap_emmc = 0xff;
+ }else
+ bm_st_info.ap_emmc = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_nfc_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP NFC chn!\n");
+ bm_st_info.ap_nfc = 0xff;
+ }else
+ bm_st_info.ap_nfc = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_usb_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP USB chn!\n");
+ bm_st_info.ap_usb = 0xff;
+ }else
+ bm_st_info.ap_usb = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_hsic_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP HSIC chn!\n");
+ bm_st_info.ap_hsic = 0xff;
+ }else
+ bm_st_info.ap_hsic = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_otg_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP OTG chn!\n");
+ bm_st_info.ap_otg = 0xff;
+ }else
+ bm_st_info.ap_otg = val[1] + (val[0] << 2);
+ if(of_property_read_u32_array(np, "sprd,ap_nandc_chn", &val[0], 2)){
+ BM_WRN("it must be sure that there is not AP NANDC chn!\n");
+ bm_st_info.ap_nandc = 0xff;
+ }else
+ bm_st_info.ap_nandc = val[1] + (val[0] << 2);
+ }
+ sci_bm_init_name_info();
+}
+
+static int sci_bm_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ u32 bm_chn, bm_mode;
+ ulong reg_addr;
+
+ if(true == bm_st_info.bm_dbg_st){
+ for (bm_chn = AXI_BM0; bm_chn < BM_SIZE; bm_chn++){
+ reg_addr = __sci_get_bm_base(bm_chn);
+ bm_store_vale[bm_chn].str_addr = __raw_readl((volatile void *)(reg_addr + AXI_BM_ADDR_MIN_REG)) + (0x80000000 & bm_store_vale[bm_chn].bm_mask);
+ bm_store_vale[bm_chn].end_addr = __raw_readl((volatile void *)(reg_addr + AXI_BM_ADDR_MAX_REG)) + (0x80000000 & bm_store_vale[bm_chn].bm_mask);
+ bm_mode = __raw_readl((volatile void *)(reg_addr + AXI_BM_CFG_REG)) & 0x3;
+ if(0 == bm_mode)
+ bm_store_vale[bm_chn].mode = RW_MODE;
+ else if(1 == bm_mode)
+ bm_store_vale[bm_chn].mode = R_MODE;
+ else if(3 == bm_mode)
+ bm_store_vale[bm_chn].mode = W_MODE;
+ if(bm_chn >= AHB_BM0){
+ bm_store_vale[bm_chn].min_data = __raw_readl((volatile void *)(reg_addr + AXI_BM_DATA_MIN_L_REG));
+ bm_store_vale[bm_chn].max_data = __raw_readl((volatile void *)(reg_addr + AXI_BM_DATA_MAX_L_REG));
+ bm_mode = sci_glb_read(REG_AP_AHB_MISC_CFG, 0xFFFFFFFF);
+ switch(bm_chn){
+ case AHB_BM0:
+ bm_store_vale[bm_chn].chn_sel = (bm_mode >> 4) & 0x3;
+ break;
+ case AHB_BM1:
+ bm_store_vale[bm_chn].chn_sel = (bm_mode >> 8) & 0x3;
+ break;
+ case AHB_BM2:
+ bm_store_vale[bm_chn].chn_sel = (bm_mode >> 10) & 0x3;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int sci_bm_resume(struct platform_device *pdev)
+{
+ u32 bm_chn, ret;
+ struct sci_bm_cfg bm_cfg;
+
+ __sci_bm_init();
+ __sci_bm_int_clr();
+ if(true == bm_st_info.bm_dbg_st){
+ for (bm_chn = AXI_BM0; bm_chn < BM_SIZE; bm_chn++){
+ if((0x00000000 == bm_store_vale[bm_chn].str_addr) && (0x00000000 == bm_store_vale[bm_chn].end_addr))
+ continue;
+ bm_cfg.addr_min = bm_store_vale[bm_chn].str_addr;
+ bm_cfg.addr_max = bm_store_vale[bm_chn].end_addr;
+ bm_cfg.bm_mode = bm_store_vale[bm_chn].mode;
+ if(bm_chn >= AHB_BM0){
+ bm_cfg.data_min_l = bm_store_vale[bm_chn].min_data;
+ bm_cfg.data_min_h = 0;
+ bm_cfg.data_max_l = bm_store_vale[bm_chn].max_data;
+ bm_cfg.data_max_h = 0;
+ }
+ ret = sci_bm_set_point(bm_chn, bm_store_vale[bm_chn].chn_sel, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int sci_bm_probe(struct platform_device *pdev)
+{
+ int ret;
+ u32 bm_index;
+ struct resource *res = NULL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "sprd_bm get io resource failed!\n");
+ return -ENODEV;
+ }
+ axi_bm_base = (unsigned long)devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res));
+ if (!axi_bm_base){
+ dev_err(&pdev->dev, "sprd_bm get base address failed!\n");
+ return -ENODEV;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "sprd_ahb_bm get io resource failed!\n");
+ return -ENODEV;
+ }
+ ahb_bm_base = (unsigned long)devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res));
+ if (!ahb_bm_base){
+ dev_err(&pdev->dev, "sprd_ahb_bm get base address failed!\n");
+ return -ENODEV;
+ }
+
+ ret = request_irq(IRQ_AXI_BM_PUB_INT, __sci_bm_isr, IRQF_TRIGGER_NONE, pdev->name, pdev);
+ if (ret)
+ return ret;
+ ret = request_irq(IRQ_BM0_INT, __sci_bm_isr, IRQF_SHARED, pdev->name, pdev);
+ if (ret)
+ return ret;
+ ret = request_irq(IRQ_BM1_INT, __sci_bm_isr, IRQF_SHARED, pdev->name, pdev);
+ if (ret)
+ return ret;
+ ret = request_irq(IRQ_BM2_INT, __sci_bm_isr, IRQF_SHARED, pdev->name, pdev);
+ if (ret)
+ return ret;
+ sci_bm_get_hw_info();
+ for (bm_index = AXI_BM0; bm_index < BM_SIZE; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, true);
+ __sci_bm_glb_count_enable(true);
+ __sci_bm_int_clr();
+ __sci_bm_init();
+ __sci_bm_glb_ahb_disable(); /*for power consumption*/
+ ret = sysfs_create_group(&pdev->dev.kobj, &bm_attr_group);
+ if (ret) {
+ BM_ERR("Unable to export sysfs\n");
+ return ret;
+ }
+
+ if(1 == bm_st_info.bm_def_mode){
+ sci_bm_get_mem_layout();
+ sci_bm_def_val_set_by_dts();
+ bm_st_info.bm_dbg_st = true;
+ bm_st_info.bm_stack_st = true;
+ bm_st_info.bm_panic_st = true;
+ //bm_ctn_dbg.bm_continue_dbg= true;
+ //bm_ctn_dbg.loop_cnt = 20;
+ bm_st_info.bm_dfs_off_st = true;
+ BM_ERR("Bus Monitor default config set success!!!\n");
+ }
+ BM_INFO("sci_bm_probe done!\n");
+ return SPRD_BM_SUCCESS;
+}
+
+static int sci_bm_remove(struct platform_device *pdev)
+{
+ u32 bm_index;
+
+ for (bm_index = AXI_BM0; bm_index < BM_SIZE; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, false);
+ __sci_bm_glb_count_enable(false);
+
+ sysfs_remove_group(&pdev->dev.kobj, &bm_attr_group);
+ free_irq(IRQ_AXI_BM_PUB_INT, pdev);
+ free_irq(IRQ_BM0_INT, pdev);
+ free_irq(IRQ_BM1_INT, pdev);
+ free_irq(IRQ_BM2_INT, pdev);
+ return SPRD_BM_SUCCESS;
+}
+
+static int sci_bm_open(struct inode *inode, struct file *filp)
+{
+ BM_INFO("%s!\n", __func__);
+ return 0;
+}
+
+static int sci_bm_release(struct inode *inode, struct file *filp)
+{
+ BM_INFO("%s!\n", __func__);
+ return 0;
+}
+
+static long sci_bm_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
+{
+ struct sci_bm_cfg bm_cfg;
+ struct task_struct *t;
+ struct timeval tv;
+ struct rtc_time tm;
+ volatile struct sci_bm_reg *bm_reg;
+ unsigned long bm_user;
+ unsigned char bm_name[18] = {0};
+ u32 i, ret, bm_index;
+
+ if(cmd >= (BM_CHANNELS << BM_CHN_NAME_CMD_OFFSET)){
+ if(BM_CHANNELS == (cmd >> BM_CHN_NAME_CMD_OFFSET)){
+ bm_index = cmd & 0xf;
+ cmd = cmd >> BM_CHN_NAME_CMD_OFFSET;
+ }
+ }
+ if(_IOC_TYPE(cmd) >= BM_CMD_MAX)
+ return -EINVAL;
+
+ switch(cmd){
+ case BM_STATE:
+ if(bm_st_info.bm_dbg_st)
+ bm_user = 1;
+ if(put_user(bm_user, (unsigned long __user *)args))
+ return -EFAULT;
+ break;
+ case BM_CHANNELS:
+ for(i = 0; i < strlen(bm_chn_name[bm_index].chn_name); i++)
+ bm_name[i] = bm_chn_name[bm_index].chn_name[i];
+ bm_name[i] = '\0';
+ if(copy_to_user((unsigned char __user *)args,
+ &bm_name, strlen(bm_name)))
+ return -EFAULT;
+ break;
+ case BM_AXI_DEBUG_SET://set axi debug point
+ if(copy_from_user(&bm_cfg, (struct sci_bm_cfg __user *)args,
+ sizeof(struct sci_bm_cfg)))
+ return -EFAULT;
+ ret = sci_bm_set_point(bm_cfg.chn, CHN0, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return ret;
+ bm_st_info.bm_dbg_st = true;
+ bm_st_info.bm_dfs_off_st = true;
+ break;
+ case BM_AHB_DEBUG_SET://set ahb debug point
+ if(copy_from_user(&bm_cfg, (struct sci_bm_cfg __user *)args,
+ sizeof(struct sci_bm_cfg)))
+ return -EFAULT;
+ ret = sci_bm_set_point(bm_cfg.chn, bm_cfg.data, &bm_cfg, NULL, NULL);
+ if(SPRD_BM_SUCCESS != ret)
+ return ret;
+ bm_st_info.bm_dbg_st = true;
+ bm_st_info.bm_dfs_off_st = true;
+ break;
+ case BM_PERFORM_SET://set performance point
+ if(per_buf == NULL){
+ per_buf = kmalloc(PER_COUNT_BUF_SIZE, GFP_KERNEL);
+ if (!per_buf)
+ BM_ERR("kmalloc failed!\n");
+ sema_init(&bm_seam, 0);
+ t = kthread_run(sci_bm_output_log, NULL, "%s", "bm_per_log");
+ if (IS_ERR(t)) {
+ BM_ERR("bm probe: Failed to run thread bm_per_log\n");
+ kthread_stop(t);
+ return -EFAULT;
+ }
+ }
+ for (bm_index = AXI_BM0; bm_index <= AHB_BM2; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, true);
+ sci_bm_set_perform_point();
+ msleep(100);
+ bm_st_info.bm_dbg_st = false;
+ bm_st_info.bm_dfs_off_st = false;
+ break;
+ case BM_PERFORM_UNSET://unset performance point
+ if(per_buf != NULL)
+ kfree(per_buf);
+ for (bm_index = AXI_BM0; bm_index <= AHB_BM2; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, false);
+ __sci_bm_glb_count_enable(false);
+ break;
+ case BM_OCCUR://read dbg info
+ if(copy_to_user((struct bm_debug_info __user *)args,
+ debug_bm_int_info, sizeof(struct bm_debug_info)))
+ return -EFAULT;
+ break;
+ case BM_CONTINUE_SET://set continue statue
+ if(copy_from_user(&bm_ctn_dbg, (struct bm_continue_debug __user *)args,
+ sizeof(struct bm_continue_debug)))
+ return -EFAULT;
+ break;
+ case BM_CONTINUE_UNSET:
+ bm_ctn_dbg.bm_continue_dbg = false;
+ break;
+ case BM_DFS_SET://set DFS statue
+ case BM_DFS_UNSET:
+ if(get_user(bm_user,(unsigned long __user *)(&args)))
+ return -EFAULT;
+ bm_st_info.bm_dfs_off_st = bm_user;
+ break;
+ case BM_PANIC_SET://set DFS statue
+ case BM_PANIC_UNSET:
+ if(get_user(bm_user,(unsigned long __user *)(&args)))
+ return -EFAULT;
+ bm_st_info.bm_panic_st = bm_user;
+ break;
+ case BM_BW_CNT_START:
+ dmc_mon_cnt_start();
+ break;
+ case BM_BW_CNT_STOP:
+ dmc_mon_cnt_stop();
+ break;
+ case BM_BW_CNT_RESUME:
+ break;
+ case BM_BW_CNT:
+ bm_user = dmc_mon_cnt_bw();
+ if(put_user(bm_user, (unsigned long __user *)args))
+ return -EFAULT;
+ case BM_BW_CNT_CLR:
+ dmc_mon_cnt_clr();
+ break;
+ case BM_DBG_INT_CLR:
+ __sci_axi_bm_int_disable();
+ break;
+ case BM_DBG_INT_SET:
+ __sci_axi_bm_int_enable();
+ break;
+ case BM_DISABLE:
+ __raw_writel(0x0, (volatile void *)(REG_PUB_APB_BUSMON_CFG));
+ break;
+ case BM_ENABLE:
+ __raw_writel(BM_CHANNEL_ENABLE_FLAGE, (volatile void *)(REG_PUB_APB_BUSMON_CFG));
+ break;
+ case BM_PROF_SET:
+ for (bm_index = AXI_BM0; bm_index <= AHB_BM2; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, true);
+ __sci_bm_init();
+ __sci_axi_bm_cnt_clr();
+ __sci_bm_int_clr();
+ __sci_bm_glb_count_enable(false);
+ __sci_axi_bm_cnt_start();
+ __sci_bm_glb_count_enable(true);
+ break;
+ case BM_PROF_CLR:
+ for (bm_index = AXI_BM0; bm_index <= AHB_BM2; bm_index++)
+ __sci_bm_glb_reset_and_enable(bm_index, false);
+ __sci_bm_glb_count_enable(false);
+ break;
+ case BM_CHN_CNT:
+ if(put_user(bm_st_info.axi_bm_cnt, (unsigned long __user *)args))
+ return -EFAULT;
+ break;
+ case BM_RD_CNT:
+ copy_from_user(&bm_user, (unsigned long __user *)args, sizeof(unsigned long));
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_user);
+ bm_user = (unsigned long)(bm_reg->rtrans_in_win);
+ if(copy_to_user((unsigned long __user *)args, &bm_user, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+ case BM_WR_CNT:
+ copy_from_user(&bm_user, (unsigned long __user *)args, sizeof(unsigned long));
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_user);
+ bm_user = (unsigned long)(bm_reg->wtrans_in_win);
+ if(copy_to_user((unsigned long __user *)args, &bm_user, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+ case BM_RD_BW:
+ copy_from_user(&bm_user, (unsigned long __user *)args, sizeof(unsigned long));
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_user);
+ bm_user = (unsigned long)(bm_reg->rbw_in_win);
+ if(copy_to_user((unsigned long __user *)args, &bm_user, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+ case BM_WR_BW:
+ copy_from_user(&bm_user, (unsigned long __user *)args, sizeof(unsigned long));
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_user);
+ bm_user = (unsigned long)(bm_reg->wbw_in_win);
+ if(copy_to_user((unsigned long __user *)args, &bm_user, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+ case BM_RD_LATENCY:
+ copy_from_user(&bm_user, (unsigned long __user *)args, sizeof(unsigned long));
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_user);
+ bm_user = (unsigned long)(bm_reg->rlatency_in_win);
+ if(copy_to_user((unsigned long __user *)args, &bm_user, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+ case BM_WR_LATENCY:
+ copy_from_user(&bm_user, (unsigned long __user *)args, sizeof(unsigned long));
+ bm_reg = (struct sci_bm_reg *)__sci_get_bm_base(bm_user);
+ bm_user = (unsigned long)(bm_reg->wlatency_in_win);
+ if(copy_to_user((unsigned long __user *)args, &bm_user, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+ case BM_KERNEL_TIME:
+ do_gettimeofday(&tv);
+ //rtc_time_to_tm(tv.tv_sec,&tm);
+ //bm_user = (tm.tm_hour * 10000) + (tm.tm_min * 100) + tm.tm_sec;
+ if(put_user(tv.tv_sec, (unsigned long __user *)args))
+ return -EFAULT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct file_operations bm_fops = {
+ .owner = THIS_MODULE,
+ .open = sci_bm_open,
+ .release = sci_bm_release,
+ .unlocked_ioctl = sci_bm_ioctl,
+};
+
+static struct miscdevice bm_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "sprd_bm",
+ .fops = &bm_fops,
+};
+
+static const struct of_device_id bm_of_match[] = {
+ { .compatible = "sprd,sprd_bm", },
+ { }
+};
+
+static struct platform_driver sprd_bm_driver = {
+ .probe = sci_bm_probe,
+ .remove = sci_bm_remove,
+ .suspend = sci_bm_suspend,
+ .resume = sci_bm_resume,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "sprd_bm",
+ .of_match_table = bm_of_match,
+ },
+};
+
+static int __init sci_bm_init(void)
+{
+ misc_register(&bm_misc);
+ return platform_driver_register(&sprd_bm_driver);
+}
+
+static void __exit sci_bm_exit(void)
+{
+ platform_driver_unregister(&sprd_bm_driver);
+ misc_deregister(&bm_misc);
+}
+
+module_init(sci_bm_init);
+module_exit(sci_bm_exit);
+
+EXPORT_SYMBOL_GPL(dmc_mon_cnt_bw);
+EXPORT_SYMBOL_GPL(dmc_mon_cnt_clr);
+EXPORT_SYMBOL_GPL(dmc_mon_cnt_start);
+EXPORT_SYMBOL_GPL(dmc_mon_cnt_stop);
+EXPORT_SYMBOL_GPL(dmc_mon_resume);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("eric.long<eric.long@spreadtrum.com>");
+MODULE_DESCRIPTION("spreadtrum platform busmonitor driver");
+
+
diff --git a/drivers/platform/sprd/clock-sc8830_dt.c b/drivers/platform/sprd/clock-sc8830_dt.c
new file mode 100644
index 00000000..72398373
--- /dev/null
+++ b/drivers/platform/sprd/clock-sc8830_dt.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * 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/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+/* FIXME:
+ * called by sprd_init_time() in CONFIG_OF, but not load DT
+ * see board-sp8830gea.c
+ */
+#if defined(CONFIG_OF)
+int __init sci_clock_init(void)
+{
+ WARN_ON(1);
+ return 0;
+}
+#endif
diff --git a/drivers/platform/sprd/clock.h b/drivers/platform/sprd/clock.h
new file mode 100644
index 00000000..eb8586b8
--- /dev/null
+++ b/drivers/platform/sprd/clock.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * 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.
+ *
+ */
+
+#undef debug
+#define debug(format, arg...) pr_debug("clk: " "@@@%s: " format, __func__, ## arg)
+#define debug0(format, arg...)// pr_info("clk: " "@@@%s: " format, __func__, ## arg)
+#define debug2(format, arg...) pr_debug("clk: " "@@@%s: " format, __func__, ## arg)
+
+/**
+ * struct clk_sel - list of sources for a given clock (pll)
+ * @sources: array of pointers to clocks
+ * @nr_sources: The size of @sources
+ */
+struct clk_sel {
+ int nr_sources;
+ u32 sources[];
+};
+
+/**
+ * struct clk_reg - register definition for clock control bits
+ * @reg: pointer to the register in virtual memory.
+ * @shift: the shift in bits to where the bitfield is.
+ * @size: the size in bits of the bitfield.
+ * @mask: the mask of the bitfield.
+ *
+ * This specifies the size and position of the bits we are interested
+ * in within the register specified by @reg.
+ */
+struct clk_reg {
+ u32 reg;
+ //unsigned short shift, size;
+ u32 mask;
+};
+
+struct clk_regs {
+ int id;
+ const char *name;
+ struct clk_reg enb, div, sel;
+
+ //pll sources select
+ int nr_sources;
+ struct clk *sources[];
+};
+
+/**
+ * struct clk_ops - standard clock operations
+ * @set_rate: set the clock rate, see clk_set_rate().
+ * @get_rate: get the clock rate, see clk_get_rate().
+ * @round_rate: round a given clock rate, see clk_round_rate().
+ * @set_parent: set the clock's parent, see clk_set_parent().
+ *
+ * Group the common clock implementations together so that we
+ * don't have to keep setting the same fields again. We leave
+ * enable in struct clk.
+ *
+ * Adding an extra layer of indirection into the process should
+ * not be a problem as it is unlikely these operations are going
+ * to need to be called quickly.
+ */
+struct clk_ops {
+ int (*set_rate) (struct clk * c, unsigned long rate);
+ unsigned long (*get_rate) (struct clk * c);
+ unsigned long (*round_rate) (struct clk * c, unsigned long rate);
+ int (*set_parent) (struct clk * c, struct clk * parent);
+};
+
+/**
+ * struct clk - class of clock for newer style spreadtrum devices.
+ * @clk: the standard clock representation
+ * @sel: the parent sources select for this clock
+ * @reg_sel: the register definition for selecting the pll clock's source
+ * @reg_div: the register definition for the clock's output divisor
+ * @reg_enb: the register definition for enable or disable the clock's source
+ *
+ * This clock implements the features required by the newer SoCs where
+ * the standard clock block provides an input mux and a post-mux divisor
+ * to provide the periperhal's clock.
+ *
+ * The array of @sel provides the mapping of mux position to the
+ * clock, and @reg_sel shows the code where to modify to change the mux
+ * position. The @reg_div defines how to change the divider settings on
+ * the output.
+ */
+
+struct clk {
+ struct module *owner;
+ struct clk *parent;
+ int usage;
+ unsigned long rate;
+ struct clk_ops *ops;
+ int (*enable) (struct clk *, int enable, unsigned long *);
+
+ const struct clk_regs *regs;
+#if defined(CONFIG_DEBUG_FS)
+ struct dentry *dent; /* For visible tree hierarchy */
+#endif
+};
+
+#define MAX_DIV (1000)
+
+#define SCI_CLK_ADD(ID, RATE, ENB, ENB_BIT, DIV, DIV_MSK, SEL, SEL_MSK, NR_CLKS, ...) \
+static const struct clk_regs REGS_##ID = { \
+ .name = #ID, \
+ .id = 0, \
+ .enb = { \
+ .reg = (u32)ENB,.mask = ENB_BIT, \
+ }, \
+ .div = { \
+ .reg = (u32)DIV,.mask = DIV_MSK, \
+ }, \
+ .sel = { \
+ .reg = (u32)SEL,.mask = SEL_MSK, \
+ }, \
+ .nr_sources = NR_CLKS, \
+ .sources = {__VA_ARGS__}, \
+}; \
+static struct clk ID = { \
+ .owner = THIS_MODULE, \
+ .parent = 0, \
+ .usage = 0, \
+ .rate = RATE, \
+ .regs = &REGS_##ID, \
+ .ops = 0, \
+ .enable = 0, \
+}; \
+const struct clk_lookup __clkinit1 CLK_LK_##ID = { \
+ .dev_id = 0, \
+ .con_id = #ID, \
+ .clk = &ID, \
+}; \
+
+int __init sci_clk_register(struct clk_lookup *cl);
+
+#define __clkinit0 __section(.rodata.clkinit0)
+#define __clkinit1 __section(.rodata.clkinit1)
+#define __clkinit2 __section(.rodata.clkinit2)
diff --git a/drivers/platform/sprd/clock_dt.h b/drivers/platform/sprd/clock_dt.h
new file mode 100644
index 00000000..bc9b58fd
--- /dev/null
+++ b/drivers/platform/sprd/clock_dt.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * 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.
+ *
+ */
+
+#undef debug
+#define debug(format, arg...) pr_debug("clk: " "@@@%s: " format, __func__, ## arg)
+#define debug0(format, arg...)// pr_info("clk: " "@@@%s: " format, __func__, ## arg)
+#define debug2(format, arg...) pr_debug("clk: " "@@@%s: " format, __func__, ## arg)
+
+/**
+ * struct clk_sel - list of sources for a given clock (pll)
+ * @sources: array of pointers to clocks
+ * @nr_sources: The size of @sources
+ */
+struct clk_sel {
+ int nr_sources;
+ u32 sources[];
+};
+
+/**
+ * struct clk_reg - register definition for clock control bits
+ * @reg: pointer to the register in virtual memory.
+ * @shift: the shift in bits to where the bitfield is.
+ * @size: the size in bits of the bitfield.
+ * @mask: the mask of the bitfield.
+ *
+ * This specifies the size and position of the bits we are interested
+ * in within the register specified by @reg.
+ */
+struct clk_reg {
+ u32 reg;
+ //unsigned short shift, size;
+ u32 mask;
+};
+
+struct clk_regs {
+ int id;
+ const char *name;
+ struct clk_reg enb, div, sel;
+
+ //pll sources select
+ int nr_sources;
+ char *sources[];
+};
+
+/**
+ * struct clk_ops - standard clock operations
+ * @set_rate: set the clock rate, see clk_set_rate().
+ * @get_rate: get the clock rate, see clk_get_rate().
+ * @round_rate: round a given clock rate, see clk_round_rate().
+ * @set_parent: set the clock's parent, see clk_set_parent().
+ *
+ * Group the common clock implementations together so that we
+ * don't have to keep setting the same fields again. We leave
+ * enable in struct clk.
+ *
+ * Adding an extra layer of indirection into the process should
+ * not be a problem as it is unlikely these operations are going
+ * to need to be called quickly.
+ */
+
+/**
+ * struct clk - class of clock for newer style spreadtrum devices.
+ * @clk: the standard clock representation
+ * @sel: the parent sources select for this clock
+ * @reg_sel: the register definition for selecting the pll clock's source
+ * @reg_div: the register definition for the clock's output divisor
+ * @reg_enb: the register definition for enable or disable the clock's source
+ *
+ * This clock implements the features required by the newer SoCs where
+ * the standard clock block provides an input mux and a post-mux divisor
+ * to provide the periperhal's clock.
+ *
+ * The array of @sel provides the mapping of mux position to the
+ * clock, and @reg_sel shows the code where to modify to change the mux
+ * position. The @reg_div defines how to change the divider settings on
+ * the output.
+ */
+
+struct sci_clk {
+ struct clk_hw hw;
+ unsigned long rate;
+
+ const struct clk_regs *regs;
+};
+
+#define MAX_DIV (1000)
+
+#define SCI_CLK_ADD(ID, RATE, ENB, ENB_BIT, DIV, DIV_MSK, SEL, SEL_MSK, NR_CLKS, ...) \
+static const struct clk_regs REGS_##ID = { \
+ .name = #ID, \
+ .id = 0, \
+ .enb = { \
+ .reg = (u32)ENB,.mask = ENB_BIT, \
+ }, \
+ .div = { \
+ .reg = (u32)DIV,.mask = DIV_MSK, \
+ }, \
+ .sel = { \
+ .reg = (u32)SEL,.mask = SEL_MSK, \
+ }, \
+ .nr_sources = NR_CLKS, \
+ .sources = {__VA_ARGS__}, \
+}; \
+static struct sci_clk ID = { \
+ .hw = {0}, \
+ .rate = RATE, \
+ .regs = &REGS_##ID, \
+}; \
+const struct clk_lookup __clkinit1 CLK_LK_##ID = { \
+ .dev_id = 0, \
+ .con_id = #ID, \
+ .clk = (struct clk *)&ID, \
+}; \
+
+int __init sci_clk_register(struct clk_lookup *cl);
+
+#define __clkinit0 __section(.rodata.clkinit0)
+#define __clkinit1 __section(.rodata.clkinit1)
+#define __clkinit2 __section(.rodata.clkinit2)
diff --git a/drivers/platform/sprd/common.c b/drivers/platform/sprd/common.c
new file mode 100644
index 00000000..57b8f327
--- /dev/null
+++ b/drivers/platform/sprd/common.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/io.h>
+#include <linux/irqchip/arm-gic.h>
+#include <soc/sprd/hardware.h>
+
+static void __iomem *gic_dist_base_addr;
+static void __iomem *gic_cpu_base;
+
+void __iomem *sprd_get_gic_dist_base(void)
+{
+ gic_dist_base_addr = (void __iomem *)CORE_GIC_DIS_VA;
+ return gic_dist_base_addr;
+}
+
+void __iomem *sprd_get_gic_cpu_base(void)
+{
+ gic_cpu_base = (void __iomem *)CORE_GIC_CPU_VA;
+ return gic_cpu_base;
+}
+
+int sprd_map_gic(void){
+ gic_dist_base_addr = (void __iomem *)CORE_GIC_DIS_VA;
+ gic_cpu_base = (void __iomem *)CORE_GIC_CPU_VA;
+ return 0;
+}
+
+/*
+ * FIXME: Remove this GIC APIs once common GIG library starts
+ * supporting it.
+ */
+void gic_cpu_enable(unsigned int cpu)
+{
+ __raw_writel(0xf0, gic_cpu_base + GIC_CPU_PRIMASK);
+ __raw_writel(1, gic_cpu_base + GIC_CPU_CTRL);
+}
+
+void gic_cpu_disable(unsigned int cpu)
+{
+ __raw_writel(0, gic_cpu_base + GIC_CPU_CTRL);
+}
+
+
+bool gic_dist_disabled(void)
+{
+ return !(__raw_readl(gic_dist_base_addr + GIC_DIST_CTRL) & 0x1);
+}
+
+void gic_dist_enable(void)
+{
+ __raw_writel(0x1, gic_dist_base_addr + GIC_DIST_CTRL);
+}
+void gic_dist_disable(void)
+{
+ __raw_writel(0, gic_dist_base_addr + GIC_CPU_CTRL);
+}
+
+void __iomem *sprd_get_scu_base(void)
+{
+ return (void __iomem *)SPRD_CORE_BASE;
+}
diff --git a/drivers/platform/sprd/cpufreq-scx35.c b/drivers/platform/sprd/cpufreq-scx35.c
new file mode 100644
index 00000000..6ef6a5a4
--- /dev/null
+++ b/drivers/platform/sprd/cpufreq-scx35.c
@@ -0,0 +1,1272 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/cpu.h>
+#include <linux/regulator/consumer.h>
+//#include <asm/system.h>
+#include <trace/events/power.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/regulator.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/arch_misc.h>
+#include <linux/of_platform.h>
+
+#if defined(CONFIG_ARCH_SC8825)
+#define MHz (1000000)
+#define GR_MPLL_REFIN_2M (2 * MHz)
+#define GR_MPLL_REFIN_4M (4 * MHz)
+#define GR_MPLL_REFIN_13M (13 * MHz)
+#define GR_MPLL_REFIN_SHIFT 16
+#define GR_MPLL_REFIN_MASK (0x3)
+#define GR_MPLL_N_MASK (0x7ff)
+#define GR_MPLL_MN (REG_GLB_M_PLL_CTL0)
+#define GR_GEN1 (REG_GLB_GEN1)
+#endif
+
+#define FREQ_TABLE_SIZE 10
+#define DVFS_BOOT_TIME (30 * HZ)
+#define SHARK_TDPLL_FREQUENCY (768000)
+#define TRANSITION_LATENCY (50 * 1000) /* ns */
+
+static DEFINE_MUTEX(freq_lock);
+struct cpufreq_freqs global_freqs;
+unsigned int percpu_target[CONFIG_NR_CPUS] = {0};
+static unsigned long boot_done;
+static unsigned int sprd_top_frequency; /* khz */
+
+struct cpufreq_conf {
+ struct clk *clk;
+ struct clk *mpllclk;
+ struct clk *tdpllclk;
+ struct regulator *regulator;
+ struct cpufreq_frequency_table *freq_tbl;
+ unsigned int *vddarm_mv;
+ unsigned int max_axi_freq;
+};
+
+struct cpufreq_table_data {
+ struct cpufreq_frequency_table freq_tbl[FREQ_TABLE_SIZE];
+ unsigned int vddarm_mv[FREQ_TABLE_SIZE];
+};
+
+struct cpufreq_conf *sprd_cpufreq_conf = NULL;
+static struct mutex cpufreq_vddarm_lock;
+
+#if defined(CONFIG_ARCH_SC8825)
+static struct cpufreq_table_data sc8825_cpufreq_table_data = {
+ .freq_tbl = {
+ {0, 1000000},
+ {1, 500000},
+ {2, CPUFREQ_TABLE_END}
+ },
+ .vddarm_mv = {
+ 0
+ },
+};
+
+struct cpufreq_conf sc8825_cpufreq_conf = {
+ .clk = NULL,
+ .regulator = NULL,
+ .freq_tbl = sc8825_cpufreq_table_data.freq_tbl,
+ .vddarm_mv = sc8825_cpufreq_table_data.vddarm_mv,
+};
+
+static void set_mcu_clk_freq(u32 mcu_freq)
+{
+ u32 val, rate, arm_clk_div, gr_gen1;
+
+ rate = mcu_freq / MHz;
+ switch(1000 / rate)
+ {
+ case 1:
+ arm_clk_div = 0;
+ break;
+ case 2:
+ arm_clk_div = 1;
+ break;
+ default:
+ panic("set_mcu_clk_freq fault\n");
+ break;
+ }
+ pr_debug("%s --- before, AHB_ARM_CLK: %08x, rate = %d, div = %d\n",
+ __func__, __raw_readl(REG_AHB_ARM_CLK), rate, arm_clk_div);
+
+ gr_gen1 = __raw_readl(GR_GEN1);
+ gr_gen1 |= BIT(9);
+ __raw_writel(gr_gen1, GR_GEN1);
+
+ val = __raw_readl(REG_AHB_ARM_CLK);
+ val &= 0xfffffff8;
+ val |= arm_clk_div;
+ __raw_writel(val, REG_AHB_ARM_CLK);
+
+ gr_gen1 &= ~BIT(9);
+ __raw_writel(gr_gen1, GR_GEN1);
+
+ pr_debug("%s --- after, AHB_ARM_CLK: %08x, rate = %d, div = %d\n",
+ __func__, __raw_readl(REG_AHB_ARM_CLK), rate, arm_clk_div);
+
+ return;
+}
+
+static unsigned int get_mcu_clk_freq(void)
+{
+ u32 mpll_refin, mpll_n, mpll_cfg = 0, rate, val;
+
+ mpll_cfg = __raw_readl(GR_MPLL_MN);
+
+ mpll_refin = (mpll_cfg >> GR_MPLL_REFIN_SHIFT) & GR_MPLL_REFIN_MASK;
+ switch(mpll_refin){
+ case 0:
+ mpll_refin = GR_MPLL_REFIN_2M;
+ break;
+ case 1:
+ case 2:
+ mpll_refin = GR_MPLL_REFIN_4M;
+ break;
+ case 3:
+ mpll_refin = GR_MPLL_REFIN_13M;
+ break;
+ default:
+ pr_err("%s mpll_refin: %d\n", __FUNCTION__, mpll_refin);
+ }
+ mpll_n = mpll_cfg & GR_MPLL_N_MASK;
+ rate = mpll_refin * mpll_n;
+
+ /*find div */
+ val = __raw_readl(REG_AHB_ARM_CLK) & 0x7;
+ val += 1;
+ return rate / val;
+}
+#endif
+
+static struct cpufreq_table_data sc8830_cpufreq_table_data_cs = {
+ .freq_tbl = {
+ {0, 1200000},
+ {1, 1000000},
+ {2, SHARK_TDPLL_FREQUENCY},
+ {3, 600000},
+ {4, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1300000,
+ 1200000,
+ 1150000,
+ 1100000,
+ 1000000,
+ },
+};
+
+/*
+for 7715 test
+*/
+static struct cpufreq_table_data sc7715_cpufreq_table_data = {
+ .freq_tbl = {
+ {0, 1000000},
+ {1, SHARK_TDPLL_FREQUENCY},
+ {2, 600000},
+ {3, SHARK_TDPLL_FREQUENCY/2},
+ {4, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1200000,
+ 1150000,
+ 1100000,
+ 1100000,
+ 1000000,
+ },
+};
+
+
+static struct cpufreq_table_data sc8830_cpufreq_table_data_es = {
+ .freq_tbl = {
+ {0, 1000000},
+ {1, SHARK_TDPLL_FREQUENCY},
+ {2, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1250000,
+ 1200000,
+ 1000000,
+ },
+};
+
+static struct cpufreq_table_data sc8830t_cpufreq_table_data_es = {
+ .freq_tbl = {
+ {0, 1300000},
+ {1, 1200000},
+ {2, 1000000},
+ {3, SHARK_TDPLL_FREQUENCY},
+ {4, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1060000,
+ 1030000,
+ 960000,
+ 900000,
+ 900000,
+ },
+};
+
+static struct cpufreq_table_data sc8730c_cpufreq_table_data = {
+ .freq_tbl = {
+ {0, 1400000},
+ {1, 1300000},
+ {2, 1200000},
+ {3, 1000000},
+ {4, SHARK_TDPLL_FREQUENCY},
+ {5, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1060000,
+ 1060000,
+ 1030000,
+ 960000,
+ 900000,
+ 900000,
+ },
+};
+
+static struct cpufreq_table_data sc8830t_cpufreq_table_data_es_1300 = {
+ .freq_tbl = {
+ {0, 1300000},
+ {1, 1200000},
+ {2, 1000000},
+ {3, SHARK_TDPLL_FREQUENCY},
+ {4, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1060000,
+ 1030000,
+ 960000,
+ 900000,
+ 900000,
+ },
+};
+
+static struct cpufreq_table_data sc9630_cpufreq_table_data = {
+ .freq_tbl = {
+ {0, 1500000},
+ {1, 1350000},
+ {2, 900000},
+ {3, 768000},
+ {4, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1100000,
+ 1000000,
+ 900000,
+ 900000,
+ 900000,
+ },
+};
+
+static struct cpufreq_table_data sc7720_cpufreq_table_data = {
+ .freq_tbl = {
+ {0, 1200000},
+ {1, 813000},
+ {2, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1200000,
+ 1050000,
+ 1050000,
+ },
+};
+
+static struct cpufreq_table_data sc9631l64_cpufreq_table_data_es = {
+ .freq_tbl = {
+ {0, 1300000},
+ {1, 1000000},
+ {2, SHARK_TDPLL_FREQUENCY},
+ {3, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1000000,
+ 900000,
+ 900000,
+ 900000,
+ },
+};
+static struct cpufreq_table_data sc9820_cpufreq_table_data = {
+ .freq_tbl = {
+ {0, 1250000},
+ {1, 800000},
+ {2, CPUFREQ_TABLE_END},
+ },
+ .vddarm_mv = {
+ 1200000,
+ 1050000,
+ 1050000,
+ },
+};
+struct cpufreq_conf sc8830_cpufreq_conf = {
+ .clk = NULL,
+ .mpllclk = NULL,
+ .tdpllclk = NULL,
+ .regulator = NULL,
+ .freq_tbl = NULL,
+ .vddarm_mv = NULL,
+};
+
+int cpufreq_table_thermal_update(unsigned int freq, unsigned int voltage)
+{
+ struct cpufreq_frequency_table *freq_tbl;
+ unsigned int *vddarm;
+ int i;
+
+ if (NULL == sprd_cpufreq_conf)
+ return -1;
+ freq_tbl = sprd_cpufreq_conf->freq_tbl;
+ vddarm = sprd_cpufreq_conf->vddarm_mv;
+ if (NULL == freq_tbl && NULL == vddarm)
+ return -1;
+
+ for (i = 0; freq_tbl[i].frequency != CPUFREQ_TABLE_END; ++i) {
+ if (freq_tbl[i].frequency == freq)
+ goto done;
+ }
+ pr_err(KERN_ERR "%s cpufreq %dMHz isn't find!\n", __func__, freq);
+ return -1;
+done:
+ printk(KERN_ERR "%s: %dMHz voltage is %dmV\n",
+ __func__, freq, voltage);
+ if (vddarm[i] == voltage)
+ return 0;
+
+ mutex_lock(&cpufreq_vddarm_lock);
+ vddarm[i] = voltage;
+ mutex_unlock(&cpufreq_vddarm_lock);
+
+ return 0;
+}
+
+static unsigned int sprd_raw_get_cpufreq(void)
+{
+#if defined(CONFIG_ARCH_SCX35)
+ return clk_get_rate(sprd_cpufreq_conf->clk) / 1000;
+#elif defined(CONFIG_ARCH_SC8825)
+ return get_mcu_clk_freq() / 1000;
+#endif
+}
+
+unsigned int last_freq = 0;
+static void dump_axi_cpu(unsigned int freq)
+{
+ u32 div, axi_freq;
+
+#ifndef CONFIG_ARCH_SCX35L
+ div = sci_glb_read(REG_AP_AHB_CA7_CKG_CFG, -1UL);
+ div &= (0x7<<8);
+ div >>= 8;
+ axi_freq = freq / (div + 1);
+#elif defined CONFIG_ARCH_SCX35LT8
+ div = sci_glb_read(REG_AP_AHB_CA7_CKG_DIV_CFG, -1UL);
+ div &= (0x7<<8);
+ div >>= 8;
+ axi_freq = freq / (div + 1);
+#endif
+ printk("%s(%d): cpu_freq %d, div %d, axi_freq %d\n", __func__, __LINE__,
+ freq, div, axi_freq);
+}
+
+static inline int get_axi_div(unsigned int freq)
+{
+ if (freq % sprd_cpufreq_conf->max_axi_freq)
+ return (freq / sprd_cpufreq_conf->max_axi_freq) + 1;
+ else
+ return freq / sprd_cpufreq_conf->max_axi_freq;
+}
+
+static void cpufreq_adjust_axi_clk(unsigned int freq)
+{
+ struct clk *clk_axi = NULL;
+ int div = 0, div_old = 0;
+ int need_adjust = 0;
+ char clk_name[50] = {0};
+
+ if (last_freq == 0) {
+ last_freq = freq;
+ need_adjust = 1;
+ }
+
+#ifndef CONFIG_ARCH_SCX35L
+ strcpy(clk_name, "clk_ca7_axi");
+#else
+ // for T8, in cpufreq-dt-sprd.c ?
+#endif
+ if (!clk_name[0]) {
+ last_freq = freq;
+ return;
+ }
+
+ div = get_axi_div(freq);
+ div_old = get_axi_div(last_freq);
+ if (!need_adjust && (div != div_old))
+ need_adjust = 1;
+
+ if (!need_adjust) {
+ last_freq = freq;
+ return;
+ }
+ last_freq = freq;
+
+ clk_axi = clk_get_sys(NULL, clk_name);
+ if (IS_ERR_OR_NULL(clk_axi)) {
+ pr_err("%s(%d) err: cannot find clock %s\n", __func__, __LINE__, clk_name);
+ return;
+ }
+
+ if (clk_set_rate(clk_axi, freq * 1000 / div))
+ pr_err("%s(%d) err: clk_set_rate failed\n", __func__, __LINE__);
+
+ clk_put(clk_axi);
+ //dump_axi_cpu(freq);
+}
+
+static void cpufreq_set_clock(unsigned int freq)
+{
+ int ret;
+
+ ret = clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->tdpllclk);
+ if (ret)
+ pr_err("Failed to set cpu parent to tdpll\n");
+ if (freq == SHARK_TDPLL_FREQUENCY/2) {
+ //ca7 clk div
+ #ifndef CONFIG_ARCH_SCX35L
+ sci_glb_set(REG_AP_AHB_CA7_CKG_CFG, BITS_CA7_MCU_CKG_DIV(1));
+ #else
+ #ifndef CONFIG_ARCH_SCX35LT8 //TODO
+ sci_glb_set(REG_AP_AHB_CA7_CKG_DIV_CFG, BITS_CA7_MCU_CKG_DIV(1));
+ #endif
+ #endif
+ } else if (freq == SHARK_TDPLL_FREQUENCY) {
+ #ifndef CONFIG_ARCH_SCX35L
+ sci_glb_clr(REG_AP_AHB_CA7_CKG_CFG, BITS_CA7_MCU_CKG_DIV(1));
+ #else
+ #ifndef CONFIG_ARCH_SCX35LT8 //TODO
+ sci_glb_clr(REG_AP_AHB_CA7_CKG_DIV_CFG, BITS_CA7_MCU_CKG_DIV(1));
+ #endif
+ #endif
+
+ } else {
+ /*
+ if (clk_get_parent(sprd_cpufreq_conf->clk) != sprd_cpufreq_conf->tdpllclk) {
+ ret = clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->tdpllclk);
+ if (ret)
+ pr_err("Failed to set cpu parent to tdpll\n");
+ }
+ */
+ if (!(sci_glb_read(REG_PMU_APB_MPLL_REL_CFG, -1) & BIT_MPLL_AP_SEL)) {
+ sci_glb_set(REG_PMU_APB_MPLL_REL_CFG, BIT_MPLL_AP_SEL);
+ udelay(500);
+ }
+ ret = clk_set_rate(sprd_cpufreq_conf->mpllclk, (freq * 1000));
+ if (ret)
+ pr_err("Failed to set mpll rate\n");
+ ret = clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->mpllclk);
+ if (ret)
+ pr_err("Failed to set cpu parent to mpll\n");
+ #ifndef CONFIG_ARCH_SCX35L
+ sci_glb_clr(REG_AP_AHB_CA7_CKG_CFG, BITS_CA7_MCU_CKG_DIV(1));
+ #else
+ #ifndef CONFIG_ARCH_SCX35LT8 //TODO
+ sci_glb_clr(REG_AP_AHB_CA7_CKG_DIV_CFG, BITS_CA7_MCU_CKG_DIV(1));
+ #endif
+ #endif
+ }
+
+ cpufreq_adjust_axi_clk(freq);
+}
+static void sprd_raw_set_cpufreq(int cpu, struct cpufreq_freqs *freq, int index)
+{
+#if defined(CONFIG_ARCH_SCX35)
+ int ret;
+
+#define CPUFREQ_SET_VOLTAGE() \
+ do { \
+ mutex_lock(&cpufreq_vddarm_lock); \
+ ret = regulator_set_voltage(sprd_cpufreq_conf->regulator, \
+ sprd_cpufreq_conf->vddarm_mv[index], \
+ sprd_cpufreq_conf->vddarm_mv[index]); \
+ mutex_unlock(&cpufreq_vddarm_lock); \
+ if (ret) \
+ pr_err("Failed to set vdd to %d mv\n", \
+ sprd_cpufreq_conf->vddarm_mv[index]); \
+ } while (0)
+#define CPUFREQ_SET_CLOCK() \
+ do { \
+ if (freq->new == SHARK_TDPLL_FREQUENCY) { \
+ ret = clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->tdpllclk); \
+ if (ret) \
+ pr_err("Failed to set cpu parent to tdpll\n"); \
+ } else { \
+ if (clk_get_parent(sprd_cpufreq_conf->clk) != sprd_cpufreq_conf->tdpllclk) { \
+ ret = clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->tdpllclk); \
+ if (ret) \
+ pr_err("Failed to set cpu parent to tdpll\n"); \
+ } \
+ ret = clk_set_rate(sprd_cpufreq_conf->mpllclk, (freq->new * 1000)); \
+ if (ret) \
+ pr_err("Failed to set mpll rate\n"); \
+ ret = clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->mpllclk); \
+ if (ret) \
+ pr_err("Failed to set cpu parent to mpll\n"); \
+ } \
+ } while (0)
+ trace_cpu_frequency(freq->new, cpu);
+
+ if (freq->new >= sprd_raw_get_cpufreq()) {
+ CPUFREQ_SET_VOLTAGE();
+ cpufreq_set_clock(freq->new);
+ } else {
+ cpufreq_set_clock(freq->new);
+ CPUFREQ_SET_VOLTAGE();
+ }
+
+ pr_info("%u --> %u, real=%u, index=%d\n",
+ freq->old, freq->new, sprd_raw_get_cpufreq(), index);
+
+#undef CPUFREQ_SET_VOLTAGE
+#undef CPUFREQ_SET_CLOCK
+
+#elif defined(CONFIG_ARCH_SC8825)
+ set_mcu_clk_freq(freq->new * 1000);
+#endif
+ return;
+}
+
+static void sprd_real_set_cpufreq(struct cpufreq_policy *policy, unsigned int new_speed, int index)
+{
+ mutex_lock(&freq_lock);
+
+ if (global_freqs.old == new_speed) {
+ pr_debug("do nothing for cpu%u, new=old=%u\n",
+ policy->cpu, new_speed);
+ mutex_unlock(&freq_lock);
+ return;
+ }
+ pr_info("--xing-- set %u khz for cpu%u\n",
+ new_speed, policy->cpu);
+ global_freqs.cpu = policy->cpu;
+ global_freqs.new = new_speed;
+
+ cpufreq_notify_transition(policy, &global_freqs, CPUFREQ_PRECHANGE);
+
+ sprd_raw_set_cpufreq(policy->cpu, &global_freqs, index);
+
+ cpufreq_notify_transition(policy, &global_freqs, CPUFREQ_POSTCHANGE);
+
+ global_freqs.old = global_freqs.new;
+
+ mutex_unlock(&freq_lock);
+ return;
+}
+
+static void sprd_find_real_index(unsigned int new_speed, int *index)
+{
+ int i;
+ struct cpufreq_frequency_table *pfreq = sprd_cpufreq_conf->freq_tbl;
+
+ *index = pfreq[0].index;
+ for (i = 0; (pfreq[i].frequency != CPUFREQ_TABLE_END); i++) {
+ if (new_speed == pfreq[i].frequency) {
+ *index = pfreq[i].index;
+ break;
+ }
+ }
+ return;
+}
+
+static int sprd_update_cpu_speed(struct cpufreq_policy *policy,
+ unsigned int target_speed, int index)
+{
+ int i, real_index = 0;
+ unsigned int new_speed = 0;
+
+ /*
+ * CONFIG_NR_CPUS cores are always in the same voltage, at the same
+ * frequency. But, cpu load is calculated individual in each cores,
+ * So we remeber the original target frequency and voltage of core0,
+ * and use the higher one
+ */
+
+ for_each_online_cpu(i) {
+ new_speed = max(new_speed, percpu_target[i]);
+ }
+
+ if (new_speed > sprd_top_frequency)
+ new_speed = sprd_top_frequency;
+
+ if (new_speed != sprd_cpufreq_conf->freq_tbl[index].frequency)
+ sprd_find_real_index(new_speed, &real_index);
+ else
+ real_index = index;
+ sprd_real_set_cpufreq(policy, new_speed, real_index);
+ return 0;
+}
+
+static int sprd_cpufreq_verify_speed(struct cpufreq_policy *policy)
+{
+ if (policy->cpu > CONFIG_NR_CPUS) {
+ pr_err("%s no such cpu id %d\n", __func__, policy->cpu);
+ return -EINVAL;
+ }
+
+ return cpufreq_frequency_table_verify(policy, sprd_cpufreq_conf->freq_tbl);
+}
+
+unsigned int cpufreq_min_limit = ULONG_MAX;
+unsigned int cpufreq_max_limit = 0;
+unsigned int dvfs_score_select = 5;
+unsigned int dvfs_unplug_select = 2;
+unsigned int dvfs_plug_select = 0;
+unsigned int dvfs_score_hi[4] = {0};
+unsigned int dvfs_score_mid[4] = {0};
+unsigned int dvfs_score_critical[4] = {0};
+extern unsigned int percpu_load[4];
+extern unsigned int cur_window_size[4];
+extern unsigned int cur_window_index[4];
+extern unsigned int ga_percpu_total_load[4][8];
+
+static DEFINE_SPINLOCK(cpufreq_state_lock);
+
+static int sprd_cpufreq_target(struct cpufreq_policy *policy,
+ unsigned int target_freq,
+ unsigned int relation)
+{
+ int ret = -EFAULT;
+ int index;
+ unsigned int new_speed;
+ struct cpufreq_frequency_table *table;
+ int max_freq = cpufreq_max_limit;
+ int min_freq = cpufreq_min_limit;
+ int cur_freq = 0;
+ unsigned long irq_flags;
+
+ /* delay 30s to enable dvfs&dynamic-hotplug,
+ * except requirment from termal-cooling device
+ */
+ if(time_before(jiffies, boot_done)){
+ return 0;
+ }
+
+ if((target_freq < min_freq) || (target_freq > max_freq))
+ {
+ pr_err("invalid target_freq: %d min_freq %d max_freq %d\n", target_freq,min_freq,max_freq);
+ return -EINVAL;
+ }
+ table = cpufreq_frequency_get_table(policy->cpu);
+
+ if (cpufreq_frequency_table_target(policy, table,
+ target_freq, relation, &index)) {
+ pr_err("invalid target_freq: %d\n", target_freq);
+ return -EINVAL;
+ }
+
+ pr_debug("CPU_%d target %d relation %d (%d-%d) selected %d\n",
+ policy->cpu, target_freq, relation,
+ policy->min, policy->max, table[index].frequency);
+
+ new_speed = table[index].frequency;
+
+ percpu_target[policy->cpu] = new_speed;
+ pr_debug("%s cpu:%d new_speed:%u on cpu%d\n",
+ __func__, policy->cpu, new_speed, smp_processor_id());
+
+ ret = sprd_update_cpu_speed(policy, new_speed, index);
+
+ return ret;
+
+}
+
+static unsigned int sprd_cpufreq_getspeed(unsigned int cpu)
+{
+ if (cpu > CONFIG_NR_CPUS) {
+ pr_err("%s no such cpu id %d\n", __func__, cpu);
+ return -EINVAL;
+ }
+
+ return sprd_raw_get_cpufreq();
+}
+
+static void sprd_set_cpureq_limit(void)
+{
+ int i;
+ struct cpufreq_frequency_table *tmp = sprd_cpufreq_conf->freq_tbl;
+ for (i = 0; (tmp[i].frequency != CPUFREQ_TABLE_END); i++) {
+ cpufreq_min_limit = min(tmp[i].frequency, cpufreq_min_limit);
+ cpufreq_max_limit = max(tmp[i].frequency, cpufreq_max_limit);
+ }
+ pr_info("--xing-- %s max=%u min=%u\n", __func__, cpufreq_max_limit, cpufreq_min_limit);
+}
+
+#if defined(CONFIG_ARCH_SCX35LT8)
+#define AON_APB_CHIP_ID REG_AON_APB_CHIP_ID0
+#else
+#define AON_APB_CHIP_ID REG_AON_APB_CHIP_ID
+#endif
+static int sprd_freq_table_init(void)
+{
+ /* we init freq table here depends on which chip being used */
+ if (soc_is_scx35_v0()) {
+ pr_info("%s es_chip\n", __func__);
+ sprd_cpufreq_conf->freq_tbl =
+ sc8830_cpufreq_table_data_es.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc8830_cpufreq_table_data_es.vddarm_mv;
+ } else if (soc_is_scx35_v1()) {
+ pr_info("%s cs_chip\n", __func__);
+ sprd_cpufreq_conf->freq_tbl =
+ sc8830_cpufreq_table_data_cs.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc8830_cpufreq_table_data_cs.vddarm_mv;
+ } else if (soc_is_sc7715()) {
+ sprd_cpufreq_conf->freq_tbl =
+ sc7715_cpufreq_table_data.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc7715_cpufreq_table_data.vddarm_mv;
+ } else if (soc_is_scx35g_v0()) {
+ sprd_cpufreq_conf->freq_tbl =
+ sc8830t_cpufreq_table_data_es.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc8830t_cpufreq_table_data_es.vddarm_mv;
+ sprd_cpufreq_conf->max_axi_freq = 500000;
+ } else if (soc_is_scx30g2_v0()) {
+ sprd_cpufreq_conf->freq_tbl =
+ sc8730c_cpufreq_table_data.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc8730c_cpufreq_table_data.vddarm_mv;
+ sprd_cpufreq_conf->max_axi_freq = 500000;
+ } else if (soc_is_scx9630_v0() || soc_is_scx9830i_v0()) {
+ sprd_cpufreq_conf->freq_tbl =
+ sc9630_cpufreq_table_data.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc9630_cpufreq_table_data.vddarm_mv;
+ } else if (soc_is_scx9820_v0()) {
+ sprd_cpufreq_conf->freq_tbl =
+ sc9820_cpufreq_table_data.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc9820_cpufreq_table_data.vddarm_mv;
+ } else if (soc_is_scx7720_v0()) {
+ sprd_cpufreq_conf->freq_tbl =
+ sc7720_cpufreq_table_data.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc7720_cpufreq_table_data.vddarm_mv;
+#if defined (CONFIG_ARCH_SCX35LT8) //TODO
+ } else if(__raw_readl(REG_AON_APB_CHIP_ID0) == 0x96310000){
+ sprd_cpufreq_conf->freq_tbl =
+ sc9631l64_cpufreq_table_data_es.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc9631l64_cpufreq_table_data_es.vddarm_mv;
+#else
+ } else if (__raw_readl(AON_APB_CHIP_ID) == 0x96310000) {
+ sprd_cpufreq_conf->freq_tbl =
+ sc9631l64_cpufreq_table_data_es.freq_tbl;
+ sprd_cpufreq_conf->vddarm_mv =
+ sc9631l64_cpufreq_table_data_es.vddarm_mv;
+#endif
+ } else {
+#if defined(CONFIG_ARCH_SCX35LT8)
+ pr_info("D-die chip id = 0x%08X\n", __raw_readl(REG_AON_APB_CHIP_ID0));
+#endif
+ pr_err("%s error chip id\n", __func__);
+ return -EINVAL;
+ }
+ pr_info("sprd_freq_table_init \n");
+ sprd_set_cpureq_limit();
+ return 0;
+}
+
+static int sprd_cpufreq_init(struct cpufreq_policy *policy)
+{
+ int ret;
+
+ cpufreq_frequency_table_cpuinfo(policy, sprd_cpufreq_conf->freq_tbl);
+ policy->cur = sprd_raw_get_cpufreq(); /* current cpu frequency: KHz*/
+ /*
+ * transition_latency 5us is enough now
+ * but sampling too often, unbalance and irregular on each online cpu
+ * so we set 500us here.
+ */
+ policy->cpuinfo.transition_latency = TRANSITION_LATENCY;
+ policy->shared_type = CPUFREQ_SHARED_TYPE_ALL;
+
+ cpufreq_frequency_table_get_attr(sprd_cpufreq_conf->freq_tbl, policy->cpu);
+
+ percpu_target[policy->cpu] = policy->cur;
+
+ ret = cpufreq_frequency_table_cpuinfo(policy, sprd_cpufreq_conf->freq_tbl);
+ if (ret != 0)
+ pr_err("%s Failed to config freq table: %d\n", __func__, ret);
+
+
+ pr_info("%s policy->cpu=%d, policy->cur=%u, ret=%d\n",
+ __func__, policy->cpu, policy->cur, ret);
+
+ cpumask_setall(policy->cpus);
+
+ return ret;
+}
+
+static int sprd_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ return 0;
+}
+
+static struct freq_attr *sprd_cpufreq_attr[] = {
+ &cpufreq_freq_attr_scaling_available_freqs,
+ NULL,
+};
+
+static struct cpufreq_driver sprd_cpufreq_driver = {
+ .verify = sprd_cpufreq_verify_speed,
+ .target = sprd_cpufreq_target,
+ .get = sprd_cpufreq_getspeed,
+ .init = sprd_cpufreq_init,
+ .exit = sprd_cpufreq_exit,
+ .name = "sprd",
+ .attr = sprd_cpufreq_attr,
+#if defined(CONFIG_ARCH_SCX35)
+ .flags = CPUFREQ_SHARED
+#endif
+};
+
+static ssize_t cpufreq_min_limit_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ memcpy(buf,&cpufreq_min_limit,sizeof(int));
+ return sizeof(int);
+}
+
+static ssize_t cpufreq_max_limit_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ memcpy(buf,&cpufreq_max_limit,sizeof(int));
+ return sizeof(int);
+}
+
+static ssize_t cpufreq_min_limit_debug_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ snprintf(buf,10,"%d\n",cpufreq_min_limit);
+ return strlen(buf) + 1;
+}
+
+static ssize_t cpufreq_max_limit_debug_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ snprintf(buf,10,"%d\n",cpufreq_max_limit);
+ return strlen(buf) + 1;
+}
+
+static ssize_t cpufreq_max_axi_freq_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ snprintf(buf, 10, "%d\n", sprd_cpufreq_conf->max_axi_freq);
+ return strlen(buf) + 1;
+}
+
+static ssize_t cpufreq_max_axi_freq_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int value;
+ int temp,max_freq = 0;
+ int i,j;
+
+ strict_strtoul(buf, 16, (long unsigned int *)&value);
+ i = 0;
+ do{
+ temp = value & 0xf;
+ for(j = 0; j < i; j++)
+ temp = temp * 10;
+ max_freq += temp;
+ value = value >> 4;
+ i++;
+ } while(value);
+
+ sprd_cpufreq_conf->max_axi_freq = max_freq;
+ return count;
+}
+
+static ssize_t cpufreq_min_limit_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int ret;
+ int value;
+ unsigned long irq_flags;
+
+ ret = strict_strtoul(buf,16,(long unsigned int *)&value);
+
+ spin_lock_irqsave(&cpufreq_state_lock, irq_flags);
+ /*
+ for debug use
+ echo 0xabcde258 > /sys/power/cpufreq_min_limit means set the minimum limit to 600Mhz
+ */
+ if((value & 0xfffff000) == 0xabcde000)
+ {
+ cpufreq_min_limit = value & 0x00000fff;
+ cpufreq_min_limit *= 1000;
+ printk(KERN_ERR"cpufreq_min_limit value %s %d\n",buf,cpufreq_min_limit);
+ }
+ else
+ {
+ cpufreq_min_limit = *(int *)buf;
+ }
+ spin_unlock_irqrestore(&cpufreq_state_lock, irq_flags);
+ return count;
+}
+
+static ssize_t cpufreq_max_limit_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int ret;
+ int value;
+ unsigned long irq_flags;
+
+ ret = strict_strtoul(buf,16,(long unsigned int *)&value);
+
+ spin_lock_irqsave(&cpufreq_state_lock, irq_flags);
+
+ /*
+ for debug use
+ echo 0xabcde4b0 > /sys/power/cpufreq_max_limit means set the maximum limit to 1200Mhz
+ */
+ if((value & 0xfffff000) == 0xabcde000)
+ {
+ cpufreq_max_limit = value & 0x00000fff;
+ cpufreq_max_limit *= 1000;
+ printk(KERN_ERR"cpufreq_max_limit value %s %d\n",buf,cpufreq_max_limit);
+ }
+ else
+ {
+ cpufreq_max_limit = *(int *)buf;
+ }
+ spin_unlock_irqrestore(&cpufreq_state_lock, irq_flags);
+
+ return count;
+}
+
+#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_SPRDEMAND
+static ssize_t dvfs_score_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int ret;
+ int value;
+ unsigned long irq_flags;
+
+ ret = strict_strtoul(buf,16,(long unsigned int *)&value);
+
+ printk(KERN_ERR"dvfs_score_input %x\n",value);
+
+ dvfs_score_select = (value >> 24) & 0x0f;
+ if(dvfs_score_select < 4)
+ {
+ dvfs_score_critical[dvfs_score_select] = (value >> 16) & 0xff;
+ dvfs_score_hi[dvfs_score_select] = (value >> 8) & 0xff;
+ dvfs_score_mid[dvfs_score_select] = value & 0xff;
+ }
+
+
+ return count;
+}
+
+static ssize_t dvfs_score_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ int ret = 0;
+
+ ret = snprintf(buf + ret,50,"dvfs_score_select %d\n",dvfs_score_select);
+ ret += snprintf(buf + ret,200,"dvfs_score_critical[1] = %d dvfs_score_hi[1] = %d dvfs_score_mid[1] = %d\n",dvfs_score_critical[1],dvfs_score_hi[1],dvfs_score_mid[1]);
+ ret += snprintf(buf + ret,200,"dvfs_score_critical[2] = %d dvfs_score_hi[2] = %d dvfs_score_mid[2] = %d\n",dvfs_score_critical[2],dvfs_score_hi[2],dvfs_score_mid[2]);
+ ret += snprintf(buf + ret,200,"dvfs_score_critical[3] = %d dvfs_score_hi[3] = %d dvfs_score_mid[3] = %d\n",dvfs_score_critical[3],dvfs_score_hi[3],dvfs_score_mid[3]);
+
+ ret += snprintf(buf + ret,200,"percpu_total_load[0] = %d,%d->%d\n",
+ percpu_load[0],ga_percpu_total_load[0][(cur_window_index[0] - 1 + 10) % 10],ga_percpu_total_load[0][cur_window_index[0]]);
+ ret += snprintf(buf + ret,200,"percpu_total_load[1] = %d,%d->%d\n",
+ percpu_load[1],ga_percpu_total_load[1][(cur_window_index[1] - 1 + 10) % 10],ga_percpu_total_load[1][cur_window_index[1]]);
+ ret += snprintf(buf + ret,200,"percpu_total_load[2] = %d,%d->%d\n",
+ percpu_load[2],ga_percpu_total_load[2][(cur_window_index[2] - 1 + 10) % 10],ga_percpu_total_load[2][cur_window_index[2]]);
+ ret += snprintf(buf + ret,200,"percpu_total_load[3] = %d,%d->%d\n",
+ percpu_load[3],ga_percpu_total_load[3][(cur_window_index[3] - 1 + 10) % 10],ga_percpu_total_load[3][cur_window_index[3]]);
+
+ return strlen(buf) + 1;
+}
+
+static ssize_t dvfs_unplug_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int ret;
+ int value;
+ unsigned long irq_flags;
+
+ ret = strict_strtoul(buf,16,(long unsigned int *)&value);
+
+ printk(KERN_ERR"dvfs_score_input %x\n",value);
+
+ dvfs_unplug_select = (value >> 24) & 0x0f;
+ if(dvfs_unplug_select > 7)
+ {
+ cur_window_size[0]= (value >> 8) & 0xff;
+ cur_window_size[1]= (value >> 8) & 0xff;
+ cur_window_size[2]= (value >> 8) & 0xff;
+ cur_window_size[3]= (value >> 8) & 0xff;
+ }
+ return count;
+}
+
+static ssize_t dvfs_unplug_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ int ret = 0;
+
+ ret = snprintf(buf + ret,50,"dvfs_unplug_select %d\n",dvfs_unplug_select);
+ ret += snprintf(buf + ret,100,"cur_window_size[0] = %d\n",cur_window_size[0]);
+ ret += snprintf(buf + ret,100,"cur_window_size[1] = %d\n",cur_window_size[1]);
+ ret += snprintf(buf + ret,100,"cur_window_size[2] = %d\n",cur_window_size[2]);
+ ret += snprintf(buf + ret,100,"cur_window_size[3] = %d\n",cur_window_size[3]);
+
+ return strlen(buf) + 1;
+}
+
+
+static ssize_t dvfs_plug_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int ret;
+ int value;
+ unsigned long irq_flags;
+
+ ret = strict_strtoul(buf,16,(long unsigned int *)&value);
+
+ printk(KERN_ERR"dvfs_plug_select %x\n",value);
+
+ dvfs_plug_select = (value ) & 0x0f;
+ return count;
+}
+
+
+static ssize_t dvfs_plug_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ int ret = 0;
+
+ ret = snprintf(buf + ret,50,"dvfs_plug_select %d\n",dvfs_plug_select);
+
+ return strlen(buf) + 1;
+}
+#endif
+
+static ssize_t cpufreq_table_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ memcpy(buf,sprd_cpufreq_conf->freq_tbl,sizeof(* sprd_cpufreq_conf->freq_tbl));
+ return sizeof(* sprd_cpufreq_conf->freq_tbl);
+}
+
+static ssize_t dvfs_prop_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int ret;
+ int value;
+ unsigned long irq_flags;
+
+ printk(KERN_ERR"dvfs_status %s\n",buf);
+ ret = strict_strtoul(buf,16,(long unsigned int *)&value);
+
+ printk(KERN_ERR"dvfs_plug_select %x\n",value);
+
+ dvfs_plug_select = (value ) & 0x0f;
+ return count;
+}
+
+static ssize_t dvfs_prop_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ int ret = 0;
+
+ ret = snprintf(buf + ret,50,"dvfs_plug_select %d\n",dvfs_plug_select);
+
+ return strlen(buf) + 1;
+}
+
+#ifdef CONFIG_SPRD_AVS_DEBUG
+extern unsigned int g_avs_log_flag;
+
+static ssize_t avs_log_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
+{
+ int ret;
+ int value;
+ unsigned long irq_flags;
+
+ printk(KERN_ERR"g_avs_log_flag %s\n",buf);
+ ret = strict_strtoul(buf,16,(long unsigned int *)&value);
+
+ printk(KERN_ERR"g_avs_log_flag %x\n",value);
+
+ g_avs_log_flag = (value ) & 0x0f;
+ return count;
+}
+
+static ssize_t avs_log_show(struct device *dev, struct device_attribute *attr,char *buf)
+{
+ int ret = 0;
+
+ ret = snprintf(buf + ret,50,"g_avs_log_flag %d\n",g_avs_log_flag);
+
+ return strlen(buf) + 1;
+}
+#endif
+static DEVICE_ATTR(cpufreq_min_limit, 0660, cpufreq_min_limit_show, cpufreq_min_limit_store);
+static DEVICE_ATTR(cpufreq_max_limit, 0660, cpufreq_max_limit_show, cpufreq_max_limit_store);
+static DEVICE_ATTR(cpufreq_min_limit_debug, 0440, cpufreq_min_limit_debug_show, NULL);
+static DEVICE_ATTR(cpufreq_max_limit_debug, 0440, cpufreq_max_limit_debug_show, NULL);
+static DEVICE_ATTR(cpufreq_table, 0440, cpufreq_table_show, NULL);
+static DEVICE_ATTR(cpufreq_max_axi_freq, 0660, cpufreq_max_axi_freq_show, cpufreq_max_axi_freq_store);
+
+#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_SPRDEMAND
+static DEVICE_ATTR(dvfs_score, 0660, dvfs_score_show, dvfs_score_store);
+static DEVICE_ATTR(dvfs_unplug, 0660, dvfs_unplug_show, dvfs_unplug_store);
+static DEVICE_ATTR(dvfs_plug, 0660, dvfs_plug_show, dvfs_plug_store);
+#endif
+static DEVICE_ATTR(dvfs_prop, 0660, dvfs_prop_show, dvfs_prop_store);
+
+#ifdef CONFIG_SPRD_AVS_DEBUG
+static DEVICE_ATTR(avs_log, 0660, avs_log_show, avs_log_store);
+#endif
+static struct attribute *g[] = {
+ &dev_attr_cpufreq_min_limit.attr,
+ &dev_attr_cpufreq_max_limit.attr,
+ &dev_attr_cpufreq_min_limit_debug.attr,
+ &dev_attr_cpufreq_max_limit_debug.attr,
+ &dev_attr_cpufreq_table.attr,
+ &dev_attr_cpufreq_max_axi_freq.attr,
+#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_SPRDEMAND
+ &dev_attr_dvfs_score.attr,
+ &dev_attr_dvfs_unplug.attr,
+ &dev_attr_dvfs_plug.attr,
+#endif
+ &dev_attr_dvfs_prop.attr,
+#ifdef CONFIG_SPRD_AVS_DEBUG
+ &dev_attr_avs_log.attr,
+#endif
+ NULL,
+};
+
+static struct attribute_group attr_group = {
+ .attrs = g,
+};
+
+static int sprd_cpufreq_policy_notifier(
+ struct notifier_block *nb, unsigned long event, void *data)
+{
+ return NOTIFY_OK;
+}
+
+static struct notifier_block sprd_cpufreq_policy_nb = {
+ .notifier_call = sprd_cpufreq_policy_notifier,
+};
+
+static int __init sprd_cpufreq_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_SPRD_CPUFREQ_DT_DRIVER)
+ struct platform_device_info devinfo = { .name = "cpufreq-dt-sprd", };
+
+ platform_device_register_full(&devinfo);
+
+ return;
+#endif
+
+#if defined(CONFIG_ARCH_SCX35)
+ sprd_cpufreq_conf = &sc8830_cpufreq_conf;
+#elif defined(CONFIG_ARCH_SC8825)
+ sprd_cpufreq_conf = &sc8825_cpufreq_conf;
+#endif
+
+#if defined(CONFIG_ARCH_SCX35)
+ ret = sprd_freq_table_init();
+ if (ret)
+ return ret;
+
+ sprd_top_frequency = sprd_cpufreq_conf->freq_tbl[0].frequency;
+ /* TODO:need verify for the initialization of limited max freq */
+
+ sprd_cpufreq_conf->clk = clk_get_sys(NULL, "clk_mcu");
+ if (IS_ERR(sprd_cpufreq_conf->clk))
+ return PTR_ERR(sprd_cpufreq_conf->clk);
+
+ sprd_cpufreq_conf->mpllclk = clk_get_sys(NULL, "clk_mpll");
+ if (IS_ERR(sprd_cpufreq_conf->mpllclk))
+ return PTR_ERR(sprd_cpufreq_conf->mpllclk);
+
+#if !defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX20)
+ sprd_cpufreq_conf->tdpllclk = clk_get_sys(NULL, "clk_tdpll");
+ if (IS_ERR(sprd_cpufreq_conf->tdpllclk))
+ return PTR_ERR(sprd_cpufreq_conf->tdpllclk);
+#else
+// sprd_cpufreq_conf->tdpllclk = clk_get_sys(NULL, "clk_twpll");
+ sprd_cpufreq_conf->tdpllclk = clk_get_sys(NULL, "clk_768m");
+ if (IS_ERR(sprd_cpufreq_conf->tdpllclk))
+ return PTR_ERR(sprd_cpufreq_conf->tdpllclk);
+#endif
+ mutex_init(&cpufreq_vddarm_lock);
+
+ sprd_cpufreq_conf->regulator = regulator_get(NULL, "vddarm");
+
+ if (IS_ERR(sprd_cpufreq_conf->regulator))
+ return PTR_ERR(sprd_cpufreq_conf->regulator);
+
+ /* set max voltage first */
+ /*
+ regulator_set_voltage(sprd_cpufreq_conf->regulator,
+ sprd_cpufreq_conf->vddarm_mv[0],
+ sprd_cpufreq_conf->vddarm_mv[0]);
+ */
+ clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->tdpllclk);
+ /*
+ * clk_set_rate(sprd_cpufreq_conf->mpllclk, (sprd_top_frequency * 1000));
+ */
+ clk_set_parent(sprd_cpufreq_conf->clk, sprd_cpufreq_conf->mpllclk);
+ global_freqs.old = sprd_raw_get_cpufreq();
+
+#endif
+
+ boot_done = jiffies + DVFS_BOOT_TIME;
+ ret = cpufreq_register_notifier(
+ &sprd_cpufreq_policy_nb, CPUFREQ_POLICY_NOTIFIER);
+ if (ret)
+ return ret;
+
+ ret = cpufreq_register_driver(&sprd_cpufreq_driver);
+
+ ret = sysfs_create_group(power_kobj, &attr_group);
+ return ret;
+}
+
+static void __exit sprd_cpufreq_modexit(void)
+{
+#if defined(CONFIG_ARCH_SCX35)
+ if (!IS_ERR_OR_NULL(sprd_cpufreq_conf->regulator))
+ regulator_put(sprd_cpufreq_conf->regulator);
+#endif
+ cpufreq_unregister_driver(&sprd_cpufreq_driver);
+ cpufreq_unregister_notifier(
+ &sprd_cpufreq_policy_nb, CPUFREQ_POLICY_NOTIFIER);
+ return;
+}
+
+module_init(sprd_cpufreq_modinit);
+module_exit(sprd_cpufreq_modexit);
+
+MODULE_DESCRIPTION("cpufreq driver for Spreadtrum");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/sprd/cpuidle-scx35.c b/drivers/platform/sprd/cpuidle-scx35.c
new file mode 100644
index 00000000..fb452336
--- /dev/null
+++ b/drivers/platform/sprd/cpuidle-scx35.c
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications 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 <linux/module.h>
+#include <linux/sched.h>
+#include <linux/cpuidle.h>
+#include <linux/cpu_pm.h>
+#include <linux/export.h>
+#include <linux/clockchips.h>
+#include <linux/suspend.h>
+#include <asm/proc-fns.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/cpuidle.h>
+
+
+/*#define SC_IDLE_DEBUG 1*/
+extern u32 emc_clk_get(void);
+#ifdef SC_IDLE_DEBUG
+unsigned int idle_debug_state[NR_CPUS];
+#endif
+
+#define SC_CPUIDLE_STATE_NUM ARRAY_SIZE(cpuidle_params_table)
+#define WAIT_WFI_TIMEOUT (20)
+#define LIGHT_SLEEP_ENABLE (BIT_MCU_LIGHT_SLEEP_EN)
+#define IDLE_DEEP_DELAY_TIME (70 * HZ)
+static unsigned long delay_done;
+static unsigned int idle_disabled_by_suspend;
+#if defined(CONFIG_ARCH_SCX15)
+static unsigned int zipenc_status;
+static unsigned int zipdec_status;
+#endif
+
+ static int light_sleep_en = 1;
+
+
+static int idle_deep_en = 0;
+static int cpuidle_debug = 0;
+module_param_named(cpuidle_debug, cpuidle_debug, int, S_IRUGO | S_IWUSR);
+module_param_named(light_sleep_en, light_sleep_en, int, S_IRUGO | S_IWUSR);
+module_param_named(idle_deep_en, idle_deep_en, int, S_IRUGO | S_IWUSR);
+/* Machine specific information to be recorded in the C-state driver_data */
+struct sc_idle_statedata {
+ u32 cpu_state;
+ u32 mpu_logic_state;
+ u32 mpu_state;
+ u8 valid;
+};
+
+/*
+ * cpuidle mach specific parameters
+ * Can board code can override the default C-states definition???
+ */
+struct cpuidle_params {
+ u32 exit_latency; /* exit_latency = sleep + wake-up latencies */
+ u32 target_residency;
+ u32 power_usage;
+ u8 valid;
+};
+
+/*
+* TODO: chip parameters
+*/
+static struct cpuidle_params cpuidle_params_table[] = {
+ /* WFI */
+ {.exit_latency = 1 , .target_residency = 1, .valid = 1},
+ /* LIGHT SLEEP */
+ {.exit_latency = 100 , .target_residency = 1000, .valid = 1},
+ /* CPU CORE POWER DOWN */
+ {.exit_latency = 20000 , .target_residency = 50000, .valid = 1},
+};
+
+
+struct sc_idle_statedata sc8830_idle_data[SC_CPUIDLE_STATE_NUM];
+char* sc_cpuidle_desc[ SC_CPUIDLE_STATE_NUM] = {
+ "standby",
+ "l_sleep",
+ "core_pd",
+};
+
+enum {
+ STANDBY = 0, /* wfi */
+ L_SLEEP, /* light sleep */
+ CORE_PD, /* cpu core power down except cpu0 */
+};
+
+static BLOCKING_NOTIFIER_HEAD(sc_cpuidle_chain_head);
+int register_sc_cpuidle_notifier(struct notifier_block *nb)
+{
+ printk("*** %s, nb->notifier_call:%pf ***\n", __func__, nb->notifier_call );
+ return blocking_notifier_chain_register(&sc_cpuidle_chain_head, nb);
+}
+EXPORT_SYMBOL_GPL(register_sc_cpuidle_notifier);
+int unregister_sc_cpuidle_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&sc_cpuidle_chain_head, nb);
+}
+EXPORT_SYMBOL_GPL(unregister_sc_cpuidle_notifier);
+int sc_cpuidle_notifier_call_chain(unsigned long val)
+{
+ int ret = blocking_notifier_call_chain(&sc_cpuidle_chain_head, val, NULL);
+ return notifier_to_errno(ret);
+}
+
+static void set_cpu_pd(void *data)
+{
+ int cpu_id = *((int*)data);
+ int on_cpu = smp_processor_id();
+ unsigned long timeout;
+
+ timeout = jiffies + msecs_to_jiffies(WAIT_WFI_TIMEOUT);
+
+ if(on_cpu != 0)
+ panic(" this function only can excute on cpu0 \n");
+ if(cpu_id == 0)
+ panic(" cpu0 can not power down \n");
+
+ /*
+ * TODO: set cpu power down
+ while (!(__raw_readl(REG_AP_AHB_CA7_STANDBY_STATUS) & (1<<cpu_id))) {
+ if (time_after(jiffies, timeout)) {
+ printk(" waiting for cpu%d wfi time out \n", cpu_id);
+ return;
+ }
+ }
+ printk(" set cpu%d power down\n", cpu_id);
+ */
+
+ return;
+}
+
+static void sc_cpuidle_debug(void)
+{
+ unsigned int val = sci_glb_read(REG_AP_AHB_MCU_PAUSE, -1UL);
+ if( val&BIT_MCU_LIGHT_SLEEP_EN ){
+ printk("*** %s, REG_AP_AHB_MCU_PAUSE:0x%x ***\n", __func__, val );
+ printk("*** %s, REG_AP_AHB_AHB_EB:0x%x ***\n",
+ __func__, sci_glb_read(REG_AP_AHB_AHB_EB, -1UL));
+ printk("*** %s, REG_AON_APB_APB_EB0:0x%x ***\n",
+ __func__, sci_glb_read(REG_AON_APB_APB_EB0, -1UL));
+ printk("*** %s, REG_AP_AHB_CA7_STANDBY_STATUS:0x%x ***\n",
+ __func__, sci_glb_read(REG_AP_AHB_CA7_STANDBY_STATUS, -1UL));
+ printk("*** %s, REG_PMU_APB_CP_SLP_STATUS_DBG0:0x%x ***\n",
+ __func__, sci_glb_read(REG_PMU_APB_CP_SLP_STATUS_DBG0, -1UL));
+#if !defined(CONFIG_ARCH_SCX35L)
+ printk("*** %s, REG_PMU_APB_CP_SLP_STATUS_DBG1:0x%x ***\n",
+ __func__, sci_glb_read(REG_PMU_APB_CP_SLP_STATUS_DBG1, -1UL));
+#endif
+ printk("*** %s, DDR_OP_MODE:0x%x ***\n",
+ __func__, __raw_readl(SPRD_LPDDR2_BASE + 0x03fc) );
+ }else
+ printk("*** %s, LIGHT_SLEEP_EN can not be set ***\n", __func__ );
+}
+
+static void sc_cpuidle_light_sleep_en(int cpu)
+{
+ int error;
+/*200M ddr clk is not necessary for SCX30G and SCX35L when light sleep status to be entered*/
+#if defined(CONFIG_PM_DEVFREQ) && !defined(CONFIG_ARCH_SCX30G) && !defined(CONFIG_ARCH_SCX35L)
+ if(emc_clk_get() > 200){
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, LIGHT_SLEEP_ENABLE | BIT_MCU_SYS_SLEEP_EN);
+ return;
+ }
+#endif
+ /*
+ * if DAP clock is disabled, arm core can not be attached.
+ * it is no necessary only disable DAP in core 0, just for debug
+ */
+
+ if(light_sleep_en){
+ if (cpu == 0) {
+#if defined(CONFIG_ARCH_SCX15)
+ if (__raw_readl(REG_AP_AHB_AHB_EB) & BIT_ZIPDEC_EB) {
+ sci_glb_clr(REG_AP_AHB_AHB_EB, BIT_ZIPDEC_EB);
+ zipdec_status = 1;
+ }
+ if (__raw_readl(REG_AP_AHB_AHB_EB) & BIT_ZIPENC_EB) {
+ sci_glb_clr(REG_AP_AHB_AHB_EB, BIT_ZIPENC_EB);
+ zipenc_status = 1;
+ }
+#endif
+ sci_glb_clr(REG_AON_APB_APB_EB0, BIT_CA7_DAP_EB);
+ if (!(sci_glb_read(REG_AP_AHB_AHB_EB, -1UL) & BIT_DMA_EB) && (num_online_cpus() == 1)) {
+ error = sc_cpuidle_notifier_call_chain(SC_CPUIDLE_PREPARE);
+ if (error) {
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, BIT_MCU_SYS_SLEEP_EN);
+ pr_debug("could not set %s ... \n", __func__);
+ }
+ else
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_SYS_SLEEP_EN);
+ }
+ }
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, LIGHT_SLEEP_ENABLE);
+ }
+ if(cpuidle_debug){
+ sc_cpuidle_debug();
+ }
+}
+
+/*
+* light sleep must be disabled before deep-sleep
+*/
+static void sc_cpuidle_light_sleep_dis(void)
+{
+ if(light_sleep_en){
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT_CA7_DAP_EB);
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, LIGHT_SLEEP_ENABLE | BIT_MCU_SYS_SLEEP_EN);
+#if defined(CONFIG_ARCH_SCX15)
+ if (zipdec_status)
+ sci_glb_set(REG_AP_AHB_AHB_EB, BIT_ZIPDEC_EB);
+ if (zipenc_status)
+ sci_glb_set(REG_AP_AHB_AHB_EB, BIT_ZIPENC_EB);
+ zipenc_status = 0;
+ zipdec_status = 0;
+#endif
+ }
+ return;
+}
+extern void deep_sleep(int);
+static void idle_into_deep(void)
+{
+ if (sci_glb_read(REG_AP_AHB_AHB_EB, -1UL) &
+ (BIT_DMA_EB | BIT_USB_EB | BIT_EMMC_EB | BIT_NFC_EB | BIT_SDIO0_EB |
+ BIT_SDIO1_EB | BIT_SDIO2_EB)) {
+ /* so we've already known ap can't go into deep now, we just do idle */
+ cpu_do_idle();
+ return;
+ }
+ /* ignore uart1_eb for uart output log now */
+ if (sci_glb_read(REG_AP_APB_APB_EB, -1UL) &
+ (BIT_UART0_EB |BIT_UART2_EB | BIT_UART3_EB | BIT_UART4_EB |
+ BIT_I2C0_EB | BIT_I2C1_EB |BIT_I2C2_EB | BIT_I2C3_EB | BIT_I2C4_EB |
+ BIT_IIS0_EB | BIT_IIS1_EB | BIT_IIS2_EB | BIT_IIS3_EB)) {
+ /* so we've already known ap can't go into deep now, we just do idle
+ * maybe we can go into light sleep or sys sleep
+ */
+ cpu_do_idle();
+ return;
+ }
+
+ if (sci_glb_read(REG_AON_APB_APB_EB0, -1UL) &
+ (BIT_MM_EB |BIT_GPU_EB)) {
+ cpu_do_idle();
+ return;
+ }
+
+ /* It is a chip design defect(MSPI CLOCK SELECTION)
+ * AP can not enter deep sleep mode when FM is working, because mspi clock is from CPLL,
+ * and it can not switch to 26MHz frequently in idle.
+ */
+ if (sci_glb_read(REG_AON_APB_APB_EB0, -1UL) & BIT_FM_EB) {
+ cpu_do_idle();
+ return;
+ }
+
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_DEEP_SLEEP_EN /*| BIT_MCU_SLEEP_FOLLOW_CA7_EN*/);
+ deep_sleep(1);
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, BIT_MCU_DEEP_SLEEP_EN /*| BIT_MCU_SLEEP_FOLLOW_CA7_EN*/);
+}
+
+/**
+ * sc_enter_idle - Programs arm core to enter the specified state
+ * @dev: cpuidle device
+ * @drv: cpuidle driver
+ * @index: the index of state to be entered
+ *
+ * Called from the CPUidle framework to program the device to the
+ * specified low power state selected by the governor.
+ * Returns the index of power state.
+ */
+static int sc_enter_idle(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index)
+{
+ int cpu_id = smp_processor_id();
+ ktime_t enter, exit;
+ s64 us;
+
+ local_irq_disable();
+ local_fiq_disable();
+
+ enter = ktime_get();
+
+#ifdef SC_IDLE_DEBUG
+ idle_debug_state[cpu_id] = index;
+ if(index)
+ printk("cpu:%d, index:%d \n", cpu_id, index );
+#endif
+
+ switch(index){
+ case STANDBY:
+ cpu_do_idle();
+ break;
+ case L_SLEEP:
+ /*
+ * TODO: enter light sleep
+ */
+ sc_cpuidle_light_sleep_en(cpu_id);
+ cpu_do_idle();
+ sc_cpuidle_light_sleep_dis();
+ break;
+ case CORE_PD:
+ if(cpu_id != 0){
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu_id);
+ /*
+ * TODO:
+ * set irq affinity to cpu0, so irq can be handled on cpu0
+ gic_affinity_to_cpu0(cpu_id);
+ smp_call_function_single(0, set_cpu_pd, &cpu_id, 0);
+ */
+ cpu_do_idle();
+ /*
+ gic_affinity_restore(cpu_id);
+ */
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu_id);
+ }else{
+ pr_debug("sc_enter_idle go into core_pd state\n");
+ sc_cpuidle_light_sleep_en(cpu_id);
+#if defined(CONFIG_ARCH_SCX15)
+ if (idle_deep_en && time_after(jiffies, delay_done))
+ idle_into_deep();
+ else
+ cpu_do_idle();
+#else
+ cpu_do_idle();
+#endif
+ sc_cpuidle_light_sleep_dis();
+ }
+ break;
+ default:
+ cpu_do_idle();
+ WARN(1, "CPUIDLE: NO THIS LEVEL!!!");
+ }
+
+ exit = ktime_sub(ktime_get(), enter);
+ us = ktime_to_us(exit);
+
+ dev->last_residency = us;
+
+ local_fiq_enable();
+ local_irq_enable();
+
+ return index;
+}
+
+DEFINE_PER_CPU(struct cpuidle_device, sc8830_idle_dev);
+
+struct cpuidle_driver sc8830_idle_driver = {
+ .name = "sc_cpuidle",
+ .owner = THIS_MODULE,
+};
+
+
+/*
+* sc_fill_cstate: initialize cpu idle status
+* @drv: cpuidle_driver
+* @idx: cpuidle status index
+*/
+static inline void sc_fill_cstate(struct cpuidle_driver *drv, struct cpuidle_device *dev, int idx)
+{
+ char *descr = sc_cpuidle_desc[idx];
+ struct cpuidle_state *state = &drv->states[idx];
+ struct cpuidle_state_usage *state_usage = &dev->states_usage[idx];
+
+ sprintf(state->name, "C%d", idx + 1);
+ strncpy(state->desc, descr, CPUIDLE_DESC_LEN);
+ state->flags = CPUIDLE_FLAG_TIME_VALID;
+ state->exit_latency = cpuidle_params_table[idx].exit_latency;
+ /*TODO*/
+ /*state->power_usage = cpuidle_params_table[idx].power_usage;*/
+ state->target_residency = cpuidle_params_table[idx].target_residency;
+ state->enter = sc_enter_idle;
+
+ /*TODO*/
+ /*state_usage->driver_data = ;*/
+}
+
+static int sc_cpuidle_register_device(struct cpuidle_driver *drv, unsigned int cpu)
+{
+ struct cpuidle_device *dev;
+ int state_idx;
+
+ dev = &per_cpu(sc8830_idle_dev, cpu);
+ dev->cpu = cpu;
+ pr_info("%s, cpu:%d \n", __func__, cpu);
+ for(state_idx=0; state_idx<SC_CPUIDLE_STATE_NUM; state_idx++){
+ sc_fill_cstate(drv, dev, state_idx);
+ }
+ dev->state_count = state_idx;
+
+ if (cpuidle_register_device(dev)) {
+ pr_err("CPU%u: failed to register idle device\n", cpu);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sc_cpuidle_pm_notify(struct notifier_block *nb,
+ unsigned long event, void *dummy)
+{
+#ifdef CONFIG_PM_SLEEP
+ if (event == PM_SUSPEND_PREPARE)
+ idle_disabled_by_suspend = true;
+ else if (event == PM_POST_SUSPEND)
+ idle_disabled_by_suspend = false;
+#endif
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block sc_cpuidle_pm_notifier = {
+ .notifier_call = sc_cpuidle_pm_notify,
+};
+
+int __init sc_cpuidle_init(void)
+{
+ unsigned int cpu_id = 0;
+ struct cpuidle_driver *drv = &sc8830_idle_driver;
+ drv->safe_state_index = 0;
+ drv->state_count = SC_CPUIDLE_STATE_NUM;
+ pr_info("%s, enter, drv->state_count:%d \n", __func__, drv->state_count );
+
+ cpuidle_register_driver(drv);
+
+ for_each_possible_cpu(cpu_id) {
+ if (sc_cpuidle_register_device(drv, cpu_id))
+ pr_err("CPU%u: error initializing idle loop\n", cpu_id);
+ }
+ delay_done = jiffies + IDLE_DEEP_DELAY_TIME;
+ register_pm_notifier(&sc_cpuidle_pm_notifier);
+
+ return 0;
+
+}
diff --git a/drivers/platform/sprd/dcdc_cal.c b/drivers/platform/sprd/dcdc_cal.c
new file mode 100644
index 00000000..dd6e69e8
--- /dev/null
+++ b/drivers/platform/sprd/dcdc_cal.c
@@ -0,0 +1,417 @@
+#include <linux/bug.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <linux/io.h>
+#include <linux/sort.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <mach/hardware.h>
+
+#include <linux/irqflags.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+
+#include <mach/hardware.h>
+#include <mach/sci.h>
+#include <mach/sci_glb_regs.h>
+#include <mach/adi.h>
+#include <mach/adc.h>
+#include <mach/efuse.h>
+
+#define REG_SYST_VALUE (SPRD_SYSCNT_BASE + 0x0004)
+
+#define debug0(format, arg...) pr_debug("dcdc: " "@@@" format, ## arg)
+#define debug(format, arg...) pr_info("dcdc: " "@@@" format, ## arg)
+#define info(format, arg...) pr_info("dcdc: " "@@@" format, ## arg)
+
+extern int in_calibration(void);
+int sprd_get_adc_cal_type(void);
+uint16_t sprd_get_adc_to_vol(uint16_t data);
+int reguator_is_trimming(void *);
+int regulator_set_trimming(struct regulator *, int, int);
+int regulator_get_trimming_step(struct regulator *, int);
+
+static uint32_t bat_numerators, bat_denominators = 0;
+
+#define CALIBRATE_TO (60 * 1) /* one minute */
+#define MEASURE_TIMES (15)
+
+static int cmp_val(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
+int dcdc_adc_get(int adc_chan)
+{
+ int ret;
+ u32 val[MEASURE_TIMES], adc_res = 0, adc_vol = 0;
+ u32 chan_numerators, chan_denominators;
+ uint32_t bat_numerators, bat_denominators;
+
+ struct adc_sample_data adc_data = {
+ .channel_id = adc_chan,
+ .channel_type = 0,
+ .hw_channel_delay = 0,
+ .scale = true,
+ .pbuf = &val[0],
+ .sample_num = MEASURE_TIMES,
+ .sample_bits = 1,
+ .sample_speed = 0,
+ .signal_mode = 0,
+ };
+
+ sci_adc_get_vol_ratio(ADC_CHANNEL_VBAT, 0, &bat_numerators,
+ &bat_denominators);
+
+ sci_adc_get_vol_ratio(adc_chan, true, &chan_numerators,
+ &chan_denominators);
+
+ ret = sci_adc_get_values(&adc_data);
+ if (0 != ret)
+ goto exit;
+
+ /*
+ for(i = 0; i < MEASURE_TIMES; i++) {
+ printk("%d\t", val[i]);
+ }
+ printk("\n");
+ */
+ sort(val, MEASURE_TIMES, sizeof(u32), cmp_val, 0);
+
+ adc_res = val[MEASURE_TIMES / 2];
+ /* info("adc chan %d, value %d\n", adc_chan, adc_res); */
+ adc_vol = sprd_get_adc_to_vol(adc_res) *
+ (bat_numerators * chan_denominators) /
+ (bat_denominators * chan_numerators);
+exit:
+ return adc_vol;
+}
+
+struct dcdc_cal_map {
+ const char *name; /* a-die DCDC or LDO name */
+ int def_on; /* 1: default ON, 0: default OFF */
+ u32 def_vol; /* default voltage (mV), could not read from a-die */
+ u32 cal_sel; /* only one ldo cal can be enable at the same time */
+ int adc_chan; /* multiplexed adc-channel id for LDOs */
+};
+
+struct dcdc_cal_map dcdc_cal_map[] = {
+ {"vddcore", 1, 1100, 0, ADC_CHANNEL_DCDCCORE},
+ {"vddarm", 1, 1200, 0, ADC_CHANNEL_DCDCARM},
+ {"vddmem", 1, 1200, 0, ADC_CHANNEL_DCDCMEM}, /*DDR2 */
+ {"vddmem1", 0, 1800, 0, ADC_CHANNEL_DCDCMEM}, /*DDR1 */
+ {"dcdcldo", 1, 2200, 0, ADC_CHANNEL_DCDCLDO},
+
+ {"vddrf", 1, 2850, BITS_LDO_RF_CAL_EN(-1), ADC_CHANNEL_LDO0},
+ {"avddbb", 1, 3000, BITS_LDO_ABB_CAL_EN(-1), ADC_CHANNEL_LDO0},
+ {"vddcama", 0, 2800, BITS_LDO_CAMA_CAL_EN(-1), ADC_CHANNEL_LDO0},
+
+ {"vdd3v", 0, 3000, BITS_LDO_VDD3V_CAL_EN(-1), ADC_CHANNEL_LDO1},
+ {"vdd28", 1, 2800, BITS_LDO_VDD28_CAL_EN(-1), ADC_CHANNEL_LDO1},
+ {"vddsim0", 0, 1800, BITS_LDO_VSIM_CAL_EN(-1), ADC_CHANNEL_LDO1},
+ {"vddsim1", 0, 1800, BITS_LDO_VSIM_CAL_EN(-1), ADC_CHANNEL_LDO1},
+ {"vddcammot", 0, 2800, BITS_LDO_CAMMOT_CAL_EN(-1), ADC_CHANNEL_LDO1},
+ {"vddsd0", 0, 2800, BITS_LDO_SD0_CAL_EN(-1), ADC_CHANNEL_LDO1},
+ {"vddusb", 0, 3300, BITS_LDO_USB_CAL_EN(-1), ADC_CHANNEL_LDO1},
+ {"vdd_a", 1, 1800, BITS_LDO_DVDD18_CAL_EN(-1), ADC_CHANNEL_LDO1}, /* alias dvdd18 */
+ {"vdd25", 1, 2500, BITS_LDO_VDD25_CAL_EN(-1), ADC_CHANNEL_LDO1},
+
+ {"vddcamio", 0, 1800, BITS_LDO_CAMIO_CAL_EN(-1), ADC_CHANNEL_LDO2},
+ {"vddcamcore", 0, 1500, BITS_LDO_CAMCORE_CAL_EN(-1), ADC_CHANNEL_LDO2},
+ {"vddcmmb1p2", 0, 1200, BITS_LDO_CMMB1P2_CAL_EN(-1), ADC_CHANNEL_LDO2},
+ {"vddcmmb1p8", 0, 1800, BITS_LDO_CMMB1P8_CAL_EN(-1), ADC_CHANNEL_LDO2},
+ {"vdd18", 1, 1800, BITS_LDO_VDD18_CAL_EN(-1), ADC_CHANNEL_LDO2},
+ {"vddsd1", 0, 1800, BITS_LDO_SD1_CAL_EN(-1), ADC_CHANNEL_LDO2},
+ {"vddsd3", 0, 1800, BITS_LDO_SD3_CAL_EN(-1), ADC_CHANNEL_LDO2},
+};
+
+int ldo_adc_get(const char *name)
+{
+ int i, adc_vol;
+ for (i = 0; i < ARRAY_SIZE(dcdc_cal_map); i++) {
+ if (0 == strcmp(name, dcdc_cal_map[i].name))
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(dcdc_cal_map))
+ return -EINVAL; /* not found */
+
+ /* enable ldo cal before adc sampling and ldo calibration */
+ if (0 != dcdc_cal_map[i].cal_sel) {
+ sci_adi_write(ANA_REG_GLB2_LDO_TRIM_SEL,
+ dcdc_cal_map[i].cal_sel, -1);
+ }
+
+ adc_vol = dcdc_adc_get(dcdc_cal_map[i].adc_chan);
+
+ /* close ldo cal */
+ if (0 != dcdc_cal_map[i].cal_sel) {
+ sci_adi_write(ANA_REG_GLB2_LDO_TRIM_SEL, 0, -1);
+ }
+
+ return adc_vol;
+}
+
+/* FIXME: sometime, adc vol is untrusted, not match the real voltage */
+static int __check_adc_validity_one(const char *name, int ctl_vol)
+{
+ int ret = 0;
+ int cal_vol, adc_vol = ldo_adc_get(name);
+ cal_vol = abs(adc_vol - ctl_vol);
+ info("check %s default %dmv, from %dmv, %c%d.%02d%%\n",
+ name, adc_vol, ctl_vol,
+ (adc_vol > ctl_vol) ? '+' : '-',
+ cal_vol * 100 / ctl_vol, cal_vol * 100 * 100 / ctl_vol % 100);
+ ret = cal_vol < ctl_vol / 50; /* margin 2% */
+ return ret;
+}
+
+static int __check_adc_validity(void)
+{
+ int ret = 0;
+ ret |= __check_adc_validity_one("vdd18", 1800);
+ ret |= __check_adc_validity_one("vdd25", 2500);
+ ret |= __check_adc_validity_one("vdd28", 2800);
+ ret |= __check_adc_validity_one("vdd_a", 1800);
+ ret |= __check_adc_validity_one("vddarm", 1200);
+ ret |= __check_adc_validity_one("vddcore", 1100);
+ return ret;
+}
+
+static int dcdc_calibrate(struct regulator *dcdc, int adc_chan,
+ const char *id, int def_vol, int to_vol, int is_cal)
+{
+ int ret = 0;
+ int acc_vol, adc_vol = 0, ctl_vol, cal_vol = 0;
+
+ if (0 != regulator_is_enabled(dcdc))
+ adc_vol = dcdc_adc_get(adc_chan);
+
+ cal_vol = abs(adc_vol - to_vol);
+
+ info("%s default %dmv, from %dmv to %dmv, %c%d.%02d%%\n", __FUNCTION__,
+ def_vol, adc_vol, to_vol,
+ (adc_vol > to_vol) ? '+' : '-',
+ cal_vol * 100 / to_vol, cal_vol * 100 * 100 / to_vol % 100);
+
+ if (!def_vol || !to_vol || !adc_vol)
+ goto exit;
+
+ if (cal_vol > to_vol / 10) /* adjust limit 10% */
+ goto exit;
+ else if (cal_vol < to_vol / 100 && !is_cal) { /* margin 1% */
+ info("%s %s is ok\n", __FUNCTION__, id);
+ return 0;
+ }
+
+ acc_vol = regulator_get_trimming_step(dcdc, to_vol);
+
+ /* always set valid vol ctrl bits */
+ ctl_vol = DIV_ROUND_UP(def_vol * to_vol, adc_vol) + acc_vol;
+ ret = regulator_set_trimming(dcdc, ctl_vol * 1000, to_vol * 1000);
+ if (IS_ERR_VALUE(ret))
+ goto exit;
+
+ WARN(!is_cal,
+ "warning: regulator (%s) voltage ctrl %dmv\n", id, ctl_vol);
+ return ctl_vol;
+
+exit:
+ info("%s %s failure\n", __FUNCTION__, id);
+ return -1;
+}
+
+int sci_dcdc_calibrate(const char *name, int def_vol, int to_vol)
+{
+ int i, ret, adc_chan, ctl_vol;
+ struct regulator *dcdc;
+ static int is_ddr2 = 0;
+ if (bat_denominators == 0) {
+ u32 chan_numerators, chan_denominators;
+ sci_adc_get_vol_ratio(ADC_CHANNEL_VBAT, 0, &bat_numerators,
+ &bat_denominators);
+ sci_adc_get_vol_ratio(ADC_CHANNEL_DCDCCORE, true,
+ &chan_numerators, &chan_denominators);
+ is_ddr2 = 0 == (sci_adi_read(ANA_REG_GLB_ANA_STATUS) & BIT(6));
+ debug
+ ("vbat cal type %d, chan scale ratio %u/%u, and dcdc %u/%u, %s\n",
+ sprd_get_adc_cal_type(), bat_numerators, bat_denominators,
+ chan_numerators, chan_denominators,
+ is_ddr2 ? "ddr2" : "ddr");
+
+ __check_adc_validity();
+ }
+
+ if (!is_ddr2 && 0 == strcmp(name, "vddmem"))
+ name = "vddmem1";
+
+ for (i = 0; i < ARRAY_SIZE(dcdc_cal_map); i++) {
+ if (0 == strcmp(name, dcdc_cal_map[i].name))
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(dcdc_cal_map))
+ return -EINVAL; /* not found */
+
+ dcdc = regulator_get(0, name);
+ if (IS_ERR(dcdc))
+ goto exit;
+
+ if (reguator_is_trimming(regulator_get_drvdata(dcdc)))
+ goto exit; /* already trimming, do nothing */
+
+ adc_chan = dcdc_cal_map[i].adc_chan;
+ ctl_vol = regulator_get_voltage(dcdc);
+ if (IS_ERR_VALUE(ctl_vol)) {
+ debug0("no valid %s vol ctrl bits\n", name);
+ } else /* dcdc maybe had been adjusted in uboot-spl */
+ ctl_vol /= 1000;
+
+ if (!def_vol)
+ def_vol =
+ (IS_ERR_VALUE(ctl_vol)) ? dcdc_cal_map[i].def_vol : ctl_vol;
+
+ if (!to_vol)
+ to_vol =
+ (IS_ERR_VALUE(ctl_vol)) ? dcdc_cal_map[i].def_vol : ctl_vol;
+
+ /* enable ldo cal before adc sampling and ldo calibration */
+ if (0 != dcdc_cal_map[i].cal_sel) {
+ sci_adi_write(ANA_REG_GLB2_LDO_TRIM_SEL,
+ dcdc_cal_map[i].cal_sel, -1);
+ }
+
+ ret = dcdc_calibrate(dcdc, adc_chan, name, def_vol, to_vol, 1);
+ debug0("dcdc_calibrate return %d\n", ret);
+ if (ret >= 0) {
+ msleep(10); /* wait a moment before cal verify */
+ ret = dcdc_calibrate(dcdc, adc_chan, name,
+ (0 == ret) ? def_vol : ret, to_vol, 0);
+ if (0 == ret) { /* cal is ok, do not cal any more */
+ dcdc_cal_map[i].def_on = 0;
+ }
+ }
+
+ /* close ldo cal */
+ if (0 != dcdc_cal_map[i].cal_sel) {
+ sci_adi_write(ANA_REG_GLB2_LDO_TRIM_SEL, 0, -1);
+ }
+
+exit:
+ regulator_put(dcdc);
+ return 0;
+}
+
+int mpll_calibrate(int cpu_freq)
+{
+ u32 val = 0;
+ unsigned long flags;
+ //BUG_ON(cpu_freq != 1200); /* only upgrade 1.2G */
+ cpu_freq /= 4;
+ local_irq_save(flags);
+ val = sci_glb_raw_read(REG_GLB_M_PLL_CTL0);
+ if ((val & MASK_MPLL_N) == cpu_freq)
+ goto exit;
+ val = (val & ~MASK_MPLL_N) | cpu_freq;
+ sci_glb_set(REG_GLB_GEN1, BIT_MPLL_CTL_WE); /* mpll unlock */
+ sci_glb_write(REG_GLB_M_PLL_CTL0, val, MASK_MPLL_N);
+ sci_glb_clr(REG_GLB_GEN1, BIT_MPLL_CTL_WE);
+exit:
+ local_irq_restore(flags);
+ debug("%s 0x%08x\n", __FUNCTION__, val);
+ return 0;
+}
+
+struct dcdc_delayed_work {
+ struct delayed_work work;
+ u32 uptime;
+ int cal_typ;
+};
+
+static struct dcdc_delayed_work dcdc_work = {
+ .work.work.func = NULL,
+ .uptime = 0,
+ .cal_typ = -1, /* Invalid */
+};
+
+static u32 sci_syst_read(void)
+{
+ u32 t = __raw_readl(REG_SYST_VALUE);
+ while (t != __raw_readl(REG_SYST_VALUE))
+ t = __raw_readl(REG_SYST_VALUE);
+ return t;
+}
+
+static void do_dcdc_work(struct work_struct *work)
+{
+ int i, cnt = CALIBRATE_TO;
+
+ /* debug("%s %d\n", __FUNCTION__, sprd_get_adc_cal_type()); */
+ if (dcdc_work.cal_typ == sprd_get_adc_cal_type())
+ goto exit; /* no change, set next delayed work */
+
+ dcdc_work.cal_typ = sprd_get_adc_cal_type();
+ debug0("%s %d %d\n", __FUNCTION__, dcdc_work.cal_typ, cnt);
+
+ /* four DCDCs and all LDOs Trimming if default ON */
+ for (i = 0; i < ARRAY_SIZE(dcdc_cal_map); i++) {
+ if (dcdc_cal_map[i].def_on)
+ sci_dcdc_calibrate(dcdc_cal_map[i].name, 0, 0);
+ }
+
+exit:
+ if (sci_syst_read() - dcdc_work.uptime < CALIBRATE_TO * 1000) {
+ schedule_delayed_work(&dcdc_work.work, msecs_to_jiffies(1000));
+ } else {
+ debug0("%s end.\n", __FUNCTION__);
+ }
+
+ return;
+}
+
+void dcdc_calibrate_callback(void *data)
+{
+ if (!dcdc_work.work.work.func) {
+ INIT_DELAYED_WORK(&dcdc_work.work, do_dcdc_work);
+ dcdc_work.uptime = sci_syst_read();
+ }
+
+ dcdc_work.cal_typ = (int)data;
+ schedule_delayed_work(&dcdc_work.work, msecs_to_jiffies(1000));
+}
+
+int ldo_trimming_callback(void *data)
+{
+ int i, ret = 0;
+ const char *name = data;
+ for (i = 0; i < ARRAY_SIZE(dcdc_cal_map); i++) {
+ if (0 == strcmp(name, dcdc_cal_map[i].name))
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(dcdc_cal_map))
+ return -EINVAL; /* not found */
+
+ if (!dcdc_cal_map[i].def_on) {
+ dcdc_cal_map[i].def_on = 1;
+ debug0("%s trimming ...\n", name);
+ }
+
+ if (dcdc_work.cal_typ >= 0)
+ dcdc_calibrate_callback(0);
+ return ret;
+}
+
+static int __init dcdc_init(void)
+{
+ if (!in_calibration()) /* bypass if in CFT */
+ dcdc_calibrate_callback(0);
+ return 0;
+}
+
+late_initcall(dcdc_init);
diff --git a/drivers/platform/sprd/dcdc_debug.c b/drivers/platform/sprd/dcdc_debug.c
new file mode 100644
index 00000000..4f0d492d
--- /dev/null
+++ b/drivers/platform/sprd/dcdc_debug.c
@@ -0,0 +1,126 @@
+#include <linux/bug.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/debugfs.h>
+#include <mach/hardware.h>
+#include <mach/regs_glb.h>
+#include <mach/regs_ana_glb.h>
+#include <mach/adi.h>
+#include <mach/adc.h>
+
+int dcdc_adc_get(int adc_chan);
+
+int mpll_calibrate(int cpu_freq);
+int sci_dcdc_calibrate(const char *name, int def_vol, int to_vol);
+
+static u32 dcdc_to_vol = 0, dcdcarm_to_vol = 0;
+static u32 dcdcldo_to_vol = 0, dcdcmem_to_vol = 0;
+
+static int debugfs_dcdc_get(void *data, u64 * val)
+{
+ *(u32 *) data = *val = dcdc_adc_get(ADC_CHANNEL_DCDCCORE);
+ return 0;
+}
+
+static int debugfs_dcdcarm_get(void *data, u64 * val)
+{
+ *(u32 *) data = *val = dcdc_adc_get(ADC_CHANNEL_DCDCARM);
+ return 0;
+}
+
+static int debugfs_dcdcldo_get(void *data, u64 * val)
+{
+ *(u32 *) data = *val = dcdc_adc_get(ADC_CHANNEL_DCDCLDO);
+ return 0;
+}
+
+static int debugfs_dcdcmem_get(void *data, u64 * val)
+{
+ *(u32 *) data = *val = dcdc_adc_get(ADC_CHANNEL_DCDCMEM);
+ return 0;
+}
+
+static int debugfs_dcdc_set(void *data, u64 val)
+{
+ int to_vol = *(u32 *) data = val;
+ sci_dcdc_calibrate("vddcore", 0, to_vol);
+ return 0;
+}
+
+static int debugfs_dcdcarm_set(void *data, u64 val)
+{
+ int to_vol = *(u32 *) data = val;
+ sci_dcdc_calibrate("vddarm", 0, to_vol);
+ return 0;
+}
+
+static int debugfs_dcdcldo_set(void *data, u64 val)
+{
+ int to_vol = *(u32 *) data = val;
+ sci_dcdc_calibrate("dcdcldo", 0, to_vol);
+ return 0;
+}
+
+static int debugfs_dcdcmem_set(void *data, u64 val)
+{
+ int to_vol = *(u32 *) data = val;
+ sci_dcdc_calibrate("vddmem", 0, to_vol);
+ return 0;
+}
+
+static u32 mpll_freq = 0;
+static int debugfs_mpll_get(void *data, u64 * val)
+{
+ if (0 == mpll_freq) {
+ *(u32 *) data = (__raw_readl(REG_GLB_M_PLL_CTL0) & MASK_MPLL_N) * 4; /* default refin */
+ }
+ *val = *(u32 *) data;
+ return 0;
+}
+
+static int debugfs_mpll_set(void *data, u64 val)
+{
+ *(u32 *) data = val;
+ mpll_calibrate(mpll_freq);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fops_dcdc,
+ debugfs_dcdc_get, debugfs_dcdc_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_dcdcarm,
+ debugfs_dcdcarm_get, debugfs_dcdcarm_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_dcdcldo,
+ debugfs_dcdcldo_get, debugfs_dcdcldo_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_dcdcmem,
+ debugfs_dcdcmem_get, debugfs_dcdcmem_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_mpll,
+ debugfs_mpll_get, debugfs_mpll_set, "%llu\n");
+
+static int __init dcdc_debugfs_init(void)
+{
+ static struct dentry *debug_root = NULL;
+ debug_root = debugfs_create_dir("vol", NULL);
+ if (IS_ERR_OR_NULL(debug_root)) {
+ printk("%s return %p\n", __FUNCTION__, debug_root);
+ return PTR_ERR(debug_root);
+ }
+ debugfs_create_file("dcdc", S_IRUGO | S_IWUGO,
+ debug_root, &dcdc_to_vol, &fops_dcdc);
+ debugfs_create_file("dcdcarm", S_IRUGO | S_IWUGO,
+ debug_root, &dcdcarm_to_vol, &fops_dcdcarm);
+ debugfs_create_file("dcdcldo", S_IRUGO | S_IWUGO,
+ debug_root, &dcdcldo_to_vol, &fops_dcdcldo);
+ debugfs_create_file("dcdcmem", S_IRUGO | S_IWUGO,
+ debug_root, &dcdcmem_to_vol, &fops_dcdcmem);
+ debugfs_create_file("mpll", S_IRUGO | S_IWUGO,
+ debug_root, &mpll_freq, &fops_mpll);
+ return 0;
+}
+
+module_init(dcdc_debugfs_init);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Robot <zhulin.lian@spreadtrum.com>");
+MODULE_DESCRIPTION("dcdc debugfs");
diff --git a/drivers/platform/sprd/devices.h b/drivers/platform/sprd/devices.h
new file mode 100644
index 00000000..ff80c401
--- /dev/null
+++ b/drivers/platform/sprd/devices.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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.
+ */
+
+#ifndef __ARCH_ARM_MACH_SPRD_DEVICES_H
+#define __ARCH_ARM_MACH_SPRD_DEVICES_H
+
+extern struct platform_device sprd_hwspinlock_device0;
+extern struct platform_device sprd_hwspinlock_device1;
+extern struct platform_device sprd_serial_device0;
+extern struct platform_device sprd_serial_device1;
+extern struct platform_device sprd_serial_device2;
+extern struct platform_device sprd_serial_device3;
+extern struct platform_device sprd_device_rtc;
+extern struct platform_device sprd_eic_gpio_device;
+extern struct platform_device sprd_nand_device;
+extern struct platform_device sprd_lcd_device0;
+extern struct platform_device sprd_lcd_device1;
+#ifdef CONFIG_PSTORE_RAM
+extern struct platform_device sprd_ramoops_device;
+#endif
+extern struct platform_device sprd_otg_device;
+extern struct platform_device sprd_backlight_device;
+extern struct platform_device sprd_i2c_device0;
+extern struct platform_device sprd_i2c_device1;
+extern struct platform_device sprd_i2c_device2;
+extern struct platform_device sprd_i2c_device3;
+#if defined(CONFIG_PIN_POWER_DOMAIN_SWITCH)
+extern struct platform_device sprd_pin_switch_device;
+#endif
+extern struct platform_device sprd_spi0_device;
+extern struct platform_device sprd_spi1_device;
+extern struct platform_device sprd_spi2_device;
+extern struct platform_device sprd_keypad_device;
+extern struct platform_device sprd_thm_device;
+extern struct platform_device sprd_thm_a_device;
+extern struct platform_device sprd_battery_device;
+extern struct platform_device sprd_headset_device;
+
+extern struct platform_device sprd_battery_device;
+extern struct platform_device sprd_vsp_device;
+extern struct platform_device sprd_jpg_device;
+#ifdef CONFIG_ION
+extern struct platform_device sprd_ion_dev;
+#endif
+extern struct platform_device sprd_iommu_gsp_device;
+extern struct platform_device sprd_iommu_mm_device;
+#ifdef CONFIG_MUX_SDIO_OPT1_HAL
+extern struct platform_device ipc_sdio_device;
+#endif
+extern struct platform_device sprd_sdio0_device;
+extern struct platform_device sprd_sdio1_device;
+extern struct platform_device sprd_sdio2_device;
+extern struct platform_device sprd_emmc_device;
+extern struct platform_device sprd_dcam_device;
+extern struct platform_device sprd_scale_device;
+extern struct platform_device sprd_gsp_device;
+extern struct platform_device sprd_rotation_device;
+extern struct platform_device sprd_sensor_device;
+extern struct platform_device sprd_isp_device;
+extern struct platform_device sprd_dma_copy_device;
+extern struct platform_device sprd_ahb_bm0_device;
+extern struct platform_device sprd_ahb_bm1_device;
+extern struct platform_device sprd_ahb_bm2_device;
+extern struct platform_device sprd_ahb_bm3_device;
+extern struct platform_device sprd_ahb_bm4_device;
+extern struct platform_device sprd_axi_bm0_device;
+extern struct platform_device sprd_axi_bm1_device;
+extern struct platform_device sprd_axi_bm2_device;
+#ifdef CONFIG_SIPC_TD
+extern struct platform_device sprd_spipe_td_device;
+extern struct platform_device sprd_slog_td_device;
+extern struct platform_device sprd_stty_td_device;
+extern struct platform_device sprd_spool_td_device;
+extern struct platform_device sprd_cproc_td_device;
+extern struct platform_device sprd_seth0_td_device;
+extern struct platform_device sprd_seth1_td_device;
+extern struct platform_device sprd_seth2_td_device;
+extern struct platform_device sprd_saudio_td_device;
+#endif
+#ifdef CONFIG_SIPC_WCDMA
+extern struct platform_device sprd_spipe_wcdma_device;
+extern struct platform_device sprd_slog_wcdma_device;
+extern struct platform_device sprd_stty_wcdma_device;
+extern struct platform_device sprd_spool_wcdma_device;
+extern struct platform_device sprd_cproc_wcdma_device;
+extern struct platform_device sprd_seth0_wcdma_device;
+extern struct platform_device sprd_seth1_wcdma_device;
+extern struct platform_device sprd_seth2_wcdma_device;
+extern struct platform_device sprd_saudio_wcdma_device;
+#endif
+#ifdef CONFIG_SIPC_WCN
+extern struct platform_device sprd_spipe_wcn_device;
+extern struct platform_device sprd_slog_wcn_device;
+extern struct platform_device sprd_cproc_wcn_device;
+extern struct platform_device sprd_sttybt_td_device;
+#endif
+#ifdef CONFIG_SIPC_GGE
+extern struct platform_device sprd_cproc_cp0_device;
+extern struct platform_device sprd_spipe_gge_device;
+extern struct platform_device sprd_slog_gge_device;
+extern struct platform_device sprd_stty_gge_device;
+extern struct platform_device sprd_seth0_gge_device;
+extern struct platform_device sprd_seth1_gge_device;
+extern struct platform_device sprd_seth2_gge_device;
+#endif
+#ifdef CONFIG_SIPC_LTE
+extern struct platform_device sprd_cproc_cp1_device;
+extern struct platform_device sprd_spipe_lte_device;
+extern struct platform_device sprd_slog_lte_device;
+extern struct platform_device sprd_stty_lte_device;
+extern struct platform_device sprd_seth0_lte_device;
+extern struct platform_device sprd_seth1_lte_device;
+extern struct platform_device sprd_seth2_lte_device;
+#endif
+
+extern struct platform_device sprd_saudio_voip_device;
+extern struct platform_device sprd_a7_pmu_device;
+extern struct platform_device sprd_memnand_system_device;
+extern struct platform_device sprd_memnand_userdata_device;
+extern struct platform_device sprd_memnand_cache_device;
+
+extern struct platform_device sprd_audio_vbc_r2p0_sprd_codec_v3_device;
+extern struct platform_device sprd_audio_i2s_null_codec_device;
+extern struct platform_device sprd_audio_platform_pcm_device;
+extern struct platform_device sprd_audio_vaudio_device;
+extern struct platform_device sprd_audio_vbc_r2p0_device;
+extern struct platform_device sprd_audio_i2s0_device;
+extern struct platform_device sprd_audio_i2s1_device;
+extern struct platform_device sprd_audio_i2s2_device;
+extern struct platform_device sprd_audio_i2s3_device;
+extern struct platform_device sprd_audio_sprd_codec_v3_device;
+extern struct platform_device sprd_audio_null_codec_device;
+
+#ifdef CONFIG_SPRD_VETH
+#ifdef CONFIG_MUX_SPI_HAL
+extern struct platform_device sprd_veth_spi0_device;
+extern struct platform_device sprd_veth_spi1_device;
+extern struct platform_device sprd_veth_spi2_device;
+extern struct platform_device sprd_veth_spi3_device;
+extern struct platform_device sprd_veth_spi4_device;
+#endif
+#ifdef CONFIG_MUX_SDIO_OPT1_HAL
+extern struct platform_device sprd_veth_sdio0_device;
+extern struct platform_device sprd_veth_sdio1_device;
+extern struct platform_device sprd_veth_sdio2_device;
+extern struct platform_device sprd_veth_sdio3_device;
+extern struct platform_device sprd_veth_sdio4_device;
+#endif
+#ifdef CONFIG_MODEM_INTF
+extern struct platform_device modem_interface_device;
+#endif
+#endif
+
+extern struct platform_device modem_interface_device;
+#endif
+#ifdef CONFIG_TS0710_MUX_ENABLE
+extern struct platform_device sprd_mux_spi_device;
+extern struct platform_device sprd_mux_sdio_device;
+#endif
diff --git a/drivers/platform/sprd/dhd_adapter.c b/drivers/platform/sprd/dhd_adapter.c
new file mode 100644
index 00000000..aa5637c0
--- /dev/null
+++ b/drivers/platform/sprd/dhd_adapter.c
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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/mach-types.h>
+#include <asm/gpio.h>
+#include <asm/io.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/wlan_plat.h>
+#include <soc/sprd/board.h>
+#include <soc/sprd/hardware.h>
+#include <linux/regulator/consumer.h>
+#include <soc/sprd/regulator.h>
+#include <linux/export.h>
+#include <linux/clk.h>
+#include <linux/fs.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdhci.h>
+#include <soc/sprd/pinmap.h>
+
+#define GET_WIFI_MAC_ADDR_FROM_NV_ITEM 1
+
+#define WLAN_STATIC_SCAN_BUF0 5
+#define WLAN_STATIC_SCAN_BUF1 6
+#define WLAN_STATIC_DHD_INFO_BUF 7
+#define WLAN_SCAN_BUF_SIZE (64 * 1024)
+#define WLAN_DHD_INFO_BUF_SIZE (16 * 1024)
+#define PREALLOC_WLAN_SEC_NUM 4
+#define PREALLOC_WLAN_NUMBER_OF_BUFFERS 160
+#define PREALLOC_WLAN_SECTION_HEADER 24
+
+#define WLAN_SECTION_SIZE_0 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 128)
+#define WLAN_SECTION_SIZE_1 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 128)
+#define WLAN_SECTION_SIZE_2 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 512)
+#define WLAN_SECTION_SIZE_3 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 1024)
+
+#define DHD_SKB_HDRSIZE 336
+#define DHD_SKB_1PAGE_BUFSIZE ((PAGE_SIZE*1)-DHD_SKB_HDRSIZE)
+#define DHD_SKB_2PAGE_BUFSIZE ((PAGE_SIZE*2)-DHD_SKB_HDRSIZE)
+#define DHD_SKB_4PAGE_BUFSIZE ((PAGE_SIZE*4)-DHD_SKB_HDRSIZE)
+
+#define WLAN_SKB_BUF_NUM 17
+#define IFHWADDRLEN 6
+
+#define CUSTOMER_MAC_FILE "/data/misc/wifi/wifimac.txt"
+
+extern void * dhd_os_open_image(char *filename);
+extern int dhd_os_get_image_block(char *buf, int len, void *image);
+extern void dhd_os_close_image(void *image);
+extern int sdhci_wifi_detect_isbusy(void);
+
+static struct mmc_host * wlan_mmc =NULL;
+static int wlan_device_cd = 0; /* WIFI virtual 'card detect' status */
+static int wlan_device_power_state;
+static int wlan_device_reset_state;
+static void (*wifi_status_cb)(int card_present, void *dev_id);
+static void *wifi_status_cb_devid;
+static struct regulator *wlan_regulator_18 = NULL;
+
+int wlan_device_power(int on);
+int wlan_device_reset(int on);
+int wlan_device_set_carddetect(int on);
+
+static struct sk_buff *wlan_static_skb[WLAN_SKB_BUF_NUM];
+
+typedef struct wifi_mem_prealloc_struct {
+ void *mem_ptr;
+ unsigned long size;
+} wifi_mem_prealloc_t;
+
+static wifi_mem_prealloc_t wlan_mem_array[PREALLOC_WLAN_SEC_NUM] = {
+ { NULL, (WLAN_SECTION_SIZE_0 + PREALLOC_WLAN_SECTION_HEADER) },
+ { NULL, (WLAN_SECTION_SIZE_1 + PREALLOC_WLAN_SECTION_HEADER) },
+ { NULL, (WLAN_SECTION_SIZE_2 + PREALLOC_WLAN_SECTION_HEADER) },
+ { NULL, (WLAN_SECTION_SIZE_3 + PREALLOC_WLAN_SECTION_HEADER) }
+};
+
+void *wlan_static_scan_buf0;
+void *wlan_static_scan_buf1;
+void *wlan_static_dhd_info_buf;
+
+static void * open_image(char *filename)
+{
+ struct file *fp;
+
+ fp = filp_open(filename, O_RDONLY, 0);
+ /*
+ * 2.6.11 (FC4) supports filp_open() but later revs don't?
+ * Alternative:
+ * fp = open_namei(AT_FDCWD, filename, O_RD, 0);
+ * ???
+ */
+ if (IS_ERR(fp))
+ fp = NULL;
+
+ return fp;
+}
+
+static int get_image_block(char *buf, int len, void *image)
+{
+ struct file *fp = (struct file *)image;
+ int rdlen;
+
+ if (!image)
+ return 0;
+
+ rdlen = kernel_read(fp, fp->f_pos, buf, len);
+ if (rdlen > 0)
+ fp->f_pos += rdlen;
+
+ return rdlen;
+}
+
+static void close_image(void *image)
+{
+ if (image)
+ filp_close((struct file *)image, NULL);
+}
+
+static void *wlan_device_mem_prealloc(int section, unsigned long size)
+{
+ if (section == PREALLOC_WLAN_SEC_NUM)
+ return wlan_static_skb;
+
+ if (section == WLAN_STATIC_SCAN_BUF0)
+ return wlan_static_scan_buf0;
+
+ if (section == WLAN_STATIC_SCAN_BUF1)
+ return wlan_static_scan_buf1;
+
+ if (section == WLAN_STATIC_DHD_INFO_BUF) {
+ if (size > WLAN_DHD_INFO_BUF_SIZE) {
+ pr_err("request DHD_INFO size(%lu) is bigger than static size(%d).\n", size, WLAN_DHD_INFO_BUF_SIZE);
+ return NULL;
+ }
+ return wlan_static_dhd_info_buf;
+ }
+
+ if ((section < 0) || (section > PREALLOC_WLAN_SEC_NUM))
+ return NULL;
+
+ if (wlan_mem_array[section].size < size)
+ return NULL;
+
+ return wlan_mem_array[section].mem_ptr;
+}
+
+int __init init_wifi_mem(void)
+{
+ int i;
+ int j;
+
+ for (i = 0; i < 8; i++) {
+ wlan_static_skb[i] = dev_alloc_skb(DHD_SKB_1PAGE_BUFSIZE);
+ if (!wlan_static_skb[i])
+ goto err_skb_alloc;
+ }
+
+ for (; i < 16; i++) {
+ wlan_static_skb[i] = dev_alloc_skb(DHD_SKB_2PAGE_BUFSIZE);
+ if (!wlan_static_skb[i])
+ goto err_skb_alloc;
+ }
+
+ wlan_static_skb[i] = dev_alloc_skb(DHD_SKB_4PAGE_BUFSIZE);
+ if (!wlan_static_skb[i])
+ goto err_skb_alloc;
+
+ for (i = 0 ; i < PREALLOC_WLAN_SEC_NUM ; i++) {
+ wlan_mem_array[i].mem_ptr =
+ kmalloc(wlan_mem_array[i].size, GFP_KERNEL);
+
+ if (!wlan_mem_array[i].mem_ptr)
+ goto err_mem_alloc;
+ }
+
+ wlan_static_scan_buf0 = kmalloc(WLAN_SCAN_BUF_SIZE, GFP_KERNEL);
+ if (!wlan_static_scan_buf0)
+ goto err_mem_alloc;
+
+ wlan_static_scan_buf1 = kmalloc(WLAN_SCAN_BUF_SIZE, GFP_KERNEL);
+ if (!wlan_static_scan_buf1)
+ goto err_mem_alloc;
+
+ wlan_static_dhd_info_buf = kmalloc(WLAN_DHD_INFO_BUF_SIZE, GFP_KERNEL);
+ if (!wlan_static_dhd_info_buf)
+ goto err_mem_alloc;
+
+ printk(KERN_INFO"%s: WIFI MEM Allocated\n", __func__);
+ return 0;
+
+ err_mem_alloc:
+ pr_err("Failed to mem_alloc for WLAN\n");
+ for (j = 0 ; j < i ; j++)
+ kfree(wlan_mem_array[j].mem_ptr);
+
+ i = WLAN_SKB_BUF_NUM;
+
+ err_skb_alloc:
+ pr_err("Failed to skb_alloc for WLAN\n");
+ for (j = 0 ; j < i ; j++)
+ dev_kfree_skb(wlan_static_skb[j]);
+
+ return -ENOMEM;
+}
+
+/* Control the BT_VDDIO and WLAN_VDDIO
+Always power on According to spec
+*/
+static int wlan_ldo_enable(void)
+{
+ int err;
+
+#ifdef CONFIG_ARCH_SCX35
+ /*temp config for clk_aux0, waiting for SC8830 pin config*/
+// pinmap_set(0x400, 0x0101);
+
+ wlan_regulator_18 = regulator_get(NULL, "vdd18");
+#else
+ wlan_regulator_18 = regulator_get(NULL, "vddsd1");
+#endif
+
+ if (IS_ERR(wlan_regulator_18)) {
+ pr_err("can't get wlan 1.8V regulator\n");
+ return -1;
+ }
+
+ err = regulator_set_voltage(wlan_regulator_18,1800000,1800000);
+ if (err){
+ pr_err("can't set wlan to 1.8V.\n");
+ return -1;
+ }
+
+ regulator_set_mode(wlan_regulator_18, REGULATOR_MODE_STANDBY);
+ regulator_enable(wlan_regulator_18);
+ return 0;
+}
+
+static void wlan_clk_init(void)
+{
+ struct clk *wlan_clk;
+ struct clk *clk_parent;
+/*8825ea/openphone use aux0 garda use aux1*/
+#ifdef CONFIG_MACH_GARDA
+ wlan_clk = clk_get(NULL, "clk_aux1");
+ if (IS_ERR(wlan_clk)) {
+ printk("clock: failed to get clk_aux1\n");
+ }
+#else
+ wlan_clk = clk_get(NULL, "clk_aux0");
+ if (IS_ERR(wlan_clk)) {
+ printk("clock: failed to get clk_aux0\n");
+ }
+#endif
+ clk_parent = clk_get(NULL, "ext_32k");
+ if (IS_ERR(clk_parent)) {
+ printk("failed to get parent ext_32k\n");
+ }
+
+ clk_set_parent(wlan_clk, clk_parent);
+ clk_set_rate(wlan_clk, 32000);
+ clk_prepare_enable(wlan_clk);
+}
+
+int wlan_device_power(int on)
+{
+ int i;
+ pr_info("%s:%d \n", __func__, on);
+
+ if(on) {
+ #if 0
+ for (i = 0; i <= 200; i++) {
+ //if(!sdhci_wifi_detect_isbusy())
+ break;
+ msleep(100);
+ }
+ #else
+// if(wlan_mmc)
+// flush_delayed_work(&wlan_mmc->detect);
+ #endif
+ printk("%s after delay %d times (100ms)\n", __func__, i);
+ /* enable SDIO clock */
+ #ifdef CONFIG_WLAN_SDIO
+ //sdhci_device_attach(1);
+ #endif
+ gpio_direction_output(GPIO_WIFI_SHUTDOWN, 0);
+ mdelay(10);
+ gpio_direction_output(GPIO_WIFI_SHUTDOWN, 1);
+// mdelay(200);
+ }
+ else {
+ /* disale SDIO clock */
+ #ifdef CONFIG_WLAN_SDIO
+ //sdhci_device_attach(0);
+ #endif
+ gpio_direction_output(GPIO_WIFI_SHUTDOWN, 0);
+
+ }
+ wlan_device_power_state = on;
+ return 0;
+}
+
+int wlan_device_reset(int on)
+{
+ pr_info("%s: do nothing\n", __func__);
+ wlan_device_reset_state = on;
+ return 0;
+}
+
+
+int wlan_device_status_register(
+ void (*callback)(int card_present, void *dev_id),
+ void *dev_id)
+{
+ if (wifi_status_cb)
+ return -EAGAIN;
+ wifi_status_cb = callback;
+ wifi_status_cb_devid = dev_id;
+ return 0;
+}
+
+EXPORT_SYMBOL(wlan_device_status_register);
+
+static __used unsigned int wlan_device_status(struct device *dev)
+{
+ return wlan_device_cd;
+}
+
+int wlan_device_set_carddetect(int val)
+{
+ pr_info("%s: %d\n", __func__, val);
+ mdelay(100);
+#if 0
+ wlan_device_cd = val;
+ if (wifi_status_cb) {
+ wifi_status_cb(val, wifi_status_cb_devid);
+ } else
+ pr_warning("%s: Nobody to notify\n", __func__);
+#endif
+
+#ifdef CONFIG_WLAN_SDIO
+// sdhci_bus_scan();
+ if(wlan_mmc) {
+ mmc_detect_change(wlan_mmc, 0);
+ } else {
+ pr_info("%s wlan_mmc is null,carddetect failed \n ",__func__);
+ }
+#endif
+ return 0;
+}
+
+
+
+#ifdef GET_WIFI_MAC_ADDR_FROM_NV_ITEM
+static unsigned char wlan_mac_addr[IFHWADDRLEN] = { 0x11,0x22,0x33,0x44,0x55,0x66 };
+
+static unsigned char char2bin( char m)
+{
+ if (( m >= 'a') && (m <= 'f')) {
+ return m - 'a' + 10;
+ }
+ if (( m >= 'A') && (m <= 'F')) {
+ return m - 'A' + 10;
+ }
+ if (( m >= '0') && (m <= '9')) {
+ return m - '0';
+ }
+ return 0;
+}
+
+static int wlan_device_get_mac_addr(unsigned char *buf)
+{
+
+ char macaddr[20];
+ int mac_len = 0;
+ void *fp;
+
+ if (!buf){
+ pr_err("%s, null parameter !!\n", __func__);
+ return -EFAULT;
+ }
+ fp = open_image(CUSTOMER_MAC_FILE);
+ if (fp == NULL)
+ return -EFAULT;
+ mac_len = get_image_block(macaddr, 17, fp);
+ if (mac_len < 17)
+ return -EFAULT;
+
+
+ wlan_mac_addr[0] = (unsigned char)((char2bin(macaddr[0]) << 4) | char2bin(macaddr[1]));
+ wlan_mac_addr[1] = (unsigned char)((char2bin(macaddr[3]) << 4) | char2bin(macaddr[4]));
+ wlan_mac_addr[2] = (unsigned char)((char2bin(macaddr[6]) << 4) | char2bin(macaddr[7]));
+ wlan_mac_addr[3] = (unsigned char)((char2bin(macaddr[9]) << 4) | char2bin(macaddr[10]));
+ wlan_mac_addr[4] = (unsigned char)((char2bin(macaddr[12]) << 4) | char2bin(macaddr[13]));
+ wlan_mac_addr[5] = (unsigned char)((char2bin(macaddr[15]) << 4) | char2bin(macaddr[16]));
+
+ memcpy(buf, wlan_mac_addr, IFHWADDRLEN);
+ pr_info("wifi mac: %x:%x:%x:%x:%x:%x\n", wlan_mac_addr[0], wlan_mac_addr[1], wlan_mac_addr[2], wlan_mac_addr[3], wlan_mac_addr[4], wlan_mac_addr[5]);
+
+ close_image(fp);
+
+ return 0;
+
+}
+#endif
+
+static struct wifi_platform_data wlan_device_control = {
+ .set_power = wlan_device_power,
+ .set_reset = wlan_device_reset,
+ .set_carddetect = wlan_device_set_carddetect,
+ .mem_prealloc = wlan_device_mem_prealloc,
+#ifdef GET_WIFI_MAC_ADDR_FROM_NV_ITEM
+ .get_mac_addr = wlan_device_get_mac_addr,
+#endif
+};
+
+static struct resource wlan_resources[] = {
+#ifdef CONFIG_WLAN_SDIO
+ [0] = {
+ .start = SPRD_SDIO1_PHYS,
+ .end = SPRD_SDIO1_PHYS + SZ_4K - 1,
+ .flags = IORESOURCE_MEM,
+ },
+#else
+ [0] = {
+ .start = SPRD_SPI0_PHYS,
+ .end = SPRD_SPI0_PHYS + SZ_4K - 1,
+ .flags = IORESOURCE_MEM,
+ },
+#endif
+ [1] = {
+ .name = "bcmdhd_wlan_irq",
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE,
+ },
+};
+
+static struct platform_device sprd_wlan_device = {
+ .name = "bcmdhd_wlan",
+ .id = 1,
+ .dev = {
+ .platform_data = &wlan_device_control,
+ },
+ .resource = wlan_resources,
+ .num_resources = ARRAY_SIZE(wlan_resources),
+};
+
+void wlan_host_get(void) {
+ struct sdhci_host *host;
+#ifndef CONFIG_OF
+ extern struct platform_device sprd_sdio1_device;
+ host = platform_get_drvdata(&sprd_sdio1_device);
+#else
+ struct platform_device * sdio1_device = to_platform_device(bus_find_device_by_name(&platform_bus_type, NULL, "sdio_wifi"));
+ host = platform_get_drvdata(sdio1_device);
+#endif
+// BUG_ON(!host->mmc);
+// wlan_mmc = host->mmc;
+ wlan_mmc = container_of(host, struct mmc_host, private);
+ BUG_ON(!wlan_mmc);
+}
+
+static int __init wlan_device_init(void)
+{
+ int ret;
+ wlan_host_get();
+ init_wifi_mem();
+// wlan_ldo_enable();//NOTE:check this function!If enble this, it cause V2.8 jump from 2.6v to 2.8v when sleep!
+ wlan_clk_init();
+ gpio_request(GPIO_WIFI_IRQ, "oob_irq");
+ gpio_direction_input(GPIO_WIFI_IRQ);
+
+ wlan_resources[1].start = gpio_to_irq(GPIO_WIFI_IRQ);
+ wlan_resources[1].end = gpio_to_irq(GPIO_WIFI_IRQ);
+
+ gpio_request(GPIO_WIFI_SHUTDOWN,"wifi_pwd");
+ gpio_direction_output(GPIO_WIFI_SHUTDOWN, 0);
+ ret = platform_device_register(&sprd_wlan_device);
+
+ return ret;
+}
+
+late_initcall(wlan_device_init);
+
+//MODULE_DESCRIPTION("Broadcomm wlan driver");
+//MODULE_LICENSE("GPL");
+
diff --git a/drivers/platform/sprd/dma_r4p0.c b/drivers/platform/sprd/dma_r4p0.c
new file mode 100644
index 00000000..fa8f4cdb
--- /dev/null
+++ b/drivers/platform/sprd/dma_r4p0.c
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/dma-mapping.h>//test
+#include <linux/delay.h>
+
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/dma_r4p0.h>
+#include <soc/sprd/dma_reg.h>
+
+//#define DMA_DEBUG
+
+struct sci_dma_chn_desc;
+#define AP_DMA_CHN_NUM 32
+#define DMA_CHN_SRC_ADR(base,chn) (DMA_CHx_BASE(base,chn) + 0x0010)
+#define DMA_CHN_DES_ADR(base,chn) (DMA_CHx_BASE(base,chn) + 0x0014)
+
+/*depict a dma controller*/
+struct sci_dma_chip {
+ void __iomem *dma_glb_base;
+ spinlock_t chip_lock;
+ /*point to them chns*/
+ struct sci_dma_chn_desc *chn_desc;
+};
+
+struct sci_dma_chn_desc {
+ void __iomem *dma_chn_base;
+ struct sci_dma_chip *dma_chip;
+ spinlock_t chn_lock;
+ const char *dev_name;
+ void (*irq_handler) (int, void *);
+ void *data;
+ u32 dev_id;
+};
+
+#define get_chn_reg_point(dma_chn) \
+ ((struct sci_dma_chn_reg *)(dma_chns[(dma_chn)].dma_chn_base))
+
+#define get_dma_chip_point(dma_chn) (dma_chns[dma_chn].dma_chip)
+
+#define SCI_DMA_DEBUG
+
+/*support 5 dma controllers*/
+#define SCI_MAX_DMA_SIZE 5
+#define AP_DMA_INDEX 0
+#define AON_DMA_INDEX 1
+
+static struct sci_dma_chip dma_chips[SCI_MAX_DMA_SIZE];
+static struct sci_dma_chn_desc dma_chns[DMA_CHN_MAX + 1];
+static DEFINE_MUTEX(dma_mutex);
+
+#ifdef DMA_DEBUG
+static int __dma_cfg_check_register(void __iomem * dma_reg_addr)
+{
+ volatile struct sci_dma_chn_reg *dma_reg;
+ dma_reg = (struct sci_dma_chn_reg *)dma_reg_addr;
+
+ printk("DMA register:pause=0x%x,\n req=0x%x,\n cfg=0x%x,\n int=0x%x,\n src_addr=0x%x,\n des_addr=0x%x,\n frg_len=0x%x,\n blk_len=0x%x,\n trsc_len=0x%x,\n trsf_step=0x%x,\n wrap_ptr=0x%x,\n wrap_to=0x%x,\n llist_ptr=0x%x,\n frg_step=0x%x,\n src_blk_step=0x%x,\n des_blk_step=0x%x\n",dma_reg->pause,dma_reg->req,dma_reg->cfg,dma_reg->intc,dma_reg->src_addr,dma_reg->des_addr,dma_reg->frg_len,dma_reg->blk_len,dma_reg->trsc_len,dma_reg->trsf_step,dma_reg->wrap_ptr,dma_reg->wrap_to,dma_reg->llist_ptr,dma_reg->frg_step,dma_reg->src_blk_step,dma_reg->des_blk_step);
+ return 0;
+}
+#endif
+
+static void __inline __ap_dma_clk_enable(void)
+{
+ if (!sci_glb_read((unsigned long)REG_AP_AHB_AHB_EB, BIT_DMA_EB)) {
+ sci_glb_set((unsigned long)REG_AP_AHB_AHB_EB, BIT_DMA_EB);
+ }
+}
+
+static void __inline __ap_dma_clk_disable(void)
+{
+ sci_glb_clr((unsigned long)REG_AP_AHB_AHB_EB, BIT_DMA_EB);
+}
+
+static void __inline __dma_softreset(void)
+{
+ sci_glb_set(REG_AP_AHB_AHB_RST, BIT_DMA_SOFT_RST);
+ udelay(1);
+ sci_glb_clr(REG_AP_AHB_AHB_RST, BIT_DMA_SOFT_RST);
+}
+
+static int __dma_set_int_type(u32 dma_chn, dma_int_type int_type)
+{
+ volatile struct sci_dma_chn_reg *dma_reg = get_chn_reg_point(dma_chn);
+
+ dma_reg->intc &= ~0x1f;
+ dma_reg->intc |= 0x1 << 4;
+ switch (int_type) {
+ case NO_INT:
+ break;
+
+ case FRAG_DONE:
+ dma_reg->intc |= 0x1;
+ break;
+
+ case BLK_DONE:
+ dma_reg->intc |= 0x2;
+ break;
+
+ case TRANS_DONE:
+ dma_reg->intc |= 0x4;
+ break;
+
+ case LIST_DONE:
+ dma_reg->intc |= 0x8;
+ break;
+
+ case CONFIG_ERR:
+ dma_reg->intc |= 0x10;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*convert struct sci_dma_cfg to struct sci_dma_chn_reg*/
+static int __dma_cfg_check_and_convert(u32 dma_chn,
+ const struct sci_dma_cfg *cfg,
+ void __iomem * dma_reg_addr)
+{
+ volatile struct sci_dma_chn_reg *dma_reg;
+ u32 datawidth = 0, req_mode = 0, list_end = 0, fix_en = 0;
+ u32 fix_mode = 0, llist_en = 0, wrap_en = 0, wrap_mode = 0;
+
+ /* only full chn support following features:
+ * 1 transcation tranfer
+ * 2 linklist
+ * 3 copy data from fifo to fifo (not test yet, config the
+ * src_trsf_step = 0 and dst_trsf_step = 0)
+ * 4 wrap mode
+ * 5 src_frag_step or dst_frag_step
+ * 6 src_blk_step or dst_blk_step
+ */
+ if (cfg->transcation_len ||
+ cfg->linklist_ptr ||
+ ((cfg->src_step | cfg->des_step) == 0) ||
+ (cfg->wrap_ptr && cfg->wrap_to) ||
+ (cfg->src_frag_step | cfg->dst_frag_step) ||
+ (cfg->src_blk_step | cfg->dst_blk_step)) {
+ if (dma_chn < FULL_CHN_START) {
+ return -EINVAL;
+ }
+ }
+
+ switch (cfg->datawidth) {
+ case BYTE_WIDTH:
+ datawidth = 0;
+ break;
+ case SHORT_WIDTH:
+ datawidth = 1;
+ break;
+ case WORD_WIDTH:
+ datawidth = 2;
+ break;
+ default:
+ /*linklist config */
+ if (cfg->linklist_ptr)
+ break;
+ printk("DMA config datawidth error!\n");
+ return -EINVAL;
+ }
+
+ /*check step, the step must be Integer multiple of the data width */
+ if (!IS_ALIGNED(cfg->src_step, cfg->datawidth))
+ return -EINVAL;
+
+ if (!IS_ALIGNED(cfg->des_step, cfg->datawidth))
+ return -EINVAL;
+
+ req_mode = cfg->req_mode;
+#if 0
+ /*linklist ptr must aligned with 8 bytes */
+ if (cfg->linklist_ptr) {
+ if (!PTR_ALIGN(cfg->linklist_ptr, 8))
+ return -EINVAL;
+ }
+#endif
+
+ /*addr fix mode */
+ if (cfg->src_step != 0 && cfg->des_step != 0) {
+ fix_en = 0x0;
+ } else {
+ if ((cfg->src_step | cfg->des_step) == 0) {
+ /*only full chn support data copy from fifo to fifo ??? */
+ fix_en = 0x0;
+ } else {
+ fix_en = 0x1;
+ if (cfg->src_step) {
+ /*dest addr is fixed */
+ fix_mode = 0x1;
+ } else {
+ /*src addr is fixed */
+ fix_mode = 0x0;
+ }
+ }
+ }
+
+ /*wrap mode, if wrap_ptr point 0x0, there're some problems, Notices!!! */
+ if (cfg->wrap_ptr && cfg->wrap_to) {
+ wrap_en = 0x1;
+ if (cfg->wrap_to == cfg->src_addr) {
+ wrap_mode = 0x0;
+ } else {
+ if (cfg->wrap_to == cfg->des_addr) {
+ wrap_mode = 0x1;
+ } else {
+ /*error message */
+ return -EINVAL;
+ }
+ }
+ }
+
+ /*Notice!! if the linlklist point 0x0, thers're some porblems */
+ if (cfg->linklist_ptr) {
+ llist_en = 0x1;
+ if (cfg->is_end) {
+ list_end = 0x1;
+ }
+ }
+
+ dma_reg = (struct sci_dma_chn_reg *)dma_reg_addr;
+
+ dma_reg->pause = 0x0;
+ dma_reg->req = 0x0;
+ /*set default priority = 1 */
+ dma_reg->cfg = DMA_PRI_1 << CHN_PRIORITY_OFFSET |
+ llist_en << LLIST_EN_OFFSET;
+ /*src and des addr */
+ dma_reg->src_addr = cfg->src_addr;
+ dma_reg->des_addr = cfg->des_addr;
+ /*frag len */
+ dma_reg->frg_len =
+ (datawidth << SRC_DATAWIDTH_OFFSET) |
+ (datawidth << DES_DATAWIDTH_OFFSET) |
+ (0x0 << SWT_MODE_OFFSET) |
+ (req_mode << REQ_MODE_OFFSET) |
+ (wrap_mode << ADDR_WRAP_SEL_OFFSET) |
+ (wrap_en << ADDR_WRAP_EN_OFFSET) |
+ (fix_mode << ADDR_FIX_SEL_OFFSET) |
+ (fix_en << ADDR_FIX_SEL_EN) |
+ (list_end << LLIST_END_OFFSET) | (cfg->fragmens_len & FRG_LEN_MASK);
+ /*blk len */
+ dma_reg->blk_len = cfg->block_len & BLK_LEN_MASK;
+
+ if (dma_chn < FULL_CHN_START)
+ return 0;
+
+ /*trac len */
+ if (0x0 == cfg->transcation_len) {
+ dma_reg->trsc_len = cfg->block_len & TRSC_LEN_MASK;
+ } else {
+ dma_reg->trsc_len = cfg->transcation_len & TRSC_LEN_MASK;
+ }
+
+ /*trsf step */
+ dma_reg->trsf_step =
+ (cfg->des_step & TRSF_STEP_MASK) << DEST_TRSF_STEP_OFFSET |
+ (cfg->src_step & TRSF_STEP_MASK) << SRC_TRSF_STEP_OFFSET;
+
+ /*wrap ptr */
+ dma_reg->wrap_ptr = cfg->wrap_ptr;
+ dma_reg->wrap_to = cfg->wrap_to;
+
+ dma_reg->llist_ptr = cfg->linklist_ptr;
+ /*frag step */
+ dma_reg->frg_step =
+ (cfg->dst_frag_step & FRAG_STEP_MASK) << DEST_FRAG_STEP_OFFSET |
+ (cfg->src_frag_step & FRAG_STEP_MASK) << SRC_FRAG_STEP_OFFSET;
+
+ /*src and dst blk step */
+ dma_reg->src_blk_step = cfg->src_blk_step;
+ dma_reg->des_blk_step = cfg->dst_blk_step;
+
+ return 0;
+}
+
+/*just clean, not disable*/
+static void __inline __dma_int_clr(u32 dma_chn)
+{
+ volatile struct sci_dma_chn_reg *dma_reg = get_chn_reg_point(dma_chn);
+
+ dma_reg->intc |= 0x1f << 24;
+}
+
+static void __inline __dma_int_dis(u32 dma_chn)
+{
+ volatile struct sci_dma_chn_reg *dma_reg = get_chn_reg_point(dma_chn);
+
+ dma_reg->intc |= 0x1f << 24;
+ dma_reg->intc &= ~0x1f;
+}
+
+static irqreturn_t __dma_irq_handle(int irq, void *dev_id)
+{
+ u32 i;
+ u32 logic_dma_chn_offset, dma_chn;
+ u32 irq_status;
+ volatile struct sci_dma_chn_reg *dma_reg;
+ struct sci_dma_chip *dma_chip = (struct sci_dma_chip*)dev_id;
+ volatile struct sci_dma_glb_reg *dma_glb_reg =
+ (struct sci_dma_glb_reg*)dma_chip->dma_glb_base;
+
+ spin_lock(&dma_chip->chip_lock);
+
+ irq_status = dma_glb_reg->int_msk_sts;
+
+ if (unlikely(0 == irq_status)) {
+ spin_unlock(&dma_chip->chip_lock);
+ return IRQ_NONE;
+ }
+
+ /*AP dma,logic_dma_chn_offset = 1 */
+ /*AON dma,logic_dma_chn_offset = 33 */
+ logic_dma_chn_offset = dma_chip->chn_desc - dma_chns;
+
+ while (irq_status) {
+ i = __ffs(irq_status);
+ irq_status &= (irq_status - 1);
+
+ dma_chn = logic_dma_chn_offset + i;
+
+ dma_reg = get_chn_reg_point(dma_chn);
+
+ /*need to deal with DMA configuration error interrupt*/
+ dma_reg->intc |= 0x1f << 24;
+
+ if (dma_chns[dma_chn].irq_handler) {
+ /*audio driver need to get the dma chn */
+ dma_chns[dma_chn].irq_handler(dma_chn, dma_chns[dma_chn].data);
+ }
+ }
+
+ spin_unlock(&dma_chip->chip_lock);
+
+ return IRQ_HANDLED;
+}
+
+static void __inline __dma_set_uid(u32 dma_chn, u32 dev_id)
+{
+ struct sci_dma_chip *dma_chip = get_dma_chip_point(dma_chn);
+
+ if (DMA_UID_SOFTWARE != dev_id) {
+ __raw_writel(dma_chn,
+ (volatile void *)DMA_REQ_CID(dma_chip->dma_glb_base, dev_id));
+ }
+}
+
+static void __inline __dma_unset_uid(u32 dma_chn, u32 dev_id)
+{
+ struct sci_dma_chip *dma_chip = get_dma_chip_point(dma_chn);
+
+ if (DMA_UID_SOFTWARE != dev_id) {
+ __raw_writel(0x0,
+ (volatile void *)DMA_REQ_CID(dma_chip->dma_glb_base, dev_id));
+ }
+}
+
+static void __inline __dma_chn_enable(u32 dma_chn)
+{
+ volatile struct sci_dma_chn_reg *dma_reg = get_chn_reg_point(dma_chn);
+
+ dma_reg->cfg |= 0x1;
+}
+
+static void __inline __dma_soft_request(u32 dma_chn)
+{
+ volatile struct sci_dma_chn_reg *dma_reg = get_chn_reg_point(dma_chn);
+
+ dma_reg->req |= 0x1;
+}
+
+static void __dma_stop_and_disable(u32 dma_chn)
+{
+ u32 timeout = 0x2000;
+
+ volatile struct sci_dma_chn_reg *dma_reg = get_chn_reg_point(dma_chn);
+
+ /*if the chn has disable already, do nothing */
+ if (!(dma_reg->cfg & 0x1)) {
+ return;
+ }
+
+ dma_reg->pause |= 0x1;
+
+ /*fixme, need to deal with timeout*/
+ while (!(dma_reg->pause & (0x1 << 16))){
+ timeout--;
+ if(timeout == 0){
+ __dma_softreset();
+ break;
+ }
+ }
+
+ dma_reg->cfg &= ~0x1;
+
+ dma_reg->pause = 0x0;
+}
+
+/*HAL layer function*/
+int sci_dma_start(u32 dma_chn, u32 dev_id)
+{
+ if (dma_chn > DMA_CHN_MAX)
+ return -EINVAL;
+
+ dma_chns[dma_chn].dev_id = dev_id;
+ /*fixme, need to check dev_id */
+ __dma_set_uid(dma_chn, dev_id);
+
+ __dma_chn_enable(dma_chn);
+
+ if (DMA_UID_SOFTWARE == dev_id)
+ __dma_soft_request(dma_chn);
+
+ return 0;
+}
+
+int sci_dma_stop(u32 dma_chn, u32 dev_id)
+{
+ if (dma_chn > DMA_CHN_MAX)
+ return -EINVAL;
+
+ __dma_stop_and_disable(dma_chn);
+
+ __dma_int_clr(dma_chn);
+
+ return 0;
+}
+
+int sci_dma_register_irqhandle(u32 dma_chn, dma_int_type int_type,
+ void (*irq_handle) (int, void *), void *data)
+{
+ int ret;
+
+ if (dma_chn > DMA_CHN_MAX)
+ return -EINVAL;
+
+ if (NULL == irq_handle)
+ return -EINVAL;
+
+ ret = __dma_set_int_type(dma_chn, int_type);
+ if (ret < 0)
+ return ret;
+
+ dma_chns[dma_chn].irq_handler = irq_handle;
+ dma_chns[dma_chn].data = data;
+
+ return 0;
+}
+
+int sci_dma_config(u32 dma_chn, struct sci_dma_cfg *cfg_list,
+ u32 node_size, struct reg_cfg_addr *cfg_addr)
+{
+ int ret, i;
+ struct sci_dma_cfg list_cfg;
+ struct sci_dma_chn_reg *dma_reg_list;
+
+ if (dma_chn > DMA_CHN_MAX)
+ return -EINVAL;
+
+ __ap_dma_clk_enable();
+
+ if (node_size > 1)
+ goto linklist_config;
+
+ ret =
+ __dma_cfg_check_and_convert(dma_chn, cfg_list,
+ dma_chns[dma_chn].dma_chn_base);
+ if (ret < 0) {
+ printk("%s %d error\n", __func__, __LINE__);
+ }
+
+ return ret;
+
+ linklist_config:
+ if (NULL == cfg_addr)
+ return -EINVAL;
+
+ dma_reg_list = (struct sci_dma_chn_reg *)cfg_addr->virt_addr;
+
+ for (i = 0; i < node_size; i++) {
+ cfg_list[i].linklist_ptr = cfg_addr->phys_addr +
+ ((i + 1) % node_size) * sizeof(struct sci_dma_chn_reg) + 0x10;
+ ret =
+ __dma_cfg_check_and_convert(dma_chn, cfg_list + i,
+ dma_reg_list + i);
+ if (ret < 0) {
+ printk("%s %d error\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+ }
+
+ memset((void *)&list_cfg, 0x0, sizeof(list_cfg));
+
+ list_cfg.linklist_ptr = cfg_addr->phys_addr + 0x10;
+ /*audio driver need get src and dest addr in first node */
+ list_cfg.src_addr = cfg_list[0].src_addr;
+ list_cfg.des_addr = cfg_list[0].des_addr;
+
+ ret =
+ __dma_cfg_check_and_convert(dma_chn, &list_cfg,
+ dma_chns[dma_chn].dma_chn_base);
+
+ return ret;
+}
+
+int sci_dma_request(const char *dev_name, dma_chn_type chn_type)
+{
+ int i;
+ int dma_chn;
+ int dma_chn_start, dma_chn_end;
+
+ if (!dev_name)
+ return -EINVAL;
+
+ dma_chn = -EBUSY;
+
+ switch (chn_type) {
+ case STD_DMA_CHN:
+ dma_chn_start = AP_DMA_CHN_START + STD_CHN_START;
+ dma_chn_end = AP_DMA_CHN_START + STD_CHN_END;
+ break;
+
+ case FULL_DMA_CHN:
+ dma_chn_start = AP_DMA_CHN_START + FULL_CHN_START;
+ dma_chn_end = AP_DMA_CHN_START + FULL_CHN_END;
+ break;
+
+#ifdef AON_DMA_SUPPORT
+ case AON_STD_DMA_CHN:
+ dma_chn_start = AON_DMA_CHN_START + STD_CHN_START;
+ dma_chn_end = AON_DMA_CHN_START + STD_CHN_END;
+ break;
+
+ case AON_FULL_DMA_CHN:
+ dma_chn_start = AON_DMA_CHN_START + FULL_CHN_START;
+ dma_chn_end = AON_DMA_CHN_START + FULL_CHN_END;
+ break;
+#endif
+
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&dma_mutex);
+
+ for (i = dma_chn_start; i <= dma_chn_end; i++) {
+ if (!dma_chns[i].dev_name) {
+ dma_chns[i].dev_name = dev_name;
+ dma_chn = i;
+ break;
+ }
+ }
+
+#ifdef SCI_DMA_DEBUG
+ if (dma_chn >= dma_chn_start) {
+ struct sci_dma_chip *dma_chip;
+ dma_chip = get_dma_chip_point(dma_chn);
+
+ printk("alloc dma chn is %d, and chn reg base is %p, dma chip base is %p\n",
+ dma_chn, dma_chns[i].dma_chn_base, dma_chip->dma_glb_base);
+ }
+#endif
+ mutex_unlock(&dma_mutex);
+
+ return dma_chn;
+}
+
+int sci_dma_free(u32 dma_chn)
+{
+ int i;
+
+ if (dma_chn > DMA_CHN_MAX)
+ return -EINVAL;
+
+ __dma_stop_and_disable(dma_chn);
+
+ __dma_int_dis(dma_chn);
+
+ /*set a valid dma chn for CID, the CID is start with 1 */
+ __dma_unset_uid(dma_chn, dma_chns[dma_chn].dev_id);
+
+ mutex_lock(&dma_mutex);
+
+ dma_chns[dma_chn].dev_name = NULL;
+ dma_chns[dma_chn].irq_handler = NULL;
+ dma_chns[dma_chn].data = NULL;
+ dma_chns[dma_chn].dev_id = 0;
+
+ /*no need to stop AON dma*/
+ if (dma_chn <= FULL_CHN_END) {
+ /*if all AP chn be free, disbale the dma's clk */
+ for (i = STD_CHN_START; i <= FULL_CHN_END; i++) {
+ if (dma_chns[i].dev_name)
+ break;
+ }
+
+ if (i >= DMA_CHN_MAX) {
+ __dma_softreset();
+ __ap_dma_clk_disable();
+ }
+ }
+
+ mutex_unlock(&dma_mutex);
+
+ return 0;
+}
+
+/*support for audio driver to get current src and dst addr*/
+u32 sci_dma_get_src_addr(u32 dma_chn)
+{
+ unsigned long dma_chn_base;
+ if (dma_chn > DMA_CHN_MAX) {
+ printk("dma chn %d is overflow!\n", dma_chn);
+ return 0;
+ }
+
+ if(dma_chn <= AP_DMA_CHN_NUM)
+ dma_chn_base = (unsigned long)dma_chips[AP_DMA_INDEX].dma_glb_base;
+ else
+ dma_chn_base = (unsigned long)dma_chips[AON_DMA_INDEX].dma_glb_base;
+
+ return __raw_readl((volatile void *)DMA_CHN_SRC_ADR(dma_chn_base,(dma_chn-1)));
+}
+
+u32 sci_dma_get_dst_addr(u32 dma_chn)
+{
+ unsigned long dma_chn_base;
+ if (dma_chn > DMA_CHN_MAX) {
+ printk("dma chn %d is overflow!\n", dma_chn);
+ return 0;
+ }
+ if(dma_chn <= AP_DMA_CHN_NUM)
+ dma_chn_base = (unsigned long)dma_chips[AP_DMA_INDEX].dma_glb_base;
+ else
+ dma_chn_base = (unsigned long)dma_chips[AON_DMA_INDEX].dma_glb_base;
+
+ return __raw_readl((volatile void *)DMA_CHN_DES_ADR(dma_chn_base,(dma_chn-1)));
+}
+
+int sci_dma_dump_reg(u32 dma_chn, u32 *reg_base)
+{
+ unsigned long dma_chn_base;
+ if (dma_chn >= DMA_CHN_MAX) {
+ return -EINVAL;
+ }
+ if(dma_chn <= AP_DMA_CHN_NUM)
+ dma_chn_base = (unsigned long)dma_chips[AP_DMA_INDEX].dma_glb_base;
+ else
+ dma_chn_base = (unsigned long)dma_chips[AON_DMA_INDEX].dma_glb_base;
+ if(reg_base) {
+ *reg_base = DMA_CHx_BASE(dma_chn_base, (dma_chn-1));
+ }
+
+ return DMA_CHx_OFFSET;
+}
+
+#define DMA_MEMCPY_MIN_SIZE 64
+#define DMA_MEMCPY_MAX_SIZE TRSC_LEN_MASK
+static void sci_dma_memcpy_irqhandle(int chn, void *data)
+{
+ struct semaphore *dma_sema = (struct semaphore *)data;
+
+ up(dma_sema);
+}
+
+int sci_dma_memcpy(u32 dest, u32 src, size_t size)
+{
+ int ret;
+ int dma_chn;
+ u32 data_width, src_step;
+ u32 irq_mode;
+ struct sci_dma_cfg cfg;
+ struct semaphore dma_seam;
+
+ if (size < DMA_MEMCPY_MIN_SIZE || size > DMA_MEMCPY_MAX_SIZE) {
+ return -EINVAL;
+ }
+
+ if (size <= BLK_LEN_MASK) {
+ dma_chn = sci_dma_request("dma memcpy", STD_DMA_CHN);
+ } else {
+ dma_chn = sci_dma_request("dma memcpy", FULL_DMA_CHN);
+ }
+ if (dma_chn < 0) {
+ printk("alloc dma chn fail\n");
+ return dma_chn;
+ }
+
+ sema_init(&dma_seam, 0);
+
+ memset(&cfg, 0x0, sizeof(cfg));
+
+ if ((size & 0x3) == 0) {
+ data_width = WORD_WIDTH;
+ src_step = 4;
+ } else {
+ if ((size & 0x1) == 0) {
+ data_width = SHORT_WIDTH;
+ src_step = 2;
+ } else {
+ data_width = BYTE_WIDTH;
+ src_step = 1;
+ }
+ }
+
+ cfg.src_addr = src;
+ cfg.des_addr = dest;
+ cfg.datawidth = data_width;
+ cfg.src_step = src_step;
+ cfg.des_step = src_step;
+ cfg.fragmens_len = DMA_MEMCPY_MIN_SIZE;
+ if (size <= BLK_LEN_MASK) {
+ cfg.block_len = size;
+ cfg.req_mode = BLOCK_REQ_MODE;
+ irq_mode = BLK_DONE;
+ } else {
+ cfg.block_len = DMA_MEMCPY_MIN_SIZE;
+ cfg.transcation_len = size;
+ cfg.req_mode = TRANS_REQ_MODE;
+ irq_mode = TRANS_DONE;
+ }
+
+ ret = sci_dma_config(dma_chn, &cfg, 1, NULL);
+ if (ret < 0) {
+ printk("dma memcpy config error!\n");
+ sci_dma_free(dma_chn);
+ return -EINVAL;
+ }
+
+ ret = sci_dma_register_irqhandle(dma_chn, irq_mode,
+ sci_dma_memcpy_irqhandle,
+ &dma_seam);
+ if (ret < 0) {
+ printk("dma register irqhandle failed!\n");
+ sci_dma_free(dma_chn);
+ return ret;
+ }
+
+ sci_dma_start(dma_chn, DMA_UID_SOFTWARE);
+ down(&dma_seam);
+
+ sci_dma_free(dma_chn);
+
+ return 0;
+}
+
+static int __init sci_init_dma(void)
+{
+ int ret,i;
+ u32 dma_irq;
+ void __iomem *dma_reg_base;
+ struct sci_dma_chip *dma_chip;
+ struct sci_dma_chn_desc *chns_desc;
+ struct device_node *dma_node;
+ dma_node = of_find_compatible_node(NULL, NULL, "sprd,sprd-dma");
+ if (!dma_node) {
+ pr_warn("Can't get the dmac node!\n");
+ return -ENODEV;
+ }
+
+ dma_irq = irq_of_parse_and_map(dma_node, 0);
+ if (dma_irq == 0) {
+ pr_warn("Can't get the dma irq number!\n");
+ return -EIO;
+ }
+ pr_info(" dma irq number is %d!\n", dma_irq);
+ dma_reg_base = (void __iomem *)SPRD_DMA0_BASE;
+
+#ifdef AON_DMA_SUPPORT
+ u32 aon_dma_irq;
+ void __iomem *aon_dma_reg_base;
+ struct device_node *aon_dma_node;
+ aon_dma_node = of_find_compatible_node(NULL, NULL, "sprd,aon_dma");
+ if (!aon_dma_node) {
+ pr_warn("Can't get the aon dmac node!\n");
+ return -ENODEV;
+ }
+
+ aon_dma_irq = irq_of_parse_and_map(aon_dma_node, 0);
+ if (dma_irq == 0) {
+ pr_warn("Can't get the aon dma irq number!\n");
+ return -EIO;
+ }
+ pr_info(" dma irq number is %d!\n", aon_dma_irq);
+ aon_dma_reg_base = (void __iomem *)REGS_AON_DMA_BASE;
+#endif
+
+ dma_chip = &dma_chips[AP_DMA_INDEX];
+ dma_chip->dma_glb_base = dma_reg_base;
+ spin_lock_init(&dma_chip->chip_lock);
+
+ chns_desc = dma_chns + AP_DMA_CHN_START;
+ for (i = STD_CHN_START; i <= FULL_CHN_END; i++) {
+ chns_desc[i].dma_chip = dma_chip;
+ chns_desc[i].dma_chn_base = DMA_CHx_BASE(dma_reg_base, i);
+ spin_lock_init(&chns_desc[i].chn_lock);
+ }
+
+ dma_chip->chn_desc = chns_desc;
+
+ ret = request_irq(dma_irq, __dma_irq_handle, 0, "sci-dma",
+ (void*)dma_chip);
+ if (ret) {
+ printk(KERN_ERR "request dma irq failed %d\n", ret);
+ return ret;
+ }
+
+#ifdef AON_DMA_SUPPORT
+ dma_chip = &dma_chips[AON_DMA_INDEX];
+ dma_chip->dma_glb_base = aon_dma_reg_base;
+ spin_lock_init(&dma_chip->chip_lock);
+
+ chns_desc = dma_chns + AON_DMA_CHN_START;
+ for (i = STD_CHN_START; i <= FULL_CHN_END; i++) {
+ chns_desc[i].dma_chip = dma_chip;
+ chns_desc[i].dma_chn_base = DMA_CHx_BASE(aon_dma_reg_base, i);
+ spin_lock_init(&chns_desc[i].chn_lock);
+ }
+
+ dma_chip->chn_desc = chns_desc;
+ ret = request_irq(aon_dma_irq, __dma_irq_handle, 0, "sci-aon-dma",
+ (void*)dma_chip);
+ if (ret) {
+ printk(KERN_ERR "request dma irq failed %d\n", ret);
+ return ret;
+ }
+#endif
+
+ return ret;
+}
+
+#ifdef DMA_DEBUG
+static int __init dma_test(void)
+{
+ sci_dma_memcpy(0x806178d0,0x80000000,256);
+ return 0;
+}
+#endif
+
+int __init dma_new_init(void)
+{
+ int ret;
+ ret = sci_init_dma();
+#ifdef DMA_DEBUG
+ dma_test();
+#endif
+ return ret;
+}
+
+void __exit dma_new_exit(void)
+{
+}
+
+module_init(dma_new_init);
+module_exit(dma_new_exit);
+
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL_GPL(sci_dma_request);
+EXPORT_SYMBOL_GPL(sci_dma_free);
+EXPORT_SYMBOL_GPL(sci_dma_config);
+EXPORT_SYMBOL_GPL(sci_dma_register_irqhandle);
+EXPORT_SYMBOL_GPL(sci_dma_start);
+EXPORT_SYMBOL_GPL(sci_dma_stop);
+EXPORT_SYMBOL_GPL(sci_dma_memcpy);
diff --git a/drivers/platform/sprd/dmc_dfs_test.c b/drivers/platform/sprd/dmc_dfs_test.c
new file mode 100644
index 00000000..3e186e32
--- /dev/null
+++ b/drivers/platform/sprd/dmc_dfs_test.c
@@ -0,0 +1,112 @@
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <soc/sprd/common.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci.h>
+#include <linux/earlysuspend.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <linux/random.h>
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/cacheflush.h>
+#include <asm/suspend.h>
+#include <linux/vmalloc.h>
+#include <linux/cpu.h>
+#include <linux/printk.h>
+#include <soc/sprd/__sc8830_dmc_dfs.h>
+#include "soc/sprd/chip_x30g/dram_phy_28nm.h"
+
+#ifdef EMC_FREQ_AUTO_TEST
+static u32 dfs_freq_array[] = {
+ //192,
+ 200,
+ //384,
+ 400
+ //466
+ //533
+};
+
+static struct wake_lock dfs_test_lock;
+static DEFINE_SPINLOCK(emc_freq_spinlock);
+
+extern int scxx30_all_nonboot_cpus_died(void);
+extern u32 emc_clk_set(u32 new_clk, u32 sene);
+
+static int emc_freq_test_thread(void * data)
+{
+ u32 i = 0;
+ unsigned long spinlock_flags = 0;
+ msleep(17000);
+ wake_lock(&dfs_test_lock);
+
+ /* close cpu. */
+// cpu_down(1);
+// msleep(200);
+
+// cpu_down(2);
+// msleep(200);
+
+// cpu_down(3);
+// msleep(2000);
+
+ while(1){
+ spin_lock_irqsave(&emc_freq_spinlock, spinlock_flags);
+
+ /* check current cpu number is 1 or not. */
+ if (scxx30_all_nonboot_cpus_died() != 1){
+ printk("*** %s, num_online_cpus:%d ***\n", __func__, num_online_cpus() );
+ return 0;
+ }
+
+ //set_current_state(TASK_INTERRUPTIBLE);
+// i = get_random_int();
+// i = i % 2;//(sizeof(dfs_freq_array)/ sizeof(dfs_freq_array[0]));
+ printk("emc_freq_test_thread i = %x, clk=%d\n", i, dfs_freq_array[i]);
+
+ emc_clk_set(dfs_freq_array[i], 0);
+
+ printk("e\n");
+
+ i++;
+ if (i >= 2) {
+ i = 0;
+ }
+
+ /* resume irq, cpu and so on. */
+ spin_unlock_irqrestore(&emc_freq_spinlock, spinlock_flags);
+
+// schedule_timeout(10);
+ msleep(50);
+ }
+
+ /* open cpu */
+// cpu_up(1);
+// cpu_up(2);
+// cpu_up(3);
+ return 0;
+}
+
+/* used this test must make sure cpu is only one. */
+/* platsmp.c : sci_get_core_num() return 1 means signal core */
+void __emc_freq_test(void)
+{
+ struct task_struct * task;
+ wake_lock_init(&dfs_test_lock, WAKE_LOCK_SUSPEND,"emc_freq_test_wakelock");
+ task = kthread_create(emc_freq_test_thread, NULL, "emc freq test test");
+ if (task == 0) {
+ printk("Can't crate emc freq test thread!\n");
+ }else {
+ wake_up_process(task);
+ }
+}
+#endif
diff --git a/drivers/platform/sprd/dmc_freq.c b/drivers/platform/sprd/dmc_freq.c
new file mode 100755
index 00000000..8408fee8
--- /dev/null
+++ b/drivers/platform/sprd/dmc_freq.c
@@ -0,0 +1,816 @@
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <mach/common.h>
+#include <mach/hardware.h>
+#include <mach/sci.h>
+#include <linux/earlysuspend.h>
+#include <mach/sci_glb_regs.h>
+#include <linux/random.h>
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/cacheflush.h>
+#include <mach/sc8830_emc_freq_data.h>
+#include <asm/suspend.h>
+#include <linux/vmalloc.h>
+#include <linux/printk.h>
+#include <mach/__sc8830_dmc_dfs.h>
+static u32 max_clk = 0;
+static volatile u32 cp_code_addr = 0x0;
+static DEFINE_MUTEX(emc_mutex);
+#define CP_DEBUG_ADDR (cp_code_addr + 0xFF8)
+#define CP_FLAGS_ADDR (cp_code_addr + 0xFFC)
+#define CP_PARAM_ADDR (cp_code_addr + 0xF00)
+#define DDR_TIMING_REG_VAL_ADDR (SPRD_IRAM0H_BASE + 0xc00)
+
+#define debug(format, arg...) pr_debug("emc_freq" "" format, ## arg)
+#define info(format, arg...) pr_info("emc_freq: " "" format, ## arg)
+
+static u32 emc_freq = 0;
+static u32 emc_delay = 20;
+static u32 chip_id = 0;
+static ddr_dfs_val_t __emc_param_configs[5];
+static void __timing_reg_dump(ddr_dfs_val_t * dfs_val_ptr);
+u32 emc_clk_get(void);
+static u32 get_dpll_clk(void);
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+static void emc_dfs_code_copy(u8 * dest);
+static int emc_dfs_call(unsigned long flag);
+void emc_dfs_main(unsigned long flag);
+#endif
+#define MAX_DLL_DISABLE_CLK 200
+#define MIN_DPLL_CLK 250
+enum{
+ CLK_EMC_SELECT_26M = 0,
+ CLK_EMC_SELECT_TDPLL = 1,
+ CLK_EMC_SELECT_DPLL = 2,
+};
+//#define EMC_FREQ_AUTO_TEST
+static ddr_dfs_val_t *__dmc_param_config(u32 clk)
+{
+ u32 i;
+ ddr_dfs_val_t * ret_timing = 0;
+ if(clk == 533) {
+ clk = 532;
+ }
+ if(clk == 333) {
+ clk = 332;
+ }
+ for(i = 0; i < (sizeof(__emc_param_configs) / sizeof(__emc_param_configs[0])); i++) {
+ if(__emc_param_configs[i].ddr_clk >= clk) {
+ ret_timing = &__emc_param_configs[i];
+ break;
+ }
+ }
+ //__timing_reg_dump(ret_timing);
+ if(ret_timing) {
+#ifndef CONFIG_SCX35_DMC_FREQ_AP
+ memcpy((void *)CP_PARAM_ADDR, ret_timing, sizeof(ddr_dfs_val_t));
+#endif
+ }
+ return ret_timing;
+}
+#ifndef CONFIG_SCX35_DMC_FREQ_AP
+static void cp_code_init(void)
+{
+ u32 *copy_data;
+ u32 copy_size;
+ u32 code_phy_addr;
+
+#ifdef CONFIG_SCX35_DMC_FREQ_CP0
+ copy_data = cp0_dfs_code_data;
+ copy_size = sizeof(cp0_dfs_code_data);
+ code_phy_addr = 0x50000000;
+#endif
+
+#ifdef CONFIG_SCX35_DMC_FREQ_CP1
+ copy_data = cp1_dfs_code_data;
+ copy_size = sizeof(cp1_dfs_code_data);
+ code_phy_addr = 0x50001800;
+#endif
+
+#ifdef CONFIG_SCX35_DMC_FREQ_CP2
+ copy_data = cp2_dfs_code_data;
+ copy_size = sizeof(cp2_dfs_code_data);
+ code_phy_addr = 0x50003000;
+#endif
+ cp_code_addr = (volatile u32)ioremap(code_phy_addr,0x1000);
+ memcpy((void *)cp_code_addr, (void *)copy_data, copy_size);
+}
+static void close_cp(void)
+{
+ u32 value;
+ u32 times;
+
+ value = __raw_readl(CP_FLAGS_ADDR);
+ times = 0;
+ while((value & EMC_FREQ_SWITCH_STATUS_MASK) != (EMC_FREQ_SWITCH_COMPLETE << EMC_FREQ_SWITCH_STATUS_OFFSET)) {
+ value = __raw_readl(CP_FLAGS_ADDR);
+ mdelay(2);
+ if(times >= 100) {
+ break;
+ }
+ times ++;
+ }
+ info("__emc_clk_set flag = 0x%08x , version flag = 0x%08x phy register = 0x%08x\n", __raw_readl(CP_FLAGS_ADDR),__raw_readl(CP_FLAGS_ADDR - 4), __raw_readl(SPRD_LPDDR2_PHY_BASE + 0x4));
+ udelay(200);
+}
+static void wait_cp_run(void)
+{
+ u32 val;
+ u32 times = 0;
+ val = __raw_readl(CP_FLAGS_ADDR - 4);
+ while(val != 0x11223344/*cp run flag*/) {
+ mdelay(2);
+ val = __raw_readl(CP_FLAGS_ADDR - 4);
+ times ++;
+ if(times >= 10) {
+ panic("wait_cp2_run timeout\n");
+ }
+ }
+ __raw_writel(0x44332211, CP_FLAGS_ADDR - 4)/*for watchdog reset, so clear it*/;
+}
+#endif
+#ifdef EMC_FREQ_AUTO_TEST
+static u32 get_sys_cnt(void)
+{
+ return __raw_readl(SPRD_GPTIMER_BASE + 0x44);
+}
+#endif
+
+static void __set_dpll_clk(u32 clk)
+{
+ u32 reg = 0;
+ u32 dpll = 0;
+ u32 dpll_lpf = 6;
+ u32 dpll_ibias = 1;
+ if(get_dpll_clk() == clk) {
+ return;
+ }
+ reg = sci_glb_read(REG_AON_APB_DPLL_CFG,-1);
+ reg &= ~0x7ff;
+ reg &= ~BITS_DPLL_LPF(0x7);
+ reg &= ~BITS_DPLL_IBIAS(0x3);
+
+ if((reg & 0x03000000) == 0x00000000){
+ dpll = clk >> 1;
+ }
+ if((reg & 0x03000000) == 0x01000000) {
+ dpll = clk >> 2;
+ }
+ if((reg & 0x03000000) == 0x02000000) {
+ dpll = clk / 13;
+ }
+ if((reg & 0x03000000) == 0x03000000) {
+ dpll = clk / 26;
+ }
+ if(dpll < MIN_DPLL_CLK) {
+ dpll_lpf = 0x2;
+ dpll_ibias = 0x1;
+ } else {
+ dpll_lpf = 0x6;
+ dpll_ibias = 0x1;
+ }
+ reg |= dpll | BITS_DPLL_LPF(dpll_lpf) | BITS_DPLL_IBIAS(dpll_ibias);
+ sci_glb_write(REG_AON_APB_DPLL_CFG, reg, -1);
+ udelay(100);
+}
+
+static u32 is_current_set = 0;
+static u32 __emc_clk_set(u32 clk, u32 sene, u32 dll_enable, u32 bps_200)
+{
+ u32 flag = 0;
+ ddr_dfs_val_t * ret_timing;
+ ret_timing = __dmc_param_config(clk);
+
+ if(!ret_timing) {
+ return -1;
+ }
+ clk = ret_timing->ddr_clk;
+ if(clk == 533) {
+ clk = 532;
+ }
+ if(clk == 333) {
+ clk = 332;
+ }
+
+
+#ifdef CONFIG_SCX35_DMC_FREQ_DDR3
+ if(clk == 400){
+ clk = 384;
+ }
+ if(clk == 466){
+ clk = 464;
+ }
+ flag = (EMC_DDR_TYPE_DDR3 << EMC_DDR_TYPE_OFFSET) | (clk << EMC_CLK_FREQ_OFFSET);
+ flag |= EMC_FREQ_NORMAL_SCENE << EMC_FREQ_SENE_OFFSET;
+ flag |= dll_enable;
+ flag |= 0x0 << EMC_CLK_DIV_OFFSET;
+#else
+ flag = (EMC_DDR_TYPE_LPDDR2 << EMC_DDR_TYPE_OFFSET) | (clk << EMC_CLK_FREQ_OFFSET);
+ flag |= sene << EMC_FREQ_SENE_OFFSET;
+ flag |= dll_enable;
+ flag |= bps_200 << EMC_BSP_BPS_200_OFFSET;
+#endif
+
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+ if((sene<2) || (sene>4))
+ {
+ panic("invalid scene for ap dfs, valid number is only 2,3,4!\n");
+ return -1;
+ }
+
+ if((sene!=EMC_FREQ_NORMAL_SWITCH_SENE) && (clk != 192))
+ {
+ panic("invalid scene clk combination, sleep and resume must be tdpll 192M!\n");
+ return -1;
+ }
+
+ if(sene == EMC_FREQ_DEEP_SLEEP_SENE)
+ {
+ __set_dpll_clk(192);
+ }
+
+ /*flush_cache_all();*/
+ cpu_suspend(flag, emc_dfs_call);
+
+ if(sene == EMC_FREQ_RESUME_SENE)
+ {
+ __set_dpll_clk(332);
+ }
+#else
+ __raw_writel(flag, CP_FLAGS_ADDR);
+
+ #ifdef CONFIG_SCX35_DMC_FREQ_CP0
+ sci_glb_set(SPRD_IPI_BASE,1 << 0);//send ipi interrupt to cp0
+ #endif
+
+ #ifdef CONFIG_SCX35_DMC_FREQ_CP1
+ sci_glb_set(SPRD_IPI_BASE,1 << 4);//send ipi interrupt to cp1
+ #endif
+
+ #ifdef CONFIG_SCX35_DMC_FREQ_CP2
+ sci_glb_set(SPRD_IPI_BASE,1 << 8);//send ipi interrupt to cp2
+ #endif
+ close_cp();
+ info("__emc_clk_set clk = %d REG_AON_APB_DPLL_CFG = %x, PUBL_DLLGCR = %x\n",
+ clk, sci_glb_read(REG_AON_APB_DPLL_CFG, -1), __raw_readl(SPRD_LPDDR2_PHY_BASE + 0x04));
+#endif
+ if(emc_clk_get() != clk) {
+ info("clk set error, set clk = %d, get clk = %d, sence = %d\n", clk, emc_clk_get(), sene);
+ }
+ else{
+ info("clk set success, set clk = %d, get clk = %d, sence = %d\n", clk, emc_clk_get(), sene);
+ }
+ return 0;
+}
+
+static u32 get_emc_clk_select(u32 clk)
+{
+ u32 sel;
+ u32 reg_val;
+ reg_val = sci_glb_read(REG_AON_CLK_EMC_CFG, -1);
+ sel = reg_val & 0x3;
+ switch(sel) {
+ case 0:
+ sel = CLK_EMC_SELECT_26M;
+ break;
+ case 1:
+ case 2:
+ sel = CLK_EMC_SELECT_TDPLL;
+ break;
+ case 3:
+ sel = CLK_EMC_SELECT_DPLL;
+ break;
+ default:
+ break;
+ }
+ return sel;
+}
+/*
+ * open or close dpll
+ * flag = 1, open dpll
+ * flag = 0, close dpll
+ */
+static u32 dpll_rel_cfg_bak = 0;
+static void __dpll_open(u32 flag)
+{
+ u32 reg;
+ u32 mask;
+ mask = BIT_DPLL_AP_SEL | BIT_DPLL_CP0_SEL | BIT_DPLL_CP1_SEL | BIT_DPLL_CP2_SEL;
+ reg = sci_glb_read(REG_PMU_APB_DPLL_REL_CFG, -1) & mask;
+ if(flag) {
+ if(reg == 0) {
+ sci_glb_set(REG_PMU_APB_DPLL_REL_CFG, mask & dpll_rel_cfg_bak);
+ udelay(500);
+ }
+ }
+ else {
+ sci_glb_clr(REG_PMU_APB_DPLL_REL_CFG, mask);
+ }
+}
+u32 emc_clk_set(u32 new_clk, u32 sene)
+{
+ u32 dll_enable = EMC_DLL_SWITCH_ENABLE_MODE;
+ u32 old_clk,old_select,new_select,div = 0x0;
+ u32 ret;
+#ifdef EMC_FREQ_AUTO_TEST
+ u32 start_t1, end_t1;
+ static u32 max_u_time = 0;
+ u32 current_u_time;
+
+ start_t1 = get_sys_cnt();
+#endif
+
+#if defined (EMC_FREQ_AUTO_TEST) || defined (CONFIG_SCX35_DMC_FREQ_AP)
+ unsigned long irq_flags;
+ if((new_clk > 200) || (sene != EMC_FREQ_NORMAL_SWITCH_SENE)) {
+ __dpll_open(1);
+ }
+ local_irq_save(irq_flags);
+#else
+ mutex_lock(&emc_mutex);
+#endif
+
+ /*info("emc clk going on %d #########################################################\n",new_clk);*/
+ //mutex_lock(&emc_mutex);
+ if(new_clk > max_clk) {
+ new_clk = max_clk;
+ }
+ if(emc_clk_get() == new_clk) {
+ // mutex_unlock(&emc_mutex);
+ goto out;
+ // return 0;
+ }
+ if(new_clk <= 200) {
+ dll_enable = 0;
+ }
+ old_clk = emc_clk_get();
+ if(is_current_set == 1) {
+ panic("now other thread set dmc clk\n");
+ goto out;
+ // return 0;
+ }
+ is_current_set ++;
+ //info("REG_AON_CLK_PUB_AHB_CFG = %x\n", __raw_readl(REG_AON_CLK_PUB_AHB_CFG));
+ //info("emc_clk_set old = %d, new = %d\n", old_clk, new_clk);
+ //info("emc_clk_set clk = %d, sene = %d, dll_enable = %d, emc_delay = %x\n", new_clk, sene,dll_enable, emc_delay);
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+ if(new_clk <= 200){
+ new_clk = 192;
+ }
+ if(new_clk > 200)
+ {
+ new_clk = 332;
+ }
+
+ if(new_clk == old_clk)
+ {
+ if(sene == EMC_FREQ_NORMAL_SWITCH_SENE)
+ {
+ is_current_set --;
+ goto out;
+ }
+ // return 0;
+ }
+ __emc_clk_set(new_clk,sene,EMC_DLL_SWITCH_DISABLE_MODE, EMC_BSP_BPS_200_NOT_CHANGE);
+#else
+ if( (new_clk >= 0) && (new_clk <= 200) )
+ {
+ new_clk = 200;
+ }
+ else if((new_clk > 200) && (new_clk <=400))
+ {
+ new_clk = 384;
+ }
+
+ if(emc_clk_get() == new_clk)
+ {
+ is_current_set--;
+ goto out;
+ //return 0;
+ }
+
+ if(new_clk <= MAX_DLL_DISABLE_CLK){
+ dll_enable = EMC_DLL_SWITCH_DISABLE_MODE;
+ }
+ old_select = get_emc_clk_select(old_clk);
+ if((new_clk == 384) || (new_clk == 256)) {
+ new_select = CLK_EMC_SELECT_TDPLL;
+ }
+ else {
+ new_select = CLK_EMC_SELECT_DPLL;
+ }
+
+
+ if(new_select == CLK_EMC_SELECT_TDPLL) {
+ ret = __emc_clk_set(new_clk, 0, dll_enable, 0);
+ }
+ else {
+ if(old_select == new_select) {
+ ret = __emc_clk_set(384, 0, EMC_DLL_SWITCH_ENABLE_MODE, 0);
+ if(ret != 0) {
+ is_current_set --;
+ goto out;
+ }
+ }
+ __set_dpll_clk(new_clk * (div + 1));
+ __emc_clk_set(new_clk, 0, dll_enable, 0);
+ }
+
+ printk("sys timer = 0x%08x, ap sys count = 0x%08x\n", __raw_readl(SPRD_SYSTIMER_CMP_BASE + 4), __raw_readl(SPRD_SYSCNT_BASE + 0xc));
+#endif
+ //mutex_unlock(&emc_mutex);
+ is_current_set --;
+
+out:
+#ifdef EMC_FREQ_AUTO_TEST
+ end_t1 = get_sys_cnt();
+
+ current_u_time = (start_t1 - end_t1)/128;
+ if(max_u_time < current_u_time) {
+ max_u_time = current_u_time;
+ }
+ info("**************emc dfs use current = %08u max %08u\n", current_u_time, max_u_time);
+#endif
+
+
+#if defined (EMC_FREQ_AUTO_TEST) || defined(CONFIG_SCX35_DMC_FREQ_AP)
+ local_irq_restore(irq_flags);
+ if(CLK_EMC_SELECT_DPLL != get_emc_clk_select(0)) {
+ __dpll_open(0);
+ }
+#else
+ mutex_unlock(&emc_mutex);
+#endif
+ info("__emc_clk_set REG_AON_APB_DPLL_CFG = %x, PUBL_DLLGCR = %x\n",sci_glb_read(REG_AON_APB_DPLL_CFG, -1), __raw_readl(SPRD_LPDDR2_PHY_BASE + 0x04));
+ return 0;
+}
+EXPORT_SYMBOL(emc_clk_set);
+
+static u32 get_dpll_clk(void)
+{
+ u32 clk;
+ u32 reg;
+ reg = sci_glb_read(REG_AON_APB_DPLL_CFG, -1);
+ clk = reg & 0x7ff;
+ if((reg & 0x03000000) == 0x00000000) {
+ clk *= 2;
+ }
+ if((reg & 0x03000000) == 0x01000000) {
+ clk *= 4;
+ }
+ if((reg & 0x03000000) == 0x02000000) {
+ clk *= 13;
+ }
+ if((reg & 0x03000000) == 0x03000000) {
+ clk *= 26;
+ }
+ return clk;
+}
+u32 emc_clk_get(void)
+{
+ u32 pll_clk;
+ u32 div;
+ u32 reg_val;
+ u32 sel;
+ u32 clk;
+ reg_val = sci_glb_read(REG_AON_CLK_EMC_CFG, -1);
+ sel = reg_val & 0x3;
+ div = (reg_val >> 8) & 0x3;
+ switch(sel) {
+ case 0:
+ pll_clk = 26;
+ break;
+ case 1:
+ pll_clk = 256;
+ break;
+ case 2:
+ pll_clk = 384;
+ break;
+ case 3:
+ pll_clk = get_dpll_clk();
+ break;
+ //default:
+ // break;
+ }
+ clk = pll_clk / (div + 1);
+ return clk;
+}
+EXPORT_SYMBOL(emc_clk_get);
+
+static int debugfs_emc_freq_get(void *data, u64 * val)
+{
+ u32 freq;
+ freq = emc_clk_get();
+ info("debugfs_emc_freq_get %d\n",freq);
+ *(u32 *) data = *val = freq;
+ return 0;
+}
+static int debugfs_emc_freq_set(void *data, u64 val)
+{
+ int new_freq = *(u32 *) data = val;
+ emc_clk_set(new_freq, 0);
+ return 0;
+}
+
+static int debugfs_emc_delay_get(void *data, u64 * val)
+{
+ *(u32 *) data = *val = emc_delay;
+ return 0;
+}
+static int debugfs_emc_delay_set(void *data, u64 val)
+{
+ emc_delay = *(u32 *) data = val;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_emc_freq,
+ debugfs_emc_freq_get, debugfs_emc_freq_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fops_emc_delay,
+ debugfs_emc_delay_get, debugfs_emc_delay_set, "%llu\n");
+static u32 get_spl_emc_clk_set(void)
+{
+ return emc_clk_get();
+}
+static void emc_debugfs_creat(void)
+{
+ static struct dentry *debug_root = NULL;
+ debug_root = debugfs_create_dir("emc", NULL);
+ if (IS_ERR_OR_NULL(debug_root)) {
+ printk("%s return %p\n", __FUNCTION__, debug_root);
+ }
+ debugfs_create_file("freq", S_IRUGO | S_IWUGO,
+ debug_root, &emc_freq, &fops_emc_freq);
+ debugfs_create_file("delay", S_IRUGO | S_IWUGO,
+ debug_root, &emc_delay, &fops_emc_delay);
+}
+static void emc_earlysuspend(struct early_suspend *h)
+{
+#ifndef EMC_FREQ_AUTO_TEST
+ emc_clk_set(200, EMC_FREQ_NORMAL_SCENE);
+#endif
+}
+static void emc_late_resume(struct early_suspend *h)
+{
+#ifndef EMC_FREQ_AUTO_TEST
+ emc_clk_set(max_clk, EMC_FREQ_NORMAL_SCENE);
+#endif
+}
+static struct early_suspend emc_early_suspend_desc = {
+ .level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 100,
+ .suspend = emc_earlysuspend,
+ .resume = emc_late_resume,
+};
+#ifdef EMC_FREQ_AUTO_TEST
+#ifdef CONFIG_SCX35_DMC_FREQ_DDR3
+static u32 emc_freq_valid_array[] = {
+ 200,
+ 384,
+ 532
+};
+#else
+static u32 emc_freq_valid_array[] = {
+ //100,
+ 200,
+ 332,
+ 384,
+ 532,
+};
+#endif //CONFIG_SCX35_DMC_FREQ_DDR3
+static struct wake_lock emc_freq_test_wakelock;
+static int emc_freq_test_thread(void * data)
+{
+ u32 i = 0;
+ wake_lock(&emc_freq_test_wakelock);
+ msleep(20000);
+ while(1){
+ set_current_state(TASK_INTERRUPTIBLE);
+ i = get_random_int();
+ i = i % (sizeof(emc_freq_valid_array)/ sizeof(emc_freq_valid_array[0]));
+ printk("emc_freq_test_thread i = %x\n", i);
+ emc_clk_set(emc_freq_valid_array[i], EMC_FREQ_NORMAL_SWITCH_SENE);
+ schedule_timeout(10);
+ }
+ return 0;
+}
+
+static void __emc_freq_test(void)
+{
+ struct task_struct * task;
+ wake_lock_init(&emc_freq_test_wakelock, WAKE_LOCK_SUSPEND,
+ "emc_freq_test_wakelock");
+ task = kthread_create(emc_freq_test_thread, NULL, "emc freq test test");
+ if (task == 0) {
+ printk("Can't crate emc freq test thread!\n");
+ }else {
+ wake_up_process(task);
+ }
+}
+#endif
+
+static void __timing_reg_dump(ddr_dfs_val_t * dfs_val_ptr)
+{
+ debug("umctl2_rfshtmg %x\n", dfs_val_ptr->ddr_clk);
+ debug("umctl2_rfshtmg %x\n", dfs_val_ptr->umctl2_rfshtmg);
+ debug("umctl2_dramtmg0 %x\n", dfs_val_ptr->umctl2_dramtmg0);
+ debug("umctl2_dramtmg1 %x\n", dfs_val_ptr->umctl2_dramtmg1);
+ debug("umctl2_dramtmg2 %x\n", dfs_val_ptr->umctl2_dramtmg2);
+ debug("umctl2_dramtmg3 %x\n", dfs_val_ptr->umctl2_dramtmg3);
+ debug("umctl2_dramtmg4 %x\n", dfs_val_ptr->umctl2_dramtmg4);
+ debug("umctl2_dramtmg5 %x\n", dfs_val_ptr->umctl2_dramtmg5);
+ debug("umctl2_dramtmg6 %x\n", dfs_val_ptr->umctl2_dramtmg6);
+ debug("umctl2_dramtmg7 %x\n", dfs_val_ptr->umctl2_dramtmg7);
+ debug("umctl2_dramtmg8 %x\n", dfs_val_ptr->umctl2_dramtmg8);
+ debug("publ_dx0gcr %x\n", dfs_val_ptr->publ_dx0gcr);
+ debug("publ_dx1gcr %x\n", dfs_val_ptr->publ_dx1gcr);
+ debug("publ_dx2gcr %x\n", dfs_val_ptr->publ_dx2gcr);
+ debug("publ_dx3gcr %x\n", dfs_val_ptr->publ_dx3gcr);
+ debug("publ_dx0dqstr %x\n", dfs_val_ptr->publ_dx0dqstr);
+ debug("publ_dx1dqstr %x\n", dfs_val_ptr->publ_dx1dqstr);
+ debug("publ_dx2dqstr %x\n", dfs_val_ptr->publ_dx2dqstr);
+ debug("publ_dx3dqstr %x\n", dfs_val_ptr->publ_dx3dqstr);
+}
+static void __emc_timing_reg_init(void)
+{
+ ddr_dfs_val_t * dfs_val_ptr;
+ ddr_dfs_val_t *dmc_timing_ptr;
+ u32 i;
+ memset(__emc_param_configs, 0, sizeof(__emc_param_configs));
+ for(i = 0; i < 5; i++) {
+ dfs_val_ptr = (ddr_dfs_val_t *)(DDR_TIMING_REG_VAL_ADDR + i * sizeof(ddr_dfs_val_t));
+ dmc_timing_ptr = 0;
+ if((dfs_val_ptr->ddr_clk >= 100) && (dfs_val_ptr->ddr_clk <= 533)) {
+ dmc_timing_ptr = &__emc_param_configs[i];
+ }
+ if(dfs_val_ptr->ddr_clk == 333) {
+ dfs_val_ptr->ddr_clk = 332;
+ }
+ if(dfs_val_ptr->ddr_clk == 533) {
+ dfs_val_ptr->ddr_clk = 532;
+ }
+ if(dfs_val_ptr->ddr_clk == 466) {
+ dfs_val_ptr->ddr_clk = 464;
+ }
+ if(dmc_timing_ptr) {
+ memcpy(dmc_timing_ptr, dfs_val_ptr, sizeof(*dfs_val_ptr));
+ }
+ }
+ for(i = 0; i < 5; i++) {
+ __timing_reg_dump(&__emc_param_configs[i]);
+ }
+}
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+static int emc_dfs_call(unsigned long flag)
+{
+ cpu_switch_mm(init_mm.pgd, &init_mm);
+ ((int (*)(unsigned long))SPRD_IRAM0H_PHYS)(flag); //iram0h must be the first function of dfs
+ return 0;
+}
+static void emc_dfs_code_copy(u8 * dest)
+{
+ memcpy_toio((void *)dest, (void *)emc_dfs_main, 0xc00);
+}
+
+#else
+static void cp_init(void)
+{
+ u32 val;
+#ifdef CONFIG_SCX35_DMC_FREQ_CP0 //dfs is at cp0
+ cp_code_init();
+ val = BIT_PD_CP0_ARM9_0_AUTO_SHUTDOWN_EN;
+ val |= BITS_PD_CP0_ARM9_0_PWR_ON_DLY(0x8);
+ val |= BITS_PD_CP0_ARM9_0_PWR_ON_SEQ_DLY(0x6);
+ val |= BITS_PD_CP0_ARM9_0_ISO_ON_DLY(0x2);
+ sci_glb_write(REG_PMU_APB_PD_CP0_ARM9_0_CFG, val, -1);
+ sci_glb_set(REG_PMU_APB_CP_SOFT_RST, 1 << 0);//reset cp0
+ udelay(200);
+ sci_glb_clr(REG_PMU_APB_PD_CP0_SYS_CFG, 1 << 25);//power on cp0
+ mdelay(4);
+ sci_glb_clr(REG_PMU_APB_PD_CP0_SYS_CFG, 1 << 28);//close cp0 force sleep
+ mdelay(2);
+ sci_glb_clr(REG_PMU_APB_CP_SOFT_RST, 1 << 0);//release cp0
+
+ wait_cp_run();
+#endif
+
+#ifdef CONFIG_SCX35_DMC_FREQ_CP1 //dfs is at cp1
+ cp_code_init();
+ val = BIT_PD_CP1_ARM9_AUTO_SHUTDOWN_EN;
+ val |= BITS_PD_CP1_ARM9_PWR_ON_DLY(0x8);
+ val |= BITS_PD_CP1_ARM9_PWR_ON_SEQ_DLY(0x6);
+ val |= BITS_PD_CP1_ARM9_ISO_ON_DLY(0x2);
+ sci_glb_write(REG_PMU_APB_PD_CP1_ARM9_CFG, val, -1);
+ sci_glb_set(REG_PMU_APB_CP_SOFT_RST, 1 << 1);//reset cp1
+ udelay(200);
+ sci_glb_clr(REG_PMU_APB_PD_CP1_SYS_CFG, 1 << 25);//power on cp1
+ mdelay(4);
+ sci_glb_clr(REG_PMU_APB_PD_CP1_SYS_CFG, 1 << 28);//close cp1 force sleep
+ mdelay(2);
+ sci_glb_clr(REG_PMU_APB_CP_SOFT_RST, 1 << 1);//release cp1
+
+ wait_cp_run();
+#endif
+
+#ifdef CONFIG_SCX35_DMC_FREQ_CP2 //dfs is at cp2
+ cp_code_init();
+ val = BIT_PD_CP2_ARM9_AUTO_SHUTDOWN_EN;
+ val |= BITS_PD_CP2_ARM9_PWR_ON_DLY(0x8);
+ val |= BITS_PD_CP2_ARM9_PWR_ON_SEQ_DLY(0x4);
+ val |= BITS_PD_CP2_ARM9_ISO_ON_DLY(0x2);
+ sci_glb_write(REG_PMU_APB_PD_CP2_ARM9_CFG, val, -1);
+
+ sci_glb_set(REG_PMU_APB_CP_SOFT_RST, 1 << 2);//reset cp2
+ udelay(200);
+ sci_glb_clr(REG_PMU_APB_PD_CP2_SYS_CFG, 1 << 25);//power on cp2
+ mdelay(4);
+ sci_glb_clr(REG_PMU_APB_PD_CP2_SYS_CFG, 1 << 28);//close cp2 force sleep
+ mdelay(2);
+ sci_glb_clr(REG_PMU_APB_CP_SOFT_RST, 1 << 2);//reset cp2
+
+ wait_cp_run();
+#endif
+}
+#endif
+
+
+static int dmcfreq_pm_notifier_do(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ unsigned long irq_flags;
+ printk("*** %s, event:0x%x ***\n", __func__, event );
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ /*
+ * DMC must be set 200MHz before deep sleep in ES chips
+ */
+ emc_clk_set(200, EMC_FREQ_NORMAL_SWITCH_SENE); //nomarl switch to tdpll 192
+ emc_clk_set(200, EMC_FREQ_DEEP_SLEEP_SENE); //deep sleep tdpll192 to dpll 192
+ return NOTIFY_OK;
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ /* Reactivate */
+ emc_clk_set(200, EMC_FREQ_RESUME_SENE); //resume dpll192 -- tdpll192
+ emc_clk_set(max_clk, EMC_FREQ_NORMAL_SWITCH_SENE); //nomarl tdpll192 -- dpll332
+ return NOTIFY_OK;
+ }
+ printk("*** %s, event:0x%x done***\n", __func__, event );
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block dmcfreq_pm_notifier = {
+ .notifier_call = dmcfreq_pm_notifier_do,
+};
+
+
+static int __init emc_early_suspend_init(void)
+{
+ dpll_rel_cfg_bak = sci_glb_read(REG_PMU_APB_DPLL_REL_CFG, -1);
+ max_clk = get_spl_emc_clk_set();
+ chip_id = __raw_readl(REG_AON_APB_CHIP_ID);
+ __emc_timing_reg_init();
+#ifndef CONFIG_SCX35_DMC_FREQ_AP
+ cp_init();
+#else
+ int ret;
+ emc_dfs_code_copy((u8 *)SPRD_IRAM0H_BASE);
+ ret = ioremap_page_range(SPRD_IRAM0H_PHYS, SPRD_IRAM0H_PHYS+SZ_4K, SPRD_IRAM0H_PHYS, PAGE_KERNEL_EXEC);
+ if(ret){
+ printk("ioremap_page_range err %d\n", ret);
+ BUG();
+ }
+#endif
+ /*
+ * if DFS is not configurated, we keep ddr 200MHz when screen off
+ */
+#ifndef CONFIG_SPRD_SCX35_DMC_FREQ
+ register_pm_notifier(&dmcfreq_pm_notifier);
+ //register_early_suspend(&emc_early_suspend_desc);
+#endif
+ emc_debugfs_creat();
+#ifdef EMC_FREQ_AUTO_TEST
+ __emc_freq_test();
+#endif
+
+ return 0;
+}
+static void __exit emc_early_suspend_exit(void)
+{
+#ifndef CONFIG_SPRD_SCX35_DMC_FREQ
+ unregister_pm_notifier(&dmcfreq_pm_notifier);
+ //unregister_early_suspend(&emc_early_suspend_desc);
+#endif
+}
+
+module_init(emc_early_suspend_init);
+module_exit(emc_early_suspend_exit);
diff --git a/drivers/platform/sprd/dmc_freq_28nm.c b/drivers/platform/sprd/dmc_freq_28nm.c
new file mode 100644
index 00000000..3b26dc24
--- /dev/null
+++ b/drivers/platform/sprd/dmc_freq_28nm.c
@@ -0,0 +1,657 @@
+
+#ifdef CONFIG_ARCH_SCX30G
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <soc/sprd/common.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci.h>
+#include <linux/earlysuspend.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <linux/random.h>
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/cacheflush.h>
+#include <asm/suspend.h>
+#include <linux/vmalloc.h>
+#include <linux/printk.h>
+#include <soc/sprd/__sc8830_dmc_dfs.h>
+#include "soc/sprd/chip_x30g/dram_phy_28nm.h"
+
+static volatile u32 mutex_flg = 0;
+
+/* other PLL clock srv, t-shark is used 384 for tdpll */
+#define PLAT_CLK 384
+
+#define DDR_TIMING_REG_VAL_ADDR (SPRD_IRAM0H_BASE + 0xc0c)
+#define DDR_TIMING_CALC_VAL_ADDR (SPRD_IRAM0H_BASE + 0xF80)
+//#define UMCTL_REG_BASE (0x30000000)
+//#define PUBL_REG_BASE (0x30010000)
+
+//SPRD_LPDDR2_BASE
+//SPRD_LPDDR2_PHY_BASE
+
+#define NINT(FREQ,REFIN) (FREQ/REFIN)
+#define KINT(FREQ,REFIN) ((FREQ-(FREQ/REFIN)*REFIN)*1048576/REFIN)
+
+#define REG32(x) (*((volatile u32 *)(x)))
+
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+static void emc_dfs_code_copy(u8 * dest);
+static int emc_dfs_call(unsigned long flag);
+void emc_dfs_main(unsigned long flag);
+#endif
+#ifdef EMC_FREQ_AUTO_TEST
+extern void __emc_freq_test(void);
+
+//static u32 get_sys_cnt(void)
+//{
+// return __raw_readl(SPRD_GPTIMER_BASE + 0x44);
+//}
+#endif
+static u32 dpll_clk_get(void)
+{
+ u32 clk = 0;
+ u32 nint, kint, refin, pnt = 0 ;
+ u32 reg, div_s, sdm_en, n;
+
+ reg = sci_glb_read(REG_AON_APB_DPLL_CFG1, -1);
+ kint = ((reg >> 12) & 0xFFFFF);
+ nint = (reg & 0x3F);
+ div_s = (reg >> 10) & 0x01;
+ sdm_en = (reg >> 6) & 0x01;
+
+ reg = sci_glb_read(REG_AON_APB_DPLL_CFG, -1);
+ n = reg & 0x3F;
+ if((reg & 0x03000000) == 0x00000000) {
+ refin = 2;
+ }
+ if((reg & 0x03000000) == 0x01000000) {
+ refin = 4;
+ }
+ if((reg & 0x03000000) == 0x02000000) {
+ refin = 13;
+ }
+ if((reg & 0x03000000) == 0x03000000) {
+ refin = 26;
+ }
+
+ if ((div_s != 0) && (sdm_en != 0)) {
+ if (((kint * refin ) & 0xFFFFF) >= 0x80000) {
+ pnt = 1;
+ }
+ else {
+ pnt = 0;
+ }
+ kint = ((kint * refin) >> 20) + pnt;
+ clk = nint * refin + kint;
+ }
+ else if ((div_s != 0) && (sdm_en == 0)) {
+ clk = nint * refin;
+ }
+ else/* if (div_s == 0) */ {
+ clk = refin * n;
+ }
+
+ return clk;
+}
+
+u32 emc_clk_get(void)
+{
+ u32 pll_clk;
+ u32 div;
+ u32 reg_val;
+ u32 sel;
+ u32 clk;
+ reg_val = sci_glb_read(REG_AON_CLK_EMC_CFG, -1);
+ sel = reg_val & 0x3;
+ div = (reg_val >> 8) & 0x3;
+ switch(sel) {
+ case 0:
+ pll_clk = 26;
+ break;
+ case 1:
+ pll_clk = 624;
+ break;
+ case 2:
+ pll_clk = 768;
+ break;
+ case 3:
+ pll_clk = dpll_clk_get();
+ break;
+ default:
+ break;
+ }
+
+ clk = (pll_clk / (div + 1)) >> 1;
+ return clk;
+}
+EXPORT_SYMBOL(emc_clk_get);
+
+static void dpll_clk_set(u32 clk)
+{
+ volatile u32 reg = 0;
+ u32 refin = 0;
+
+ if(dpll_clk_get() == clk) {
+ return;
+ }
+
+ /* get refin value */
+ refin = 26;
+
+ /* set kint, nint */
+ reg = sci_glb_read(REG_AON_APB_DPLL_CFG1, -1);
+ reg |= 1 << 10; /* set fractional divider */
+ reg &=~(0xfffff<<12 | 0x3f);
+ reg |= (KINT(clk, refin) & 0xfffff) << 12;
+ reg |= (NINT(clk, refin)) & 0x3f;
+
+ if (0 == (reg & 0xfffff000)) {
+ reg &= ~(1<<6); /* sdm_en disable */
+ } else {
+ reg |= 1<<6; /* sdm_en */
+ }
+
+ sci_glb_write(REG_AON_APB_DPLL_CFG1, reg, -1);
+
+ /* Set REFIN */
+ reg = sci_glb_read(REG_AON_APB_DPLL_CFG,-1);
+ reg &= ~(BITS_DPLL_REFIN(0x03));
+ if (refin == 26) {
+ reg |= BITS_DPLL_REFIN(0x03);
+ }
+ else if (refin == 13){
+ reg |= BITS_DPLL_REFIN(0x02);
+ }
+/*
+ else if (refin = 4){
+ reg |= BITS_DPLL_REFIN(0x02);
+ }
+ else if (refin = 2){
+ }
+*/
+ sci_glb_write(REG_AON_APB_DPLL_CFG, reg, -1);
+
+ udelay(100);
+}
+
+static u32 calc_tprd(u32 reg_addr, u32 new_clk, u32 cur_clk, u32 init_val)
+{
+ u32 tprd, reg;
+
+ reg = sci_glb_read(reg_addr ,-1);
+ tprd = (reg >> 8) & 0xFF;
+ tprd = tprd * cur_clk / new_clk;
+
+ reg = (init_val & (~0xFF00));
+ reg |= ((tprd & 0xFF) << 8 );
+
+ return reg;
+}
+
+static void emc_calc_phy_param(u32 new_clk)
+{
+ ddr_dfs_v2_t *init = (ddr_dfs_v2_t *)DDR_TIMING_REG_VAL_ADDR;
+ publ_calc_t *calc = (publ_calc_t *)DDR_TIMING_CALC_VAL_ADDR;
+ u32 cur_clk, i;
+ u32 cur_tprd, init_tprd;
+ u32 acd, r0wld, r1wld, wdqd, rdqsd, rdqsnd, r0dqsgd, r1dqsgd;
+
+ for (i = 0; i < 5; i++) {
+ if (init->ddr_clk == new_clk) {
+ break;
+ }
+ init++;
+ }
+ if (i == 5) {
+ return ;
+ }
+
+ cur_clk = emc_clk_get();
+ /* get register acmdlr.tprd [15:8]*/
+ calc->publ_acmdlr = calc_tprd((SPRD_LPDDR2_PHY_BASE +0x38),
+ new_clk, cur_clk, init->publ_acmdlr);
+ /* get register dx0mdlr.tprd [15:8]*/
+ calc->publ_dx0mdlr = calc_tprd((SPRD_LPDDR2_PHY_BASE +0x2C4),
+ new_clk, cur_clk, init->publ_dx0mdlr);
+ /* get register dx1mdlr.tprd [15:8]*/
+ calc->publ_dx1mdlr = calc_tprd((SPRD_LPDDR2_PHY_BASE +0x344),
+ new_clk, cur_clk, init->publ_dx1mdlr);
+ /* get register dx2mdlr.tprd [15:8]*/
+ calc->publ_dx2mdlr = calc_tprd((SPRD_LPDDR2_PHY_BASE +0x3C4),
+ new_clk, cur_clk, init->publ_dx2mdlr);
+ /* get register dx3mdlr.tprd [15:8]*/
+ calc->publ_dx3mdlr = calc_tprd((SPRD_LPDDR2_PHY_BASE +0x444),
+ new_clk, cur_clk, init->publ_dx3mdlr);
+
+ /* get alcdlr.acd [7:0] */
+ cur_tprd = (calc->publ_acmdlr >> 8 ) & 0xFF;
+ init_tprd = (init->publ_acmdlr >> 8) & 0xFF;
+ if (init_tprd == 0) {
+ printk("[emc_calc_phy_param] publ_acmdlr.init_tprd is 0x%d, error!\n\r", init_tprd);
+ return;
+ }
+ acd = (init->publ_aclcdlr & 0xFF) * cur_tprd / init_tprd;
+ calc->publ_aclcdlr = (init->publ_aclcdlr & (~0xFF)) | (acd & 0xFF);
+
+ /* DX0LCDLR0~3 */
+ cur_tprd = (calc->publ_dx0mdlr >> 8 ) & 0xFF;
+ init_tprd = (init->publ_dx0mdlr >> 8) & 0xFF;
+ if (init_tprd == 0) {
+ printk("[emc_calc_phy_param] publ_dx0mdlr.init_tprd is 0x%d, error!\n\r", init_tprd);
+ return;
+ }
+ r0wld = ((init->publ_dx0lcdlr0 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1wld = (((init->publ_dx0lcdlr0 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx0lcdlr0 = (init->publ_dx0lcdlr0 & (~0xFFFF)) | (r1wld << 8) | r0wld;
+
+ wdqd = ((init->publ_dx0lcdlr1 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsd = (((init->publ_dx0lcdlr1 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsnd = (((init->publ_dx0lcdlr1 >> 16) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx0lcdlr1 = (rdqsnd << 16) | (rdqsd << 8) | wdqd;
+
+ r0dqsgd = ((init->publ_dx0lcdlr2 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1dqsgd = (((init->publ_dx0lcdlr2 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx0lcdlr2 = (init->publ_dx0lcdlr2 & (~0xFFFF)) | (r1dqsgd << 8) | r0dqsgd;
+
+ /* DX1LCDLR0~3 */
+ cur_tprd = (calc->publ_dx1mdlr >> 8 ) & 0xFF;
+ init_tprd = (init->publ_dx1mdlr >> 8) & 0xFF;
+ if (init_tprd == 0) {
+ printk("[emc_calc_phy_param] publ_dx1mdlr.init_tprd is 0x%d, error!\n\r", init_tprd);
+ return;
+ }
+ r0wld = ((init->publ_dx1lcdlr0 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1wld = (((init->publ_dx1lcdlr0 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx1lcdlr0 = (init->publ_dx1lcdlr0 & (~0xFFFF)) | (r1wld << 8) | r0wld;
+
+ wdqd = ((init->publ_dx1lcdlr1 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsd = (((init->publ_dx1lcdlr1 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsnd = (((init->publ_dx1lcdlr1 >> 16) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx1lcdlr1 = (rdqsnd << 16) | (rdqsd << 8) | wdqd;
+
+ r0dqsgd = ((init->publ_dx1lcdlr2 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1dqsgd = (((init->publ_dx1lcdlr2 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx1lcdlr2 = (init->publ_dx1lcdlr2 & (~0xFFFF)) | (r1dqsgd << 8) | r0dqsgd;
+
+ /* DX2LCDLR0~3 */
+ cur_tprd = (calc->publ_dx2mdlr >> 8 ) & 0xFF;
+ init_tprd = (init->publ_dx2mdlr >> 8) & 0xFF;
+ if (init_tprd == 0) {
+ printk("[emc_calc_phy_param] publ_dx2mdlr.init_tprd is 0x%d, error!\n\r", init_tprd);
+ return;
+ }
+ r0wld = ((init->publ_dx2lcdlr0 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1wld = (((init->publ_dx2lcdlr0 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx2lcdlr0 = (init->publ_dx2lcdlr0 & (~0xFFFF)) | (r1wld << 8) | r0wld;
+
+ wdqd = ((init->publ_dx2lcdlr1 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsd = (((init->publ_dx2lcdlr1 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsnd = (((init->publ_dx2lcdlr1 >> 16) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx2lcdlr1 = (rdqsnd << 16) | (rdqsd << 8) | wdqd;
+
+ r0dqsgd = ((init->publ_dx2lcdlr2 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1dqsgd = (((init->publ_dx2lcdlr2 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx2lcdlr2 = (init->publ_dx2lcdlr2 & (~0xFFFF)) | (r1dqsgd << 8) | r0dqsgd;
+
+ /* DX3LCDLR0~3 */
+ cur_tprd = (calc->publ_dx3mdlr >> 8 ) & 0xFF;
+ init_tprd = (init->publ_dx3mdlr >> 8) & 0xFF;
+ if (init_tprd == 0) {
+ printk("[emc_calc_phy_param] publ_dx3mdlr.init_tprd is 0x%d, error!\n\r", init_tprd);
+ return;
+ }
+ r0wld = ((init->publ_dx3lcdlr0 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1wld = (((init->publ_dx3lcdlr0 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx3lcdlr0 = (init->publ_dx3lcdlr0 & (~0xFFFF)) | (r1wld << 8) | r0wld;
+
+ wdqd = ((init->publ_dx3lcdlr1 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsd = (((init->publ_dx3lcdlr1 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ rdqsnd = (((init->publ_dx3lcdlr1 >> 16) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx3lcdlr1 = (rdqsnd << 16) | (rdqsd << 8) | wdqd;
+
+ r0dqsgd = ((init->publ_dx3lcdlr2 & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ r1dqsgd = (((init->publ_dx3lcdlr2 >> 8) & 0xFF) * cur_tprd / init_tprd) & 0xFF;
+ calc->publ_dx3lcdlr2 = (init->publ_dx3lcdlr2 & (~0xFFFF)) | (r1dqsgd << 8) | r0dqsgd;
+
+ /* write new_clk in struct */
+ calc->ddr_clk = new_clk;
+}
+
+static u32 __emc_clk_set(u32 clk)
+{
+ u32 flag = 0;
+
+ /* check timing params */
+ flag = (clk << EMC_CLK_FREQ_OFFSET);
+
+ emc_calc_phy_param(clk);
+
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+ flush_cache_all();
+ cpu_suspend(flag, emc_dfs_call);
+#endif
+ if(emc_clk_get() != clk) {
+ printk("[__emc_clk_set] error, set clk = %d, get clk = %d\n\r", clk, emc_clk_get());
+ }
+ return 0;
+}
+
+static u32 get_dpll_from_clk (u32 new_clk)
+{
+ u32 dpll = 0;
+
+ switch (new_clk){
+ case 200:
+ case 400:
+ dpll = 800;
+ break;
+ case 384:
+ /* this clock from tdpll */
+ break;
+ case 466:
+ dpll = 932;
+ break;
+ case 533:
+ dpll = 1066;
+ break;
+ default :
+ break;
+ }
+
+ return dpll;
+}
+/* How To look PLL Setting
+ reg name val bit
+ MPLL_CFG reFin [25:24]
+ MPLL_CFG N [5:0]
+
+ MPLL_CFG1 div_s [10]
+ MPLL_CFG1 sdm_en [6]
+ MPLL_CFG1 Nint [5:0]
+ MPLL_CFG1 Kint [31:12]
+ div_s = 1 sdm_en = 1 Fout = Fin * ( Nint + Kint/1048576)
+ div_s = 1 sdm_en = 0 Fout = Fin * Nint
+ div_s = 0 sdm_en = x Fout = Fin * N */
+/* reg[0x402D0024] :*/
+/* [9:8] : clk_emc_div(clk_div= clk_src/(div+1)) */
+/* [1:0] : clk_emc_sel (0:pub 26m, 1: CPLL, 2:TDPLL, 3:DPLL)*/
+static u32 check_need_plat_clk(u32 new_clk)
+{
+ u32 sel, cur_dpll, nd_dpll = 0;
+
+ if (PLAT_CLK == new_clk) {
+ return 0;
+ }
+
+ sel = sci_glb_read(REG_AON_CLK_EMC_CFG, -1);
+ sel &= 0x3;
+ /* if current not used dpll return 0 don't need platform clock */
+ if (sel != 0x03) {
+ return 0;
+ }
+
+ /* get dpll clock to check */
+ cur_dpll = dpll_clk_get();
+ nd_dpll = get_dpll_from_clk(new_clk);
+ if (nd_dpll == 0){
+ /* must be not go to this, if running, new_clk error! */
+ return 1;
+ }
+
+ if (cur_dpll == nd_dpll) {
+ return 0;
+ }
+
+ return 1;
+}
+
+u32 emc_clk_set(u32 new_clk, u32 sene)
+{
+ u32 old_clk, nd_dpll;
+#if defined (EMC_FREQ_AUTO_TEST) || defined (CONFIG_SCX35_DMC_FREQ_AP)
+ unsigned long irq_flags = 0;
+#endif
+
+/* used to get dfs hold times for debug */
+#if 0
+ u32 start_t1, end_t1;
+ static u32 max_u_time = 0;
+ u32 current_u_time;
+
+ start_t1 = get_sys_cnt();
+#endif
+
+ /* check new_clk */
+ if ((new_clk != 200) && (new_clk != 384) && (new_clk != 400)
+ && (new_clk != 466) && (new_clk != 533)) {
+ printk("[emc_clk_set] : new_clk[%d] error!\n\r", new_clk);
+ return 0;
+ }
+
+ /* make sure no other thread running this code */
+ if(mutex_flg >= 1) {
+ panic("now other thread set dmc clk\n");
+ goto out;
+ }
+ mutex_flg ++;
+
+ /* step 1: save irq and lock it */
+#if defined (EMC_FREQ_AUTO_TEST) || defined (CONFIG_SCX35_DMC_FREQ_AP)
+ local_irq_save(irq_flags);
+#endif
+
+ /* check current clock */
+ old_clk = emc_clk_get();
+ if(old_clk == new_clk) {
+ goto out;
+ }
+
+ /* step 2: check new clock need alter dpll( paltform clock ) or not */
+ if (check_need_plat_clk(new_clk)) {
+ __emc_clk_set(PLAT_CLK);
+
+ /* set dpll to need */
+ nd_dpll = get_dpll_from_clk(new_clk);
+ if (nd_dpll == 0){
+ /* must be not go to this, if running, new_clk error! */
+ goto out;
+ }
+ dpll_clk_set(nd_dpll);
+ }
+
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+ __emc_clk_set(new_clk);
+#endif
+
+out:
+#if defined (EMC_FREQ_AUTO_TEST) || defined(CONFIG_SCX35_DMC_FREQ_AP)
+ local_irq_restore(irq_flags);
+#endif
+ mutex_flg --;
+#if 0
+ end_t1 = get_sys_cnt();
+
+ current_u_time = (start_t1 - end_t1)/128;
+ if(max_u_time < current_u_time) {
+ max_u_time = current_u_time;
+ }
+ printk("emc dfs use current = %08u max %08u\n", current_u_time, max_u_time);
+#endif
+ return 0;
+}
+EXPORT_SYMBOL(emc_clk_set);
+
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+static int emc_dfs_call(unsigned long flag)
+{
+ cpu_switch_mm(init_mm.pgd, &init_mm);
+ ((int (*)(unsigned long))SPRD_IRAM0H_PHYS)(flag); //iram0h must be the first function of dfs
+ return 0;
+}
+static void emc_dfs_code_copy(u8 * dest)
+{
+ memcpy_toio((void *)dest, (void *)emc_dfs_main, 0xc00);
+}
+#endif
+
+
+static int __init emc_early_suspend_init(void)
+{
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+ int ret;
+ emc_dfs_code_copy((u8 *)SPRD_IRAM0H_BASE);
+ ret = ioremap_page_range(SPRD_IRAM0H_PHYS,
+ SPRD_IRAM0H_PHYS+SZ_4K, SPRD_IRAM0H_PHYS, PAGE_KERNEL_EXEC);
+ if (ret) {
+ printk("ioremap_page_range err %d\n", ret);
+ BUG();
+ }
+#endif
+
+#ifdef EMC_FREQ_AUTO_TEST
+ __emc_freq_test();
+#endif
+
+ return 0;
+}
+
+static void __exit emc_early_suspend_exit(void)
+{
+}
+
+#ifndef CONFIG_ARCH_SCX20
+module_init(emc_early_suspend_init);
+module_exit(emc_early_suspend_exit);
+#endif
+
+#endif
+
+#ifdef CONFIG_ARCH_SCX35L
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <soc/sprd/common.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci.h>
+#include <linux/earlysuspend.h>
+#include <soc/sprd/sci_glb_regs.h>
+
+#define DDR_TIMING_REG_VAL_ADDR (SPRD_IRAM0H_BASE + 0xc00)
+#define DDR_TIMING_CALC_VAL_ADDR (SPRD_IRAM0H_BASE + 0xF80)
+
+static u32 dpll_clk_get(void)
+{
+ u32 clk = 0;
+ u32 nint, kint, refin, pnt = 0 ;
+ u32 reg1, reg2, div_s, sdm_en, n;
+
+ reg1 = sci_glb_read(REG_AON_APB_DPLL_CFG1, -1);
+ reg2 = sci_glb_read(REG_AON_APB_DPLL_CFG2, -1);
+ kint = reg2 & 0xFFFFF;
+ nint = (reg2>>24) & 0x3F;
+ div_s = (reg1 >> 26) & 0x01;
+ sdm_en = (reg1 >> 24) & 0x01;
+ n = reg1 & 0x7ff;
+
+ switch((reg1>>18)&0x3)
+ {
+ case 0:
+ refin = 2;
+ break;
+ case 1:
+ refin = 4;
+ break;
+ case 2:
+ refin = 13;
+ break;
+ case 3:
+ refin = 26;
+ break;
+ }
+
+
+ if ((div_s != 0) && (sdm_en != 0)) {
+ if (((kint * refin ) & 0xFFFFF) >= 0x80000) {
+ pnt = 1;
+ }
+ else {
+ pnt = 0;
+ }
+ kint = ((kint * refin) >> 20) + pnt;
+ clk = nint * refin + kint;
+ }
+ else if ((div_s != 0) && (sdm_en == 0)) {
+ clk = nint * refin;
+ }
+ else/* if (div_s == 0) */ {
+ clk = refin * n;
+ }
+
+ return clk;
+}
+
+u32 emc_clk_get(void)
+{
+ u32 pll_clk;
+ u32 div;
+ u32 reg_val;
+ u32 sel;
+ u32 clk;
+ reg_val = sci_glb_read(REG_AON_CLK_EMC_CFG, -1);
+ sel = reg_val & 0x7;
+ div = (reg_val >> 8) & 0x7;
+ switch(sel) {
+ case 0:
+ pll_clk = 26;
+ break;
+ case 1:
+ pll_clk = 192;
+ break;
+ case 2:
+ pll_clk = 307;
+ break;
+ case 3:
+ pll_clk = 384;
+ break;
+ case 4:
+ pll_clk = 512;
+ break;
+ case 5:
+ pll_clk = 614;
+ break;
+ case 6:
+ pll_clk = 768;
+ break;
+ case 7:
+ pll_clk = dpll_clk_get();
+ break;
+ default:
+ break;
+ }
+
+ clk = (pll_clk / (div + 1)) >> 1;
+ return clk;
+}
+EXPORT_SYMBOL(emc_clk_get);
+#endif
diff --git a/drivers/platform/sprd/dmc_freq_ap.c b/drivers/platform/sprd/dmc_freq_ap.c
new file mode 100644
index 00000000..ac8aff36
--- /dev/null
+++ b/drivers/platform/sprd/dmc_freq_ap.c
@@ -0,0 +1,838 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/input/matrix_keypad.h>
+#include <mach/hardware.h>
+#include <mach/__sc8830_dmc_dfs.h>
+#define REG32(x) (*((volatile uint32 *)(x)))
+#define REG16(x) (*((volatile uint16 *)(x)))
+#define UMCTL2_REG_GET(reg_addr) (*((volatile uint32 *)(reg_addr)))
+#define UMCTL2_REG_SET( reg_addr, value ) *(volatile uint32 *)(reg_addr) = value
+#define UMCTL_REG_BASE (0x30000000)
+#define PUBL_REG_BASE (0x30010000)
+
+#define UMCTL2_REG_(x) (UMCTL_REG_BASE+(x))
+
+#define SPRD_UART1_PHYS (0x70100000)
+/*
+ *uMCTL2 DDRC registers
+*/
+#define UMCTL_MSTR UMCTL2_REG_(0x0000) /*master*/
+#define UMCTL_STAT UMCTL2_REG_(0x0004) /*oeration mode status*/
+#define UMCTL_MRCTRL0 UMCTL2_REG_(0x0010)/*mode register read/werite control*/
+#define UMCTL_MRCTRL1 UMCTL2_REG_(0x0014)
+#define UMCTL_MRSTAT UMCTL2_REG_(0x0018)/*mode register read/weite status*/
+
+#define UMCTL_DERATEEN UMCTL2_REG_(0x0020)/*temperature derate enable*/
+#define UMCTL_DERATEINT UMCTL2_REG_(0x0024)/*temperature derate interval*/
+#define UMCTL_PWRCTL UMCTL2_REG_(0x0030)/*low power control*/
+#define UMCTL_PWRTMG UMCTL2_REG_(0x0034)/*low power timing*/
+
+#define UMCTL_RFSHCTL0 UMCTL2_REG_(0x0050)/*refresh control0*/
+#define UMCTL_RFSHCTL1 UMCTL2_REG_(0x0054)
+#define UMCTL_RFSHCTL2 UMCTL2_REG_(0x0058)
+#define UMCTL_RFSHCTL3 UMCTL2_REG_(0x0060)
+#define UMCTL_RFSHTMG UMCTL2_REG_(0x0064)/*refresh Timing*/
+#define UMCTL_ECCCFG0 UMCTL2_REG_(0x0070)/*ecc configuration*/
+#define UMCTL_ECCCFG1 UMCTL2_REG_(0x0074)
+#define UMCTL_ECCSTAT UMCTL2_REG_(0x0078)/*ecc status*/
+#define UMCTL_ECCCLR UMCTL2_REG_(0x007C)/*ecc clear*/
+#define UMCTL_ECCERRCNT UMCTL2_REG_(0x0080)/*ecc error counter*/
+
+#define UMCTL_ECCADDR0 UMCTL2_REG_(0x0084)/*ecc corrected error address reg0*/
+#define UMCTL_ECCADDR1 UMCTL2_REG_(0x0088)
+#define UMCTL_ECCSYN0 UMCTL2_REG_(0x008C)/*ecc corected syndrome reg0*/
+#define UMCTL_ECCSYN1 UMCTL2_REG_(0x0090)
+#define UMCTL_ECCSYN2 UMCTL2_REG_(0x0094)
+#define UMCTL_ECCBITMASK0 UMCTL2_REG_(0x0098)
+#define UMCTL_ECCBITMASK1 UMCTL2_REG_(0x009C)
+#define UMCTL_ECCBITMASK2 UMCTL2_REG_(0x00A0)
+#define UMCTL_ECCUADDR0 UMCTL2_REG_(0x00A4)/*ecc uncorrected error address reg0*/
+#define UMCTL_ECCUADDR1 UMCTL2_REG_(0x00A8)
+#define UMCTL_ECCUSYN0 UMCTL2_REG_(0x00AC)/*ecc UNcorected syndrome reg0*/
+#define UMCTL_ECCUSYN1 UMCTL2_REG_(0x00B0)
+#define UMCTL_ECCUSYN2 UMCTL2_REG_(0x00B4)
+#define UMCTL_ECCPOISONADDR0 UMCTL2_REG_(0x00B8)/*ecc data poisoning address reg0*/
+#define UMCTL_ECCPOISONADDR1 UMCTL2_REG_(0x00BC)
+
+
+#define UMCTL_PARCTL UMCTL2_REG_(0x00C0)/*parity control register*/
+#define UMCTL_PARSTAT UMCTL2_REG_(0x00C4)/*parity status register*/
+
+#define UMCTL_INIT0 UMCTL2_REG_(0x00D0)/*SDRAM initialization register0*/
+#define UMCTL_INIT1 UMCTL2_REG_(0x00D4)
+#define UMCTL_INIT2 UMCTL2_REG_(0x00D8)
+#define UMCTL_INIT3 UMCTL2_REG_(0x00DC)
+#define UMCTL_INIT4 UMCTL2_REG_(0x00E0)
+#define UMCTL_INIT5 UMCTL2_REG_(0x00E4)
+
+
+#define UMCTL_DIMMCTL UMCTL2_REG_(0x00F0)/*DIMM control register*/
+#define UMCTL_RANKCTL UMCTL2_REG_(0x00F4)
+
+#define UMCTL_DRAMTMG0 UMCTL2_REG_(0x0100)/*SDRAM timing register0*/
+#define UMCTL_DRAMTMG1 UMCTL2_REG_(0x0104)
+#define UMCTL_DRAMTMG2 UMCTL2_REG_(0x0108)
+#define UMCTL_DRAMTMG3 UMCTL2_REG_(0x010C)
+#define UMCTL_DRAMTMG4 UMCTL2_REG_(0x0110)
+#define UMCTL_DRAMTMG5 UMCTL2_REG_(0x0114)
+#define UMCTL_DRAMTMG6 UMCTL2_REG_(0x0118)
+#define UMCTL_DRAMTMG7 UMCTL2_REG_(0x011C)
+#define UMCTL_DRAMTMG8 UMCTL2_REG_(0x0120)
+
+#define UMCTL_ZQCTL0 UMCTL2_REG_(0x0180)/*ZQ control register0*/
+#define UMCTL_ZQCTL1 UMCTL2_REG_(0x0184)
+#define UMCTL_ZQCTL2 UMCTL2_REG_(0x0188)
+#define UMCTL_ZQSTAT UMCTL2_REG_(0x018C)
+
+
+#define UMCTL_DFITMG0 UMCTL2_REG_(0x0190)/*DFI timing register0*/
+#define UMCTL_DFITMG1 UMCTL2_REG_(0x0194)
+
+#define UMCTL_DFILPCFG0 UMCTL2_REG_(0x0198)/*DFI low power configuration*/
+#define UMCTL_DFIUPD0 UMCTL2_REG_(0x01A0)/*DFI update register0*/
+#define UMCTL_DFIUPD1 UMCTL2_REG_(0x01A4)
+#define UMCTL_DFIUPD2 UMCTL2_REG_(0x01A8)
+#define UMCTL_DFIUPD3 UMCTL2_REG_(0x01AC)
+
+#define UMCTL_DFIMISC UMCTL2_REG_(0x01B0)
+
+#define UMCTL_TRAINCTL0 UMCTL2_REG_(0x01D0)/*PHY eval training control reg0*/
+#define UMCTL_TRAINCTL1 UMCTL2_REG_(0x01D4)
+#define UMCTL_TRAINCTL2 UMCTL2_REG_(0x01D8)
+#define UMCTL_TRAINSTAT UMCTL2_REG_(0x01DC)
+
+#define UMCTL_ADDRMAP0 UMCTL2_REG_(0x0200)/*address map register0*/
+#define UMCTL_ADDRMAP1 UMCTL2_REG_(0x0204)
+#define UMCTL_ADDRMAP2 UMCTL2_REG_(0x0208)
+#define UMCTL_ADDRMAP3 UMCTL2_REG_(0x020C)
+#define UMCTL_ADDRMAP4 UMCTL2_REG_(0x0210)
+#define UMCTL_ADDRMAP5 UMCTL2_REG_(0x0214)
+#define UMCTL_ADDRMAP6 UMCTL2_REG_(0x0218)
+
+#define UMCTL_ODTCFG UMCTL2_REG_(0x0240)/*ODT configuration register*/
+#define UMCTL_ODTMAP UMCTL2_REG_(0x0244)
+
+#define UMCTL_SCHED UMCTL2_REG_(0x0250)/*scheduler control register*/
+#define UMCTL_PERFHPR0 UMCTL2_REG_(0x0258)/*high priority read CAM reg0*/
+#define UMCTL_PERFHPR1 UMCTL2_REG_(0x025C)
+#define UMCTL_PERFLPR0 UMCTL2_REG_(0x0260)
+#define UMCTL_PERFLPR1 UMCTL2_REG_(0x0264)
+
+#define UMCTL_PERFWR0 UMCTL2_REG_(0x0268)/*write CAM reg0*/
+#define UMCTL_PERFWR1 UMCTL2_REG_(0x026C)
+#define UMCTL_DBG0 UMCTL2_REG_(0x0300)/*debug register0*/
+#define UMCTL_DBG1 UMCTL2_REG_(0x0304)
+#define UMCTL_DBGCAM UMCTL2_REG_(0x0308)/*cam debug register*/
+
+
+/*
+ *uMCTL2 Multi-Port registers
+*/
+#define UMCTL_PCCFG UMCTL2_REG_(0x0400)/*port common configuration*/
+
+#define UMCTL_PCFGR_0 UMCTL2_REG_(0x0404+(0x00)*0xB0)/*Port n configuration read reg*/
+#define UMCTL_PCFGR_1 UMCTL2_REG_(0x0404+(0x01)*0xB0)
+#define UMCTL_PCFGR_2 UMCTL2_REG_(0x0404+(0x02)*0xB0)
+#define UMCTL_PCFGR_3 UMCTL2_REG_(0x0404+(0x03)*0xB0)
+#define UMCTL_PCFGR_4 UMCTL2_REG_(0x0404+(0x04)*0xB0)
+#define UMCTL_PCFGR_5 UMCTL2_REG_(0x0404+(0x05)*0xB0)
+#define UMCTL_PCFGR_6 UMCTL2_REG_(0x0404+(0x06)*0xB0)
+#define UMCTL_PCFGR_7 UMCTL2_REG_(0x0404+(0x07)*0xB0)
+#define UMCTL_PCFGR_8 UMCTL2_REG_(0x0404+(0x08)*0xB0)
+#define UMCTL_PCFGR_9 UMCTL2_REG_(0x0404+(0x09)*0xB0)
+#define UMCTL_PCFGR_10 UMCTL2_REG_(0x0404+(0x0A)*0xB0)
+#define UMCTL_PCFGR_11 UMCTL2_REG_(0x0404+(0x0B)*0xB0)
+#define UMCTL_PCFGR_12 UMCTL2_REG_(0x0404+(0x0C)*0xB0)
+#define UMCTL_PCFGR_13 UMCTL2_REG_(0x0404+(0x0D)*0xB0)
+#define UMCTL_PCFGR_14 UMCTL2_REG_(0x0404+(0x0E)*0xB0)
+#define UMCTL_PCFGR_15 UMCTL2_REG_(0x0404+(0x0F)*0xB0)
+
+
+#define UMCTL_PCFGW_0 UMCTL2_REG_(0x0408+(0x00)*0xB0)/*Port n configuration write reg*/
+#define UMCTL_PCFGW_1 UMCTL2_REG_(0x0408+(0x01)*0xB0)
+#define UMCTL_PCFGW_2 UMCTL2_REG_(0x0408+(0x02)*0xB0)
+#define UMCTL_PCFGW_3 UMCTL2_REG_(0x0408+(0x03)*0xB0)
+#define UMCTL_PCFGW_4 UMCTL2_REG_(0x0408+(0x04)*0xB0)
+#define UMCTL_PCFGW_5 UMCTL2_REG_(0x0408+(0x05)*0xB0)
+#define UMCTL_PCFGW_6 UMCTL2_REG_(0x0408+(0x06)*0xB0)
+#define UMCTL_PCFGW_7 UMCTL2_REG_(0x0408+(0x07)*0xB0)
+#define UMCTL_PCFGW_8 UMCTL2_REG_(0x0408+(0x08)*0xB0)
+#define UMCTL_PCFGW_9 UMCTL2_REG_(0x0408+(0x09)*0xB0)
+#define UMCTL_PCFGW_10 UMCTL2_REG_(0x0408+(0x0A)*0xB0)
+#define UMCTL_PCFGW_11 UMCTL2_REG_(0x0408+(0x0B)*0xB0)
+#define UMCTL_PCFGW_12 UMCTL2_REG_(0x0408+(0x0C)*0xB0)
+#define UMCTL_PCFGW_13 UMCTL2_REG_(0x0408+(0x0D)*0xB0)
+#define UMCTL_PCFGW_14 UMCTL2_REG_(0x0408+(0x0E)*0xB0)
+#define UMCTL_PCFGW_15 UMCTL2_REG_(0x0408+(0x0F)*0xB0)
+
+
+#define UMCTL_PORT_EN_0 0xFFFF0000//UMCTL2_REG_(0x0490+(0x00)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_1 0xFFFF0000//UMCTL2_REG_(0x0490+(0x01)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_2 0xFFFF0000//UMCTL2_REG_(0x0490+(0x02)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_3 0xFFFF0000//UMCTL2_REG_(0x0490+(0x03)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_4 0xFFFF0000//UMCTL2_REG_(0x0490+(0x04)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_5 0xFFFF0000//UMCTL2_REG_(0x0490+(0x05)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_6 0xFFFF0000//UMCTL2_REG_(0x0490+(0x06)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_7 0xFFFF0000//UMCTL2_REG_(0x0490+(0x07)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_8 0xFFFF0000//UMCTL2_REG_(0x0490+(0x08)*0xB0)/*Port n enable reg*/
+#define UMCTL_PORT_EN_9 0xFFFF0000//UMCTL2_REG_(0x0490+(0x09)*0xB0)/*Port n enable reg*/
+
+
+/*
+#define UMCTL2_PCFGIDMASKCH_m_N
+#define UMCTL2_PCFGIDVALUECH_m_N
+*/
+
+
+/*
+ *Refer to PUBL databook 1.44a for detail,Chapter3.3 Registers.
+*/
+#define PUBL_RIDR (PUBL_REG_BASE+0x00*4) // R - Revision Identification Register
+#define PUBL_PIR (PUBL_REG_BASE+0x01*4) // R/W - PHY Initialization Register
+#define PUBL_PGCR (PUBL_REG_BASE+0x02*4) // R/W - PHY General Configuration Register
+#define PUBL_PGSR (PUBL_REG_BASE+0x03*4) // R - PHY General Status Register
+#define PUBL_DLLGCR (PUBL_REG_BASE+0x04*4) // R/W - DLL General Control Register
+#define PUBL_ACDLLCR (PUBL_REG_BASE+0x05*4) // R/W - AC DLL Control Register
+#define PUBL_PTR0 (PUBL_REG_BASE+0x06*4) // R/W - PHY Timing Register 0
+#define PUBL_PTR1 (PUBL_REG_BASE+0x07*4) // R/W - PHY Timing Register 1
+#define PUBL_PTR2 (PUBL_REG_BASE+0x08*4) // R/W - PHY Timing Register 2
+#define PUBL_ACIOCR (PUBL_REG_BASE+0x09*4) // R/W - AC I/O Configuration Register
+#define PUBL_DXCCR (PUBL_REG_BASE+0x0A*4) // R/W - DATX8 I/O Configuration Register
+#define PUBL_DSGCR (PUBL_REG_BASE+0x0B*4) // R/W - DFI Configuration Register
+#define PUBL_DCR (PUBL_REG_BASE+0x0C*4) // R/W - DRAM Configuration Register
+#define PUBL_DTPR0 (PUBL_REG_BASE+0x0D*4) // R/W - SDRAM Timing Parameters Register 0
+#define PUBL_DTPR1 (PUBL_REG_BASE+0x0E*4) // R/W - SDRAM Timing Parameters Register 1
+#define PUBL_DTPR2 (PUBL_REG_BASE+0x0F*4) // R/W - SDRAM Timing Parameters Register 2
+#define PUBL_MR0 (PUBL_REG_BASE+0x10*4) // R/W - Mode Register
+#define PUBL_MR1 (PUBL_REG_BASE+0x11*4) // R/W - Ext}ed Mode Register
+#define PUBL_MR2 (PUBL_REG_BASE+0x12*4) // R/W - Ext}ed Mode Register 2
+#define PUBL_MR3 (PUBL_REG_BASE+0x13*4) // R/W - Ext}ed Mode Register 3
+#define PUBL_ODTCR (PUBL_REG_BASE+0x14*4) // R/W - ODT Configuration Register
+#define PUBL_DTAR (PUBL_REG_BASE+0x15*4) // R/W - Data Training Address Register
+#define PUBL_DTDR0 (PUBL_REG_BASE+0x16*4) // R/W - Data Training Data Register 0
+#define PUBL_DTDR1 (PUBL_REG_BASE+0x17*4) // R/W - Data Training Data Register 1
+#define PUBL_DCUAR (PUBL_REG_BASE+0X30*4) // R/W - DCU Address Resiter
+#define PUBL_DCUDR (PUBL_REG_BASE+0x31*4) // R/W - DCU Data Register
+#define PUBL_DCURR (PUBL_REG_BASE+0x32*4) // R/W - DCU Run Register
+#define PUBL_DCULR (PUBL_REG_BASE+0x33*4) // R/W - DCU Loop Register
+#define PUBL_DCUGCR (PUBL_REG_BASE+0x34*4) // R/W - DCU General Configuration Register
+#define PUBL_DCUTPR (PUBL_REG_BASE+0x35*4) // R/W - DCU Timing Parameters Registers
+#define PUBL_DCUSR0 (PUBL_REG_BASE+0x36*4) // R - DCU Status Register 0
+#define PUBL_DCUSR1 (PUBL_REG_BASE+0x37*4) // R - DCU Status Register 1
+#define PUBL_BISTRR (PUBL_REG_BASE+0x40*4) // R/W - BIST Run Register
+#define PUBL_BISTMSKR0 (PUBL_REG_BASE+0x41*4) // R/W - BIST Mask Register 0
+#define PUBL_BISTMSKR1 (PUBL_REG_BASE+0x42*4) // R/W - BIST Mask Register 1
+#define PUBL_BISTWCR (PUBL_REG_BASE+0x43*4) // R/W - BIST Word Count Register
+#define PUBL_BISTLSR (PUBL_REG_BASE+0x44*4) // R/W - BIST LFSR Seed Register
+#define PUBL_BISTAR0 (PUBL_REG_BASE+0x45*4) // R/W - BIST Address Register 0
+#define PUBL_BISTAR1 (PUBL_REG_BASE+0x46*4) // R/W - BIST Address Register 1
+#define PUBL_BISTAR2 (PUBL_REG_BASE+0x47*4) // R/W - BIST Address Register 2
+#define PUBL_BISTUDPR (PUBL_REG_BASE+0x48*4) // R/W - BIST User Data Pattern Register
+#define PUBL_BISTGSR (PUBL_REG_BASE+0x49*4) // R - BIST General Status Register
+#define PUBL_BISTWER (PUBL_REG_BASE+0x4A*4) // R - BIST Word Error Register
+#define PUBL_BISTBER0 (PUBL_REG_BASE+0x4B*4) // R - BIST Bit Error Register 0
+#define PUBL_BISTBER1 (PUBL_REG_BASE+0x4C*4) // R - BIST Bit Error Register 1
+#define PUBL_BISTBER2 (PUBL_REG_BASE+0x4D*4) // R - BIST Bit Error Register 2
+#define PUBL_BISTWCSR (PUBL_REG_BASE+0x4E*4) // R - BIST Word Count Status Register
+#define PUBL_BISTFWR0 (PUBL_REG_BASE+0x4F*4) // R - BIST Fail Word Register 0
+#define PUBL_BISTFWR1 (PUBL_REG_BASE+0x50*4) // R - BIST Fail Word Register 1
+#define PUBL_ZQ0CR0 (PUBL_REG_BASE+0x60*4) // R/W - ZQ 0 Impedance Control Register 0
+#define PUBL_ZQ0CR1 (PUBL_REG_BASE+0x61*4) // R/W - ZQ 0 Impedance Control Register 1
+#define PUBL_ZQ0SR0 (PUBL_REG_BASE+0x62*4) // R - ZQ 0 Impedance Status Register 0
+#define PUBL_ZQ0SR1 (PUBL_REG_BASE+0x63*4) // R - ZQ 0 Impedance Status Register 1
+#define PUBL_ZQ1CR0 (PUBL_REG_BASE+0x64*4) // R/W - ZQ 1 Impedance Control Register 0
+#define PUBL_ZQ1CR1 (PUBL_REG_BASE+0x65*4) // R/W - ZQ 1 Impedance Control Register 1
+#define PUBL_ZQ1SR0 (PUBL_REG_BASE+0x66*4) // R - ZQ 1 Impedance Status Register 0
+#define PUBL_ZQ1SR1 (PUBL_REG_BASE+0x67*4) // R - ZQ 1 Impedance Status Register 1
+#define PUBL_ZQ2CR0 (PUBL_REG_BASE+0x68*4) // R/W - ZQ 2 Impedance Control Register 0
+#define PUBL_ZQ2CR1 (PUBL_REG_BASE+0x69*4) // R/W - ZQ 2 Impedance Control Register 1
+#define PUBL_ZQ2SR0 (PUBL_REG_BASE+0x6A*4) // R - ZQ 2 Impedance Status Register 0
+#define PUBL_ZQ2SR1 (PUBL_REG_BASE+0x6B*4) // R - ZQ 2 Impedance Status Register 1
+#define PUBL_ZQ3CR0 (PUBL_REG_BASE+0x6C*4) // R/W - ZQ 3 Impedance Control Register 0
+#define PUBL_ZQ3CR1 (PUBL_REG_BASE+0x6D*4) // R/W - ZQ 3 Impedance Control Register 1
+#define PUBL_ZQ3SR0 (PUBL_REG_BASE+0x6E*4) // R - ZQ 3 Impedance Status Register 0
+#define PUBL_ZQ3SR1 (PUBL_REG_BASE+0x6F*4) // R - ZQ 3 Impedance Status Register 1
+#define PUBL_DX0GCR (PUBL_REG_BASE+0x70*4) // R/W - DATX8 0 General Configuration Register
+#define PUBL_DX0GSR0 (PUBL_REG_BASE+0x71*4) // R - DATX8 0 General Status Register
+#define PUBL_DX0GSR1 (PUBL_REG_BASE+0x72*4) // R - DATX8 0 General Status Register 1
+#define PUBL_DX0DLLCR (PUBL_REG_BASE+0x73*4) // R - DATX8 0 DLL Control Register
+#define PUBL_DX0DQTR (PUBL_REG_BASE+0x74*4) // R/W - DATX8 0 DQ Timing Register
+#define PUBL_DX0DQSTR (PUBL_REG_BASE+0x75*4) // R/W - DATX8 0 DQS Timing Register
+#define PUBL_DX1GCR (PUBL_REG_BASE+0x80*4) // R - DATX8 1 General Configration Register
+#define PUBL_DX1GSR0 (PUBL_REG_BASE+0x81*4) // R - DATX8 1 General Status Register
+#define PUBL_DX1GSR1 (PUBL_REG_BASE+0x82*4) // R - DATX8 1 General Status Register
+#define PUBL_DX1DLLCR (PUBL_REG_BASE+0x83*4) // R - DATX8 1 DLL Control Register
+#define PUBL_DX1DQTR (PUBL_REG_BASE+0x84*4) // R/W - DATX8 1 DQ Timing Register
+#define PUBL_DX1DQSTR (PUBL_REG_BASE+0x85*4) // R/W - DATX8 1 DQS Timing Register
+#define PUBL_DX2GCR (PUBL_REG_BASE+0x90*4) // R - DATX8 2 General Configration Register
+#define PUBL_DX2GSR0 (PUBL_REG_BASE+0x91*4) // R - DATX8 2 General Status Register
+#define PUBL_DX2GSR1 (PUBL_REG_BASE+0x92*4) // R - DATX8 2 General Status Register
+#define PUBL_DX2DLLCR (PUBL_REG_BASE+0x93*4) // R - DATX8 2 DLL Control Register
+#define PUBL_DX2DQTR (PUBL_REG_BASE+0x94*4) // R/W - DATX8 2 DQ Timing Register
+#define PUBL_DX2DQSTR (PUBL_REG_BASE+0x95*4) // R/W - DATX8 2 DQS Timing Register
+#define PUBL_DX3GCR (PUBL_REG_BASE+0xA0*4) // R - DATX8 3 General Configration Register
+#define PUBL_DX3GSR0 (PUBL_REG_BASE+0xA1*4) // R - DATX8 3 General Status Register
+#define PUBL_DX3GSR1 (PUBL_REG_BASE+0xA2*4) // R - DATX8 3 General Status Register
+#define PUBL_DX3DLLCR (PUBL_REG_BASE+0xA3*4) // R - DATX8 3 DLL Control Register
+#define PUBL_DX3DQTR (PUBL_REG_BASE+0xA4*4) // R/W - DATX8 3 DQ Timing Register
+#define PUBL_DX3DQSTR (PUBL_REG_BASE+0xA5*4) // R/W - DATX8 3 DQS Timing Register
+#define PUBL_DX4GCR (PUBL_REG_BASE+0xB0*4) // R - DATX8 4 General Configration Register
+#define PUBL_DX4GSR0 (PUBL_REG_BASE+0xB1*4) // R - DATX8 4 General Status Register
+#define PUBL_DX4GSR1 (PUBL_REG_BASE+0xB2*4) // R - DATX8 4 General Status Register
+#define PUBL_DX4DLLCR (PUBL_REG_BASE+0xB3*4) // R - DATX8 4 DLL Control Register
+#define PUBL_DX4DQTR (PUBL_REG_BASE+0xB4*4) // R/W - DATX8 4 DQ Timing Register
+#define PUBL_DX4DQSTR (PUBL_REG_BASE+0xB5*4) // R/W - DATX8 4 DQS Timing Register
+#define PUBL_DX5GCR (PUBL_REG_BASE+0xC0*4) // R - DATX8 5 General Configration Register
+#define PUBL_DX5GSR0 (PUBL_REG_BASE+0xC1*4) // R - DATX8 5 General Status Register
+#define PUBL_DX5GSR1 (PUBL_REG_BASE+0xC2*4) // R - DATX8 5 General Status Register
+#define PUBL_DX5DLLCR (PUBL_REG_BASE+0xC3*4) // R - DATX8 5 DLL Control Register
+#define PUBL_DX5DQTR (PUBL_REG_BASE+0xC4*4) // R/W - DATX8 5 DQ Timing Register
+#define PUBL_DX5DQSTR (PUBL_REG_BASE+0xC5*4) // R/W - DATX8 5 DQS Timing Register
+#define PUBL_DX6GCR (PUBL_REG_BASE+0xD0*4) // R - DATX8 6 General Configration Register
+#define PUBL_DX6GSR0 (PUBL_REG_BASE+0xD1*4) // R - DATX8 6 General Status Register
+#define PUBL_DX6GSR1 (PUBL_REG_BASE+0xD2*4) // R - DATX8 6 General Status Register
+#define PUBL_DX6DLLCR (PUBL_REG_BASE+0xD3*4) // R - DATX8 6 DLL Control Register
+#define PUBL_DX6DQTR (PUBL_REG_BASE+0xD4*4) // R/W - DATX8 6 DQ Timing Register
+#define PUBL_DX6DQSTR (PUBL_REG_BASE+0xD5*4) // R/W - DATX8 6 DQS Timing Register
+#define PUBL_DX7GCR (PUBL_REG_BASE+0xE0*4) // R - DATX8 7 General Configration Register
+#define PUBL_DX7GSR0 (PUBL_REG_BASE+0xE1*4) // R - DATX8 7 General Status Register
+#define PUBL_DX7GSR1 (PUBL_REG_BASE+0xE2*4) // R - DATX8 7 General Status Register
+#define PUBL_DX7DLLCR (PUBL_REG_BASE+0xE3*4) // R - DATX8 7 DLL Control Register
+#define PUBL_DX7DQTR (PUBL_REG_BASE+0xE4*4) // R/W - DATX8 7 DQ Timing Register
+#define PUBL_DX7DQSTR (PUBL_REG_BASE+0xE5*4) // R/W - DATX8 7 DQS Timing Register
+#define PUBL_DX8GCR (PUBL_REG_BASE+0xF0*4) // R - DATX8 8 General Configration Register
+#define PUBL_DX8GSR0 (PUBL_REG_BASE+0xF1*4) // R - DATX8 8 General Status Register
+#define PUBL_DX8GSR1 (PUBL_REG_BASE+0xF2*4) // R - DATX8 8 General Status Register
+#define PUBL_DX8DLLCR (PUBL_REG_BASE+0xF3*4) // R - DATX8 8 DLL Control Register
+#define PUBL_DX8DQTR (PUBL_REG_BASE+0xF4*4) // R/W - DATX8 8 DQ Timing Register
+#define PUBL_DX8DQSTR (PUBL_REG_BASE+0xF5*4) // R/W - DATX8 8 DQS Timing Register
+
+typedef unsigned long int uint32;
+
+
+static inline void reg_bits_set(uint32 addr,uint32 start_bitpos,uint32 bit_num,uint32 value)
+{
+ /*create bit mask according to input param*/
+ uint32 bit_mask = (1<<bit_num)-1;
+ uint32 reg_data = *((volatile uint32*)(addr));
+
+ reg_data &= ~(bit_mask<<start_bitpos);
+ reg_data |= ((value&bit_mask)<<start_bitpos);
+
+ *((volatile uint32*)(addr)) = reg_data;
+}__attribute__((always_inline))
+
+
+static inline void assert_reset_dll(void)
+{
+ REG32(PUBL_ACDLLCR) &= ~(0x1<<30);
+ REG32(PUBL_DX0DLLCR) &= ~(0x1<<30);
+ REG32(PUBL_DX1DLLCR) &= ~(0x1<<30);
+ REG32(PUBL_DX2DLLCR) &= ~(0x1<<30);
+ REG32(PUBL_DX3DLLCR) &= ~(0x1<<30);
+}__attribute__((always_inline))
+
+static inline void deassert_reset_dll(void)
+{
+ REG32(PUBL_ACDLLCR) |= (0x1<<30);
+ REG32(PUBL_DX0DLLCR) |= (0x1<<30);
+ REG32(PUBL_DX1DLLCR) |= (0x1<<30);
+ REG32(PUBL_DX2DLLCR) |= (0x1<<30);
+ REG32(PUBL_DX3DLLCR) |= (0x1<<30);
+}__attribute__((always_inline))
+
+static inline void disable_ddrphy_dll(void) {
+ REG32(PUBL_PIR) |= (1 << 17);
+}__attribute__((always_inline))
+
+static inline void enable_ddrphy_dll(void) {
+ REG32(PUBL_PIR) &= ~(1 << 17);
+}__attribute__((always_inline))
+
+static inline void move_upctl_state_to_self_refresh(void)
+{
+ volatile uint32 val;
+
+ REG32(UMCTL_PWRCTL) = 0;
+ val = REG32(UMCTL_STAT);
+ //wait umctl2 core is in self-refresh mode
+ while((val & 0x7) != 1) {
+ val = REG32(UMCTL_STAT);
+ }
+
+ REG32(UMCTL_DFILPCFG0) = 0x0700f000;
+// val = REG32(UMCTL_STAT);
+ //wait umctl2 core is in self-refresh mode
+// while((val & 0x7) != 3) {
+// val = REG32(UMCTL_STAT);
+// }
+ //check self refresh was due to software
+}__attribute__((always_inline))
+static inline void move_upctl_state_exit_self_refresh(u32 dll_enable)
+{
+ if(dll_enable == EMC_DLL_SWITCH_DISABLE_MODE) {
+ REG32(UMCTL_DFILPCFG0) = 0x0700f100;
+ REG32(UMCTL_PWRCTL) = 0x9;
+ }
+ else{
+// REG32(UMCTL_DFILPCFG0) = 0x0700f000;
+ REG32(UMCTL_PWRCTL) = 0xa;
+ }
+
+// val = REG32(UMCTL_STAT);
+ //wait umctl2 core is in self-refresh mode
+// while((val & 0x7) != 1) {
+// val = REG32(UMCTL_STAT);
+// }
+
+}__attribute__((always_inline))
+
+static inline void disable_cam_command_deque(void)
+{
+ REG32(UMCTL_DBG1) |= (1 << 0);
+}__attribute__((always_inline))
+static inline void enable_cam_command_deque(void)
+{
+ REG32(UMCTL_DBG1) &= ~(1 << 0);
+}__attribute__((always_inline))
+static inline void ddr_clk_set(uint32 new_clk, uint32 delay, uint32 sene, ddr_dfs_val_t *timing);
+static inline void ddr_timing_update(ddr_dfs_val_t *timing);
+static inline void manual_issue_autorefresh(void)
+{
+ uint32 val;
+ val = REG32(UMCTL_RFSHCTL3);
+ val |= (1 << 0) ;
+ val ^= (1 << 1);
+ REG32(UMCTL_RFSHCTL3) = val;
+
+ val = REG32(UMCTL_RFSHCTL3);
+ val &= ~(1 << 0) ;
+ val ^= (1 << 1);
+ REG32(UMCTL_RFSHCTL3) = val;
+}__attribute__((always_inline))
+static inline void wait_queue_complete(void)
+{
+ volatile u32 i = 0;
+ volatile u32 value_temp;
+ while(i < 10)
+ {
+ value_temp = REG32(UMCTL_DBG1);
+ if(value_temp == 1) {
+ i++;
+ }
+ }
+}__attribute__((always_inline))
+static inline void dll_bypass_switch(u32 dll_mode, ddr_dfs_val_t *timing)
+{
+ volatile uint32 i;
+ //for(i = 0; i < 0x80000000; i++);
+ move_upctl_state_to_self_refresh();
+ //timing->publ_mr1 = 0x1234556C;
+ disable_cam_command_deque();
+ REG32(UMCTL_DFIMISC) &= ~(1 << 0);
+ wait_queue_complete();
+ //for(i = 0; i < 20; i++);
+
+ //phy clock close
+// REG32(PMU_APB_BASE + 0xc8) &= ~(1 << 2);
+ REG32(SPRD_PMU_PHYS + 0xc8) &= ~(1 << 6);
+// for(i = 0 ; i < 2; i++);
+
+ if(dll_mode == EMC_DLL_SWITCH_DISABLE_MODE) {
+ disable_ddrphy_dll();
+ //phy clock open
+// REG32(PMU_APB_BASE + 0xc8) |= (1 << 2);
+ REG32(SPRD_PMU_PHYS + 0xc8) |= (1 << 6);
+ for(i = 0 ; i < 2; i++);
+// deassert_reset_dll();
+ REG32(PUBL_PIR) |= (1 << 4) | (1 << 0);
+ while((REG32(PUBL_PGSR) & 1) != 1);
+ while((REG32(PUBL_PGSR) & 1) != 1);
+ REG32(PUBL_PIR) |= (1 << 4);
+ }
+ else if(dll_mode == EMC_DLL_SWITCH_ENABLE_MODE){
+ assert_reset_dll();
+ for(i = 0 ; i < 2; i++);
+ enable_ddrphy_dll();
+ //phy clock open
+// REG32(PMU_APB_BASE + 0xc8) |= (1 << 2);
+ REG32(SPRD_PMU_PHYS + 0xc8) |= (1 << 6);
+ for(i = 0 ; i < 2; i++);
+ deassert_reset_dll();
+ for(i = 0 ; i < 40; i++);
+ }
+ REG32(UMCTL_DFIMISC) |= (1 << 0);
+ enable_cam_command_deque();
+ move_upctl_state_exit_self_refresh(dll_mode);
+}__attribute__((always_inline))
+static inline void umctl2_freq_set(uint32 clk, uint32 DDR_TYPE, uint32 dll_mode,uint32 sene,ddr_dfs_val_t *timing)
+{
+ #ifdef CONFIG_SCX35_DMC_FREQ_AP
+ ddr_clk_set(clk,0,sene,timing);
+ #else
+ if(EMC_DLL_SWITCH_ENABLE_MODE == dll_mode) {
+ dll_bypass_switch(dll_mode, timing);
+ ddr_clk_set(clk, 10, sene,timing);
+ }
+ else if(EMC_DLL_SWITCH_DISABLE_MODE == dll_mode) {
+ ddr_clk_set(clk, 10, sene,timing);
+ dll_bypass_switch(dll_mode, timing);
+ }
+ else if(EMC_DLL_NOT_SWITCH_MODE) {
+ ddr_clk_set(clk, 10, sene,timing);
+ }
+ #endif
+}__attribute__((always_inline))
+static inline void ddr_timing_update(ddr_dfs_val_t *timing)
+{
+ disable_cam_command_deque();
+ REG32(UMCTL_RFSHTMG) = timing->umctl2_rfshtmg;
+// REG32(UMCTL_RFSHTMG) = val;
+ REG32(UMCTL_RFSHCTL3) ^= (1 << 1);
+// manual_issue_autorefresh();
+ REG32(UMCTL_DRAMTMG0) = timing->umctl2_dramtmg0;
+ REG32(UMCTL_DRAMTMG1) = timing->umctl2_dramtmg1;
+ REG32(UMCTL_DRAMTMG2) = timing->umctl2_dramtmg2;
+ //REG32(UMCTL_DRAMTMG3) = timing->umctl2_dramtmg3;
+ REG32(UMCTL_DRAMTMG4) = timing->umctl2_dramtmg4;
+ //REG32(UMCTL_DRAMTMG5) = timing->umctl2_dramtmg5;
+ REG32(UMCTL_DRAMTMG6) = timing->umctl2_dramtmg6;
+ //REG32(UMCTL_DRAMTMG7) = timing->umctl2_dramtmg7;
+ //REG32(UMCTL_DRAMTMG8) = timing->umctl2_dramtmg8;
+ //REG32(UMCTL_DRAMTMG8) = 0x2;
+ REG32(PUBL_DX0DQSTR) = timing->publ_dx0dqstr;
+ REG32(PUBL_DX1DQSTR) = timing->publ_dx1dqstr;
+ REG32(PUBL_DX2DQSTR) = timing->publ_dx2dqstr;
+ REG32(PUBL_DX3DQSTR) = timing->publ_dx3dqstr;
+ REG32(PUBL_DX0GCR) = timing->publ_dx0gcr;
+ REG32(PUBL_DX1GCR) = timing->publ_dx1gcr;
+ REG32(PUBL_DX2GCR) = timing->publ_dx2gcr;
+ REG32(PUBL_DX3GCR) = timing->publ_dx3gcr;
+ enable_cam_command_deque();
+}__attribute__((always_inline))
+
+static int get_dpll_refin(void)
+{
+ return ((REG32(SPRD_AONAPB_PHYS + 0X18) >> 24) & 0x3);
+}__attribute__((always_inline))
+
+#ifdef CONFIG_SCX35_DMC_FREQ_AP
+static inline void exit_lowpower_mode(void)
+{
+ uint32 val;
+
+ *(volatile uint32*)(UMCTL_PWRCTL) = 0x8;
+ val = *(volatile uint32*)(UMCTL_STAT);
+ //wait umctl2 core is in self-refresh mode
+ while((val & 0x7) != 1) {
+ val = *(volatile uint32*)(UMCTL_STAT);
+ }
+ *(volatile uint32*)(UMCTL_DFILPCFG0) = 0x0700f000;
+}__attribute__((always_inline))
+
+static inline void ddr_cam_command_dequeue(uint32 isEnable)
+{
+ if(isEnable)
+ {
+ *(volatile uint32*)(UMCTL_DBG1) &= ~(1<<0);
+ }
+ else
+ {
+ *(volatile uint32*)(UMCTL_DBG1) |= (1<<0);
+ }
+}__attribute__((always_inline))
+
+
+static inline void ddr_timing_update_ex(ddr_dfs_val_t *timing_param)
+{
+ //minimum time from refresh to refresh or active
+ //reg_bits_set(UMCTL_RFSHTMG,0,9,timing_param->umctl2_rfshtmg);
+ //toggle this signel indicate refresh register has been update
+ *(volatile uint32*)(UMCTL_RFSHTMG) = timing_param->umctl2_rfshtmg;
+ *(volatile uint32*)(UMCTL_RFSHCTL3) ^= (1<<1);
+ //update umctl & publ timing
+ *(volatile uint32*)UMCTL_DRAMTMG0 = timing_param->umctl2_dramtmg0;
+ *(volatile uint32*)UMCTL_DRAMTMG1 = timing_param->umctl2_dramtmg1;
+ *(volatile uint32*)UMCTL_DRAMTMG2 = timing_param->umctl2_dramtmg2;
+ //*(volatile uint32*)UMCTL_DRAMTMG3 = timing_param->umctl2_dramtmg3;
+ *(volatile uint32*)UMCTL_DRAMTMG4 = timing_param->umctl2_dramtmg4;
+ //*(volatile uint32*)UMCTL_DRAMTMG5 = timing_param->umctl2_dramtmg5;
+ *(volatile uint32*)UMCTL_DRAMTMG6 = timing_param->umctl2_dramtmg6;
+ //*(volatile uint32*)UMCTL_DRAMTMG7 = timing_param->umctl2_dramtmg7;
+ //*(volatile uint32*)UMCTL_DRAMTMG8 = timing_param->umctl2_dramtmg8;
+ //*(volatile uint32*)UMCTL_DRAMTMG8 = 1;
+
+ *(volatile uint32*)PUBL_DX0DQSTR = timing_param->publ_dx0dqstr;
+ *(volatile uint32*)PUBL_DX1DQSTR = timing_param->publ_dx1dqstr;
+ *(volatile uint32*)PUBL_DX2DQSTR = timing_param->publ_dx2dqstr;
+ *(volatile uint32*)PUBL_DX3DQSTR = timing_param->publ_dx3dqstr;
+ *(volatile uint32*)PUBL_DX0GCR = timing_param->publ_dx0gcr;
+ *(volatile uint32*)PUBL_DX1GCR = timing_param->publ_dx1gcr;
+ *(volatile uint32*)PUBL_DX2GCR = timing_param->publ_dx2gcr;
+ *(volatile uint32*)PUBL_DX3GCR = timing_param->publ_dx3gcr;
+}__attribute__((always_inline))
+
+static inline void uart_putch(uint32 c)
+{
+ *(volatile uint32*)SPRD_UART1_PHYS = c;
+}__attribute__((always_inline))
+
+static inline void ddr_clk_set(uint32 new_clk, uint32 delay, uint32 sene,ddr_dfs_val_t *timing)
+{
+ volatile uint32 i;
+ uint32 reg;
+// uint32 time_1,time_2;
+ uart_putch('0');
+ uart_putch('\n');
+ exit_lowpower_mode();
+ uart_putch('1');
+ uart_putch('\n');
+ //hold bus
+ //*(volatile uint32*)(0X40050048) = 0xc0; //open AON AP TIMER2
+ //time_1 = *(volatile uint32*)(0X40050044);
+ ddr_cam_command_dequeue(0);
+ //uart_putch('2');
+ //uart_putch('\n');
+ //confirm hold bus success
+ //hold not trigger sdram initialization
+ *(volatile uint32*)UMCTL_DFIMISC &= ~0x1;
+ wait_queue_complete();
+ //uart_putch('3');
+ //uart_putch('\n');
+ //disable auto refresh
+ REG32(UMCTL_RFSHCTL3) |= (1<<0);
+ for(i=0;i<10;i++);
+ switch(new_clk)
+ {
+ case 192:
+ {
+ if((sene == EMC_FREQ_NORMAL_SWITCH_SENE) || (sene == EMC_FREQ_RESUME_SENE))
+ {
+ //set tdpll clock divider
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024),0x8,2,0x1);
+
+ for(i=0;i<2;i++);
+
+ //switch to tdpll source 384Mhz
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024),0x0,2,0x2);
+
+ for(i=0;i<10;i++);
+ }
+ if(sene == EMC_FREQ_DEEP_SLEEP_SENE)
+ {
+ //switch to dpll source
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024),0x8,2,0x0);
+
+ for(i=0;i<2;i++);
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024),0x0,2,0x3);
+ for(i=0;i<10;i++);
+ }
+
+ //phy clock close
+ *(volatile uint32*)(SPRD_PMU_PHYS+0x00c8) &= ~(1<<6);
+ for(i=0;i<10;i++);
+ //close dll
+ disable_ddrphy_dll();
+
+ //phy clock open
+ *(volatile uint32*)(SPRD_PMU_PHYS+0x00c8) |= (1<<6);
+// *(volatile uint32*)0x022b00c8 |= (1<<2);
+ for(i=0;i<10;i++);
+ //deassert_reset_dll();
+
+// wait DLL lock in memory side;
+// use MRS to close the DLL in memory side in 192MHz
+ REG32(PUBL_PIR) |= (1 << 4) | (1 << 0);
+ while((REG32(PUBL_PGSR) & 1) != 1);
+ while((REG32(PUBL_PGSR) & 1) != 1);
+// REG32(PUBL_PIR) |= (1 << 4);
+ REG32(UMCTL_PERFLPR1) &= ~0xFFFF;
+ REG32(UMCTL_PERFWR1) &= ~0xFFFF;
+
+ break;
+ }
+
+ case 332:
+ {
+ //config dpll divider
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024),0x8,2,0x0);
+
+ for(i=0;i<2;i++);
+
+ //switch to dpll source
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024),0x0,2,0x3);
+
+ for(i=0;i<10;i++);
+ //phy clock close
+ *(volatile uint32*)(SPRD_PMU_PHYS+0x00c8) &= ~(1<<6);
+ for(i=0;i<10;i++);
+
+ assert_reset_dll();
+ for(i=0;i<10;i++);
+ //open dll
+ enable_ddrphy_dll();
+
+ for(i=0;i<10;i++);
+
+ //phy clock open
+ *(volatile uint32*)(SPRD_PMU_PHYS+0x00c8) |= (1<<6);
+// *(volatile uint32*)0x022b00c8 |= (1<<2);
+ for(i=0;i<10;i++);
+ //release dll
+ deassert_reset_dll();
+ for(i=0;i<30;i++);
+
+ REG32(UMCTL_PERFLPR1) |= 0x100;
+ REG32(UMCTL_PERFWR1) |= 0x20;
+ break;
+ }
+ default:
+ break;
+ }
+
+ //enable auto refresh
+ REG32(UMCTL_RFSHCTL3) &= ~(1<<0);
+ //uart_putch('4');
+ //uart_putch('\n');
+ ddr_timing_update_ex(timing);
+ //uart_putch('5');
+ //uart_putch('\n');
+ *(volatile uint32*)UMCTL_DFIMISC |= (1<<0);
+ ddr_cam_command_dequeue(1);
+ //time_2 = *(volatile uint32*)(0X40050044);
+
+ //if(new_clk == 192)
+ //*(volatile uint32*)(0x1dc0) = time_1 - time_2;
+ //else
+ //*(volatile uint32*)(0x1dd0) = time_1 - time_2;
+ uart_putch('6');
+ uart_putch('\n');
+ if(new_clk == 192)
+ {
+ REG32(UMCTL_DFILPCFG0) = 0x0700f100;
+ REG32(UMCTL_PWRCTL) = 0x9;
+ }
+ else
+ {
+ REG32(UMCTL_PWRCTL) = 0xa;
+ }
+}__attribute__((always_inline))
+
+#else
+static inline void ddr_clk_set(uint32 new_clk, uint32 delay, uint32 sene,ddr_dfs_val_t *timing)
+{
+ uint32 reg_val;
+ uint32 old_clk;
+ uint32 steps;
+ uint32 i;
+ uint32 j;
+ reg_val = REG32(SPRD_AONAPB_PHYS + 0X18);
+ old_clk = reg_val & 0xfff;
+ old_clk *= 4;
+ if(old_clk > new_clk) {
+ steps = (old_clk - new_clk) / 4;
+// ddr_autorefresh_timing_update();
+ for(i = 0; i < steps; i++) {
+ REG32(SPRD_AONAPB_PHYS + 0X18) = REG32(SPRD_AONAPB_PHYS + 0X18) - 1;
+ for(j = 0; j < delay; j++);
+ }
+ ddr_timing_update(timing);
+ }
+ else if(old_clk < new_clk){
+ ddr_timing_update(timing);
+ steps = (new_clk - old_clk) / 4;
+ for(i = 0; i < steps; i++) {
+ REG32(SPRD_AONAPB_PHYS + 0X18) = REG32(SPRD_AONAPB_PHYS + 0X18) + 1;
+ for(j = 0; j < delay; j++);
+ }
+// ddr_autorefresh_timing_update();
+ }
+}__attribute__((always_inline))
+#endif
+
+
+#define DFS_PARAM_ADDR (0x1C00)
+#define DFS_AP_REQ_ARRAY_ADDR (SPRD_IRAM0_PHYS + 0x1BF0)
+inline void dev_freq_set(unsigned long req)
+{
+ u32 ddr_type;
+ u32 clk;
+ u32 dll_mode;
+ u32 sene;
+ ddr_dfs_val_t *timing;
+ REG32(PUBL_DSGCR) &= ~(0x10);
+ ddr_type = (req & EMC_DDR_TYPE_MASK) >> EMC_DDR_TYPE_OFFSET;
+ clk = (req & EMC_CLK_FREQ_MASK) >> EMC_CLK_FREQ_OFFSET;
+ dll_mode = (req & EMC_DLL_MODE_MASK);
+ sene = (req & EMC_FREQ_SENE_MASK) >> EMC_FREQ_SENE_OFFSET;
+ timing = (ddr_dfs_val_t *)(DFS_PARAM_ADDR);
+
+ if(clk == 332)
+ {
+ timing += 1;
+ }
+
+ if(timing->ddr_clk != clk)
+ {
+ uart_putch('d');
+ uart_putch('f');
+ uart_putch('s');
+ uart_putch('e');
+ uart_putch('r');
+ uart_putch('r');
+ uart_putch('o');
+ uart_putch('r');
+ uart_putch('\n');
+ while(1);
+ }
+ umctl2_freq_set(clk, ddr_type, dll_mode,sene, timing);
+
+
+} __attribute__((always_inline))
+void emc_dfs_main(unsigned long flag)
+{
+ uint32_t reg, val;
+ /* disable mmu, cache */
+ asm volatile (
+// "ldr %1, =10000 \n"
+// "1: nop \n"
+// "sub %1, %1, #1 \n"
+// "cmp %1, #0 \n"
+// "bne 1b \n"
+ "mrc p15, 0, %0, c1, c0, 0 \n"
+ "ldr %1, =0x1005 \n"
+ "bic %0, %0, %1 \n"
+ "mcr p15, 0, %0, c1, c0, 0\n"
+ "ldr sp, =0x1BF0 \n"
+ "ldr r11, =0x1F00 \n"
+ : "=r"(reg), "=r"(val)
+ );
+ //for(i = 0; i < 0x80000000; i++);
+ dev_freq_set(flag); /* call dfs freq set function */
+ /* "ldr r11, =0x1c00 \n" */
+ /* jump back */
+ asm volatile (
+ "ldr r0, =cpu_resume \n"
+ "sub r0, r0, #0xc0000000 \n"
+ "add r0, r0, #0x80000000 \n"
+ "mov pc, r0 \n"
+ );
+}
diff --git a/drivers/platform/sprd/dmc_freq_ap_28nm.c b/drivers/platform/sprd/dmc_freq_ap_28nm.c
new file mode 100755
index 00000000..f9b80a5c
--- /dev/null
+++ b/drivers/platform/sprd/dmc_freq_ap_28nm.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/input/matrix_keypad.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/__sc8830_dmc_dfs.h>
+
+#include "soc/sprd/chip_x30g/dram_phy_28nm.h"
+
+#define REG32(x) (*((volatile uint32 *)(x)))
+
+#define DFS_PARAM_ADDR (0x1C0C)
+#define DFS_CALC_PARAM_ADDR (0x1F80)
+#define UMCTL_REG_BASE (0x30000000)
+#define PUBL_REG_BASE (0x30010000)
+#define UART1_PHYS_ADDR (0x70100000)
+
+//#define DPLL_CLK 800
+//#define DPLL_REFIN 26
+//#define NINT(FREQ,REFIN) (FREQ/REFIN)
+//#define KINT(FREQ,REFIN) ((FREQ-(FREQ/REFIN)*REFIN)*1048576/REFIN)
+
+typedef unsigned long int uint32;
+
+static inline void uart_putch(uint32 c)
+{
+ REG32(UART1_PHYS_ADDR) = c;
+}__attribute__((always_inline))
+
+static inline uint32 reg_bits_set(uint32 addr,uint32 start_bitpos,uint32 bit_num,uint32 value)
+{
+ /*create bit mask according to input param*/
+ volatile uint32 bit_mask = (1 << bit_num) - 1;
+ volatile uint32 reg_data = REG32(addr);
+
+ reg_data &= ~(bit_mask<<start_bitpos);
+ reg_data |= ((value&bit_mask)<<start_bitpos);
+
+ REG32(addr) = reg_data;
+
+ return 0;
+}__attribute__((always_inline))
+
+static inline void move_upctl_state_to_self_refresh(void)
+{
+ volatile uint32 val;
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+
+ p_umctl_reg->umctl_pwrctl = BIT_PWRCTL_SELFREF_SW;
+
+ /* wait umctl2 core is in self-refresh mode */
+ val = p_umctl_reg->umctl_stat;
+ while ((val & (BIT_STAT_SELFREF_TYPE | BIT_STAT_OP_MODE)) != 0x23) {
+ val = p_umctl_reg->umctl_stat;
+ }
+}__attribute__((always_inline))
+
+static inline void move_upctl_state_exit_self_refresh(void )
+{
+ volatile uint32 val;
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+
+ p_umctl_reg->umctl_pwrctl = 0x00;
+
+ /* wait umctl2 core is in self-refresh mode */
+ val = p_umctl_reg->umctl_stat;
+ while ((val & BIT_STAT_OP_MODE) != 0x1) {
+ val = p_umctl_reg->umctl_stat;
+ }
+
+}__attribute__((always_inline))
+
+#if 0
+static inline void disable_ddrphy_pll(void) {
+ DMC_PUBL_REG_INFO_PTR_T p_publ_reg = (DMC_PUBL_REG_INFO_PTR_T) PUBL_REG_BASE;
+ p_publ_reg->publ_pir |= BIT_PIR_PLLBYP;
+}__attribute__((always_inline))
+
+static inline void enable_ddrphy_pll(void) {
+ DMC_PUBL_REG_INFO_PTR_T p_publ_reg = (DMC_PUBL_REG_INFO_PTR_T) PUBL_REG_BASE;
+ p_publ_reg->publ_pir &= ~BIT_PIR_PLLBYP;
+}__attribute__((always_inline))
+
+static inline void disable_cam_command_deque(void)
+{
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+ p_umctl_reg->umctl_dbg[1] |= BIT_DBG1_DIS_DQ;
+}__attribute__((always_inline))
+
+static inline void enable_cam_command_deque(void)
+{
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+ p_umctl_reg->umctl_dbg[1] &= ~BIT_DBG1_DIS_DQ;
+}__attribute__((always_inline))
+
+static inline uint32 dqs_gating_training(uint32 new_clk)
+{
+ volatile uint32 s_pgcr, s_dsgcr, i;
+ DMC_PUBL_REG_INFO_PTR_T p_publ_reg = (DMC_PUBL_REG_INFO_PTR_T) PUBL_REG_BASE;
+
+ /* phy rst */
+ p_publ_reg->publ_pgcr[1] &= ~(0x1 << 25);
+ for(i = 0; i < 2; i++);
+ p_publ_reg->publ_pgcr[1] |= (0x1 << 25);
+
+ /* ACBVT, [4:3]:RX FIFO Read Mode,00 asynchronous. */
+ s_pgcr = p_publ_reg->publ_pgcr[3];
+ p_publ_reg->publ_pgcr[3] &= ~((0x3 << 3) | (0x1 << 24));
+
+ /* DQSGX: DQS Gate extention, do not extend the gate. */
+ s_dsgcr = p_publ_reg->publ_dsgcr;
+ p_publ_reg->publ_dsgcr &= ~(0x03 << 6);
+
+ if( new_clk == 200 ) {
+ p_publ_reg->publ_dtpr[3] = 0x1;
+ }
+ else if (new_clk == 400){
+ p_publ_reg->publ_dtpr[3] = 0xa;
+ }
+
+ /*do training*/
+ p_publ_reg->publ_pir |= ((1 << 10)|(1 << 0));
+
+ while( (p_publ_reg->publ_pgsr[0] & (0x1 << 0)) != 0x1 );
+ /*check ddr training status*/
+ if( p_publ_reg->publ_pgsr[0] & (1<<22) ){
+ while(1);
+ }
+
+ p_publ_reg->publ_pgcr[3] = s_pgcr;
+ p_publ_reg->publ_dsgcr = s_dsgcr;
+}__attribute__((always_inline))
+#endif
+
+static inline void wait_queue_complete(void)
+{
+ volatile uint32 value_temp;
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+
+ while(1) {
+ value_temp = p_umctl_reg->umctl_dbgcam;
+ if (((value_temp & BIT_DBGCAM_WR_PP_EPTY) != 0) &&
+ ((value_temp & BIT_DBGCAM_RD_PP_EPTY) != 0)) {
+
+ return;
+ }
+ }
+}__attribute__((always_inline))
+
+static inline void exit_lowpower_mode(uint32 *reg_store)
+{
+ volatile uint32 val;
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+#if 0
+ p_umctl_reg->umctl_pwrctl = BIT_PWRCTL_DFICLK_DIS;
+ val = p_umctl_reg->umctl_stat;
+ /* wait umctl2 core is in self-refresh mode */
+ while((val & 0x7) != 1) {
+ val = p_umctl_reg->umctl_stat;
+ }
+#endif
+
+ /* disable_cgm */
+ REG32(SPRD_PMU_PHYS + 0xF8) &= ~((3 << 30) );
+
+ /* disable lp interface. */
+ reg_store[0] = p_umctl_reg->umctl_hwlpctl;
+ p_umctl_reg->umctl_hwlpctl &= ~0x03;
+ //p_umctl_reg->umctl_dfilpcfg[0] = 0x0700f000;
+
+ /* disable_powerdown */
+ reg_store[1] = p_umctl_reg->umctl_pwrctl;
+ p_umctl_reg->umctl_pwrctl = 0;
+ /* wait status to normal mode */
+ val = p_umctl_reg->umctl_stat;
+ while ((val & BIT_STAT_OP_MODE) != 0x1) {
+ val = p_umctl_reg->umctl_stat;
+ }
+
+ /* disable dfi low power interface */
+ p_umctl_reg->umctl_dfilpcfg[0] &= ~(1 << 8);
+
+}__attribute__((always_inline))
+
+static inline void enable_lowpower_mode(uint32 *reg_store)
+{
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+
+ /* enable_sleep */
+ p_umctl_reg->umctl_dfilpcfg[0] |= (1 << 8);
+
+ /* enable lp interface. */
+ p_umctl_reg->umctl_hwlpctl = reg_store[0];
+
+ /* enable_powerdown */
+ p_umctl_reg->umctl_pwrctl = reg_store[1];
+
+ /* enable_cgm */
+ REG32(SPRD_PMU_PHYS + 0xF8) |= ((3 << 30) );
+
+}__attribute__((always_inline))
+
+static inline void ddr_cam_command_dequeue(uint32 isEnable)
+{
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+
+ if(isEnable) {
+ p_umctl_reg->umctl_dbg[1] &= ~BIT_DBG1_DIS_DQ;
+ }
+ else {
+ p_umctl_reg->umctl_dbg[1] |= BIT_DBG1_DIS_DQ;
+ }
+}__attribute__((always_inline))
+
+
+static inline void ddr_timing_update(ddr_dfs_v2_t *timing_param)
+{
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+ DMC_PUBL_REG_INFO_PTR_T p_publ_reg = (DMC_PUBL_REG_INFO_PTR_T) PUBL_REG_BASE;
+ publ_calc_t *calc = (publ_calc_t *)DFS_CALC_PARAM_ADDR;
+
+ /* minimum time from refresh to refresh or active */
+ //toggle this signel indicate refresh register has been update
+ p_umctl_reg->umctl_rfshtmg = timing_param->umctl_rfshtmg;
+ p_umctl_reg->umctl_rfshctl3 ^= (1<<1);
+
+ //update umctl & publ timing
+ p_umctl_reg->umctl_dramtmg[0] = timing_param->umctl_dramtmg0;
+ p_umctl_reg->umctl_dramtmg[1] = timing_param->umctl_dramtmg1;
+ p_umctl_reg->umctl_dramtmg[2] = timing_param->umctl_dramtmg2;
+ p_umctl_reg->umctl_dramtmg[3] = timing_param->umctl_dramtmg3;
+ p_umctl_reg->umctl_dramtmg[4] = timing_param->umctl_dramtmg4;
+ p_umctl_reg->umctl_dramtmg[5] = timing_param->umctl_dramtmg5;
+ p_umctl_reg->umctl_dramtmg[7] = timing_param->umctl_dramtmg7;
+ p_umctl_reg->umctl_dramtmg[8] = timing_param->umctl_dramtmg8;
+
+ p_umctl_reg->umctl_dfitmg[0] = timing_param->umctl_dfitmg0;
+ p_umctl_reg->umctl_init[3] = timing_param->umctl_init3;
+ p_umctl_reg->umctl_zqctl[0] = timing_param->umctl_zqctl0;
+ p_umctl_reg->umctl_zqctl[1] = timing_param->umctl_zqctl1;
+
+ p_publ_reg->publ_mr[2] = timing_param->publ_mr2;
+
+ p_publ_reg->publ_dx0gtr = timing_param->publ_dx0gtr;
+ p_publ_reg->publ_dx1gtr = timing_param->publ_dx1gtr;
+ p_publ_reg->publ_dx2gtr = timing_param->publ_dx2gtr;
+ p_publ_reg->publ_dx3gtr = timing_param->publ_dx3gtr;
+
+ p_publ_reg->publ_pgcr[3] = timing_param->publ_pgcr3;
+ p_publ_reg->publ_acmdlr = calc->publ_acmdlr;
+ p_publ_reg->publ_dx0mdlr = calc->publ_dx0mdlr;
+ p_publ_reg->publ_dx1mdlr = calc->publ_dx1mdlr;
+ p_publ_reg->publ_dx2mdlr = calc->publ_dx2mdlr;
+ p_publ_reg->publ_dx3mdlr = calc->publ_dx3mdlr;
+ p_publ_reg->publ_aclcdlr = calc->publ_aclcdlr;
+ p_publ_reg->publ_dx0lcdlr[0] = calc->publ_dx0lcdlr0;
+ p_publ_reg->publ_dx0lcdlr[1] = calc->publ_dx0lcdlr1;
+ p_publ_reg->publ_dx0lcdlr[2] = calc->publ_dx0lcdlr2;
+ p_publ_reg->publ_dx1lcdlr[0] = calc->publ_dx1lcdlr0;
+ p_publ_reg->publ_dx1lcdlr[1] = calc->publ_dx1lcdlr1;
+ p_publ_reg->publ_dx1lcdlr[2] = calc->publ_dx1lcdlr2;
+ p_publ_reg->publ_dx2lcdlr[0] = calc->publ_dx2lcdlr0;
+ p_publ_reg->publ_dx2lcdlr[1] = calc->publ_dx2lcdlr1;
+ p_publ_reg->publ_dx2lcdlr[2] = calc->publ_dx2lcdlr2;
+ p_publ_reg->publ_dx3lcdlr[0] = calc->publ_dx3lcdlr0;
+ p_publ_reg->publ_dx3lcdlr[1] = calc->publ_dx3lcdlr1;
+ p_publ_reg->publ_dx3lcdlr[2] = calc->publ_dx3lcdlr2;
+
+ p_publ_reg->publ_dsgcr = timing_param->publ_dsgcr;
+ p_publ_reg->publ_dtpr[0] = timing_param->publ_dtpr0;
+ p_publ_reg->publ_dtpr[1] = timing_param->publ_dtpr1;
+ p_publ_reg->publ_dtpr[2] = timing_param->publ_dtpr2;
+ p_publ_reg->publ_dtpr[3] = timing_param->publ_dtpr3;
+
+}__attribute__((always_inline))
+
+static inline void ddr_clk_set(uint32 new_clk, ddr_dfs_v2_t *timing)
+{
+ volatile uint32 i;
+ uint32 reg_store[3];
+
+ DMC_UMCTL_REG_INFO_PTR_T p_umctl_reg = (DMC_UMCTL_REG_INFO_PTR_T)UMCTL_REG_BASE;
+ DMC_PUBL_REG_INFO_PTR_T p_publ_reg = (DMC_PUBL_REG_INFO_PTR_T) PUBL_REG_BASE;
+
+ uart_putch('d');
+ exit_lowpower_mode(&(reg_store[0]));
+
+#if 0
+REG32(0x8F001000)= 0x11223344;
+
+for (i = 0; i < 0x20; i ++)
+{
+ REG32(0x1F00 + (i << 2)) = 0xAAAAAAAA;
+}
+#endif
+ /* step a: move dram into self-refresh. */
+ move_upctl_state_to_self_refresh();
+
+ /* step b: changing the input clock period. */
+ /* hold bus : set DBG1.dis_dq = 1 */
+ ddr_cam_command_dequeue(0);
+
+ /* DFIMISC.dfi_init_complete_en =0.hold not trigger sdram initialization */
+ p_umctl_reg->umctl_dfimisc &= ~BIT_DFIMISC_DFI_COMP_EN;
+ wait_queue_complete();
+
+ /* step c: change the clock frequency to the DWC_ddr_umctl2 and ensure no glitchs. */
+ /* phy clock close : DDR_PHY_AUTO_GATE_EN */
+ reg_store[2] = REG32(SPRD_PMU_PHYS+0x00D0);
+ REG32(SPRD_PMU_PHYS+0x00D0) &= ~((1 << 6) | 0x07);
+ for(i = 0; i < 0x2; i++);
+
+ switch(new_clk) {
+ case 192:
+ {
+ reg_bits_set((SPRD_AONCKG_PHYS + 0x0024), 0x8, 2, 0x1);
+ for(i = 0; i < 0x2; i++);
+
+ reg_bits_set((SPRD_AONCKG_PHYS + 0x0024), 0x0, 2, 0x2);
+ for(i = 0; i < 0x2; i++);
+ break;
+ }
+
+ case 200:
+ {
+ /* switch to dpll source */
+ reg_bits_set((SPRD_AONCKG_PHYS + 0x0024), 0x8, 2, 0x1);
+ for(i = 0; i < 0x2; i++);
+
+ reg_bits_set((SPRD_AONCKG_PHYS + 0x0024), 0x0, 2, 0x3);
+ for(i = 0; i < 0x2; i++);
+ break;
+ }
+
+ case 384:
+ {
+ /* set dpll clock to 192M : set DPLL_REFIN to 26M * 26 = 676*/
+ //reg_bits_set((SPRD_AONAPB_PHYS + 0x3004), 24, 2, 0x03);
+ //reg_bits_set((SPRD_AONAPB_PHYS + 0x3074), 0, 6, 26);
+
+ /* switch to dpll source */
+ /* reg[0x402D0024] :*/
+ /* [9:8] : clk_emc_div(clk_div= clk_src/(div+1)) */
+ /* [1:0] : clk_emc_sel (0:pub 26m, 1: CPLL, 2:TDPLL, 3:DPLL)*/
+ reg_bits_set((SPRD_AONCKG_PHYS + 0x0024), 0x8, 2, 0x0);
+ for(i = 0; i < 0x2; i++);
+
+ reg_bits_set((SPRD_AONCKG_PHYS + 0x0024), 0x0, 2, 0x2);
+ for(i = 0; i < 0x2; i++);
+
+ break;
+ }
+
+ case 400:
+ case 466:
+ case 533:
+ {
+ /* set dpll clock to 400M : set DPLL_REFIN to 4M*/
+ /* reg[0x402E3004] :*/
+ /* [25:24] : DPLL_REFIN(00: 2M, 01 :4M, 10:13M, 11:26M) */
+ /* [10:0] : DPLL_N( dpll = dpll_refin * dpll_n)*/
+ /* reg[0x402E3074] :*/
+ /* [31:12] : DPLL_KINT */
+ /* [10] : DPLL_DIV_S */
+ /* [7] : DPLL_MOD_EN */
+ /* [6] : DPLL_SDM_EN */
+ /* [5:0] : DPLL_NINT */
+ #if 0
+ /* set dpll clock to 201M : set DPLL_REFIN to 26M * 31 = 806M*/
+ reg_val = REG32(REG_AON_APB_DPLL_CFG1);
+ reg_val |= 1 << 10; // fractional divider
+ reg_val &= ~(0xFFFFF << 12 | 0x3f);
+ reg_val |= (KINT(DPLL_CLK, DPLL_REFIN) & 0xFFFFF) << 12;
+ reg_val |= (NINT(DPLL_CLK, DPLL_REFIN)) & 0x3f;
+ REG32(REG_AON_APB_DPLL_CFG1)= reg_val;
+
+ reg_val = REG32(REG_AON_APB_DPLL_CFG);
+ reg_val &= ~(3 << 24);
+ reg_val |= (3 << 24);
+ REG32(REG_AON_APB_DPLL_CFG) = reg_val;
+ #endif
+
+ /* config dpll divider */
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024), 0x8, 2, 0x0);
+ for(i = 0; i < 0x2; i++);
+
+ /*switch to dpll source */
+ reg_bits_set((SPRD_AONCKG_PHYS+0x0024), 0x0, 2, 0x3);
+ for(i = 0; i < 0x2; i++);
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ /* phy clock open */
+ REG32(SPRD_PMU_PHYS+0x00D0) = reg_store[2];
+ for(i = 0; i < 0x2; i++);
+
+ /* step d: re-lock mode sequence. */
+ /* step e: set VT inhibit register pgcr1[26] and DCAL bypass PIR[29]. */
+ p_publ_reg->publ_pgcr[1] |= (0x1 << 26);
+ for(i = 0; i < 0x2; i++);
+ while((p_publ_reg->publ_pgsr[1] & (1<<30)) == 0);
+
+ p_publ_reg->publ_pir |= BIT_PIR_DCALBYP;
+ for(i = 0; i < 0x2; i++);
+ while ((p_publ_reg->publ_pgsr[0] & 0x01) != 1);
+
+ /* step f: update phy timing register. static and dynamic register */
+ ddr_timing_update(timing);
+
+ /* step g: trigger the initization PHY */
+ p_publ_reg->publ_pgcr[1] &= ~(0x1 << 26);
+ for(i = 0; i < 0x2; i++);
+ p_publ_reg->publ_pir &= ~BIT_PIR_DCALBYP;
+ for(i = 0; i < 0x2; i++);
+
+ /* dfi complete enable */
+ p_umctl_reg->umctl_dfimisc |= BIT_DFIMISC_DFI_COMP_EN;
+
+ uart_putch('f');
+ /* step i: require to exit sel_refresh by PWRCTL.selfref_sw. */
+ move_upctl_state_exit_self_refresh();
+
+ /* set ddr mr2 */
+ p_umctl_reg->umctl_mrctrl[1] = (2 << 8) | (timing->publ_mr2 & 0xFF);
+ p_umctl_reg->umctl_mrctrl[0] = (3 << 4) | (1 << 31);
+ while((p_umctl_reg->umctl_mrstat & 0x01) != 0);
+
+#if 0
+ /* step 9: do dqs traning. */
+ dqs_gating_training(new_clk);
+#endif
+
+ /* step j: FBG1.dis_dq = 0 */
+ ddr_cam_command_dequeue(1);
+
+ /* enable lowpower */
+ enable_lowpower_mode(&(reg_store[0]));
+#if 0
+for (i = 0; i < 0x20; i ++)
+{
+ REG32(0x1F00 + (i << 2)) = REG32(0x8F001000);
+}
+
+for (i = 0; i < 0x20; i ++)
+{
+ if (REG32(0x1F00 + (i << 2)) != 0x11223344) {
+
+ while(1);
+ }
+}
+#endif
+
+ uart_putch('s');
+}__attribute__((always_inline))
+
+static inline ddr_dfs_v2_t *get_clk_timing( uint32 clk )
+{
+ volatile uint32 i;
+ ddr_dfs_v2_t *timing;
+ publ_calc_t *calc;
+
+ timing = (ddr_dfs_v2_t *)(DFS_PARAM_ADDR);
+ calc = (publ_calc_t *)DFS_CALC_PARAM_ADDR;
+
+ for (i = 0; i < 5; i++) {
+ if (clk == timing->ddr_clk) {
+ if (clk == calc->ddr_clk) {
+ return timing;
+ }
+ }
+ timing++;
+ }
+
+ return (ddr_dfs_v2_t *)0;
+}__attribute__((always_inline))
+
+inline void dev_freq_set(unsigned long req)
+{
+ u32 clk;
+// u32 sene;
+ ddr_dfs_v2_t *timing;
+
+ //ddr_type = (req & EMC_DDR_TYPE_MASK) >> EMC_DDR_TYPE_OFFSET;
+ clk = (req & EMC_CLK_FREQ_MASK) >> EMC_CLK_FREQ_OFFSET;
+ //dll_mode = (req & EMC_DLL_MODE_MASK);
+ //sene = (req & EMC_FREQ_SENE_MASK) >> EMC_FREQ_SENE_OFFSET;
+
+ timing = get_clk_timing(clk);
+ if(timing->ddr_clk != clk) {
+ uart_putch('d');
+ uart_putch('f');
+ uart_putch('s');
+ uart_putch('e');
+ uart_putch('r');
+ uart_putch('r');
+ uart_putch('o');
+ uart_putch('r');
+ uart_putch('\n');
+ while(1);
+ }
+
+ ddr_clk_set(clk, timing);
+} __attribute__((always_inline))
+
+void emc_dfs_main(unsigned long flag)
+{
+ volatile uint32 reg, val;
+ /* disable mmu, cache */
+ /* SP : 0x1B00 ~ 0x1BFF */
+ /* ddr timing params : 0x1C00 ~ 0x1CFF */
+ asm volatile (
+ "mrc p15, 0, %0, c1, c0, 0 \n"
+ "ldr %1, =0x1005 \n"
+ "bic %0, %0, %1 \n"
+ "mcr p15, 0, %0, c1, c0, 0\n"
+ "ldr sp, =0x1B00 \n"
+ "ldr r11, =0x1F00 \n"
+ : "=r"(reg), "=r"(val)
+ );
+
+ dev_freq_set(flag); /* call dfs freq set function */
+ /* jump back */
+ asm volatile (
+ "ldr r0, =cpu_resume \n"
+ "sub r0, r0, #0xc0000000 \n"
+ "add r0, r0, #0x80000000 \n"
+ "mov pc, r0 \n"
+ );
+}
diff --git a/drivers/platform/sprd/fix_v7_tag_ram_bug.S b/drivers/platform/sprd/fix_v7_tag_ram_bug.S
new file mode 100644
index 00000000..255ac530
--- /dev/null
+++ b/drivers/platform/sprd/fix_v7_tag_ram_bug.S
@@ -0,0 +1,183 @@
+/*
+* fix arm-v7 tag ram bug
+*
+*/
+
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+#include <mach/hardware.h>
+#include <mach/sci_glb_regs.h>
+
+#define REG_HOLDING_PEN_VADDR (SPRD_AHB_BASE + 0x4c)
+#define REG_CPU1_JUMP_VADDR (SPRD_AHB_BASE + 0x54)
+#define REG_CA7_STANDBY_STATUS_VADDR (SPRD_AHB_BASE + 0x48)
+
+ .data
+
+@other_cpu_fix function phys
+.globl other_cpu_fix_phys
+other_cpu_fix_phys:
+ .long 0x0
+
+@invalidation_flag for other 3 cpus
+invalidation_flag:
+ .long 0x0
+ .long 0x0
+ .long 0x0
+all_complete:
+ .long 0x0
+
+ .text
+
+ENTRY(fix_tag_ram_bug)
+ stmfd sp!, {r4-r12, lr}
+
+ mrc p15, 0, r1, c1, c0, 0
+ bic r0, r1, #(1 << 2) @disable d-cache
+ mcr p15, 0, r0, c1, c0, 0
+ bl v7_flush_dcache_all @flush d-cache
+ isb
+
+ mov r2, #0x0
+ ldr r1, =invalidation_flag
+ str r2, [r1] @clean all invalidation flag
+ str r2, [r1, #4]
+ str r2, [r1, #8]
+ ldr r1, =all_complete
+ str r2, [r1] @clean complete flag
+
+ ldr r1, =other_cpu_fix_phys @other_cpu_fix function virt_to_phys
+ ldr r3, [r1]
+ cmp r3, #0 @function phys shouldn't be zero
+ beq finished
+ ldr r1, =REG_CPU1_JUMP_VADDR @change other cpus jump address
+ str r3, [r1]
+ str r3, [r1, #4]
+ str r3, [r1, #8]
+ ldr r1, =REG_HOLDING_PEN_VADDR @write holding pen for other cpus
+ mov r2, #(0x02 + 0x04 + 0x08)
+ str r2, [r1]
+ dsb
+
+ mov r0, #0x01 @power on other cpus
+ bl poweron_cpus
+ mov r0, #0x02
+ bl poweron_cpus
+ mov r0, #0x03
+ bl poweron_cpus
+ ldr r0, =0x0000ffff
+ bl my_delay
+ sev
+
+loop_wfe:
+ wfe
+ ldr r1, =invalidation_flag
+ ldr r2, [r1]
+ cmp r2, #1
+ bne loop_wfe
+ ldr r2, [r1, #4]
+ cmp r2, #1
+ bne loop_wfe
+ ldr r2, [r1, #8]
+ cmp r2, #1
+ bne loop_wfe
+
+ mov r0, #0x1
+ ldr r1, =all_complete
+ str r0, [r1]
+ dsb
+ sev
+ wfe @just ignore sev before
+ nop
+/*
+ ldr r0, =0x00ffffff @make sure other cpus go into wfi
+ bl my_delay
+*/
+#if 0 //uart debug
+ adr r0, str_cpu0
+ bl printascii_phy
+#endif
+
+ ldr r2, =physical_from_idle @wether or not from physical_from_idle
+ ldr r5, [r2]
+ ldr r2, =REG_CA7_STANDBY_STATUS_VADDR
+wait_others_wfi:
+ ldr r3, [r2]
+ eor r3, r5
+ and r3, #0x0e
+ teq r3, #0x0e @check wfi? which not from physical_from_idle
+ bne wait_others_wfi
+
+ tst r5, #(1 << 1)
+ mov r0, #0x1
+ bleq powerdown_cpus
+ tst r5, #(1 << 2)
+ mov r0, #0x2
+ bleq powerdown_cpus
+ tst r5, #(1 << 3)
+ mov r0, #0x3
+ bleq powerdown_cpus
+
+finished:
+ mrc p15, 0, r1, c1, c0, 0
+ orr r0, r1, #(1 << 2) @enable d-cache
+ mcr p15, 0, r0, c1, c0, 0
+
+ ldmfd sp!, {r4-r12, pc}
+ENDPROC(fix_tag_ram_bug)
+
+
+ENTRY(other_cpu_fix)
+ mov r0, #0x0
+ mcr p15, 0, r0, c8, c3, 1
+ isb
+ dsb
+ nop
+ mrc p15, 0, r1, c0, c0, 5
+ and r1, r1, #3 @ r1=cpu mpid
+ cmp r1, #0x0
+ beq wfi_for_powerdown @ mpid shouldn't be zero
+ mov r9, r1 @ r9=saved cpu mpid
+ sub r1, #1
+ lsl r1, #2
+
+ ldr r6, =invalidation_flag
+ ldr r4, =other_cpu_fix
+ adr r5, other_cpu_fix
+ add r6, r6, r5
+ sub r6, r6, r4 @ r6=invalidation_flag phys
+ ldr r7, =all_complete
+ add r7, r7, r5
+ sub r7, r7, r4 @ r7=all_complete phys
+ ldr r8, =physical_from_idle
+ add r8, r8, r5
+ sub r8, r8, r4 @ r8=physical_from_idle phys
+
+ mov r2, #0x1
+ str r2, [r6, r1]
+ dsb
+ sev
+
+loop_wfe_other:
+ wfe
+ ldr r0, [r7]
+ cmp r0, #0x1
+ bne loop_wfe_other
+#if 0 //uart debug
+ adr r0, str_cpus
+ bl printascii_phy
+#endif
+ ldr r0, [r8]
+ mov r2, #0x1
+ lsl r2, r9
+ tst r0, r2
+ beq wfi_for_powerdown
+ mov pc, #0x0 @if from idle, cpu pc should goto 0x0
+
+wfi_for_powerdown:
+ wfi
+ b wfi_for_powerdown
+ENDPROC(other_cpu_fix)
+
+str_cpu0: .asciz "!! cpu0 !!\n"
+str_cpus: .asciz "-others-\n"
diff --git a/drivers/platform/sprd/glb.c b/drivers/platform/sprd/glb.c
new file mode 100644
index 00000000..2f9da396
--- /dev/null
+++ b/drivers/platform/sprd/glb.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/hwspinlock.h>
+#include <linux/io.h>
+
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/arch_lock.h>
+
+u32 sci_glb_read(unsigned long reg, u32 msk)
+{
+ return __raw_readl((void *)reg) & msk;
+}
+
+int sci_glb_write(unsigned long reg, u32 val, u32 msk)
+{
+ unsigned long flags;
+ __arch_default_lock(HWLOCK_GLB, &flags);
+ __raw_writel((__raw_readl((void *)reg) & ~msk) | val, (void *)reg);
+ __arch_default_unlock(HWLOCK_GLB, &flags);
+ return 0;
+}
+
+static int __is_glb(unsigned long reg)
+{
+#if defined (CONFIG_ARCH_SCX35)
+ return 0;
+ return rounddown(reg, SZ_64K) == rounddown(REGS_GLB_BASE, SZ_64K) ||
+ rounddown(reg, SZ_64K) == rounddown(REGS_AHB_BASE, SZ_64K) ||
+ rounddown(reg, SZ_64K) == rounddown(REGS_AP_APB_BASE, SZ_64K) ||
+ rounddown(reg, SZ_64K) == rounddown(REGS_PMU_APB_BASE, SZ_64K);
+
+#else
+ return rounddown(reg, SZ_64K) == rounddown(REGS_GLB_BASE, SZ_64K) ||
+ rounddown(reg, SZ_64K) == rounddown(REGS_AHB_BASE, SZ_64K);
+#endif
+}
+
+int sci_glb_set(unsigned long reg, u32 bit)
+{
+ if (__is_glb(reg)) {
+ __raw_writel(bit, (void *)REG_GLB_SET(reg));
+ } else {
+ unsigned long flags;
+ __arch_default_lock(HWLOCK_GLB, &flags);
+ __raw_writel(__raw_readl((void *)reg) | bit, (void *)reg);
+ __arch_default_unlock(HWLOCK_GLB, &flags);
+ }
+ return 0;
+}
+
+int sci_glb_clr(unsigned long reg, u32 bit)
+{
+ if (__is_glb(reg)) {
+ __raw_writel(bit, (void *)REG_GLB_CLR(reg));
+ } else {
+ unsigned long flags;
+ __arch_default_lock(HWLOCK_GLB, &flags);
+ __raw_writel((__raw_readl((void *)reg) & ~bit), (void *)reg);
+ __arch_default_unlock(HWLOCK_GLB, &flags);
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(sci_glb_read);
+EXPORT_SYMBOL(sci_glb_write);
+EXPORT_SYMBOL(sci_glb_set);
+EXPORT_SYMBOL(sci_glb_clr);
+
diff --git a/drivers/platform/sprd/headsmp.S b/drivers/platform/sprd/headsmp.S
new file mode 100644
index 00000000..8a112000
--- /dev/null
+++ b/drivers/platform/sprd/headsmp.S
@@ -0,0 +1,84 @@
+/*
+ * linux/arch/arm/mach-sc/headsmp.S
+ *
+ *
+ * Copyright (c) 2003 ARM Limited
+ * 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.
+ */
+#include <linux/linkage.h>
+#include <linux/init.h>
+
+ __CPUINIT
+
+/*
+ * sci smp specific entry point for secondary CPUs.
+ * This provides a "holding pen" into which all secondary cores are held
+ * until we're ready for them to initialise.
+ */
+ENTRY(sci_secondary_startup)
+ mrc p15, 0, r0, c0, c0, 5
+#ifndef CONFIG_OF
+ and r0, r0, #15
+#else
+ /*and r0, r0, #0xFFFFFF*/ /* mpidr is 0xf00 0xf01, 0xf02, 0xf03 */
+ and r0, r0, #15 /* mpidr is 0xf00 0xf01, 0xf02, 0xf03 */
+#endif
+ adr r4, 1f
+ ldmia r4, {r5, r6}
+ sub r4, r4, r5
+ add r6, r6, r4
+pen: ldr r7, [r6]
+ and r7, r7, #15/* mpidr is 0xf00 0xf01, 0xf02, 0xf03 */
+ cmp r7, r0
+ bne pen
+
+ /*
+ * we've been released from the holding pen: secondary_stack
+ * should now contain the SVC stack for this core
+ */
+ b secondary_startup
+
+ .align
+1: .long .
+ .long pen_release
+
+ENTRY(sci_shark_enter_lowpower)
+ stmfd sp!, {r4-r12, lr}
+
+ mrc p15, 0, r1, c1, c0, 0
+ bic r0, r1, #(1 << 2) @disable d-cache
+ mcr p15, 0, r0, c1, c0, 0
+
+ bl v7_flush_dcache_louis
+
+ clrex
+
+ mrc p15, 0, r1, c1, c0, 1
+ bic r1, r1, #(1 << 6)
+ mcr p15, 0, r1, c1, c0, 1
+
+ isb
+ dsb
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+
+ wfi
+
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+
+ ldmfd sp!, {r4-r12, pc}
+ENDPROC(sci_shark_enter_lowpower)
diff --git a/drivers/platform/sprd/hotplug.c b/drivers/platform/sprd/hotplug.c
new file mode 100644
index 00000000..cf176183
--- /dev/null
+++ b/drivers/platform/sprd/hotplug.c
@@ -0,0 +1,167 @@
+/* linux arch/arm/mach-sc8830/hotplug.c
+ *
+ * Cloned from linux/arch/arm/mach-realview/hotplug.c
+ *
+ * Copyright (C) 2002 ARM Ltd.
+ * 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.
+*/
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/smp.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+
+#include <soc/sprd/hardware.h>
+#include <asm/cacheflush.h>
+#include <asm/cp15.h>
+#include <asm/smp_plat.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+
+extern volatile int pen_release;
+#ifdef CONFIG_ARCH_SCX35
+int powerdown_cpus(int cpu);
+int sci_shark_enter_lowpower(void);
+#endif
+static inline void cpu_enter_lowpower(void)
+{
+ unsigned int v;
+
+ flush_cache_all();
+ asm volatile(
+ " mcr p15, 0, %1, c7, c5, 0\n"
+ " mcr p15, 0, %1, c7, c10, 4\n"
+ /*
+ * Turn off coherency
+ */
+ " mrc p15, 0, %0, c1, c0, 1\n"
+ " bic %0, %0, #0x20\n"
+ " mcr p15, 0, %0, c1, c0, 1\n"
+ " mrc p15, 0, %0, c1, c0, 0\n"
+ " bic %0, %0, %2\n"
+ " mcr p15, 0, %0, c1, c0, 0\n"
+ : "=&r" (v)
+ : "r" (0), "Ir" (CR_C)
+ : "cc");
+}
+
+static inline void cpu_leave_lowpower(void)
+{
+ unsigned int v;
+
+ asm volatile( "mrc p15, 0, %0, c1, c0, 0\n"
+ " orr %0, %0, %1\n"
+ " mcr p15, 0, %0, c1, c0, 0\n"
+ " mrc p15, 0, %0, c1, c0, 1\n"
+ " orr %0, %0, #0x20\n"
+ " mcr p15, 0, %0, c1, c0, 1\n"
+ : "=&r" (v)
+ : "Ir" (CR_C)
+ : "cc");
+}
+
+extern unsigned int sprd_boot_magnum;
+
+static inline void platform_do_lowpower(unsigned int cpu, int *spurious)
+{
+ unsigned int val = sprd_boot_magnum;
+
+ /*
+ * there is no power-control hardware on this platform, so all
+ * we can do is put the core into WFI; this is safe as the calling
+ * code will have already disabled interrupts
+ */
+
+ val |= (cpu_logical_map(cpu) & 0x0000000f);
+ for (;;) {
+ /*
+ * here's the WFI
+ */
+ asm(".word 0xe320f003\n"
+ :
+ :
+ : "memory", "cc");
+
+ if (pen_release == val) {
+ /*
+ * OK, proper wakeup, we're done
+ */
+ pr_info("platform_do_lowpower %x %x\n",pen_release,val);
+ break;
+ }
+
+ /*
+ * Getting here, means that we have come out of WFI without
+ * having been woken up - this shouldn't happen
+ *
+ * Just note it happening - when we're woken, we can report
+ * its occurrence.
+ */
+ (*spurious)++;
+ }
+}
+
+int sprd_cpu_kill(unsigned int cpu)
+{
+#ifdef CONFIG_ARCH_SCX35
+ int i = 0;
+ printk("!! %d platform_cpu_kill %d !!\n", smp_processor_id(), cpu);
+ while (i < 20) {
+ //check wfi?
+ if (sci_glb_read(REG_AP_AHB_CA7_STANDBY_STATUS, -1UL) & (1 << cpu_logical_map(cpu)))
+ {
+ powerdown_cpus(cpu);
+ break;
+ }
+ udelay(100);
+ i++;
+ }
+ printk("platform_cpu_kill finished i=%d !!\n", i);
+ return (i >= 20? 0 : 1);
+#endif
+ return 1;
+}
+
+/*
+ * platform-specific code to shutdown a CPU
+ *
+ * Called with IRQs disabled
+ */
+void __cpuinit sprd_cpu_die(unsigned int cpu)
+{
+ int spurious = 0;
+
+#ifdef CONFIG_ARCH_SCX35
+ sci_shark_enter_lowpower();
+ panic("shouldn't be here \n");
+#endif
+ /*
+ * we're ready for shutdown now, so do it
+ */
+ cpu_enter_lowpower();
+ platform_do_lowpower(cpu, &spurious);
+
+ /*
+ * bring this CPU back into the world of cache
+ * coherency, and then restore interrupts
+ */
+ cpu_leave_lowpower();
+
+ if (spurious)
+ pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious);
+}
+
+int sprd_cpu_disable(unsigned int cpu)
+{
+ /*
+ * we don't allow CPU 0 to be shutdown (it is still too special
+ * e.g. clock tick interrupts)
+ */
+ return cpu == 0 ? -EPERM : 0;
+}
diff --git a/drivers/platform/sprd/io-sc8830.c b/drivers/platform/sprd/io-sc8830.c
new file mode 100644
index 00000000..a512c892
--- /dev/null
+++ b/drivers/platform/sprd/io-sc8830.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ * Author: steve.zhan <steve.zhan@spreadtrum.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/kernel.h>
+#include <linux/init.h>
+#include <linux/bug.h>
+
+#include <asm/io.h>
+#include <asm/page.h>
+#include <asm/mach/map.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/arch_misc.h>
+
+#define SPRD_DEVICE(name) { \
+ .virtual = SPRD_##name##_BASE, \
+ .pfn = __phys_to_pfn(SPRD_##name##_PHYS),\
+ .length = SPRD_##name##_SIZE, \
+ .type = MT_DEVICE_NONSHARED, \
+ }
+#define SPRD_IRAM(name) { \
+ .virtual = SPRD_##name##_BASE, \
+ .pfn = __phys_to_pfn(SPRD_##name##_PHYS),\
+ .length = SPRD_##name##_SIZE, \
+ .type = MT_MEMORY, \
+ }
+
+static struct map_desc sprd_io_desc[] __initdata = {
+ SPRD_DEVICE(UART0),
+ SPRD_DEVICE(UART1),
+ SPRD_DEVICE(UART2),
+ SPRD_DEVICE(UART3),
+ SPRD_DEVICE(UART4),
+#ifdef CONFIG_ARCH_SCX20L
+ SPRD_DEVICE(ARM7AHBRF),
+#endif
+
+ SPRD_IRAM(IRAM0),
+ SPRD_IRAM(IRAM0H),
+#if defined(CONFIG_ARCH_SCX30G)
+ SPRD_IRAM(IRAM1),
+ SPRD_DEVICE(LPDDR2_PHY),
+#else
+ SPRD_DEVICE(IRAM1),
+#endif
+#ifndef CONFIG_ARCH_SCX35L
+ SPRD_DEVICE(IRAM2),
+#endif
+ SPRD_DEVICE(PWM),
+ SPRD_DEVICE(LPDDR2),
+};
+
+void __init sci_map_io(void)
+{
+ iotable_init(sprd_io_desc, ARRAY_SIZE(sprd_io_desc));
+}
+
diff --git a/drivers/platform/sprd/iomap.c b/drivers/platform/sprd/iomap.c
new file mode 100644
index 00000000..7a912fea
--- /dev/null
+++ b/drivers/platform/sprd/iomap.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 Spreadtrum Communications 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 <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <asm/io.h>
+#include <soc/sprd/sci_glb_regs.h>
+
+struct iotable_sprd io_addr_sprd;
+EXPORT_SYMBOL_GPL(io_addr_sprd);
+
+#define ADD_SPRD_DEVICE_BY_COMPAT(compat, id) \
+do { \
+ np = of_find_compatible_node(NULL, NULL, compat);\
+ if (of_can_translate_address(np)) { \
+ of_address_to_resource(np, 0, &res); \
+ io_addr_sprd.id.paddr = res.start; \
+ io_addr_sprd.id.length = \
+ resource_size(&res); \
+ io_addr_sprd.id.vaddr = base + res.start;\
+ pr_debug("sprd io map: phys=%16lx virt=%16lx size=%16lx\n", \
+ io_addr_sprd.id.paddr, io_addr_sprd.id.vaddr, io_addr_sprd.id.length);\
+ } \
+} while (0)
+
+#define ADD_SPRD_DEVICE_BY_NAME(name, id) \
+do { \
+ np = of_find_node_by_name(NULL, name); \
+ if (of_can_translate_address(np)) { \
+ of_address_to_resource(np, 0, &res); \
+ io_addr_sprd.id.paddr = res.start; \
+ io_addr_sprd.id.length = \
+ resource_size(&res); \
+ io_addr_sprd.id.vaddr = base + res.start;\
+ pr_debug("sprd io map: phys=%16lx virt=%16lx size=%16lx\n", \
+ io_addr_sprd.id.paddr, io_addr_sprd.id.vaddr, io_addr_sprd.id.length);\
+ } \
+} while (0)
+
+static int sprd_create_iomap(void)
+{
+ struct device_node *np;
+ struct resource res;
+ unsigned long base;
+
+ base = (unsigned long) ioremap_nocache(0, SZ_2G);
+ if (!base)
+ panic("%s: unable to alloc such memory!", __func__);
+ io_addr_sprd.base = base;
+ pr_debug("sprd io map base address: %016x\n", base);
+
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,ahb", AHB);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,apbckg", APBCKG);
+ //ADD_SPRD_DEVICE_BY_COMPAT("sprd,hwlock0", HWLOCK0);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,pub_apb", PUB);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,aon_apb", AONAPB);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,pmu_apb", PMU);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,mm_ahb", MMAHB);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,mm_clk", MMCKG);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,ap_apb", APBREG);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,dma0", DMA0);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,gpu_apb", GPUAPB);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,ap_ckg", APCKG);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,aon_dma", AONDMA);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,pwm", PWM);
+ ADD_SPRD_DEVICE_BY_NAME("hwspinlock0", HWSPINLOCK0);
+ ADD_SPRD_DEVICE_BY_NAME("hwspinlock1", HWSPINLOCK1);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,intc2", INTC2);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,aonckg", AONCKG);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,core", CORE);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,gpuckg", GPUCKG);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,int", INT);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,intc0", INTC0);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,intc1", INTC1);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,intc3", INTC3);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,uidefuse", UIDEFUSE);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,isp", ISP);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,csi2", CSI2);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,d-eic-gpio", EIC);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,ipi", IPI);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,dcam", DCAM);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,syscnt", SYSCNT);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,pin", PIN);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,d-gpio-gpio", GPIO);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,codecahb", CODECAHB);
+#endif
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,lpddr2", LPDDR2);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,uart0", UART0);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,uart1", UART1);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,uart2", UART2);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,uart3", UART3);
+ ADD_SPRD_DEVICE_BY_COMPAT("sprd,uart4", UART4);
+ return 0;
+}
+
+early_initcall(sprd_create_iomap);
diff --git a/drivers/platform/sprd/irq.c b/drivers/platform/sprd/irq.c
new file mode 100644
index 00000000..4a3db24c
--- /dev/null
+++ b/drivers/platform/sprd/irq.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * Contact: steve.zhan <steve.zhan@spreadtrum.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/kernel.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <linux/irqchip/arm-gic.h>
+
+#include <soc/sprd/hardware.h>
+//#include <mach/irqs.h>
+#include <soc/sprd/irqs.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/arch_misc.h>
+//#include <asm/fiq.h>
+#include <soc/sprd/adi.h>
+#include <linux/of.h>
+#include <linux/irqchip.h>
+
+#include <soc/sprd/sci_glb_regs.h>
+
+/* general interrupt registers */
+#define INTC_IRQ_MSKSTS (0x0000)
+#define INTC_IRQ_RAW (0x0004)
+#define INTC_IRQ_EN (0x0008)
+#define INTC_IRQ_DIS (0x000C)
+#define INTC_IRQ_SOFT (0x0010)
+#define INTC_FIQ_STS (0x0020)
+#define INTC_FIQ_EN (0x0028)
+#define INTC_FIQ_DIS (0x002C)
+
+struct intc {
+ u32 intc_reg_base;
+ u32 min_int_num;/*global logical number max value in intc*/
+ u32 max_min_num;/*global logical number min value in intc */
+ u32 min_bit_offset;/*It is equal to "min_int_num - actual bit offset"*/
+};
+
+struct intc_mux_irq {/*multi irqs mapping to one bit*/
+ u32 irq_number;
+ u32 intc_base;
+ u32 bits;
+ u32 is_unmask;
+};
+
+#if defined(CONFIG_ARCH_SCX35)
+/*_intc and mux_irq is separately, we just simply enable/disable it at the same time
+ When system deepsleep, only mux_irq(AON) can wakeup system(intc0,1,2,3,gic,armcore)
+ intc0/1/2/3 will lose if system in deepsleep, _mux can wakeup. intc0/1/2/3 need SW retention.
+ gic clock will stop if all of core has been wfi.intc0/1/2/3 can wakeup.
+*/
+#define UNINITIALIZED 0xdeadbeaf
+static struct intc _intc[] = {
+ {UNINITIALIZED, 2, 31, 0},
+ {UNINITIALIZED, 34, 63, 32},
+ {UNINITIALIZED, 66, 95, 64},
+ {UNINITIALIZED, 98, 124, 96},
+};
+#if defined(CONFIG_ARCH_SCX35L)
+static struct intc_mux_irq _mux[] = {
+ {30,UNINITIALIZED,2,0},{31,UNINITIALIZED,2,0},{28,UNINITIALIZED,2,0},
+ {121,UNINITIALIZED,2,0},{120,UNINITIALIZED,2,0},{119,UNINITIALIZED,2,0},
+ {118,UNINITIALIZED,2,0},{29,UNINITIALIZED,2,0},
+ {21,UNINITIALIZED,3,0},{22,UNINITIALIZED,3,0},{23,UNINITIALIZED,3,0},{24,UNINITIALIZED,3,0},
+ {35,UNINITIALIZED,4,0},{36,UNINITIALIZED,4,0},{38,UNINITIALIZED,4,0},{25,UNINITIALIZED,4,0},
+ {20,UNINITIALIZED,4,0},{34,UNINITIALIZED,4,0},/* missing lvds_trx, mdar */
+ {41,UNINITIALIZED,5,0},{40,UNINITIALIZED,5,0},{42,UNINITIALIZED,5,0},{44,UNINITIALIZED,5,0},
+ {45,UNINITIALIZED,5,0},{43,UNINITIALIZED,5,0},
+ {39,UNINITIALIZED,6,0},
+ {84,UNINITIALIZED,7,0},{83,UNINITIALIZED,7,0},
+ {123,UNINITIALIZED,8,0},{124,UNINITIALIZED,8,0},
+ {122,UNINITIALIZED,9,0},{26,UNINITIALIZED,9,0},
+ {86,UNINITIALIZED,10,0},
+ /* bit11: missing mbox_tar_arm7 */
+ {69,UNINITIALIZED,12,0},
+ {37,UNINITIALIZED,31,0},
+};
+#else
+static struct intc_mux_irq _mux[] = {
+ {30, UNINITIALIZED, 2, 0},
+ {31, UNINITIALIZED, 2, 0},
+ {28, UNINITIALIZED, 2, 0},
+ {121,UNINITIALIZED, 2, 0},
+ {120,UNINITIALIZED, 2, 0},
+ {119,UNINITIALIZED, 2, 0},
+ {118,UNINITIALIZED, 2, 0},
+ {29, UNINITIALIZED, 2, 0},
+ {21, UNINITIALIZED, 3, 0},
+ {22, UNINITIALIZED, 3, 0},
+ {23, UNINITIALIZED, 3, 0},
+ {24, UNINITIALIZED, 3, 0},
+ {35, UNINITIALIZED, 4, 0},
+ {36, UNINITIALIZED, 4, 0},
+ {38, UNINITIALIZED, 4, 0},
+ {25, UNINITIALIZED, 4, 0},
+ {27, UNINITIALIZED, 4, 0},
+ {20, UNINITIALIZED, 4, 0},
+ {34, UNINITIALIZED, 4, 0},
+ {41, UNINITIALIZED, 5, 0},
+ {40, UNINITIALIZED, 5, 0},
+ {42, UNINITIALIZED, 5, 0},
+ {44, UNINITIALIZED, 5, 0},
+ {45, UNINITIALIZED, 5, 0},
+ {43, UNINITIALIZED, 5, 0},
+ {39, UNINITIALIZED, 6, 0},
+ {85, UNINITIALIZED, 7, 0},
+ {84, UNINITIALIZED, 7, 0},
+ {83, UNINITIALIZED, 7, 0},
+ {67, UNINITIALIZED, 8, 0},
+ {68, UNINITIALIZED, 8, 0},
+ {69, UNINITIALIZED, 8, 0},
+ {70, UNINITIALIZED, 9, 0},
+ {71, UNINITIALIZED, 9, 0},
+ {72, UNINITIALIZED, 9, 0},
+ {73, UNINITIALIZED,10, 0},
+ {74, UNINITIALIZED,10, 0},
+ {123,UNINITIALIZED,11, 0},
+ {124,UNINITIALIZED,11, 0},
+ {26, UNINITIALIZED,12, 0},
+ {122,UNINITIALIZED,12, 0},
+ {86, UNINITIALIZED,13, 0},
+ {37, UNINITIALIZED,14, 0},
+};
+#endif
+
+#define LEGACY_FIQ_BIT (32)
+#define LEGACY_IRQ_BIT (29)
+
+static __init void __irq_init(void)
+{
+ if (soc_is_scx35_v0())
+ sci_glb_clr(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG,BIT_CA7_CORE_AUTO_GATE_EN);
+
+ /*enable all intc*/
+ sci_glb_set(REG_AP_APB_APB_EB, BIT_INTC0_EB | BIT_INTC1_EB |
+ BIT_INTC2_EB | BIT_INTC3_EB);
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT_INTC_EB);
+
+ /*disable default for startup*/
+ __raw_writel(~0, (void *)(SPRD_INTC0_BASE + INTC_IRQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INTC0_BASE + INTC_FIQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INTC1_BASE + INTC_IRQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INTC1_BASE + INTC_FIQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INTC2_BASE + INTC_IRQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INTC2_BASE + INTC_FIQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INTC3_BASE + INTC_IRQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INTC3_BASE + INTC_FIQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INT_BASE + INTC_IRQ_DIS));
+ __raw_writel(~0, (void *)(SPRD_INT_BASE + INTC_FIQ_DIS));
+}
+
+#else
+
+static const struct intc _intc[] = {
+ {SPRD_INTC0_BASE, 0, 31, 0},
+ {SPRD_INTC1_BASE, 32, 61, 32},
+};
+
+static struct intc_mux_irq _mux[] = {};
+
+#define LEGACY_FIQ_BIT (31)
+#define LEGACY_IRQ_BIT (28)
+
+static __init void __irq_init(void)
+{
+ /*
+ * gic clock will be stopped after 2 cores enter standby in the same time,
+ * dsp assert if IRQ_DSP0_INT and IRQ_DSP1_INT are disabled. so enable IRQ_DSP0_INT
+ * and IRQ_DSP1_INT in INTC0 here.
+ */
+ u32 val = __raw_readl(SPRD_INTC0_BASE + INTC_IRQ_EN);
+ val |= (SCI_INTC_IRQ_BIT(IRQ_DSP0_INT) | SCI_INTC_IRQ_BIT(IRQ_DSP1_INT) | SCI_INTC_IRQ_BIT(IRQ_EPT_INT));
+ val |= (SCI_INTC_IRQ_BIT(IRQ_SIM0_INT) | SCI_INTC_IRQ_BIT(IRQ_SIM1_INT));
+ val |= (SCI_INTC_IRQ_BIT(IRQ_TIMER0_INT));
+ __raw_writel(val, SPRD_INTC0_BASE + INTC_IRQ_EN);
+}
+#endif
+
+static inline int __irq_mux_irq_find(u32 irq, u32 *base, u32 *bit, int *p_index)
+{
+ int s = ARRAY_SIZE(_mux);
+ while (s--)
+ if (_mux[s].irq_number == irq) {
+ *base = _mux[s].intc_base;
+ *bit = _mux[s].bits;
+ *p_index = s;
+ return 0;
+ }
+ return -ENXIO;
+}
+
+static inline int __irq_find_base(u32 irq, u32 *base, u32 *bit)
+{
+ int s = ARRAY_SIZE(_intc);
+ while (s--)
+ if (irq >= _intc[s].min_int_num && irq <= _intc[s].max_min_num) {
+ *base = _intc[s].intc_reg_base;
+ *bit = irq - _intc[s].min_bit_offset;
+ return 0;
+ }
+ return -ENXIO;
+}
+
+static inline void __mux_irq(u32 irq, u32 offset, u32 is_unmask)
+{
+ u32 base, bit, s;
+ int index = 0;
+ int dont_mask = 0;
+ if (!__irq_mux_irq_find(irq, &base, &bit, &index)) {
+ _mux[index].is_unmask = !!is_unmask;
+
+ if (is_unmask) {/*if any one need unmask(wakeup source), let the irq mux bit enable*/
+ __raw_writel(1 << bit, (void *)(base + offset));
+ } else {/*maybe mask*/
+ s = ARRAY_SIZE(_mux);
+ while (s--) {
+ if (_mux[s].is_unmask) {
+ dont_mask = 1;
+ break;
+ }
+ }
+ if (!dont_mask)
+ __raw_writel(1 << bit, (void *)(base + offset));
+ }
+ }
+}
+
+void sci_intc_mask(u32 __irq)
+{
+ unsigned int irq = SCI_GET_INTC_IRQ(__irq);
+ u32 base;
+ u32 bit;
+ u32 offset = INTC_IRQ_DIS;
+#ifdef CONFIG_SPRD_WATCHDOG_SYS_FIQ
+ if (unlikely(irq == (IRQ_CA7WDG_INT - IRQ_GIC_START)))
+ offset = INTC_FIQ_DIS;
+#endif
+ __mux_irq(irq, offset, 0);
+ if (!__irq_find_base(irq, &base, &bit))
+ __raw_writel(1 << bit, (void *)(base + offset));
+}
+EXPORT_SYMBOL(sci_intc_mask);
+
+void sci_intc_unmask(u32 __irq)
+{
+ unsigned int irq = SCI_GET_INTC_IRQ(__irq);
+ u32 base;
+ u32 bit;
+ u32 offset = INTC_IRQ_EN;
+#ifdef CONFIG_SPRD_WATCHDOG_SYS_FIQ
+ if (unlikely(irq == (IRQ_CA7WDG_INT - IRQ_GIC_START)))
+ offset = INTC_FIQ_EN;
+#endif
+ __mux_irq(irq, offset, 1);
+ if (!__irq_find_base(irq, &base, &bit))
+ __raw_writel(1 << bit, (void *)(base + offset));
+}
+
+EXPORT_SYMBOL(sci_intc_unmask);
+
+static void __irq_mask(struct irq_data *data)
+{
+ sci_intc_mask(data->irq);
+}
+
+static void __irq_unmask(struct irq_data *data)
+{
+ sci_intc_unmask(data->irq);
+}
+
+static int __set_wake(struct irq_data *d, unsigned int on)
+{
+ /*we don't add mask/unmask in this now, this is not mean mask interrupt*/
+ return 0;
+}
+
+extern int sprd_init_before_irq(void);
+void __init sci_init_irq(void)
+{
+ int i;
+ sprd_init_before_irq();
+ irqchip_init();
+
+ _intc[0].intc_reg_base = SPRD_INTC0_BASE;
+ _intc[1].intc_reg_base = SPRD_INTC1_BASE;
+ _intc[2].intc_reg_base = SPRD_INTC2_BASE;
+ _intc[3].intc_reg_base = SPRD_INTC3_BASE;
+
+ for (i = 0; i < ARRAY_SIZE(_mux); i++)
+ _mux[i].intc_base = SPRD_INT_BASE;
+
+ gic_arch_extn.irq_mask = __irq_mask;
+ gic_arch_extn.irq_unmask = __irq_unmask;
+ gic_arch_extn.irq_set_wake = __set_wake;
+
+
+ /*disable legacy interrupt*/
+#if (LEGACY_FIQ_BIT < 32)
+ __raw_writel(1<<LEGACY_FIQ_BIT, (void *)(CORE_GIC_DIS_VA + GIC_DIST_ENABLE_CLEAR));
+#endif
+ __raw_writel(1<<LEGACY_IRQ_BIT, (void *)(CORE_GIC_DIS_VA + GIC_DIST_ENABLE_CLEAR));
+
+ __irq_init();
+}
diff --git a/drivers/platform/sprd/mailbox.c b/drivers/platform/sprd/mailbox.c
new file mode 100644
index 00000000..c2b1445a
--- /dev/null
+++ b/drivers/platform/sprd/mailbox.c
@@ -0,0 +1,205 @@
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/debugfs.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/resource.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/irqs.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/mailbox.h>
+
+unsigned long sprd_mailbox_base;
+#define REGS_SEND_MBOX_BASE sprd_mailbox_base
+#define REGS_RECV_MBOX_BASE (sprd_mailbox_base + 0x8000)
+
+/*reg offset*/
+#define MBOX_ID 0x00
+#define MBOX_MSG_L 0x04
+#define MBOX_MSG_H 0x08
+#define MBOX_TRI 0x0c
+#define MBOX_FIFO_RST 0x10
+#define MBOX_FIFO_STS 0x14
+#define MBOX_IRQ_STS 0x18
+#define MBOX_IRQ_MSK 0x1c
+#define MBOX_LOCK 0x20
+#define FIFO_BLOCK_STS (0x20)
+#define MBOX_UNLOCK_KEY 0x5a5a5a5a
+
+struct mbox_chn {
+ spinlock_t mbox_lock;
+ irq_handler_t mbox_recv_irq_handler;
+ void *mbox_priv_data;
+};
+
+static struct mbox_chn mbox_chns[MBOX_NR];
+static irqreturn_t mbox_src_irqhandle(int irq_num, void *dev)
+{
+ u8 target_id;
+ u32 irq_sts;
+ u32 reg_val;
+
+ reg_val = __raw_readl(REGS_SEND_MBOX_BASE + MBOX_ID);
+ irq_sts = __raw_readl(REGS_SEND_MBOX_BASE + MBOX_FIFO_STS);
+ irq_sts = irq_sts & 0x3f;
+ if(irq_sts & FIFO_BLOCK_STS )
+ panic("target 0x%x mailbox blocked", reg_val);
+}
+
+static irqreturn_t mbox_recv_irqhandle(int irq_num, void *dev)
+{
+ u8 target_id;
+ u32 irq_sts;
+ u32 reg_val;
+ void *priv_data;
+ unsigned long flags;
+
+ irq_sts = __raw_readl((void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_FIFO_STS));
+ irq_sts = irq_sts & 0x0000ff00;
+ __raw_writel(irq_sts, (void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_STS));
+ irq_sts = (irq_sts) >> 8;
+
+ while (irq_sts) {
+ target_id = __ffs(irq_sts);
+ if (target_id >= MBOX_NR){
+ break;
+ }
+ irq_sts &= irq_sts - 1;
+ spin_lock_irqsave(&mbox_chns[target_id].mbox_lock,flags);
+ if (mbox_chns[target_id].mbox_recv_irq_handler) {
+ priv_data = mbox_chns[target_id].mbox_priv_data;
+ /*we will use tasklet later*/
+ mbox_chns[target_id].mbox_recv_irq_handler(irq_num, priv_data);
+ }
+ spin_unlock_irqrestore(&mbox_chns[target_id].mbox_lock,flags);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int mbox_register_irq_handle(u8 target_id, irq_handler_t irq_handler, void *priv_data)
+{
+ u32 reg_val;
+
+ if (target_id >= MBOX_NR || mbox_chns[target_id].mbox_recv_irq_handler) {
+ return -EINVAL;
+ }
+
+ mbox_chns[target_id].mbox_recv_irq_handler = irq_handler;
+ mbox_chns[target_id].mbox_priv_data = priv_data;
+
+ /*enable the irq*/
+ reg_val = __raw_readl((void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_MSK));
+ reg_val &= ~((0x1 << target_id) << 8);
+ __raw_writel(reg_val, (void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_MSK));
+
+ return 0;
+}
+
+int mbox_unregister_irq_handle(u8 target_id)
+{
+ u32 reg_val;
+
+ if (target_id >= MBOX_NR || !mbox_chns[target_id].mbox_recv_irq_handler) {
+ return -EINVAL;
+ }
+
+ spin_lock(&mbox_chns[target_id].mbox_lock);
+
+ mbox_chns[target_id].mbox_recv_irq_handler = NULL;
+
+ /*disable the irq*/
+ reg_val = __raw_readl((void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_MSK));
+ reg_val |= (0x1 << target_id) << 8;
+ __raw_writel(reg_val, (void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_MSK));
+ reg_val = __raw_readl((void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_STS));
+ reg_val |= (0x1 << target_id) << 8;
+ __raw_writel(reg_val, (void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_STS));
+
+ /*clean the irq status*/
+ spin_unlock(&mbox_chns[target_id].mbox_lock);
+
+ return 0;
+}
+
+int mbox_raw_sent(u8 target_id, u64 msg)
+{
+ /*lock the mbox, fix me*/
+ while (!(__raw_readl((void __iomem *)(REGS_SEND_MBOX_BASE + MBOX_LOCK)) & 0x1));
+#if 0
+ __raw_writeq(msg, REGS_SEND_MBOX_BASE + MBOX_MSG_L );
+#endif
+ __raw_writel(target_id, (void __iomem *)(REGS_SEND_MBOX_BASE + MBOX_ID));
+ __raw_writel(0x1, (void __iomem *)(REGS_SEND_MBOX_BASE + MBOX_TRI));
+ __raw_writel(MBOX_UNLOCK_KEY, (void __iomem *)(REGS_SEND_MBOX_BASE + MBOX_LOCK));
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(mbox_register_irq_handle);
+EXPORT_SYMBOL_GPL(mbox_unregister_irq_handle);
+EXPORT_SYMBOL_GPL(mbox_raw_sent);
+
+static int __init mbox_init(void)
+{
+ int i;
+ int ret;
+ struct resource res;
+ struct device_node *np;
+ int vmailbox_tag_irq = SCI_IRQ(69);
+ int vmailbox_src_irq = SCI_IRQ(68);
+
+ np = of_find_compatible_node(NULL, NULL, "sprd,mailbox");
+ if (of_can_translate_address(np)) {
+ of_address_to_resource(np, 0, &res);
+ sprd_mailbox_base = ioremap_nocache(res.start,
+ resource_size(&res));
+ }
+ if (!sprd_mailbox_base)
+ panic("can't create iomap for mailbox!");
+
+ /*glb enable and rst*/
+ sci_glb_set(REG_AON_APB_APB_EB1, BIT_MBOX_EB);
+
+ for (i = 0; i < 0x100; i++);
+
+ __raw_writel(0x1, (void __iomem *)(REGS_SEND_MBOX_BASE + MBOX_FIFO_RST));
+ __raw_writel(0x1, (void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_FIFO_RST));
+
+ for (i = 0; i < 0x100; i++);
+
+ __raw_writel(0x0, (void __iomem *)(REGS_SEND_MBOX_BASE + MBOX_FIFO_RST));
+ __raw_writel(~FIFO_BLOCK_STS, REGS_SEND_MBOX_BASE + MBOX_IRQ_MSK);
+ ret = request_irq(vmailbox_src_irq, mbox_src_irqhandle, IRQF_NO_SUSPEND, "sprd-mailbox_source", NULL);
+ if (ret) {
+ pr_err("mbox request source irq:%d failed\n", vmailbox_src_irq);
+ return -1; /*fixme*/
+ }
+ enable_irq_wake(vmailbox_src_irq);
+ /*recv use the irq_type_1 default*/
+ __raw_writel(0x10000, (void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_FIFO_RST));
+ /* FIXME: irq num */
+ ret = request_irq(vmailbox_tag_irq, mbox_recv_irqhandle, IRQF_NO_SUSPEND, "sprd-mailbox_target", NULL);
+ if (ret) {
+ pr_err("mbox request target irq:%d failed\n",vmailbox_tag_irq);
+ return -1; /*fixme*/
+ }
+ enable_irq_wake(vmailbox_tag_irq);
+ /*disable recv irq*/
+ __raw_writel(0xff00, (void __iomem *)(REGS_RECV_MBOX_BASE + MBOX_IRQ_MSK));
+
+ for (i = 0; i < MBOX_NR; i++) {
+ spin_lock_init(&mbox_chns[i].mbox_lock);
+ }
+
+ return 0;
+}
+
+arch_initcall(mbox_init);
diff --git a/drivers/platform/sprd/pin_switch.c b/drivers/platform/sprd/pin_switch.c
new file mode 100755
index 00000000..58591d3b
--- /dev/null
+++ b/drivers/platform/sprd/pin_switch.c
@@ -0,0 +1,559 @@
+// #define DEBUG
+// #define pr_fmt(fmt) "---sprd pinswitch---"fmt
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <asm/uaccess.h>
+#include <linux/printk.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/arch_lock.h>
+#include <linux/of.h>
+#include <soc/sprd/pin_switch.h>
+#include <linux/regulator/consumer.h>
+
+#ifdef CONFIG_PROC_FS
+
+#undef SPRD_PIN_BASE
+unsigned long SPRD_PIN_BASE = 0;
+struct sci_pin_switch {
+ const char *dirname;
+ const char *filename;
+ u32 reg;
+ u32 bit_offset;
+ u32 bit_width;
+ u32 func;
+};
+
+static struct sci_pin_switch sci_pin_switch_array[] = {
+#if !(defined(CONFIG_ARCH_SCX15)||defined(CONFIG_ARCH_SCX35L))
+ {"", "vbc_dac_iislrck_pin_in_sel", 0x4, 24, 1, 0},
+ {"", "vbc_dac_iisclk_pin_in_sel", 0x4, 23, 1, 0},
+ {"", "vbc_adc_iislrck_pin_in_sel", 0x4, 22, 1, 0},
+ {"", "vbc_adc_iisdi_pin_in_sel", 0x4, 21, 1, 0},
+ {"", "vbc_adc_iisclk_pin_in_sel", 0x4, 20, 1, 0},
+ {"", "iis3mck_pin_in_sel", 0x4, 19, 1, 0},
+ {"", "iis3lrck_pin_in_sel", 0x4, 18, 1, 0},
+ {"", "iis3di_pin_in_sel", 0x4, 17, 1, 0},
+ {"", "iis3clk_pin_in_sel", 0x4, 16, 1, 0},
+ {"", "iis2mck_pin_in_sel", 0x4, 15, 1, 0},
+ {"", "iis2lrck_pin_in_sel", 0x4, 14, 1, 0},
+ {"", "iis2di_pin_in_sel", 0x4, 13, 1, 0},
+ {"", "iis2clk_pin_in_sel", 0x4, 12, 1, 0},
+#if defined(CONFIG_ARCH_SCX30G) /*tshark add */
+ {"", "bt_iis_con_eb", 0xc, 30, 1, 0},
+ {"", "pad_oe_iis3_mck", 0xc, 27, 1, 0},
+ {"", "pad_oe_iis2_mck", 0xc, 26, 1, 0},
+ {"", "pad_oe_iis1_mck", 0xc, 25, 1, 0},
+ {"", "pad_oe_iis0_mck", 0xc, 24, 1, 0},
+#endif
+#endif
+ {"", "iis23_loop_sel", 0xc, 5, 1, 0},
+ {"", "iis13_loop_sel", 0xc, 4, 1, 0},
+ {"", "iis12_loop_sel", 0xc, 3, 1, 0},
+ {"", "iis03_loop_sel", 0xc, 2, 1, 0},
+ {"", "iis02_loop_sel", 0xc, 1, 1, 0},
+ {"", "iis01_loop_sel", 0xc, 0, 1, 0},
+};
+
+
+#if defined(CONFIG_ARCH_SCX35L)
+static struct sci_pin_switch iis_0_array[] = {
+ {"iis0_sys_sel", "ap_iis0", 0xc, 6, 3, 0},
+ {"iis0_sys_sel", "cp0_iis0", 0xc, 6, 3, 1},
+ {"iis0_sys_sel", "cp1_iis0", 0xc, 6, 3, 2},
+ {"iis0_sys_sel", "vbc_iis0", 0xc, 6, 3, 3},
+ {"iis0_sys_sel", "vbc_iis1", 0xc, 6, 3, 4},
+};
+
+static struct sci_pin_switch iis_1_array[] = {
+ {"iis1_sys_sel", "ap_iis1", 0xc, 9, 3, 0},
+ {"iis1_sys_sel", "cp0_iis1", 0xc, 9, 3, 1},
+ {"iis1_sys_sel", "cp1_iis1", 0xc, 9, 3, 2},
+ {"iis1_sys_sel", "vbc_iis0", 0xc, 9, 3, 3},
+ {"iis1_sys_sel", "vbc_iis1", 0xc, 9, 3, 4},
+};
+
+static struct sci_pin_switch iis_2_array[] = {
+ {"iis2_sys_sel", "ap_iis2", 0xc, 12, 3, 0},
+ {"iis2_sys_sel", "cp0_iis2", 0xc, 12, 3, 1},
+ {"iis2_sys_sel", "cp1_iis2", 0xc, 12, 3, 2},
+ {"iis2_sys_sel", "vbc_iis0", 0xc, 12, 3, 3},
+ {"iis2_sys_sel", "vbc_iis1", 0xc, 12, 3, 4},
+};
+
+static struct sci_pin_switch iis_3_array[] = {
+ {"iis3_sys_sel", "ap_iis3", 0xc, 15, 3, 0},
+ {"iis3_sys_sel", "cp0_iis3", 0xc, 15, 3, 1},
+ {"iis3_sys_sel", "cp1_iis3", 0xc, 15, 3, 2},
+ {"iis3_sys_sel", "vbc_iis0", 0xc, 15, 3, 3},
+ {"iis3_sys_sel", "vbc_iis1", 0xc, 15, 3, 4},
+};
+#else
+static struct sci_pin_switch bt_iis_sys_sel_array[] = {
+ {"bt_iis_sys_sel", "cp0_iis0", 0x8, 28, 4, 0},
+ {"bt_iis_sys_sel", "cp0_iis1", 0x8, 28, 4, 1},
+ {"bt_iis_sys_sel", "cp0_iis2", 0x8, 28, 4, 2},
+ {"bt_iis_sys_sel", "cp0_iis3", 0x8, 28, 4, 3},
+ {"bt_iis_sys_sel", "cp1_iis0", 0x8, 28, 4, 4},
+ {"bt_iis_sys_sel", "cp1_iis1", 0x8, 28, 4, 5},
+ {"bt_iis_sys_sel", "cp1_iis2", 0x8, 28, 4, 6},
+ {"bt_iis_sys_sel", "cp1_iis3", 0x8, 28, 4, 7},
+ {"bt_iis_sys_sel", "ap_iis0", 0x8, 28, 4, 8},
+ {"bt_iis_sys_sel", "ap_iis1", 0x8, 28, 4, 9},
+ {"bt_iis_sys_sel", "ap_iis2", 0x8, 28, 4, 10},
+ {"bt_iis_sys_sel", "ap_iis3", 0x8, 28, 4, 11},
+ {"bt_iis_sys_sel", "vbc_iis", 0x8, 28, 4, 12},
+ {"bt_iis_sys_sel", "rsv_d", 0x8, 28, 4, 13},
+ {"bt_iis_sys_sel", "rsv_e", 0x8, 28, 4, 14},
+ {"bt_iis_sys_sel", "rsv_f", 0x8, 28, 4, 15},
+};
+
+static struct sci_pin_switch iis_0_array[] = {
+ {"iis0_sys_sel", "ap_iis0", 0xc, 6, 3, 0},
+ {"iis0_sys_sel", "cp0_iis0", 0xc, 6, 3, 1},
+ {"iis0_sys_sel", "cp1_iis0", 0xc, 6, 3, 2},
+ {"iis0_sys_sel", "cp2_iis0", 0xc, 6, 3, 3},
+ {"iis0_sys_sel", "vbc_iis0", 0xc, 6, 3, 4},
+};
+
+static struct sci_pin_switch iis_1_array[] = {
+ {"iis1_sys_sel", "ap_iis1", 0xc, 9, 3, 0},
+ {"iis1_sys_sel", "cp0_iis1", 0xc, 9, 3, 1},
+ {"iis1_sys_sel", "cp1_iis1", 0xc, 9, 3, 2},
+ {"iis1_sys_sel", "cp2_iis1", 0xc, 9, 3, 3},
+};
+
+#if !defined(CONFIG_ARCH_SCX15)
+static struct sci_pin_switch iis_2_array[] = {
+ {"iis2_sys_sel", "ap_iis2", 0xc, 12, 3, 0},
+ {"iis2_sys_sel", "cp0_iis2", 0xc, 12, 3, 1},
+ {"iis2_sys_sel", "cp1_iis2", 0xc, 12, 3, 2},
+ {"iis2_sys_sel", "cp2_iis2", 0xc, 12, 3, 3},
+};
+
+static struct sci_pin_switch iis_3_array[] = {
+ {"iis3_sys_sel", "ap_iis3", 0xc, 15, 3, 0},
+ {"iis3_sys_sel", "cp0_iis3", 0xc, 15, 3, 1},
+ {"iis3_sys_sel", "cp1_iis3", 0xc, 15, 3, 2},
+ {"iis3_sys_sel", "cp2_iis3", 0xc, 15, 3, 3},
+};
+#endif
+#endif
+
+#if defined(CONFIG_PIN_POWER_DOMAIN_SWITCH)
+struct pin_reg_desc {
+ struct sprd_pin_switch_platform_data *platform_data;
+ struct notifier_block nb;
+ struct regulator_bulk_data supplies;
+} pin_reg_desc_array[PD_CNT];
+#endif
+
+static struct sci_pin_switch_dir {
+ struct sci_pin_switch *sci_pin_switch;
+ u32 array_size;
+} sci_pin_switch_dir_array[] = {
+#if !defined(CONFIG_ARCH_SCX35L)
+ {
+ bt_iis_sys_sel_array, ARRAY_SIZE(bt_iis_sys_sel_array)},
+#endif
+ {
+ iis_0_array, ARRAY_SIZE(iis_0_array)}, {
+ iis_1_array, ARRAY_SIZE(iis_1_array)},
+#if !defined(CONFIG_ARCH_SCX15)
+ {
+ iis_2_array, ARRAY_SIZE(iis_2_array)}, {
+ iis_3_array, ARRAY_SIZE(iis_3_array)},
+#endif
+};
+
+u32 pinmap_get(u32 offset)
+{
+ return readl(SPRD_PIN_BASE + offset);
+}
+
+EXPORT_SYMBOL(pinmap_get);
+int pinmap_set(u32 offset, u32 value)
+{
+ unsigned long flags;
+ __arch_default_lock(HWLOCK_GLB, &flags);
+ writel(value, SPRD_PIN_BASE + offset);
+ __arch_default_unlock(HWLOCK_GLB, &flags);
+ return 0;
+}
+
+EXPORT_SYMBOL(pinmap_set);
+
+/*
+* #define IIS_TO_AP (0)
+* #define IIS_TO_CP0 (1)
+* #define IIS_TO_CP1 (2)
+* #define IIS_TO_CP2 (3)
+* #define PIN_CTL_REG3 (SPRD_PIN_BASE + 0xc)
+*/
+static int read_write_pin_switch(int is_read, int v, struct sci_pin_switch *p)
+{
+ int val = 0;
+ u32 shift = p->bit_offset;
+ u32 mask = (1 << (p->bit_width)) - 1;
+ if ((shift > 31))
+ BUG_ON(1);
+ if (v > mask)
+ printk("v:0x%x overflow bitwidth:%ud, mask:0x%x it\n",
+ v, p->bit_width, mask);
+ val = pinmap_get(p->reg);
+ if (is_read) {
+ val >>= shift;
+ val &= mask;
+ return val;
+ } else {
+ val &= ~(mask << shift);
+ val |= (v & mask) << shift;
+ pinmap_set(p->reg, val);
+ }
+ return val;
+}
+
+static int pin_switch_proc_show(struct seq_file *m, void *v)
+{
+ int val = 0;
+ struct sci_pin_switch *p = (struct sci_pin_switch *)(m->private);
+ val = read_write_pin_switch(1, (int)val, p);
+ seq_printf(m, "%d\n", val);
+ return 0;
+}
+
+static int pin_switch_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pin_switch_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t pin_switch_proc_write(struct file *file,
+ const char __user * buffer,
+ size_t count, loff_t * pos)
+{
+ long val = 0;
+ int ret = 0;
+ struct sci_pin_switch *p =
+ (struct sci_pin_switch *)(PDE_DATA(file_inode(file)));
+ ret = kstrtol_from_user(buffer, count, 0, &val);
+ if (ret) {
+ pr_err("input err\n");
+ return -EINVAL;
+ }
+ read_write_pin_switch(0, val, p);
+ return count;
+}
+
+static const struct file_operations pin_switch_fops = {
+ .open = pin_switch_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = pin_switch_proc_write,
+};
+
+static int pin_switch_dir_proc_show(struct seq_file *m, void *v)
+{
+ int val = 0;
+ int func_tmp = 0;
+ struct sci_pin_switch *p = (struct sci_pin_switch *)m->private;
+ func_tmp = read_write_pin_switch(1, val, p);
+ if (p->func == func_tmp)
+ val = 1;
+ else
+ val = 0;
+ seq_printf(m, "%d\n", val);
+ return 0;
+}
+
+static int pin_switch_dir_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pin_switch_dir_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t pin_switch_dir_proc_write(struct file *file,
+ const char __user * buffer,
+ size_t count, loff_t * pos)
+{
+ int val = 0;
+ int ret = 0;
+ struct sci_pin_switch *p =
+ (struct sci_pin_switch *)(PDE_DATA(file_inode(file)));
+ ret = kstrtol_from_user(buffer, count, 0, &val);
+ if (ret) {
+ pr_err("input err\n");
+ return -EINVAL;
+ }
+ if (val == 1)
+ val = p->func;
+ else /*if val = 0 or other value, just ignore it */
+ return 0;
+ read_write_pin_switch(0, val, p);
+ return count;
+}
+
+static const struct file_operations pin_switch_dir_fops = {
+ .open = pin_switch_dir_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = pin_switch_dir_proc_write,
+};
+
+static struct proc_dir_entry *pin_switch_proc_base;
+static int __init pin_switch_proc_add(struct sci_pin_switch *pin_switch)
+{
+ struct proc_dir_entry *tmp_proc;
+ tmp_proc =
+ proc_create_data(pin_switch->filename, S_IRWXUGO,
+ pin_switch_proc_base, &pin_switch_fops,
+ pin_switch);
+ if (!tmp_proc)
+ return -ENOMEM;
+ return 0;
+}
+
+static int __init pin_switch_proc_add_dir(struct sci_pin_switch_dir
+ *pin_switch_dir)
+{
+ struct proc_dir_entry *tmp_proc_dir;
+ struct proc_dir_entry *tmp_proc;
+ int i;
+ if (pin_switch_dir->sci_pin_switch->dirname == NULL)
+ return EINVAL;
+ /* has dir name, first create parent dir */
+ tmp_proc_dir =
+ proc_mkdir(pin_switch_dir->sci_pin_switch->dirname,
+ pin_switch_proc_base);
+ if (!tmp_proc_dir)
+ return -ENOMEM;
+
+ for (i = 0; i < pin_switch_dir->array_size; ++i) {
+ if (pin_switch_dir->sci_pin_switch[i].filename == NULL)
+ return EINVAL;
+ tmp_proc =
+ proc_create_data(pin_switch_dir->sci_pin_switch[i].filename,
+ S_IRWXUGO, tmp_proc_dir,
+ &pin_switch_dir_fops,
+ &pin_switch_dir->sci_pin_switch[i]);
+ if (!tmp_proc)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#if defined(CONFIG_PIN_POWER_DOMAIN_SWITCH)
+static int sc271x_regulator_event(struct notifier_block *regu_nb,
+ unsigned long event, void *data)
+{
+ unsigned long flags;
+ struct pin_reg_desc *p_pin_reg_desc =
+ container_of(regu_nb, struct pin_reg_desc, nb);
+ unsigned long best_data = (unsigned long *)data;
+ const char *regu_name;
+ u32 bit;
+ u32 reg;
+
+ if (!p_pin_reg_desc) {
+ return -EINVAL;
+ }
+ regu_name = p_pin_reg_desc->supplies.supply;
+ bit = p_pin_reg_desc->platform_data->bit_offset;
+ reg = p_pin_reg_desc->platform_data->reg;
+ pr_info("event:0x%x, best_data:0x%x\n", event, best_data);
+
+ if (event & REGULATOR_EVENT_VOLTAGE_CHANGE) {
+ if (p_pin_reg_desc) {
+ if (best_data > VOL_THRESHOLD) {
+ #if (defined(CONFIG_ARCH_SCX20L)||defined(CONFIG_ARCH_SCX20))
+ pinmap_set(reg, (pinmap_get(reg) | (1 << bit)));
+ #else
+ pinmap_set(reg, (pinmap_get(reg) & ~(1 << bit)));
+ #endif
+ pr_info
+ ("%s()->Line:%d, --REGULATOR_EVENT_VOLTAGE_CHANGE--> %s event %ld, data %ld\n",
+ __func__, __LINE__, regu_name, event,
+ best_data);
+ } else {
+ #if (defined(CONFIG_ARCH_SCX20L)||defined(CONFIG_ARCH_SCX20))
+ pinmap_set(reg, (pinmap_get(reg) & ~(1 << bit)));
+ #else
+ pinmap_set(reg, (pinmap_get(reg) | (1 << bit)));
+ #endif
+ pr_info
+ ("%s()->Line:%d, --REGULATOR_EVENT_VOLTAGE_CHANGE--> %s event %ld, data %ld\n",
+ __func__, __LINE__, regu_name, event,
+ best_data);
+ }
+ }
+ } else if (event & REGULATOR_EVENT_DISABLE) {
+ pr_info
+ ("%s()->Line:%d, --REGULATOR_EVENT_DISABLE--> %s event %ld\n",
+ __func__, __LINE__, regu_name, event);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int pin_regulator_notifier_register(struct pin_reg_desc *table)
+{
+ int i, ret;
+ struct regulator *regu;
+
+ for (i = 0; i < PD_CNT; i++) {
+ if (IS_ERR_OR_NULL(table[i].platform_data->power_domain)) {
+ pr_err("invalid platform data: 0x%p\n",
+ table[i].platform_data->power_domain);
+ return -EINVAL;
+ }
+ regu =
+ regulator_get(NULL, table[i].platform_data->power_domain);
+ pr_info("%s, %d, regu_name:%s\n", __FUNCTION__, __LINE__,
+ table[i].platform_data->power_domain);
+ if (IS_ERR_OR_NULL(regu)) {
+ pr_err("no regu %s !\n",
+ table[i].platform_data->power_domain);
+ return -ENXIO;;
+ }
+ table[i].supplies.consumer = regu;
+ table[i].supplies.supply = table[i].platform_data->power_domain;
+ table[i].nb.notifier_call = sc271x_regulator_event;
+ ret = regulator_register_notifier(regu, &(table[i].nb));
+ if (ret) {
+ pr_err("alert: regu %s reg notifier failed\n",
+ table[i].platform_data->power_domain);
+ }
+ }
+ return 0;
+}
+
+static int sprd_pin_switch_probe(struct platform_device *pdev)
+{
+ int i, ret;
+ struct sprd_pin_switch_platform_data *p_pin_switch_data;
+
+#ifdef CONFIG_OF
+ struct resource *res;
+ struct device_node *np = pdev->dev.of_node;
+
+ p_pin_switch_data =
+ devm_kzalloc(&pdev->dev, sizeof(*p_pin_switch_data) * PD_CNT,
+ GFP_KERNEL);
+ if (!p_pin_switch_data) {
+ pr_err("pin_switch alloc err\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No reg of property specified\n");
+ return -ENODEV;
+ }
+
+ SPRD_PIN_BASE = (unsigned long)ioremap_nocache(res->start,res->end - res->start);
+ if (!SPRD_PIN_BASE)
+ panic("pin_switch get address failed!\n");
+
+
+ pr_info("-----SPRD_PIN_BASE:0x%x-----\n", SPRD_PIN_BASE);
+
+ for (i = 0; i < PD_CNT; i++) {
+ /*1. get pin power domain vddname */
+ ret =
+ of_property_read_string_index(np, "pwr_domain", i,
+ &p_pin_switch_data[i].
+ power_domain);
+ if (ret) {
+ pr_err("fail to get pwr_domain\n");
+ return ret;
+ }
+ pr_info("pwr_domain[%d]:%s\n", i,
+ p_pin_switch_data[i].power_domain);
+ /*2. get pin power domain reg ctrl description info */
+ ret =
+ of_property_read_u32_index(np, "ctrl_desc", i * 3,
+ &p_pin_switch_data[i].reg);
+ if (ret) {
+ pr_err("fail to get ctrl_desc reg\n");
+ return ret;
+ }
+ pr_info("reg[%d]:0x%x\n", i, p_pin_switch_data[i].reg);
+
+ ret =
+ of_property_read_u32_index(np, "ctrl_desc", i * 3 + 1,
+ &p_pin_switch_data[i].
+ bit_offset);
+ if (ret) {
+ pr_err("fail to get ctrl_desc bit_offset\n");
+ return ret;
+ }
+ pr_info("bit_offset[%d]:0x%x\n", i,
+ p_pin_switch_data[i].bit_offset);
+ ret =
+ of_property_read_u32_index(np, "ctrl_desc", i * 3 + 2,
+ &p_pin_switch_data[i].bit_width);
+ if (ret) {
+ pr_err("fail to get ctrl_desc bit_width\n");
+ return ret;
+ }
+ pr_info("bit_width[%d]:0x%x\n", i,
+ p_pin_switch_data[i].bit_width);
+ }
+#else
+ p_pin_switch_data = dev_get_platdata(&pdev->dev);
+ if (IS_ERR_OR_NULL(p_pin_switch_data)) {
+ pr_err("invalid platform data: 0x%p\n", p_pin_switch_data);
+ return -EINVAL;
+ }
+#endif
+ for (i = 0; i < PD_CNT; i++) {
+ pin_reg_desc_array[i].platform_data = &p_pin_switch_data[i]; // + i*sizeof(struct sprd_pin_switch_platform_data);
+ }
+ ret = pin_regulator_notifier_register(pin_reg_desc_array);
+ if (ret) {
+ pr_err("register notifier err\n");
+ }
+ return ret;
+}
+
+static struct of_device_id sprd_pinctrl_match_table[] = {
+ {.compatible = "sprd,pinctrl",},
+ {},
+};
+
+static struct platform_driver pin_switch_driver = {
+ .probe = sprd_pin_switch_probe,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "pin_switch",
+ .of_match_table = of_match_ptr(sprd_pinctrl_match_table),
+ },
+};
+#endif
+
+int __init pin_switch_proc_init(void)
+{
+ int i;
+ int ret = 0;
+ pin_switch_proc_base = proc_mkdir("pin_switch", NULL);
+ if (!pin_switch_proc_base)
+ return -ENOMEM;
+ for (i = 0; i < ARRAY_SIZE(sci_pin_switch_array); ++i) {
+ pin_switch_proc_add(&sci_pin_switch_array[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(sci_pin_switch_dir_array); ++i) {
+ pin_switch_proc_add_dir(&sci_pin_switch_dir_array[i]);
+ }
+#if defined(CONFIG_PIN_POWER_DOMAIN_SWITCH)
+ ret = platform_driver_register(&pin_switch_driver);
+#endif
+ return ret;
+}
+
+subsys_initcall_sync(pin_switch_proc_init);
+#else
+#error "CONFIG_PROC_FS needed by mach-sc/pin_switch"
+#endif /* CONFIG_PROC_FS */
diff --git a/drivers/platform/sprd/platsmp.c b/drivers/platform/sprd/platsmp.c
new file mode 100644
index 00000000..dcccde1a
--- /dev/null
+++ b/drivers/platform/sprd/platsmp.c
@@ -0,0 +1,382 @@
+/* linux/arch/arm/mach-sc8830/platsmp.c
+ *
+ * Copyright (c) 2010-2012 Spreadtrum Co., Ltd.
+ * http://www.spreadtrum.com
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Cloned from linux/arch/arm/mach-vexpress/platsmp.c
+ *
+ * Copyright (C) 2002 ARM Ltd.
+ * 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.
+*/
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/smp.h>
+#include <linux/io.h>
+
+#include <asm/cacheflush.h>
+#include <asm/smp_plat.h>
+#include <linux/irqchip/arm-gic.h>
+#include <asm/mach-types.h>
+#include <asm/smp_scu.h>
+#include <asm/unified.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/sci.h>
+#include <asm/atomic.h>
+
+extern void sci_secondary_startup(void);
+
+#if (defined CONFIG_ARCH_SC8825)
+static int __cpuinit boot_secondary_cpus(int cpu_id, u32 paddr)
+{
+ if (cpu_id != 1)
+ return -1;
+ writel(paddr,CPU_JUMP_VADDR);
+ writel(1 << (cpu_id * 4),HOLDING_PEN_VADDR);
+ return 0;
+}
+
+#elif (defined CONFIG_ARCH_SCX35)
+
+#define CA7_CORE1_PD (BIT(8)|BIT(9)|BIT(10))
+#define CA7_CORE2_PD (BIT(12)|BIT(13)|BIT(14))
+#define CA7_CORE3_PD (BIT(16)|BIT(17)|BIT(18))
+int scxx30_all_nonboot_cpus_died(void)
+{
+ u32 val;
+ u32 core1_pd , core2_pd , core3_pd;
+
+ val = sci_glb_read(REG_PMU_APB_PWR_STATUS0_DBG, -1UL);
+
+ core1_pd = val & CA7_CORE1_PD;
+ core2_pd = val & CA7_CORE2_PD;
+ core3_pd = val & CA7_CORE3_PD;
+
+ if(core1_pd && core2_pd && core3_pd){
+ pr_debug("*** %s, REG_PMU_APB_PWR_STATUS0_DBG:0x%x ***\n", __func__, val);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int poweron_cpus(int cpu)
+{
+ u32 poweron, val;
+
+ if (cpu == 1)
+ poweron = REG_PMU_APB_PD_CA7_C1_CFG;
+ else if (cpu == 2)
+ poweron = REG_PMU_APB_PD_CA7_C2_CFG;
+ else if (cpu == 3)
+ poweron = REG_PMU_APB_PD_CA7_C3_CFG;
+ else
+ return -1;
+
+
+ val = sci_glb_read(REG_AP_AHB_CA7_RST_SET, -1UL);
+ val |= (1 << cpu);
+ sci_glb_write(REG_AP_AHB_CA7_RST_SET, val, -1UL);
+
+ val = BITS_PD_CA7_C3_PWR_ON_DLY(4) | BITS_PD_CA7_C3_PWR_ON_SEQ_DLY(4) | BITS_PD_CA7_C3_ISO_ON_DLY(4);
+ sci_glb_write(poweron, val, -1UL);
+
+ val = (BIT_PD_CA7_C3_AUTO_SHUTDOWN_EN | __raw_readl(poweron)) &~(BIT_PD_CA7_C3_FORCE_SHUTDOWN);
+ sci_glb_write(poweron, val, -1UL);
+ dmb();
+ udelay(1000);
+
+ while((__raw_readl(poweron)&BIT_PD_CA7_C3_FORCE_SHUTDOWN) ||
+ !(__raw_readl(REG_AP_AHB_CA7_RST_SET)&(1 << cpu)) ){
+ pr_debug("??? %s set regs failed ???\n", __func__ );
+ writel((__raw_readl(REG_AP_AHB_CA7_RST_SET) | (1 << cpu)), REG_AP_AHB_CA7_RST_SET);
+ val = (BIT_PD_CA7_C3_AUTO_SHUTDOWN_EN | __raw_readl(poweron)) &~(BIT_PD_CA7_C3_FORCE_SHUTDOWN);
+ writel(val,poweron);
+ dmb();
+ udelay(500);
+ }
+ writel((__raw_readl(REG_AP_AHB_CA7_RST_SET) & ~(1 << cpu)), REG_AP_AHB_CA7_RST_SET);
+ return 0;
+}
+
+int powerdown_cpus(int cpu)
+{
+ u32 poweron, val, i = 0;
+ if (cpu == 1)
+ poweron = REG_PMU_APB_PD_CA7_C1_CFG;
+ else if (cpu == 2)
+ poweron = REG_PMU_APB_PD_CA7_C2_CFG;
+ else if (cpu == 3)
+ poweron = REG_PMU_APB_PD_CA7_C3_CFG;
+ else
+ return -1;
+
+ val = (BIT_PD_CA7_C3_FORCE_SHUTDOWN | __raw_readl(poweron)) &~(BIT_PD_CA7_C3_AUTO_SHUTDOWN_EN);
+ writel(val, poweron);
+
+
+ while (i < 20) {
+ //check power down?
+ if (((__raw_readl(REG_PMU_APB_PWR_STATUS0_DBG) >> (4 * (cpu_logical_map(cpu) + 1))) & 0x0f) == 0x07) {
+ break;
+ }
+ udelay(60);
+ i++;
+ }
+ printk("powerdown_cpus i=%d !!\n", i);
+ BUG_ON(i >= 20);
+ udelay(60);
+
+ return 0;
+}
+
+static int __cpuinit boot_secondary_cpus(int cpu_id, u32 paddr)
+{
+ if (cpu_id < 1 || cpu_id > 3)
+ return -1;
+
+ writel(paddr,(CPU_JUMP_VADDR + (cpu_id << 2)));
+ writel(readl(HOLDING_PEN_VADDR) | (1 << cpu_id),HOLDING_PEN_VADDR);
+ poweron_cpus(cpu_id);
+
+ return 0;
+}
+
+#endif
+
+/*
+ * control for which core is the next to come out of the secondary
+ * boot "holding pen"
+ */
+extern volatile int pen_release;
+
+/*
+ * Write pen_release in a way that is guaranteed to be visible to all
+ * observers, irrespective of whether they're taking part in coherency
+ * or not. This is necessary for the hotplug code to work reliably.
+ */
+static void __cpuinit write_pen_release(int val)
+{
+ pen_release = val;
+ smp_wmb();
+ __cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release));
+ outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1));
+}
+
+static DEFINE_SPINLOCK(boot_lock);
+
+
+unsigned int sprd_boot_magnum = 0xbadf1a90;
+
+#define SPRD_UP_FLAG0 (0x63507530)
+#define SPRD_UP_FLAG1 (0x63507531)
+#define SPRD_UP_FLAG2 (0x63507532)
+#define SPRD_UP_FLAG3 (0x63507533)
+
+unsigned int g_sprd_boot_flag_1 = 0;
+unsigned int g_sprd_boot_flag_2 = 0;
+char * ga_boot_flag1[4] =
+ {
+ "bf10",
+ "bf11",
+ "bf12",
+ "bf13"
+ };
+char * ga_boot_flag2[4] =
+ {
+ "bf20",
+ "bf21",
+ "bf22",
+ "bf23"
+ };
+
+unsigned int g_sprd_up_flag[4] =
+ {
+ SPRD_UP_FLAG0,
+ SPRD_UP_FLAG1,
+ SPRD_UP_FLAG2,
+ SPRD_UP_FLAG3
+ };
+
+atomic_t boot_lock_cnt = ATOMIC_INIT(0);
+atomic_t boot_unlock_cnt = ATOMIC_INIT(0);
+
+void __cpuinit sprd_secondary_init(unsigned int cpu)
+{
+ /*
+ * if any interrupts are already enabled for the primary
+ * core (e.g. timer irq), then they will not have been enabled
+ * for us: do so
+ */
+ //gic_secondary_init(0);
+
+ /*
+ * let the primary processor know we're out of the
+ * pen, then head off into the C entry point
+ */
+
+ write_pen_release(g_sprd_up_flag[cpu]);
+
+ /*
+ * Synchronise with the boot thread.
+ */
+ spin_lock(&boot_lock);
+ spin_unlock(&boot_lock);
+ atomic_inc(&boot_unlock_cnt);
+ g_sprd_boot_flag_1 = 0;
+ g_sprd_boot_flag_2 = 0;
+
+}
+
+
+
+int __cpuinit sprd_boot_secondary(unsigned int cpu, struct task_struct *idle)
+{
+ unsigned long timeout;
+ int ret;
+ unsigned int val = sprd_boot_magnum;
+
+ /*
+ * Set synchronisation state between this boot processor
+ * and the secondary one
+ */
+ spin_lock(&boot_lock);
+ atomic_inc(&boot_lock_cnt);
+
+ /*
+ * The secondary processor is waiting to be released from
+ * the holding pen - release it, then wait for it to flag
+ * that it has been released by resetting pen_release.
+ *
+ */
+
+ memcpy(&g_sprd_boot_flag_1,ga_boot_flag1[cpu],4);
+
+ val |= (cpu_logical_map(cpu) & 0x0000000f);
+
+ write_pen_release((int)val);
+
+ memcpy(&g_sprd_boot_flag_2,ga_boot_flag2[cpu],4);
+
+ ret = boot_secondary_cpus(cpu, virt_to_phys(sci_secondary_startup));
+ if (ret < 0)
+ pr_warn("SMP: boot_secondary(%u) error\n", cpu);
+
+ dsb_sev();
+
+
+ timeout = jiffies + (1 * HZ);
+
+ while (time_before(jiffies, timeout)) {
+ smp_rmb();
+ if (pen_release == g_sprd_up_flag[cpu])
+ break;
+
+ udelay(10);
+ dsb_sev();
+ }
+
+ spin_unlock(&boot_lock);
+
+ /*
+ * now the secondary core is starting up let it run its
+ * calibrations, then wait for it to finish
+ */
+ if(pen_release != g_sprd_up_flag[cpu]){
+ printk("*** %s, pen_release:%x ***\n ", __func__, pen_release);
+ printk("*** %s, REG_PMU_APB_PWR_STATUS0_DBG:0x%x \n", __func__, sci_glb_read(REG_PMU_APB_PWR_STATUS0_DBG, -1UL));
+ printk("*** %s, REG_AP_AHB_CA7_RST_SET:0x%x \n", __func__, sci_glb_read(REG_AP_AHB_CA7_RST_SET, -1UL));
+ printk("*** %s, REG_AP_AHB_CA7_STANDBY_STATUS:0x%x \n", __func__, sci_glb_read(REG_AP_AHB_CA7_STANDBY_STATUS, -1UL));
+#ifndef CONFIG_ARCH_SCX35L
+ printk("*** %s, REG_AON_APB_MPLL_CFG:0x%x \n", __func__, sci_glb_read(REG_AON_APB_MPLL_CFG, -1UL));
+#endif
+ }
+
+ return pen_release != g_sprd_up_flag[cpu] ? -ENOSYS : 0;
+}
+
+#ifdef CONFIG_HAVE_ARM_SCU
+static unsigned int __get_core_num(void)
+{
+ void __iomem *scu_base = (void __iomem *)(SPRD_CORE_BASE);
+ return (scu_base ? scu_get_core_count(scu_base) : 1);
+}
+#else
+static unsigned int __get_core_num(void)
+{
+ return 4;
+}
+#endif
+
+static unsigned int sci_get_core_num(void)
+{
+ return __get_core_num();
+}
+
+/*
+ * Initialise the CPU possible map early - this describes the CPUs
+ * which may be present or become present in the system.
+ */
+void __init sprd_smp_init_cpus(void)
+{
+ unsigned int i, ncores;
+
+ ncores = sci_get_core_num();
+
+ /* sanity check */
+ if (ncores > nr_cpu_ids) {
+ pr_warn("SMP: %u cores greater than maximum (%u), clipping\n",
+ ncores, nr_cpu_ids);
+ ncores = nr_cpu_ids;
+ }
+
+ for (i = 0; i < ncores; i++)
+ set_cpu_possible(i, true);
+
+}
+#ifdef CONFIG_FIX_V7TAGRAM_BUG
+unsigned long physical_from_idle = 0;
+#endif
+void __init sprd_smp_prepare_cpus(unsigned int max_cpus)
+{
+#ifdef CONFIG_HAVE_ARM_SCU
+ scu_enable((void __iomem *)(SPRD_CORE_BASE));
+#endif
+#ifdef CONFIG_FIX_V7TAGRAM_BUG
+ extern unsigned long other_cpu_fix_phys;
+ extern void fix_tag_ram_bug(void);
+ extern void other_cpu_fix(void);
+
+ if(!other_cpu_fix_phys)
+ other_cpu_fix_phys = virt_to_phys(other_cpu_fix);
+ local_irq_disable();
+ fix_tag_ram_bug();
+ local_irq_enable();
+#endif
+}
+
+extern int sprd_cpu_kill(unsigned int cpu);
+extern void sprd_cpu_die(unsigned int cpu);
+extern int sprd_cpu_disable(unsigned int cpu);
+
+struct smp_operations sprd_smp_ops __initdata = {
+ .smp_init_cpus = sprd_smp_init_cpus,
+ .smp_prepare_cpus = sprd_smp_prepare_cpus,
+ .smp_secondary_init = sprd_secondary_init,
+ .smp_boot_secondary = sprd_boot_secondary,
+#ifdef CONFIG_HOTPLUG_CPU
+ .cpu_kill = sprd_cpu_kill,
+ .cpu_disable = sprd_cpu_disable,
+ .cpu_die = sprd_cpu_die,
+#endif
+};
diff --git a/drivers/platform/sprd/pm-scx35.c b/drivers/platform/sprd/pm-scx35.c
new file mode 100644
index 00000000..7ec92fa0
--- /dev/null
+++ b/drivers/platform/sprd/pm-scx35.c
@@ -0,0 +1,1829 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/io.h>
+#include <linux/kthread.h>
+#include <asm/irqflags.h>
+//#include <asm/hardware/cache-l2x0.h>
+#include <asm/cacheflush.h>
+#include <soc/sprd/system.h>
+#include <soc/sprd/pm_debug.h>
+#include <soc/sprd/common.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci_glb_regs.h>
+//#include <mach/irqs.h>
+#include <soc/sprd/sci.h>
+//#include "emc_repower.h"
+#include <linux/clockchips.h>
+#include <linux/wakelock.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/arch_misc.h>
+#include <asm/suspend.h>
+#include <asm/barrier.h>
+#include <linux/cpu_pm.h>
+#if defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)
+#include <soc/sprd/sprd_persistent_clock.h>
+#endif
+#if defined(CONFIG_SPRD_DEBUG)
+/* For saving Fault status */
+#include <soc/sprd/sprd_debug.h>
+#endif
+
+extern void (*arm_pm_restart)(char str, const char *cmd);
+extern int sc8830_get_clock_status(void);
+extern void secondary_startup(void);
+extern int sp_pm_collapse(unsigned int cpu, unsigned int save_state);
+extern void sp_pm_collapse_exit(void);
+extern void sc8830_standby_iram(void);
+extern void sc8830_standby_iram_end(void);
+extern void sc8830_standby_exit_iram(void);
+extern int sc_cpuidle_init(void);
+void pm_ana_ldo_config(void);
+struct ap_ahb_reg_bak {
+ u32 ahb_eb;
+ u32 ca7_ckg_cfg;
+ u32 ca7_div_cfg;
+ u32 misc_ckg_en;
+ u32 misc_cfg;
+ u32 usb_phy_tune;
+};
+struct ap_clk_reg_bak {
+ u32 ap_ahb_cfg;
+ u32 ap_apb_cfg;
+ u32 clk_gsp_cfg;
+ u32 dispc0_cfg;
+ u32 dispc0_dbi_cfg;
+ u32 dispc0_dpi_cfg;
+ u32 dispc1_cfg;
+ u32 dispc1_dbi_cfg;
+ u32 dispc1_dpi_cfg;
+ u32 nfc_cfg;
+ u32 sdio0_cfg;
+ u32 sdio1_cfg;
+ u32 sdio2_cfg;
+ u32 emmc_cfg;
+ u32 gps_cfg;
+ u32 gps_tcxo_cfg;
+ u32 usb_ref_cfg;
+ u32 uart0_cfg;
+ u32 uart1_cfg;
+ u32 uart2_cfg;
+ u32 uart3_cfg;
+ u32 uart4_cfg;
+ u32 i2c0_cfg;
+ u32 i2c1_cfg;
+ u32 i2c2_cfg;
+ u32 i2c3_cfg;
+ u32 i2c4_cfg;
+ u32 spi0_cfg;
+ u32 spi1_cfg;
+ u32 spi2_cfg;
+ u32 iis0_cfg;
+ u32 iis1_cfg;
+ u32 iis2_cfg;
+ u32 iis3_cfg;
+};
+struct ap_apb_reg_bak {
+ u32 apb_eb;
+ u32 usb_phy_tune;
+ u32 usb_phy_ctrl;
+ u32 apb_misc_ctrl;
+};
+struct pub_reg_bak {
+ u32 ddr_qos_cfg1;
+ u32 ddr_qos_cfg2;
+ u32 ddr_qos_cfg3;
+};
+struct mm_reg_bak {
+ u32 mm_cfg1;
+ u32 mm_cfg2;
+};
+
+struct auto_pd_en {
+ volatile u32 magic_header;
+ volatile u32 bits;
+ volatile u32 magic_end;
+ char pd_config_menu[6][16];
+};
+
+struct dcdc_core_ds_step_info{
+ u32 ctl_reg;
+ u32 ctl_sht;
+ u32 cal_reg;
+ u32 cal_sht;
+};
+
+static struct ap_ahb_reg_bak ap_ahb_reg_saved;
+static struct ap_clk_reg_bak ap_clk_reg_saved;
+static struct ap_apb_reg_bak ap_apb_reg_saved;
+static struct pub_reg_bak pub_reg_saved;
+
+#if defined(CONFIG_ARCH_SCX15)
+static struct mm_reg_bak mm_reg_saved;
+#endif
+
+#if defined(CONFIG_ARCH_SCX30G)
+static unsigned long ana_chip_id;
+#endif
+
+/* bits definition
+ * bit_0 : ca7 top
+ * bit_1 : ca7 c0
+ * bit_2 : pub
+ * bit_3 : mm
+ * bit_4 : ap sys
+ * bit_5 : dcdc arm
+*/
+static struct auto_pd_en pd_config = {
+ 0x6a6aa6a6, 0x3f,0xa6a66a6a,
+ .pd_config_menu = {
+ "ca7_top",
+ "ca7_c0",
+ "pub",
+ "mm",
+ "ap_sys",
+ "dcdc_arm",
+ },
+};
+
+/*
+ * we just conigure the auto_pd_en property for parts of power domain, ldo here
+ * clr = 0 means called when kernel init and after real deep sleep
+ * clr = 1 means just called before real deep sleep
+ */
+static void configure_for_deepsleep(int clr)
+{
+#if defined(CONFIG_ARCH_SCX15)
+ if (clr) {
+ sci_glb_clr(REG_PMU_APB_PD_MM_TOP_CFG,BIT_PD_MM_TOP_AUTO_SHUTDOWN_EN);
+ sci_glb_clr(REG_PMU_APB_PD_AP_SYS_CFG, BIT_PD_AP_SYS_AUTO_SHUTDOWN_EN);
+ sci_glb_clr(REG_PMU_APB_PD_PUB_SYS_CFG, BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+ /* FIXME: we can't powerdown ca7 because of stability problem when idle deep sleep */
+ //sci_glb_clr(REG_PMU_APB_PD_CA7_TOP_CFG, BIT_PD_CA7_TOP_AUTO_SHUTDOWN_EN);
+ //sci_glb_clr(REG_PMU_APB_PD_CA7_C0_CFG, BIT_PD_CA7_C0_AUTO_SHUTDOWN_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_clr(REG_PMU_APB_PD_APCPU_TOP_CFG, BIT_PD_CA53_TOP_AUTO_SHUTDOWN_EN);
+ sci_glb_clr(REG_PMU_APB_PD_APCPU_C0_CFG, BIT_PD_CA53_LIT_C0_AUTO_SHUTDOWN_EN);
+#else
+ sci_glb_clr(REG_PMU_APB_PD_CA7_TOP_CFG, BIT_PD_CA7_TOP_AUTO_SHUTDOWN_EN);
+ sci_glb_clr(REG_PMU_APB_PD_CA7_C0_CFG, BIT_PD_CA7_C0_AUTO_SHUTDOWN_EN);
+#endif
+ sci_adi_clr(ANA_REG_GLB_PWR_SLP_CTRL0, BIT_SLP_DCDCARM_PD_EN);
+ /*
+ * because we can power down rf0 and vdd25 in idle deep now
+ * so we can let it be configured in u-boot
+ */
+ /* sci_adi_set(ANA_REG_GLB_PWR_SLP_CTRL0, BIT_SLP_LDORF0_PD_EN); */
+ /* sci_adi_set(ANA_REG_GLB_PWR_SLP_CTRL0, BIT_SLP_LDOVDD25_PD_EN); */
+#if defined(CONFIG_MACH_CORSICA_VE)
+ sci_adi_clr(ANA_REG_GLB_PWR_SLP_CTRL1, BIT_SLP_LDOSIM2_PD_EN);
+ sci_adi_clr(ANA_REG_GLB_PWR_SLP_CTRL1, BIT_SLP_LDOCAMMOT_PD_EN);
+#endif
+ } else {
+ if (pd_config.bits & (0x1 << 0))
+ //sci_glb_set(REG_PMU_APB_PD_CA7_TOP_CFG, BIT_PD_CA7_TOP_AUTO_SHUTDOWN_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_set(REG_PMU_APB_PD_APCPU_TOP_CFG, BIT_PD_CA53_TOP_AUTO_SHUTDOWN_EN);
+#else
+ sci_glb_set(REG_PMU_APB_PD_CA7_TOP_CFG, BIT_PD_CA7_TOP_AUTO_SHUTDOWN_EN);
+#endif
+ if (pd_config.bits & (0x1 << 1))
+ //sci_glb_set(REG_PMU_APB_PD_CA7_C0_CFG, BIT_PD_CA7_C0_AUTO_SHUTDOWN_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_set(REG_PMU_APB_PD_APCPU_C0_CFG, BIT_PD_CA53_LIT_C0_AUTO_SHUTDOWN_EN);
+#else
+ sci_glb_set(REG_PMU_APB_PD_CA7_C0_CFG, BIT_PD_CA7_C0_AUTO_SHUTDOWN_EN);
+#endif
+ if (pd_config.bits & (0x1 << 2))
+ sci_glb_set(REG_PMU_APB_PD_PUB_SYS_CFG, BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+ if (pd_config.bits & (0x1 << 3))
+ sci_glb_set(REG_PMU_APB_PD_MM_TOP_CFG,BIT_PD_MM_TOP_AUTO_SHUTDOWN_EN);
+ if (pd_config.bits & (0x1 << 4))
+ sci_glb_set(REG_PMU_APB_PD_AP_SYS_CFG, BIT_PD_AP_SYS_AUTO_SHUTDOWN_EN);
+ if (pd_config.bits & (0x1 << 5))
+ sci_adi_set(ANA_REG_GLB_PWR_SLP_CTRL0, BIT_SLP_DCDCARM_PD_EN);
+#if defined(CONFIG_MACH_CORSICA_VE)
+ sci_adi_set(ANA_REG_GLB_PWR_SLP_CTRL1, BIT_SLP_LDOSIM2_PD_EN);
+ sci_adi_set(ANA_REG_GLB_PWR_SLP_CTRL1, BIT_SLP_LDOCAMMOT_PD_EN);
+#endif
+ #if defined(CONFIG_SS_FUNCTION)
+ sci_glb_set(REG_PMU_APB_PD_PUB_SYS_CFG,BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+ #endif
+ }
+#endif
+}
+
+static void setup_autopd_mode(void)
+{
+ if (soc_is_scx35_v0())
+ sci_glb_set(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG, 0x3a);
+ else
+ sci_glb_set(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG, 0x3B);
+ sci_glb_write(REG_PMU_APB_AP_WAKEUP_POR_CFG, 0x1, -1UL);//AP_WAKEUP_POR_CFG
+
+ //sci_glb_set(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG, BIT_AP_EMC_AUTO_GATE_EN | BIT_CA7_EMC_AUTO_GATE_EN | BIT_CA7_CORE_AUTO_GATE_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_set(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG, BIT_AP_EMC_AUTO_GATE_EN | BIT_CA53_EMC_AUTO_GATE_EN | BIT_CA53_CORE_AUTO_GATE_EN);
+#else
+ sci_glb_set(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG, BIT_AP_EMC_AUTO_GATE_EN | BIT_CA7_EMC_AUTO_GATE_EN | BIT_CA7_CORE_AUTO_GATE_EN);
+#endif
+
+ // INTC0_EB, INTC1_EB, INTC2_EB, INTC3_EB
+ sci_glb_set(REG_AP_APB_APB_EB, 0xf<<19);
+
+ //set PD_CA7_C0_AUTO_SHUTDOWN_EN
+ //sci_glb_set(REG_PMU_APB_PD_CA7_TOP_CFG, BIT_PD_CA7_TOP_AUTO_SHUTDOWN_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_set(REG_PMU_APB_PD_APCPU_TOP_CFG, BIT_PD_CA53_TOP_AUTO_SHUTDOWN_EN);
+#else
+ sci_glb_set(REG_PMU_APB_PD_CA7_TOP_CFG, BIT_PD_CA7_TOP_AUTO_SHUTDOWN_EN);
+#endif
+ //set PD_CA7_C0_AUTO_SHUTDOWN_EN
+ //sci_glb_set(REG_PMU_APB_PD_CA7_C0_CFG, BIT_PD_CA7_C0_AUTO_SHUTDOWN_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_set(REG_PMU_APB_PD_CA53_LIT_C0_CFG, BIT_PD_CA53_LIT_C0_AUTO_SHUTDOWN_EN);
+#else
+ sci_glb_set(REG_PMU_APB_PD_CA7_C0_CFG, BIT_PD_CA7_C0_AUTO_SHUTDOWN_EN);
+#endif
+ sci_glb_set(SPRD_INT_BASE + 0x8, BIT(14) | BIT(4)); //ana & eic
+ /* Temporarily put it in here for AON Timer can wakeup cluster gating */
+ sci_glb_set(SPRD_INT_BASE + 0x8, BIT(2));
+
+ sci_glb_set(SPRD_INTC1_BASE + 0x8, BIT(6) | BIT(4)); //ana & kpd
+ //sci_glb_set(SPRD_INT_BASE + 0x8, BIT(2)); //ana & eic
+ //sci_glb_set(SPRD_INTC0_BASE + 0x8, BIT(30) | BIT(31)); //syst & aon_syst
+
+ //sci_adi_set(ANA_REG_GLB_LDO_SLP_CTRL0, BIT_SLP_IO_EN | BIT_SLP_LDORF0_PD_EN | BIT_SLP_LDORF1_PD_EN | BIT_SLP_LDORF2_PD_EN);
+ //sci_adi_clr(ANA_REG_GLB_LDO_SLP_CTRL0, BIT_SLP_IO_EN | BIT_SLP_LDORF0_PD_EN | BIT_SLP_LDORF1_PD_EN | BIT_SLP_LDORF2_PD_EN);
+
+ //sci_adi_set(ANA_REG_GLB_LDO_SLP_CTRL1, BIT_SLP_LDO_PD_EN);
+ sci_adi_clr(ANA_REG_GLB_XTL_WAIT_CTRL, BIT_SLP_XTLBUF_PD_EN);
+ //sci_adi_set(ANA_REG_GLB_XTL_WAIT_CTRL, BIT_SLP_XTLBUF_PD_EN);
+ //sci_adi_set(ANA_REG_GLB_LDO_SLP_CTRL2, BIT_SLP_LDORF2_LP_EN | BIT_SLP_LDORF1_LP_EN | BIT_SLP_LDORF0_LP_EN);
+
+ //sci_adi_clr(ANA_REG_GLB_LDO_SLP_CTRL2, BIT_SLP_LDORF2_LP_EN | BIT_SLP_LDORF1_LP_EN | BIT_SLP_LDORF0_LP_EN);
+
+ //sci_glb_set(REG_PMU_APB_DDR_SLEEP_CTRL, 7<<4);
+ //sci_glb_set(REG_PMU_APB_DDR_SLEEP_CTRL, 5<<4);
+#if defined(CONFIG_ARCH_SCX15)
+ sci_glb_set(REG_PMU_APB_PD_PUB_SYS_CFG, BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+#else
+ if (soc_is_scx35_v1()) {
+ sci_glb_set(REG_PMU_APB_PD_PUB_SYS_CFG, BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+ }
+#if !(defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L))
+ else {
+ sci_glb_clr(REG_PMU_APB_PD_PUB_SYS_CFG, BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+ }
+#endif
+
+#if defined(CONFIG_SCX35_DMC_FREQ_DDR3)
+ sci_glb_clr(REG_PMU_APB_PD_PUB_SYS_CFG, BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+#endif
+
+#endif
+
+#if defined(CONFIG_ARCH_SCX15)
+ sci_glb_set(REG_PMU_APB_PD_MM_TOP_CFG,BIT_PD_MM_TOP_AUTO_SHUTDOWN_EN);
+ sci_glb_clr(REG_PMU_APB_PD_PUB_SYS_CFG,BIT_PD_PUB_SYS_AUTO_SHUTDOWN_EN);
+#else
+ sci_glb_clr(REG_PMU_APB_PD_MM_TOP_CFG,BIT_PD_MM_TOP_AUTO_SHUTDOWN_EN);
+#endif
+ sci_glb_write(REG_PMU_APB_AP_WAKEUP_POR_CFG, 0x1, -1UL);
+ /* KEEP eMMC/SD power */
+#if defined(CONFIG_ADIE_SC2723)
+ sci_adi_clr(ANA_REG_GLB_PWR_SLP_CTRL0, BIT_SLP_LDOEMMCCORE_PD_EN);
+ sci_adi_clr(ANA_REG_GLB_PWR_SLP_CTRL1, BIT_SLP_LDOSDCORE_PD_EN | BIT_SLP_LDOSDIO_PD_EN);
+#elif defined(CONFIG_ADIE_SC2723S)
+ sci_adi_clr(ANA_REG_GLB_PWR_SLP_CTRL0, BIT_SLP_LDOEMMCCORE_PD_EN); /* | BIT_SLP_LDOEMMCIO_PD_EN);*/
+ sci_adi_clr(ANA_REG_GLB_PWR_SLP_CTRL1, BIT_SLP_LDOSDIO_PD_EN );
+#else
+ sci_adi_clr(ANA_REG_GLB_LDO_SLP_CTRL0, BIT_SLP_LDOEMMCCORE_PD_EN | BIT_SLP_LDOEMMCIO_PD_EN);
+ sci_adi_clr(ANA_REG_GLB_LDO_SLP_CTRL1, BIT_SLP_LDOSD_PD_EN );
+#endif
+ /***************** for idle to deep ****************/
+ configure_for_deepsleep(1);
+ return;
+}
+void disable_mm(void)
+{
+ sci_glb_clr(REG_MM_AHB_GEN_CKG_CFG, BIT_JPG_AXI_CKG_EN | BIT_VSP_AXI_CKG_EN |\
+ BIT_ISP_AXI_CKG_EN | BIT_DCAM_AXI_CKG_EN | BIT_SENSOR_CKG_EN | BIT_MIPI_CSI_CKG_EN | BIT_CPHY_CFG_CKG_EN);
+ sci_glb_clr(REG_MM_AHB_AHB_EB, BIT_JPG_EB | BIT_CSI_EB | BIT_VSP_EB | BIT_ISP_EB | BIT_CCIR_EB | BIT_DCAM_EB);
+ sci_glb_clr(REG_MM_AHB_GEN_CKG_CFG, BIT_MM_AXI_CKG_EN);
+ sci_glb_clr(REG_MM_AHB_GEN_CKG_CFG, BIT_MM_MTX_AXI_CKG_EN);
+ sci_glb_clr(REG_MM_AHB_AHB_EB, BIT_MM_CKG_EB);
+ sci_glb_clr(REG_PMU_APB_PD_MM_TOP_CFG,BIT_PD_MM_TOP_AUTO_SHUTDOWN_EN);
+ sci_glb_clr(REG_AON_APB_APB_EB0, BIT_MM_EB);
+ sci_glb_set(REG_PMU_APB_PD_MM_TOP_CFG,BIT_PD_MM_TOP_AUTO_SHUTDOWN_EN);
+ return;
+}
+void bak_restore_mm(int bak)
+{
+ static uint32_t gen_ckg, ahb_eb, mm_top, aon_eb0;
+ static uint32_t gen_ckg_en_mask = BIT_JPG_AXI_CKG_EN | BIT_VSP_AXI_CKG_EN |\
+ BIT_ISP_AXI_CKG_EN | BIT_DCAM_AXI_CKG_EN | BIT_SENSOR_CKG_EN | BIT_MIPI_CSI_CKG_EN | BIT_CPHY_CFG_CKG_EN;
+ static uint32_t ahb_eb_mask = BIT_JPG_EB | BIT_CSI_EB | BIT_VSP_EB | BIT_ISP_EB | BIT_CCIR_EB | BIT_DCAM_EB;
+ if(bak){
+ gen_ckg = sci_glb_read(REG_MM_AHB_GEN_CKG_CFG, -1UL);
+ ahb_eb = sci_glb_read(REG_MM_AHB_AHB_EB, -1UL);
+ mm_top = sci_glb_read(REG_PMU_APB_PD_MM_TOP_CFG, -1UL);
+ aon_eb0 = sci_glb_read(REG_AON_APB_APB_EB0, -1UL);
+ }else{
+ sci_glb_clr(REG_PMU_APB_PD_MM_TOP_CFG,BIT_PD_MM_TOP_AUTO_SHUTDOWN_EN);
+ if(aon_eb0 & BIT_MM_EB)
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT_MM_EB);
+ if(ahb_eb & BIT_MM_CKG_EB)
+ sci_glb_set(REG_MM_AHB_AHB_EB, BIT_MM_CKG_EB);
+ if(gen_ckg & BIT_MM_MTX_AXI_CKG_EN)
+ sci_glb_set(REG_MM_AHB_GEN_CKG_CFG, BIT_MM_MTX_AXI_CKG_EN);
+ if(gen_ckg & BIT_MM_AXI_CKG_EN)
+ sci_glb_set(REG_MM_AHB_GEN_CKG_CFG, BIT_MM_AXI_CKG_EN);
+ if(ahb_eb & ahb_eb_mask)
+ sci_glb_write(REG_MM_AHB_AHB_EB, ahb_eb, ahb_eb_mask);
+ if(gen_ckg & gen_ckg_en_mask)
+ sci_glb_write(REG_MM_AHB_GEN_CKG_CFG, gen_ckg, gen_ckg_en_mask);
+ }
+ return;
+}
+void disable_dma(void)
+{
+ int reg = sci_glb_read(SPRD_DMA0_BASE, BIT(16));
+ int cnt = 30;
+ if(reg){
+ sci_glb_set((SPRD_DMA0_BASE), BIT(0)); // pause dma
+ while(cnt-- && reg){
+ sci_glb_read(SPRD_DMA0_BASE, BIT(16));
+ udelay(100);
+ reg = sci_glb_read(SPRD_DMA0_BASE, BIT(16));
+ }
+ if(reg)
+ printk("disable dma failed\n");
+ }
+ sci_glb_clr(REG_AP_AHB_AHB_EB, BIT_DMA_EB);
+}
+void disable_ahb_module(void)
+{
+ //sci_glb_write(REG_AP_AHB_AHB_EB, 0, -1UL);
+ sci_glb_clr(REG_AP_AHB_AHB_EB, 0x1fff);
+
+ // AP_PERI_FORCE_SLP
+
+ sci_glb_set(REG_AP_AHB_AP_SYS_FORCE_SLEEP_CFG, BIT_AP_PERI_FORCE_SLP);
+ //sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_SLEEP_FOLLOW_CA7_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_SLEEP_FOLLOW_CA53_EN);
+#else
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_SLEEP_FOLLOW_CA7_EN);
+#endif
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_DEEP_SLEEP_EN);
+
+ //AP_SYS_AUTO_SLEEP_CFG
+ sci_glb_set(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG, 0x3B);
+
+ return;
+}
+void bak_restore_ahb(int bak)
+{
+ volatile u32 i;
+ if(bak){
+ ap_ahb_reg_saved.ahb_eb = sci_glb_read(REG_AP_AHB_AHB_EB, -1UL);
+#if defined(CONFIG_ARCH_SCX35L)
+ //ap_ahb_reg_saved.ca7_ckg_cfg = sci_glb_read(REG_AP_AHB_CA7_CKG_SEL_CFG, -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ ap_ahb_reg_saved.ca7_ckg_cfg = sci_glb_read(REG_AP_AHB_APCPU_CKG_SEL_CFG, -1UL);
+#else
+ ap_ahb_reg_saved.ca7_ckg_cfg = sci_glb_read(REG_AP_AHB_CA7_CKG_SEL_CFG, -1UL);
+#endif
+ ap_ahb_reg_saved.usb_phy_tune = sci_glb_read(REG_AP_AHB_OTG_PHY_TUNE, -1UL);
+ //ap_ahb_reg_saved.ca7_div_cfg = sci_glb_read(REG_AP_AHB_CA7_CKG_DIV_CFG, -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ ap_ahb_reg_saved.ca7_div_cfg = sci_glb_read(REG_AP_AHB_APCPU_CKG_DIV_CFG, -1UL);
+#else
+ ap_ahb_reg_saved.ca7_div_cfg = sci_glb_read(REG_AP_AHB_CA7_CKG_DIV_CFG, -1UL);
+#endif
+#else
+ //ap_ahb_reg_saved.ca7_ckg_cfg = sci_glb_read(REG_AP_AHB_CA7_CKG_CFG, -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ ap_ahb_reg_saved.ca7_ckg_cfg = sci_glb_read(REG_AP_AHB_APCPU_CKG_CFG, -1UL);
+#else
+ ap_ahb_reg_saved.ca7_ckg_cfg = sci_glb_read(REG_AP_AHB_CA7_CKG_CFG, -1UL);
+#endif
+#endif
+ ap_ahb_reg_saved.misc_ckg_en = sci_glb_read(REG_AP_AHB_MISC_CKG_EN, -1UL);
+ ap_ahb_reg_saved.misc_cfg = sci_glb_read(REG_AP_AHB_MISC_CFG, -1UL);
+ } else{
+ sci_glb_write(REG_AP_AHB_AHB_EB, ap_ahb_reg_saved.ahb_eb, -1UL);
+#if defined(CONFIG_ARCH_SCX35L)
+ //sci_glb_write(REG_AP_AHB_CA7_CKG_DIV_CFG, ap_ahb_reg_saved.ca7_div_cfg & (~0x7), -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_write(REG_AP_AHB_APCPU_CKG_DIV_CFG, ap_ahb_reg_saved.ca7_div_cfg & (~0x7), -1UL);
+#else
+ sci_glb_write(REG_AP_AHB_CA7_CKG_DIV_CFG, ap_ahb_reg_saved.ca7_div_cfg & (~0x7), -1UL);
+#endif
+ for(i = 0; i < 20; i++);
+ //sci_glb_write(REG_AP_AHB_CA7_CKG_DIV_CFG, ap_ahb_reg_saved.ca7_div_cfg, -1UL);
+ //sci_glb_write(REG_AP_AHB_CA7_CKG_SEL_CFG, ap_ahb_reg_saved.ca7_ckg_cfg & (~0x7), -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_write(REG_AP_AHB_APCPU_CKG_DIV_CFG, ap_ahb_reg_saved.ca7_div_cfg, -1UL);
+ sci_glb_write(REG_AP_AHB_APCPU_CKG_SEL_CFG, ap_ahb_reg_saved.ca7_ckg_cfg & (~0x7), -1UL);
+#else
+ sci_glb_write(REG_AP_AHB_CA7_CKG_DIV_CFG, ap_ahb_reg_saved.ca7_div_cfg, -1UL);
+ sci_glb_write(REG_AP_AHB_CA7_CKG_SEL_CFG, ap_ahb_reg_saved.ca7_ckg_cfg & (~0x7), -1UL);
+#endif
+ for(i = 0; i < 20; i++);
+ //sci_glb_write(REG_AP_AHB_CA7_CKG_SEL_CFG, ap_ahb_reg_saved.ca7_ckg_cfg, -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_write(REG_AP_AHB_APCPU_CKG_SEL_CFG, ap_ahb_reg_saved.ca7_ckg_cfg, -1UL);
+#else
+ sci_glb_write(REG_AP_AHB_CA7_CKG_SEL_CFG, ap_ahb_reg_saved.ca7_ckg_cfg, -1UL);
+#endif
+ sci_glb_write(REG_AP_AHB_OTG_PHY_TUNE, ap_ahb_reg_saved.usb_phy_tune, -1UL);
+#else
+ //sci_glb_write(REG_AP_AHB_CA7_CKG_CFG, ap_ahb_reg_saved.ca7_ckg_cfg & (~0x7), -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_write(REG_AP_AHB_APCPU_CKG_CFG, ap_ahb_reg_saved.ca7_ckg_cfg & (~0x7), -1UL);
+#else
+ sci_glb_write(REG_AP_AHB_CA7_CKG_CFG, ap_ahb_reg_saved.ca7_ckg_cfg & (~0x7), -1UL);
+#endif
+ for(i = 0; i < 20; i++);
+ //sci_glb_write(REG_AP_AHB_CA7_CKG_CFG, ap_ahb_reg_saved.ca7_ckg_cfg, -1UL);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_write(REG_AP_AHB_APCPU_CKG_CFG, ap_ahb_reg_saved.ca7_ckg_cfg, -1UL);
+#else
+ sci_glb_write(REG_AP_AHB_CA7_CKG_CFG, ap_ahb_reg_saved.ca7_ckg_cfg, -1UL);
+#endif
+#endif
+ sci_glb_write(REG_AP_AHB_MISC_CKG_EN, ap_ahb_reg_saved.misc_ckg_en, -1UL);
+ sci_glb_write(REG_AP_AHB_MISC_CFG, ap_ahb_reg_saved.misc_cfg, -1UL);
+ }
+ return;
+}
+
+void disable_apb_module(void)
+{
+ int stat;
+ stat = sci_glb_read(REG_AP_APB_APB_EB, -1UL);
+ stat &=(0xf<<19 | BIT_UART1_EB | BIT_UART0_EB); // leave intc on
+ sci_glb_write(REG_AP_APB_APB_EB, stat, -1UL);
+ return;
+}
+void bak_restore_apb(int bak)
+{
+ if(bak){
+ ap_apb_reg_saved.apb_eb = sci_glb_read(REG_AP_APB_APB_EB, -1UL);
+ ap_apb_reg_saved.apb_misc_ctrl = sci_glb_read(REG_AP_APB_APB_MISC_CTRL, -1UL);
+#if !defined(CONFIG_ARCH_SCX35L)
+#if !defined(CONFIG_ARCH_SCX20)
+ ap_apb_reg_saved.usb_phy_tune = sci_glb_read(REG_AP_APB_USB_PHY_TUNE, -1UL);
+#endif //#if !defined(CONFIG_ARCH_SCX20)
+#endif
+ }else{
+ sci_glb_write(REG_AP_APB_APB_EB, ap_apb_reg_saved.apb_eb, -1UL);
+ sci_glb_write(REG_AP_APB_APB_MISC_CTRL, ap_apb_reg_saved.apb_misc_ctrl, -1UL);
+#if !defined(CONFIG_ARCH_SCX35L)
+#if !defined(CONFIG_ARCH_SCX20)
+ sci_glb_write(REG_AP_APB_USB_PHY_TUNE, ap_apb_reg_saved.usb_phy_tune, -1UL);
+#endif //#if !defined(CONFIG_ARCH_SCX20)
+#endif
+ }
+ return;
+}
+#if defined(CONFIG_ARCH_SCX15)
+extern void __iomap_page(unsigned long virt, unsigned long size, int enable);
+void bak_restore_mm_scx15(int bak)
+{
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT_MM_EB);
+ __iomap_page(REGS_MM_AHB_BASE, SZ_4K, 1);
+ if(bak){
+ mm_reg_saved.mm_cfg1= sci_glb_read(SPRD_MMAHB_BASE, -1UL);
+ mm_reg_saved.mm_cfg2 = sci_glb_read(SPRD_MMAHB_BASE + 0x8, -1UL);
+ }else{
+ sci_glb_write(SPRD_MMAHB_BASE, mm_reg_saved.mm_cfg1, -1UL);
+ sci_glb_write(SPRD_MMAHB_BASE + 0x8, mm_reg_saved.mm_cfg2, -1UL);
+ }
+ __iomap_page(REGS_MM_AHB_BASE, SZ_4K, 0);
+ sci_glb_clr(REG_AON_APB_APB_EB0, BIT_MM_EB);
+ return;
+}
+#endif
+static void bak_ap_clk_reg(int bak)
+{
+ volatile u32 i;
+ if(bak) {
+ ap_clk_reg_saved.ap_ahb_cfg = sci_glb_read(REG_AP_CLK_AP_AHB_CFG , -1UL);
+ ap_clk_reg_saved.ap_apb_cfg = sci_glb_read(REG_AP_CLK_AP_APB_CFG , -1UL);
+ ap_clk_reg_saved.clk_gsp_cfg = sci_glb_read(REG_AP_CLK_GSP_CFG , -1UL);
+ ap_clk_reg_saved.dispc0_cfg = sci_glb_read(REG_AP_CLK_DISPC0_CFG , -1UL);
+ ap_clk_reg_saved.dispc0_dbi_cfg = sci_glb_read(REG_AP_CLK_DISPC0_DBI_CFG , -1UL);
+ ap_clk_reg_saved.dispc0_dpi_cfg = sci_glb_read(REG_AP_CLK_DISPC0_DPI_CFG , -1UL);
+#if defined(CONFIG_ARCH_SCX35L)
+ ap_clk_reg_saved.nfc_cfg = sci_glb_read(REG_AP_CLK_NANDC_ECC_CFG , -1UL);
+#else
+ ap_clk_reg_saved.dispc1_cfg = sci_glb_read(REG_AP_CLK_DISPC1_CFG , -1UL);
+ ap_clk_reg_saved.dispc1_dbi_cfg = sci_glb_read(REG_AP_CLK_DISPC1_DBI_CFG , -1UL);
+ ap_clk_reg_saved.dispc1_dpi_cfg = sci_glb_read(REG_AP_CLK_DISPC1_DPI_CFG , -1UL);
+ ap_clk_reg_saved.nfc_cfg = sci_glb_read(REG_AP_CLK_NFC_CFG , -1UL);
+#endif
+ ap_clk_reg_saved.sdio0_cfg = sci_glb_read(REG_AP_CLK_SDIO0_CFG , -1UL);
+ ap_clk_reg_saved.sdio1_cfg = sci_glb_read(REG_AP_CLK_SDIO1_CFG , -1UL);
+ ap_clk_reg_saved.sdio2_cfg = sci_glb_read(REG_AP_CLK_SDIO2_CFG , -1UL);
+ ap_clk_reg_saved.emmc_cfg = sci_glb_read(REG_AP_CLK_EMMC_CFG , -1UL);
+#if defined(CONFIG_ARCH_SCX35L)
+ ap_clk_reg_saved.usb_ref_cfg = sci_glb_read(REG_AP_CLK_OTG_REF_CFG , -1UL);
+#else
+ ap_clk_reg_saved.gps_cfg = sci_glb_read(REG_AP_CLK_GPS_CFG , -1UL);
+ ap_clk_reg_saved.gps_tcxo_cfg = sci_glb_read(REG_AP_CLK_GPS_TCXO_CFG , -1UL);
+ ap_clk_reg_saved.usb_ref_cfg = sci_glb_read(REG_AP_CLK_USB_REF_CFG , -1UL);
+#endif
+ ap_clk_reg_saved.uart0_cfg = sci_glb_read(REG_AP_CLK_UART0_CFG , -1UL);
+ ap_clk_reg_saved.uart1_cfg = sci_glb_read(REG_AP_CLK_UART1_CFG , -1UL);
+ ap_clk_reg_saved.uart2_cfg = sci_glb_read(REG_AP_CLK_UART2_CFG , -1UL);
+ ap_clk_reg_saved.uart3_cfg = sci_glb_read(REG_AP_CLK_UART3_CFG , -1UL);
+ ap_clk_reg_saved.uart4_cfg = sci_glb_read(REG_AP_CLK_UART4_CFG , -1UL);
+ ap_clk_reg_saved.i2c0_cfg = sci_glb_read(REG_AP_CLK_I2C0_CFG , -1UL);
+ ap_clk_reg_saved.i2c1_cfg = sci_glb_read(REG_AP_CLK_I2C1_CFG , -1UL);
+ ap_clk_reg_saved.i2c2_cfg = sci_glb_read(REG_AP_CLK_I2C2_CFG , -1UL);
+ ap_clk_reg_saved.i2c3_cfg = sci_glb_read(REG_AP_CLK_I2C3_CFG , -1UL);
+ ap_clk_reg_saved.i2c4_cfg = sci_glb_read(REG_AP_CLK_I2C4_CFG , -1UL);
+ ap_clk_reg_saved.spi0_cfg = sci_glb_read(REG_AP_CLK_SPI0_CFG , -1UL);
+ ap_clk_reg_saved.spi1_cfg = sci_glb_read(REG_AP_CLK_SPI1_CFG , -1UL);
+ ap_clk_reg_saved.spi2_cfg = sci_glb_read(REG_AP_CLK_SPI2_CFG , -1UL);
+ ap_clk_reg_saved.iis0_cfg = sci_glb_read(REG_AP_CLK_IIS0_CFG , -1UL);
+ ap_clk_reg_saved.iis1_cfg = sci_glb_read(REG_AP_CLK_IIS1_CFG , -1UL);
+ ap_clk_reg_saved.iis2_cfg = sci_glb_read(REG_AP_CLK_IIS2_CFG , -1UL);
+ ap_clk_reg_saved.iis3_cfg = sci_glb_read(REG_AP_CLK_IIS3_CFG , -1UL);
+ }
+ else {
+ sci_glb_write(REG_AP_CLK_AP_AHB_CFG , ap_clk_reg_saved.ap_ahb_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_AP_APB_CFG , ap_clk_reg_saved.ap_apb_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_GSP_CFG , ap_clk_reg_saved.clk_gsp_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_DISPC0_CFG , ap_clk_reg_saved.dispc0_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC0_CFG , ap_clk_reg_saved.dispc0_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_DISPC0_DBI_CFG , ap_clk_reg_saved.dispc0_dbi_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC0_DBI_CFG , ap_clk_reg_saved.dispc0_dbi_cfg ,-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC0_DPI_CFG , ap_clk_reg_saved.dispc0_dpi_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC0_DPI_CFG , ap_clk_reg_saved.dispc0_dpi_cfg ,-1UL);
+#if defined(CONFIG_ARCH_SCX35L)
+ sci_glb_write(REG_AP_CLK_NANDC_ECC_CFG , ap_clk_reg_saved.nfc_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_NANDC_ECC_CFG , ap_clk_reg_saved.nfc_cfg ,-1UL);
+#else
+ sci_glb_write(REG_AP_CLK_DISPC1_CFG , ap_clk_reg_saved.dispc1_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC1_CFG , ap_clk_reg_saved.dispc1_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_DISPC1_DBI_CFG , ap_clk_reg_saved.dispc1_dbi_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC1_DBI_CFG , ap_clk_reg_saved.dispc1_dbi_cfg ,-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC1_DPI_CFG , ap_clk_reg_saved.dispc1_dpi_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_DISPC1_DPI_CFG , ap_clk_reg_saved.dispc1_dpi_cfg ,-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_NFC_CFG , ap_clk_reg_saved.nfc_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_NFC_CFG , ap_clk_reg_saved.nfc_cfg ,-1UL);
+#endif
+ sci_glb_write(REG_AP_CLK_SDIO0_CFG , ap_clk_reg_saved.sdio0_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_SDIO0_CFG , ap_clk_reg_saved.sdio0_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_SDIO1_CFG , ap_clk_reg_saved.sdio1_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_SDIO2_CFG , ap_clk_reg_saved.sdio2_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_EMMC_CFG , ap_clk_reg_saved.emmc_cfg ,-1UL);
+#if defined(CONFIG_ARCH_SCX35L)
+ sci_glb_write(REG_AP_CLK_OTG_REF_CFG , ap_clk_reg_saved.usb_ref_cfg ,-1UL);
+#else
+ sci_glb_write(REG_AP_CLK_GPS_CFG , ap_clk_reg_saved.gps_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_GPS_TCXO_CFG , ap_clk_reg_saved.gps_tcxo_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_USB_REF_CFG , ap_clk_reg_saved.usb_ref_cfg ,-1UL);
+#endif
+ sci_glb_write(REG_AP_CLK_UART0_CFG , ap_clk_reg_saved.uart0_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_UART0_CFG , ap_clk_reg_saved.uart0_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_UART1_CFG , ap_clk_reg_saved.uart1_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_UART1_CFG , ap_clk_reg_saved.uart1_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_UART2_CFG , ap_clk_reg_saved.uart2_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_UART2_CFG , ap_clk_reg_saved.uart2_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_UART3_CFG , ap_clk_reg_saved.uart3_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_UART3_CFG , ap_clk_reg_saved.uart3_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_UART4_CFG , ap_clk_reg_saved.uart4_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_UART4_CFG , ap_clk_reg_saved.uart4_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_I2C0_CFG , ap_clk_reg_saved.i2c0_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_I2C0_CFG , ap_clk_reg_saved.i2c0_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_I2C1_CFG , ap_clk_reg_saved.i2c1_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_I2C1_CFG , ap_clk_reg_saved.i2c1_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_I2C2_CFG , ap_clk_reg_saved.i2c2_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_I2C2_CFG , ap_clk_reg_saved.i2c2_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_I2C3_CFG , ap_clk_reg_saved.i2c3_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_I2C3_CFG , ap_clk_reg_saved.i2c3_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_I2C4_CFG , ap_clk_reg_saved.i2c4_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_I2C4_CFG , ap_clk_reg_saved.i2c4_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_SPI0_CFG , ap_clk_reg_saved.spi0_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_SPI0_CFG , ap_clk_reg_saved.spi0_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_SPI1_CFG , ap_clk_reg_saved.spi1_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_SPI1_CFG , ap_clk_reg_saved.spi1_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_SPI2_CFG , ap_clk_reg_saved.spi2_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_SPI2_CFG , ap_clk_reg_saved.spi2_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_IIS0_CFG , ap_clk_reg_saved.iis0_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_IIS0_CFG , ap_clk_reg_saved.iis0_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_IIS1_CFG , ap_clk_reg_saved.iis1_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_IIS1_CFG , ap_clk_reg_saved.iis1_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_IIS2_CFG , ap_clk_reg_saved.iis2_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_IIS2_CFG , ap_clk_reg_saved.iis2_cfg ,-1UL);
+ sci_glb_write(REG_AP_CLK_IIS3_CFG , ap_clk_reg_saved.iis3_cfg & (~0x3),-1UL);
+ for(i = 0; i < 10; i++);
+ sci_glb_write(REG_AP_CLK_IIS3_CFG , ap_clk_reg_saved.iis3_cfg ,-1UL);
+ }
+}
+void disable_aon_module(void)
+{
+ //sci_glb_clr(REG_AON_APB_APB_EB0, BIT_CA7_DAP_EB | BIT_GPU_EB);
+ //sci_glb_clr(REG_AON_APB_APB_EB0, BIT_CA7_DAP_EB);
+
+ sci_glb_clr(REG_AON_APB_APB_EB0, BIT_AON_TMR_EB | BIT_AP_TMR0_EB);
+ sci_glb_clr(REG_AON_APB_APB_EB1, BIT_AP_TMR2_EB | BIT_AP_TMR1_EB);
+ sci_glb_clr(REG_AON_APB_APB_EB1, BIT_DISP_EMC_EB);
+}
+void bak_restore_aon(int bak)
+{
+ static uint32_t apb_eb1, pwr_ctl, apb_eb0;
+ if(bak){
+ apb_eb0 = sci_glb_read(REG_AON_APB_APB_EB0, -1UL);
+ apb_eb1 = sci_glb_read(REG_AON_APB_APB_EB1, -1UL);
+ pwr_ctl = sci_glb_read(REG_AON_APB_PWR_CTRL, -1UL);
+ }else{
+ if(apb_eb1 & BIT_DISP_EMC_EB)
+ sci_glb_set(REG_AON_APB_APB_EB1, BIT_DISP_EMC_EB);
+ sci_glb_write(REG_AON_APB_APB_EB0, apb_eb0, -1UL);
+ sci_glb_write(REG_AON_APB_APB_EB1, apb_eb1, -1UL);
+ }
+ return;
+}
+
+void disable_ana_module(void)
+{
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ sci_adi_set(ANA_REG_GLB_LDO_PD_CTRL, BIT_LDO_SD_PD | BIT_LDO_SIM0_PD | BIT_LDO_SIM1_PD | BIT_LDO_SIM2_PD | BIT_LDO_CAMA_PD |\
+ BIT_LDO_CAMD_PD | BIT_LDO_CAMIO_PD | BIT_LDO_CAMMOT_PD | BIT_DCDC_WPA_PD);
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ sci_adi_set(ANA_REG_GLB_LDO_PD_CTRL, BIT_LDO_SDIO_PD | BIT_LDO_SIM0_PD | BIT_LDO_SIM1_PD | BIT_LDO_SIM2_PD | BIT_LDO_CAMA_PD |\
+ BIT_LDO_CAMD_PD | BIT_LDO_CAMIO_PD | BIT_LDO_CAMMOT_PD | BIT_DCDC_WPA_PD);
+#endif
+#if defined(CONFIG_ADIE_SC2723)
+ sci_adi_set(ANA_REG_GLB_LDO_PD_CTRL, BIT_LDO_SDIO_PD | BIT_LDO_SIM0_PD | BIT_LDO_SIM1_PD | BIT_LDO_SIM2_PD | BIT_LDO_CAMA_PD |\
+ BIT_LDO_CAMD_PD | BIT_LDO_CAMIO_PD | BIT_LDO_CAMMOT_PD | BIT_LDO_SDCORE_PD | BIT_LDO_WIFIPA_PD | BIT_DCDC_CON_PD | BIT_DCDC_WPA_PD);
+#endif
+}
+void bak_restore_ana(int bak)
+{
+ static uint32_t ldo_pd_ctrl;
+ static uint32_t mask;
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ mask = BIT_LDO_SD_PD | BIT_LDO_SIM0_PD | BIT_LDO_SIM1_PD | BIT_LDO_SIM2_PD | BIT_LDO_CAMA_PD |\
+ BIT_LDO_CAMD_PD | BIT_LDO_CAMIO_PD | BIT_LDO_CAMMOT_PD | BIT_DCDC_WPA_PD;
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ mask = BIT_LDO_SDIO_PD | BIT_LDO_SIM0_PD | BIT_LDO_SIM1_PD | BIT_LDO_SIM2_PD | BIT_LDO_CAMA_PD |\
+ BIT_LDO_CAMD_PD | BIT_LDO_CAMIO_PD | BIT_LDO_CAMMOT_PD | BIT_DCDC_WPA_PD;
+#endif
+
+ if(bak){
+ ldo_pd_ctrl = sci_adi_read(ANA_REG_GLB_LDO_PD_CTRL);
+ }else{
+ sci_adi_write(ANA_REG_GLB_LDO_PD_CTRL, ldo_pd_ctrl, mask);
+ }
+ return;
+}
+static void bak_restore_pub(int bak)
+{
+ /*v0 not set auto power down*/
+ if (soc_is_scx35_v0()) {
+ return ;
+ }
+ if(bak) {
+ pub_reg_saved.ddr_qos_cfg1 = sci_glb_read(REG_PUB_APB_DDR_QOS_CFG1, -1UL);
+ pub_reg_saved.ddr_qos_cfg2 = sci_glb_read(REG_PUB_APB_DDR_QOS_CFG2, -1UL);
+ pub_reg_saved.ddr_qos_cfg3 = sci_glb_read(REG_PUB_APB_DDR_QOS_CFG3, -1UL);
+ } else {
+ sci_glb_write(REG_PUB_APB_DDR_QOS_CFG1, pub_reg_saved.ddr_qos_cfg1, -1UL);
+ sci_glb_write(REG_PUB_APB_DDR_QOS_CFG2, pub_reg_saved.ddr_qos_cfg2, -1UL);
+ sci_glb_write(REG_PUB_APB_DDR_QOS_CFG3, pub_reg_saved.ddr_qos_cfg3, -1UL);
+ }
+}
+
+static void bak_restore_ap_intc(int bak)
+{
+ static u32 intc0_eb, intc1_eb, intc2_eb,intc3_eb;
+ if(bak) {
+ intc0_eb = sci_glb_read(SPRD_INTC0_BASE + 0x8, -1UL);
+ intc1_eb = sci_glb_read(SPRD_INTC1_BASE + 0x8, -1UL);
+ intc2_eb = sci_glb_read(SPRD_INTC2_BASE + 0x8, -1UL);
+ intc3_eb = sci_glb_read(SPRD_INTC3_BASE + 0x8, -1UL);
+ } else {
+ sci_glb_write(SPRD_INTC0_BASE + 0x8, intc0_eb, -1UL);
+ sci_glb_write(SPRD_INTC1_BASE + 0x8, intc1_eb, -1UL);
+ sci_glb_write(SPRD_INTC2_BASE + 0x8, intc2_eb, -1UL);
+ sci_glb_write(SPRD_INTC3_BASE + 0x8, intc3_eb, -1UL);
+ }
+}
+
+#define REG_PIN_XTLEN ( SPRD_PIN_BASE + 0x0138 )
+#define REG_PIN_CHIP_SLEEP ( SPRD_PIN_BASE + 0x0208 )
+#define REG_PIN_XTL_BUF_EN0 ( SPRD_PIN_BASE + 0x020C )
+#define REG_PIN_XTL_BUF_EN1 ( SPRD_PIN_BASE + 0x0210 )
+#define REG_PIN_XTL_BUF_EN2 ( SPRD_PIN_BASE + 0x0214 )
+void show_pin_reg(void)
+{
+ sci_glb_set(SPRD_INT_BASE + 0x8, BIT(14) | BIT(4)); //ana & eic
+ printk("REG_PIN_XTLEN 0x%08x\n", sci_glb_read(REG_PIN_XTLEN, -1UL));
+ printk("REG_PIN_XTL_BUF_EN0 0x%08x\n", sci_glb_read(REG_PIN_XTL_BUF_EN0, -1UL));
+ printk("REG_PIN_XTL_BUF_EN1 0x%08x\n", sci_glb_read(REG_PIN_XTL_BUF_EN1, -1UL));
+ printk("REG_PIN_XTL_BUF_EN2 0x%08x\n", sci_glb_read(REG_PIN_XTL_BUF_EN2, -1UL));
+ printk("REG_PIN_CHIP_SLEEP 0x%08x\n", sci_glb_read(REG_PIN_CHIP_SLEEP, -1UL));
+ printk("### uart1 ckd 0x%08x\n", sci_glb_read(SPRD_UART1_BASE + 0X24, -1UL));
+ printk("### uart1 ctl 0x%08x\n", sci_glb_read(SPRD_UART1_BASE + 0X18, -1UL));
+#if defined(CONFIG_ARCH_SCX15)
+ sci_glb_set(REG_PMU_APB_XTL0_REL_CFG, 0x4);
+ sci_glb_set(REG_PMU_APB_XTLBUF0_REL_CFG, 0x4);
+#if !defined(CONFIG_ARCH_SCX35L)
+ sci_glb_clr(REG_PMU_APB_PD_CP1_TD_CFG, BIT(24));
+#endif /* CONFIG_ARCH_SCX35L */
+#endif
+ printk("REG_PMU_APB_XTL0_REL_CFG 0x%08x\n", sci_glb_read(REG_PMU_APB_XTL0_REL_CFG, -1UL));
+ printk("REG_PMU_APB_XTL1_REL_CFG 0x%08x\n", sci_glb_read(REG_PMU_APB_XTL1_REL_CFG, -1UL));
+#if !defined(CONFIG_ARCH_SCX35L)
+ printk("REG_PMU_APB_XTL2_REL_CFG 0x%08x\n", sci_glb_read(REG_PMU_APB_XTL2_REL_CFG, -1UL));
+#endif
+ printk("REG_PMU_APB_XTLBUF0_REL_CFG 0x%08x\n", sci_glb_read(REG_PMU_APB_XTLBUF0_REL_CFG, -1UL));
+ printk("REG_PMU_APB_XTLBUF1_REL_CFG 0x%08x\n", sci_glb_read(REG_PMU_APB_XTLBUF1_REL_CFG, -1UL));
+#if !defined(CONFIG_ARCH_SCX35L)
+ printk("REG_PMU_APB_PD_CP1_TD_CFG 0x%08x\n", sci_glb_read(REG_PMU_APB_PD_CP1_TD_CFG, -1Ul));
+#endif
+}
+void force_mcu_core_sleep(void)
+{
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_CORE_SLEEP);
+}
+void enable_mcu_deep_sleep(void)
+{
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_DEEP_SLEEP_EN | BIT_MCU_LIGHT_SLEEP_EN);
+}
+void disable_mcu_deep_sleep(void)
+{
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, BIT_MCU_DEEP_SLEEP_EN | BIT_MCU_LIGHT_SLEEP_EN);
+}
+#define BITS_SET_CHECK(__reg, __bits, __string) do{\
+ uint32_t mid =sci_glb_read(__reg, __bits); \
+ if(mid != __bits) \
+ printk(__string " not 1" "\n"); \
+ }while(0)
+#define BITS_CLEAR_CHECK(__reg, __bits, __string) do{\
+ uint32_t mid = sci_glb_read(__reg, __bits); \
+ if(mid & __bits) \
+ printk(__string " not 0" "\n"); \
+ }while(0)
+#define PRINT_REG(__reg) do{\
+ uint32_t mid = sci_glb_read(__reg, -1UL); \
+ printk(#__reg "value 0x%x\n", mid); \
+ }while(0)
+void show_reg_status(void)
+{
+ //BITS_CLEAR_CHECK(REG_AON_APB_APB_EB0, BIT_CA7_DAP_EB, "ca7_dap_eb");
+#if defined(CONFIG_ARCH_SCX35LT8)
+ /*BITS_CLEAR_CHECK(REG_AON_APB_APB_EB0, BIT_DAP_CA53_CCI_EN, "ca7_dap_eb");*/
+#else
+ BITS_CLEAR_CHECK(REG_AON_APB_APB_EB0, BIT_CA7_DAP_EB, "ca7_dap_eb");
+#endif
+ BITS_CLEAR_CHECK(REG_AP_AHB_AHB_EB, BIT_DMA_EB, "dma_eb");
+ BITS_CLEAR_CHECK((SPRD_DMA0_BASE + 0x1c), BIT(20), "dma_busy");
+ BITS_CLEAR_CHECK(REG_AP_AHB_AHB_EB, BIT_USB_EB, "usb_eb");
+ BITS_CLEAR_CHECK(REG_AP_AHB_AHB_EB, BIT_SDIO0_EB, "sdio0_eb");
+ BITS_CLEAR_CHECK(REG_AP_AHB_AHB_EB, BIT_SDIO1_EB, "sdio1_eb");
+ BITS_CLEAR_CHECK(REG_AP_AHB_AHB_EB, BIT_SDIO2_EB, "sdio2_eb");
+ BITS_CLEAR_CHECK(REG_AP_AHB_AHB_EB, BIT_EMMC_EB, "emmc_eb");
+ BITS_CLEAR_CHECK(REG_AP_AHB_AHB_EB, BIT_NFC_EB, "nfc_eb");
+ PRINT_REG(REG_PMU_APB_SLEEP_STATUS);
+}
+uint32_t pwr_stat0=0, pwr_stat1=0, pwr_stat2=0, pwr_stat3=0, apb_eb0=0, apb_eb1=0, ahb_eb=0, apb_eb=0, mcu_pause=0, sys_force_sleep=0, sys_auto_sleep_cfg=0;
+uint32_t ldo_slp_ctrl0, ldo_slp_ctrl1, ldo_slp_ctrl2, xtl_wait_ctrl, ldo_slp_ctrl3, ldo_slp_ctrl4, ldo_aud_ctrl4;
+uint32_t mm_apb, ldo_pd_ctrl, arm_module_en;
+uint32_t cp_slp_status_dbg0, cp_slp_status_dbg1, sleep_ctrl, ddr_sleep_ctrl, sleep_status, pwr_ctrl, ca7_standby_status;
+uint32_t pd_pub_sys;
+#if defined(CONFIG_ARCH_SCX15)
+uint32_t ldo_dcdc_pd_ctrl;
+uint32_t xtl0_rel_cfg, xtl1_rel_cfg, xtl2_rel_cfg, xtlbuf0_rel_cfg, xtlbuf1_rel_cfg;
+uint32_t mpll_rel_cfg, dpll_rel_cfg, tdpll_rel_cfg, wpll_rel_cfg, cpll_rel_cfg, wifipll1_rel_cfg, wifipll2_rel_cfg;
+uint32_t ddr_chn_sleep_ctrl0, ddr_chn_sleep_ctrl1;
+#endif
+#if defined(CONFIG_ARCH_SCX35L)
+extern void cp0_power_domain_debug(void);
+extern void cp1_power_domain_debug(void);
+#endif
+
+void bak_last_reg(void)
+{
+#if defined(CONFIG_ARCH_SCX35L)
+ cp0_power_domain_debug();
+ cp1_power_domain_debug();
+#endif
+ pd_pub_sys = __raw_readl(REG_PMU_APB_PD_PUB_SYS_CFG);
+ cp_slp_status_dbg0 = __raw_readl(REG_PMU_APB_CP_SLP_STATUS_DBG0);
+#if !defined(CONFIG_ARCH_SCX35L)
+ cp_slp_status_dbg1 = __raw_readl(REG_PMU_APB_CP_SLP_STATUS_DBG1);
+#endif
+ pwr_stat0 = __raw_readl(REG_PMU_APB_PWR_STATUS0_DBG);
+ pwr_stat1 = __raw_readl(REG_PMU_APB_PWR_STATUS1_DBG);
+ pwr_stat2 = __raw_readl(REG_PMU_APB_PWR_STATUS2_DBG);
+#if !defined(CONFIG_ARCH_SCX35L)
+ pwr_stat3 = __raw_readl(REG_PMU_APB_PWR_STATUS3_DBG);
+#endif
+ sleep_ctrl = __raw_readl(REG_PMU_APB_SLEEP_CTRL);
+ ddr_sleep_ctrl = __raw_readl(REG_PMU_APB_DDR_SLEEP_CTRL);
+ sleep_status = __raw_readl(REG_PMU_APB_SLEEP_STATUS);
+
+#if defined(CONFIG_ARCH_SCX15)
+ xtl0_rel_cfg = __raw_readl(REG_PMU_APB_XTL0_REL_CFG);
+ xtl1_rel_cfg = __raw_readl(REG_PMU_APB_XTL1_REL_CFG);
+ xtl2_rel_cfg = __raw_readl(REG_PMU_APB_XTL2_REL_CFG);
+ xtlbuf0_rel_cfg = __raw_readl(REG_PMU_APB_XTLBUF0_REL_CFG);
+ xtlbuf1_rel_cfg = __raw_readl(REG_PMU_APB_XTLBUF1_REL_CFG);
+ mpll_rel_cfg = __raw_readl(REG_PMU_APB_MPLL_REL_CFG);
+ dpll_rel_cfg = __raw_readl(REG_PMU_APB_DPLL_REL_CFG);
+ tdpll_rel_cfg = __raw_readl(REG_PMU_APB_TDPLL_REL_CFG);
+ wpll_rel_cfg = __raw_readl(REG_PMU_APB_WPLL_REL_CFG);
+ cpll_rel_cfg = __raw_readl(REG_PMU_APB_CPLL_REL_CFG);
+ wifipll1_rel_cfg = __raw_readl(REG_PMU_APB_WIFIPLL1_REL_CFG);
+ wifipll2_rel_cfg = __raw_readl(REG_PMU_APB_WIFIPLL2_REL_CFG);
+ ddr_chn_sleep_ctrl0 = __raw_readl(REG_PMU_APB_DDR_CHN_SLEEP_CTRL0);
+ ddr_chn_sleep_ctrl1 = __raw_readl(REG_PMU_APB_DDR_CHN_SLEEP_CTRL1);
+#endif
+
+ apb_eb0 = __raw_readl(REG_AON_APB_APB_EB0);
+ apb_eb1 = __raw_readl(REG_AON_APB_APB_EB1);
+ pwr_ctrl = __raw_readl(REG_AON_APB_PWR_CTRL);
+
+ ahb_eb = __raw_readl(REG_AP_AHB_AHB_EB);
+
+ apb_eb = __raw_readl(REG_AP_APB_APB_EB);
+ mcu_pause = __raw_readl(REG_AP_AHB_MCU_PAUSE);
+ sys_force_sleep = __raw_readl(REG_AP_AHB_AP_SYS_FORCE_SLEEP_CFG);
+ sys_auto_sleep_cfg = __raw_readl(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG);
+ //ca7_standby_status = __raw_readl(REG_AP_AHB_CA7_STANDBY_STATUS);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ ca7_standby_status = __raw_readl(REG_AP_AHB_APCPU_STANDBY_STATUS);
+#else
+ ca7_standby_status = __raw_readl(REG_AP_AHB_CA7_STANDBY_STATUS);
+#endif
+
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ ldo_slp_ctrl0 = sci_adi_read(ANA_REG_GLB_LDO_SLP_CTRL0);
+ ldo_slp_ctrl1 = sci_adi_read(ANA_REG_GLB_LDO_SLP_CTRL1);
+ ldo_slp_ctrl2 = sci_adi_read(ANA_REG_GLB_LDO_SLP_CTRL2);
+ ldo_slp_ctrl3 = sci_adi_read(ANA_REG_GLB_LDO_SLP_CTRL3);
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ ldo_slp_ctrl0 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL0);
+ ldo_slp_ctrl1 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL1);
+ ldo_slp_ctrl2 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL2);
+ ldo_slp_ctrl3 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL3);
+#endif
+#if defined(CONFIG_ADIE_SC2723)
+ ldo_slp_ctrl0 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL0);
+ ldo_slp_ctrl1 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL1);
+ ldo_slp_ctrl2 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL2);
+ ldo_slp_ctrl3 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL3);
+ ldo_slp_ctrl4 = sci_adi_read(ANA_REG_GLB_PWR_SLP_CTRL4);
+#endif
+
+#if defined(CONFIG_ARCH_SCX15)
+#else
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ ldo_aud_ctrl4 = sci_adi_read(ANA_REG_GLB_AUD_SLP_CTRL4);
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ ldo_aud_ctrl4 = sci_adi_read(ANA_REG_GLB_AUD_SLP_CTRL);
+#endif
+#if defined(CONFIG_ADIE_SC2723)
+ ldo_aud_ctrl4 = sci_adi_read(ANA_REG_GLB_AUD_SLP_CTRL);
+#endif
+#endif
+ xtl_wait_ctrl = sci_adi_read(ANA_REG_GLB_XTL_WAIT_CTRL);
+#if defined(CONFIG_ARCH_SCX15)
+ ldo_dcdc_pd_ctrl = sci_adi_read(ANA_REG_GLB_LDO_DCDC_PD);
+#endif
+ ldo_pd_ctrl = sci_adi_read(ANA_REG_GLB_LDO_PD_CTRL);
+ arm_module_en = sci_adi_read(ANA_REG_GLB_ARM_MODULE_EN);
+}
+void print_last_reg(void)
+{
+ printk("aon pmu status reg\n");
+ printk("REG_PMU_APB_PD_PUB_SYS_CFG ------ 0x%08x\n", pd_pub_sys);
+ printk("REG_PMU_APB_PD_MM_TOP_CFG ------ 0x%08x\n",
+ sci_glb_read(REG_PMU_APB_PD_MM_TOP_CFG, -1UL) );
+ printk("REG_PMU_APB_PD_AP_SYS_CFG ------ 0x%08x\n",
+ sci_glb_read(REG_PMU_APB_PD_AP_SYS_CFG, -1UL) );
+#ifdef REG_PMU_APB_PD_AP_DISP_CFG
+ printk("REG_PMU_APB_PD_AP_DISP_CFG ------ 0x%08x\n",
+ sci_glb_read(REG_PMU_APB_PD_AP_DISP_CFG, -1UL) );
+#endif
+
+#if defined(CONFIG_ARCH_SCX30G)
+ printk("REG_PMU_APB_PD_DDR_PUBL_CFG ------ 0x%08x\n",
+ sci_glb_read(REG_PMU_APB_PD_DDR_PUBL_CFG, -1UL) );
+ printk("REG_PMU_APB_PD_DDR_PHY_CFG ------ 0x%08x\n",
+ sci_glb_read(REG_PMU_APB_PD_DDR_PHY_CFG, -1UL) );
+#endif
+ printk("REG_PMU_APB_CP_SLP_STATUS_DBG0 ----- 0x%08x\n", cp_slp_status_dbg0);
+#if !defined(CONFIG_ARCH_SCX35L)
+ printk("REG_PMU_APB_CP_SLP_STATUS_DBG1 ----- 0x%08x\n", cp_slp_status_dbg1);
+#endif
+ printk("REG_PMU_APB_PWR_STATUS0_DBG ----- 0x%08x\n", pwr_stat0);
+ printk("REG_PMU_APB_PWR_STATUS1_DBG ----- 0x%08x\n", pwr_stat1);
+ printk("REG_PMU_APB_PWR_STATUS2_DBG ----- 0x%08x\n", pwr_stat2);
+#if !defined(CONFIG_ARCH_SCX35L)
+ printk("REG_PMU_APB_PWR_STATUS3_DBG ----- 0x%08x\n", pwr_stat3);
+#endif
+ printk("REG_PMU_APB_SLEEP_CTRL ----- 0x%08x\n", sleep_ctrl);
+ printk("REG_PMU_APB_DDR_SLEEP_CTRL ----- 0x%08x\n", ddr_sleep_ctrl);
+ printk("REG_PMU_APB_SLEEP_STATUS ----- 0x%08x\n", sleep_status);
+#if defined(CONFIG_ARCH_SCX15)
+ printk("REG_PMU_APB_XTL0_REL_CFG ----- 0x%08x\n", xtl0_rel_cfg);
+ printk("REG_PMU_APB_XTL1_REL_CFG ----- 0x%08x\n", xtl1_rel_cfg);
+ printk("REG_PMU_APB_XTL2_REL_CFG ----- 0x%08x\n", xtl2_rel_cfg);
+ printk("REG_PMU_APB_XTLBUF0_REL_CFG ----- 0x%08x\n", xtlbuf0_rel_cfg);
+ printk("REG_PMU_APB_XTLBUF1_REL_CFG ----- 0x%08x\n", xtlbuf1_rel_cfg);
+ printk("REG_PMU_APB_MPLL_REL_CFG ----- 0x%08x\n", mpll_rel_cfg);
+ printk("REG_PMU_APB_DPLL_REL_CFG ----- 0x%08x\n", dpll_rel_cfg);
+ printk("REG_PMU_APB_TDPLL_REL_CFG ----- 0x%08x\n", tdpll_rel_cfg);
+ printk("REG_PMU_APB_WPLL_REL_CFG ----- 0x%08x\n", wpll_rel_cfg);
+ printk("REG_PMU_APB_CPLL_REL_CFG ----- 0x%08x\n", cpll_rel_cfg);
+ printk("REG_PMU_APB_WIFIPLL1_REL_CFG ----- 0x%08x\n", wifipll1_rel_cfg);
+ printk("REG_PMU_APB_WIFIPLL2_REL_CFG ----- 0x%08x\n", wifipll2_rel_cfg);
+ printk("REG_PMU_APB_DDR_CHN_SLEEP_CTRL0 ----- 0x%08x\n", ddr_chn_sleep_ctrl0);
+ printk("REG_PMU_APB_DDR_CHN_SLEEP_CTRL1 ----- 0x%08x\n", ddr_chn_sleep_ctrl1);
+#endif
+
+ printk("aon apb reg\n");
+ printk("REG_AON_APB_APB_EB0 ----- 0x%08x\n", apb_eb0);
+ printk("REG_AON_APB_APB_EB1 ----- 0x%08x\n", apb_eb1);
+ printk("REG_AON_APB_PWR_CTRL ----- 0x%08x\n", pwr_ctrl);
+ printk("REG_AON_APB_BB_BG_CTRL ------ 0x%08x\n",
+ sci_glb_read(REG_AON_APB_BB_BG_CTRL, -1UL) );
+
+ printk("ap ahb reg \n");
+ printk("REG_AP_AHB_AHB_EB ----- 0x%08x\n", ahb_eb);
+
+ printk("ap apb reg\n");
+ printk("REG_AP_APB_APB_EB ---- 0x%08x\n", apb_eb);
+ printk("REG_AP_AHB_MCU_PAUSE --- 0x%08x\n", mcu_pause);
+ printk("REG_AP_AHB_AP_SYS_FORCE_SLEEP_CFG --- 0x%08x\n", sys_force_sleep);
+ printk("REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG ---- 0x%08x\n", sys_auto_sleep_cfg);
+ printk("REG_AP_AHB_CA7_STANDBY_STATUS ---- 0x%08x\n", ca7_standby_status);
+
+ printk("ana reg \n");
+ printk("ANA_REG_GLB_LDO_SLP_CTRL0 --- 0x%08x\n", ldo_slp_ctrl0);
+ printk("ANA_REG_GLB_LDO_SLP_CTRL1 --- 0x%08x\n", ldo_slp_ctrl1);
+ printk("ANA_REG_GLB_LDO_SLP_CTRL2 --- 0x%08x\n", ldo_slp_ctrl2);
+ printk("ANA_REG_GLB_LDO_SLP_CTRL3 --- 0x%08x\n", ldo_slp_ctrl3);
+ printk("ANA_REG_GLB_AUD_SLP_CTRL4 --- 0x%08x\n", ldo_aud_ctrl4);
+ printk("ANA_REG_GLB_XTL_WAIT_CTRL --- 0x%08x\n", xtl_wait_ctrl);
+
+ printk("ANA_REG_GLB_PWR_XTL_EN0 -- 0x%08x\n", sci_adi_read(ANA_REG_GLB_PWR_XTL_EN0));
+ printk("ANA_REG_GLB_PWR_XTL_EN1 -- 0x%08x\n", sci_adi_read(ANA_REG_GLB_PWR_XTL_EN1));
+ printk("ANA_REG_GLB_PWR_XTL_EN2 -- 0x%08x\n", sci_adi_read(ANA_REG_GLB_PWR_XTL_EN2));
+ printk("ANA_REG_GLB_PWR_XTL_EN3 -- 0x%08x\n", sci_adi_read(ANA_REG_GLB_PWR_XTL_EN3));
+#if !defined(CONFIG_ADIE_SC2723) && !defined(CONFIG_ADIE_SC2723S)
+ printk("ANA_REG_GLB_PWR_XTL_EN4 -- 0x%08x\n", sci_adi_read(ANA_REG_GLB_PWR_XTL_EN4));
+#endif
+#if !defined(CONFIG_ARCH_SCX15) && !defined(CONFIG_ADIE_SC2723) && !defined(CONFIG_ADIE_SC2723S)
+ printk("ANA_REG_GLB_PWR_XTL_EN5 -- 0x%08x\n", sci_adi_read(ANA_REG_GLB_PWR_XTL_EN5));
+#endif
+ printk("mm reg\n");
+ printk("REG_MM_AHB_AHB_EB ---- 0x%08x\n", mm_apb);
+
+ printk("ana reg\n");
+#if defined(CONFIG_ARCH_SCX15)
+ printk("ANA_REG_GLB_LDO_DCDC_PD --- 0x%08x\n", ldo_dcdc_pd_ctrl);
+#endif
+ printk("ANA_REG_GLB_LDO_PD_CTRL --- 0x%08x\n", ldo_pd_ctrl);
+ printk("ANA_REG_GLB_ARM_MODULE_EN --- 0x%08x\n", arm_module_en);
+}
+#if 0
+static void test_setup_timer(int load)
+{
+ //AP_TMR0_EB
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT(12));
+
+ //AP_TMR0_RTC_EB
+ sci_glb_set(REG_AON_APB_APB_RTC_EB, BIT(5));
+
+ //load value
+ sci_glb_write(SPRD_APTIMER0_BASE, load, -1UL);
+
+ //int en
+ sci_glb_set(SPRD_APTIMER0_BASE + 0xc, BIT(0));
+
+
+ //AP_TMR0_RTC_EB --- TIMER_INT_EN timer0 INTC0 en
+ sci_glb_set(SPRD_INT_BASE + 0x8, BIT(2) | BIT(29));
+
+ //int en
+ sci_glb_set(SPRD_APTIMER0_BASE + 0x8, BIT(7));
+
+ sci_glb_write(SPRD_INT_BASE + 0x8, 0xffffffff, -1UL);
+ return;
+}
+#endif
+void check_ldo(void)
+{
+}
+
+void check_pd(void)
+{
+}
+
+unsigned int sprd_irq_pending(void)
+{
+ uint32_t bits = 0;
+ bits = sci_glb_read(SPRD_INT_BASE, -1UL);
+ if(bits)
+ return 1;
+ else
+ return 0;
+}
+
+/*copy code for deepsleep return */
+#define SAVED_VECTOR_SIZE 64
+static uint32_t *sp_pm_reset_vector = NULL;
+static uint32_t saved_vector[SAVED_VECTOR_SIZE];
+void __iomem *iram_start;
+struct emc_repower_param *repower_param;
+
+
+#define SPRD_RESET_VECTORS 0X00000000
+#define SLEEP_RESUME_CODE_PHYS 0X400
+#define SLEEP_CODE_SIZE 0x800
+#define SPRD_IRAM0_PHYS 0x0
+
+extern unsigned long reg_aon_apb_eb0_vir;
+extern unsigned long reg_aon_apb_eb0_phy;
+extern unsigned long reg_uart1_enable_vir;
+extern unsigned long reg_uart1_enable_phy;
+
+static int init_reset_vector(void)
+{
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ sp_pm_reset_vector = (u32 *)SPRD_IRAM0_BASE;
+
+ reg_aon_apb_eb0_vir = SPRD_AONAPB_BASE;
+ reg_aon_apb_eb0_phy = SPRD_AONAPB_PHYS;
+ reg_uart1_enable_vir = SPRD_APBREG_BASE;
+ reg_uart1_enable_phy = SPRD_APBREG_PHYS;
+
+ iram_start = (void __iomem *)(SPRD_IRAM0_BASE + SLEEP_RESUME_CODE_PHYS);
+ /* copy sleep code to (IRAM). */
+ if ((sc8830_standby_iram_end - sc8830_standby_iram) > SLEEP_CODE_SIZE) {
+ panic("##: code size is larger than expected, need more memory!\n");
+ }
+ memcpy_toio(iram_start, sc8830_standby_iram, SLEEP_CODE_SIZE);
+
+
+ /* just make sure*/
+ flush_cache_all();
+ outer_flush_all();
+ sp_pm_reset_vector[65] = virt_to_phys(sp_pm_collapse_exit);
+#endif
+ return 0;
+}
+void save_reset_vector(void)
+{
+ int i = 0;
+ for (i = 0; i < SAVED_VECTOR_SIZE; i++)
+ saved_vector[i] = sp_pm_reset_vector[i];
+}
+
+void set_reset_vector(void)
+{
+ int i = 0;
+ printk("SAVED_VECTOR_SIZE 0x%x\n", SAVED_VECTOR_SIZE);
+ for (i = 0; i < SAVED_VECTOR_SIZE; i++)
+ sp_pm_reset_vector[i] = 0xe320f000; /* nop*/
+
+ sp_pm_reset_vector[SAVED_VECTOR_SIZE - 2] = 0xE51FF004; /* ldr pc, 4 */
+
+ sp_pm_reset_vector[SAVED_VECTOR_SIZE - 1] = (sc8830_standby_exit_iram -
+ sc8830_standby_iram + SLEEP_RESUME_CODE_PHYS); /* place v7_standby_iram here */
+}
+
+#ifdef CONFIG_DDR_VALIDITY_TEST
+#include <linux/slab.h>
+static unsigned long *vtest = NULL;
+static unsigned long *ptest = NULL;
+static void test_memory(void)
+{
+ int i;
+#ifndef CONFIG_ARCH_SCX30G
+ vtest = kmalloc(64*1024, GFP_KERNEL);
+ if (vtest) {
+ for (i = 0; i < (64*1024/4); i++) {
+ vtest[i] = 0x12345678;
+ }
+#else
+ vtest = kmalloc(2 * sizeof(int), GFP_KERNEL);
+ if (vtest) {
+ vtest[0] = 0x12345678;
+#endif
+ printk("%s %p %p\n", __func__, vtest, virt_to_phys(vtest));
+ ptest = virt_to_phys(vtest);
+ } else {
+ printk("error kmalloc\n");
+ }
+ sp_pm_reset_vector[64] = ptest;
+}
+#endif
+
+void restore_reset_vector(void)
+{
+ int i;
+ for (i = 0; i < SAVED_VECTOR_SIZE; i++)
+ sp_pm_reset_vector[i] = saved_vector[i];
+}
+/* irq functions */
+#define hw_raw_irqs_disabled_flags(flags) \
+({ \
+ (int)((flags) & PSR_I_BIT); \
+})
+
+#define hw_irqs_disabled() \
+({ \
+ unsigned long _flags; \
+ local_irq_save(_flags); \
+ hw_raw_irqs_disabled_flags(_flags); \
+})
+
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+u32 __attribute__ ((naked)) read_cpsr(void)
+{
+ __asm__ __volatile__("mrs r0, cpsr\nbx lr");
+}
+#endif
+/* make sure printk is end, if not maybe some messy code in SERIAL1 output */
+#define UART_TRANSFER_REALLY_OVER (0x1UL << 15)
+#define UART_STS0 (SPRD_UART1_BASE + 0x08)
+#define UART_STS1 (SPRD_UART1_BASE + 0x0c)
+
+static __used void wait_until_uart1_tx_done(void)
+{
+ u32 tx_fifo_val;
+ u32 really_done = 0;
+ u32 timeout = 2000;
+
+ tx_fifo_val = __raw_readl(UART_STS1);
+ tx_fifo_val >>= 8;
+ tx_fifo_val &= 0xff;
+ while(tx_fifo_val != 0) {
+ if (timeout <= 0) break;
+ udelay(100);
+ tx_fifo_val = __raw_readl(UART_STS1);
+ tx_fifo_val >>= 8;
+ tx_fifo_val &= 0xff;
+ timeout--;
+ }
+
+ timeout = 30;
+ really_done = __raw_readl(UART_STS0);
+ while(!(really_done & UART_TRANSFER_REALLY_OVER)) {
+ if (timeout <= 0) break;
+ udelay(100);
+ really_done = __raw_readl(UART_STS0);
+ timeout--;
+ }
+}
+#define SAVE_GLOBAL_REG do{ \
+ bak_restore_apb(1); \
+ bak_ap_clk_reg(1); \
+ bak_restore_ahb(1); \
+ bak_restore_aon(1); \
+ bak_restore_pub(1); \
+ bak_restore_ap_intc(1);\
+ }while(0)
+#define RESTORE_GLOBAL_REG do{ \
+ bak_restore_apb(0); \
+ bak_ap_clk_reg(0); \
+ bak_restore_aon(0); \
+ bak_restore_ahb(0); \
+ bak_restore_pub(0); \
+ bak_restore_ap_intc(0);\
+ }while(0)
+
+/* arm core sleep*/
+static void arm_sleep(void)
+{
+ cpu_do_idle();
+ hard_irq_set();
+}
+
+/* arm core + arm sys */
+static void mcu_sleep(void)
+{
+ SAVE_GLOBAL_REG;
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_SYS_SLEEP_EN);
+ cpu_do_idle();
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, BIT_MCU_SYS_SLEEP_EN);
+ hard_irq_set();
+ RESTORE_GLOBAL_REG;
+}
+
+static void light_sleep(void)
+{
+ sci_glb_set(REG_AP_AHB_MCU_PAUSE, BIT_MCU_LIGHT_SLEEP_EN);
+ cpu_do_idle();
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, BIT_MCU_LIGHT_SLEEP_EN);
+}
+void int_work_round(void)
+{
+ //sci_glb_set(SPRD_AONAPB_BASE + 0x8, (0x1<<30 | 1<<8 /*| 1<<1*/));
+ //sci_glb_clr(SPRD_LPDDR2_PHY_BASE + 0x2c, BIT(4));
+ //sci_glb_set(SPRD_LPDDR2_PHY_BASE + 0x2c, BIT(4));
+ //sci_glb_write(SPRD_LPDDR2_BASE + 0x30, 0, -1UL);
+ //sci_glb_set(SPRD_PMU_BASE + 0xa8, 1<<1);
+ //sci_adi_clr(ANA_EIC_BASE + 0x18, 0x20);
+}
+void show_deep_reg_status(void)
+{
+ printk("PWR_STATUS0_DBG 0x%08x\n", sci_glb_read(REG_PMU_APB_PWR_STATUS0_DBG, -1UL));
+ printk("PWR_STATUS1_DBG 0x%08x\n", sci_glb_read(REG_PMU_APB_PWR_STATUS1_DBG, -1UL));
+ printk("PWR_STATUS2_DBG 0x%08x\n", sci_glb_read(REG_PMU_APB_PWR_STATUS2_DBG, -1UL));
+#if !defined(CONFIG_ARCH_SCX35L)
+ printk("PWR_STATUS3_DBG 0x%08x\n", sci_glb_read(REG_PMU_APB_PWR_STATUS3_DBG, -1UL));
+#endif /* CONFIG_ARCH_SCX35L */
+}
+
+
+#if defined(CONFIG_MACH_SC9620OPENPHONE)
+void cp0_sys_power_domain_close(void)
+{
+ sci_glb_set(REG_PMU_APB_PD_CP0_SYS_CFG, BIT_CP0_FORCE_DEEP_SLEEP);//enable cp0 force sleep
+}
+
+void cp0_sys_power_domain_open(void)
+{
+ sci_glb_set(REG_PMU_APB_CP_SOFT_RST, BIT_CP0_SOFT_RST);//reset cp0
+ sci_glb_clr(REG_PMU_APB_PD_CP0_SYS_CFG, BIT_CP0_FORCE_DEEP_SLEEP);//close cp0 force sleep
+ mdelay(2);
+ sci_glb_clr(REG_PMU_APB_CP_SOFT_RST, BIT_CP0_SOFT_RST);//release cp0
+}
+#endif
+
+extern void pm_debug_set_wakeup_timer(void);
+extern void pm_debug_set_apwdt(void);
+typedef u32 (*iram_standby_entry_ptr)(u32,u32);
+static int sc_sleep_call(unsigned long flag)
+{
+ u32 ret = 0;
+ struct mm_struct *mm = current->active_mm;
+
+ iram_standby_entry_ptr func_ptr;
+ cpu_switch_mm(init_mm.pgd, &init_mm);
+ func_ptr = (iram_standby_entry_ptr)(SLEEP_RESUME_CODE_PHYS + (u32)sp_pm_collapse - (u32)sc8830_standby_iram);
+ ret = (func_ptr)(0, flag);
+ cpu_switch_mm(mm->pgd, mm);
+
+ return ret;
+}
+
+int deep_sleep(int from_idle)
+{
+ int ret = 0;
+ static unsigned int cnt = 0;
+
+ if(!from_idle){
+ SAVE_GLOBAL_REG;
+ /* some prepare here */
+#if defined(CONFIG_ARCH_SCX15)
+ bak_restore_mm_scx15(1);
+#endif
+ show_pin_reg();
+ enable_mcu_deep_sleep();
+ configure_for_deepsleep(0);
+ disable_ahb_module();
+ //disable_pmu_ddr_module();
+
+ //sci_glb_set(SPRD_PMU_BASE+0x00F4, 0x3FF);
+
+ disable_dma();
+ //disable_mm();
+ //disable_ana_module();
+ disable_aon_module();
+ show_reg_status();
+ bak_last_reg();
+ print_last_reg();
+ print_int_status();
+ //wait_until_uart1_tx_done();
+ int_work_round();
+ //pm_debug_set_apwdt();
+ disable_apb_module();
+ //pm_debug_set_wakeup_timer();
+ //force_mcu_core_sleep();
+ //bak_last_reg();
+
+#if defined(CONFIG_ARCH_SCX20L)
+ __raw_writel(0x20, REG_ARM7_AHB_RF_AP_COMM_CTL);
+ /*for pikeL, AP can't read/write ARM7 register when ARM7 sleep.
+ * so, disable ARM7 BUS sleep when AP wake, enable when AP sleep*/
+ sci_glb_set(REG_AON_APB_ARM7_CFG_BUS, BIT_ARM7_CFG_BUS_SLEEP);
+#else
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ __raw_writel(0x0, REG_PMU_APB_CA7_C0_CFG);
+#endif
+#endif
+ show_deep_reg_status();
+ } else {
+ /* __raw_writel(0x0, REG_PMU_APB_CA7_C0_CFG); */
+ }
+ /*
+ * sp_pm_collapse(param0, param1)
+ * param0, param1 are not used before
+ * so we use second param to distinguish idle deep or real deep
+ */
+#define dmc_retention_func (SPRD_IRAM1_BASE + 0x1800)
+#define dmc_retention_para_vaddr (SPRD_IRAM1_BASE + 0x2400)
+
+#ifdef CONFIG_ARCH_SCX20
+ /* set auto-self refresh mode */
+ sci_glb_clr(SPRD_LPDDR2_BASE+0x124, BIT(0)| BIT(1));
+ sci_glb_set(SPRD_LPDDR2_BASE+0X124, BIT(2));
+#else
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L) || defined(CONFIG_ARCH_SCX35L64) || defined(CONFIG_ARCH_SCX35LT8)
+ /* set auto-self refresh mode */
+ sci_glb_clr(SPRD_LPDDR2_BASE+0x30, BIT(1));
+ sci_glb_set(SPRD_LPDDR2_BASE+0x30, BIT(0));
+#if defined(CONFIG_ARCH_SCX30G)
+ *(volatile unsigned int*)(SPRD_IRAM1_BASE+0x1800 + 0xc00 + 0x0c) = SPRD_PUB_BASE;
+ *(volatile unsigned int*)(SPRD_IRAM1_BASE+0x1800 + 0xc00 + 0x10) = SPRD_LPDDR2_BASE;
+ *(volatile unsigned int*)(SPRD_IRAM1_BASE+0x1800 + 0xc00 + 0x14) = SPRD_LPDDR2_PHY_BASE;
+ ((void (*)(unsigned long,unsigned long))(dmc_retention_func))(1,dmc_retention_para_vaddr);
+#endif
+#endif
+#endif
+#if defined(CONFIG_MACH_SC9620OPENPHONE)
+ cp0_sys_power_domain_close();
+#endif
+#if defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)
+ ret = cpu_suspend(5);
+ ret = (ret ? 0 : 1);
+#else
+ ret = sc_sleep_call(from_idle);
+#endif
+#if defined(CONFIG_MACH_SC9620OPENPHONE)
+ cp0_sys_power_domain_open();
+#endif
+
+#if defined(CONFIG_ARCH_SCX20L)
+ sci_glb_clr(REG_AON_APB_ARM7_CFG_BUS, BIT_ARM7_CFG_BUS_SLEEP);
+#endif
+
+#ifdef CONFIG_ARCH_SCX20
+ /* set auto powerdown mode */
+ sci_glb_set(SPRD_LPDDR2_BASE+0x124, BIT(0)| BIT(1));
+ sci_glb_clr(SPRD_LPDDR2_BASE+0x124, BIT(2));
+#else
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L) || defined(CONFIG_ARCH_SCX35L64) || defined(CONFIG_ARCH_SCX35LT8)
+ /* set auto powerdown mode */
+ sci_glb_set(SPRD_LPDDR2_BASE+0x30, BIT(1));
+ sci_glb_clr(SPRD_LPDDR2_BASE+0x30, BIT(0));
+#endif
+#endif
+
+ udelay(50);
+
+
+ if(!from_idle){
+#if defined(CONFIG_ARCH_SCX20L)
+ __raw_writel(0x30, REG_ARM7_AHB_RF_AP_COMM_CTL);
+#else
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ __raw_writel(0x1, REG_PMU_APB_CA7_C0_CFG);
+#endif
+#endif
+ printk("ret %d not from idle\n", ret);
+ if(ret){
+#if defined(CONFIG_DDR_VALIDITY_TEST) && defined(CONFIG_ARCH_SCX30G)
+ printk("DDR test: read times: %ld\n", *(vtest + 1));
+#endif
+ printk("deep sleep %u times\n", cnt);
+ cnt++;
+ }
+ sci_glb_set(REG_AP_APB_APB_EB, 0xf<<19);
+ hard_irq_set();
+ sci_glb_clr(REG_AP_APB_APB_EB, 0xf<<19);
+ disable_mcu_deep_sleep();
+ configure_for_deepsleep(1);
+ //sci_glb_clr(SPRD_PMU_BASE+0x00F4, 0x3FF);
+ RESTORE_GLOBAL_REG;
+#if defined(CONFIG_ARCH_SCX15)
+ bak_restore_mm_scx15(0);
+#endif
+ } else {
+ /* __raw_writel(0x1, REG_PMU_APB_CA7_C0_CFG); */
+ pr_debug("ret %d from idle\n", ret);
+ }
+
+ udelay(5);
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ if (ret) cpu_init();
+#endif
+
+ if (soc_is_scx35_v0())
+ //sci_glb_clr(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG,BIT_CA7_CORE_AUTO_GATE_EN);
+#if defined(CONFIG_ARCH_SCX35LT8)
+ sci_glb_clr(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG,BIT_CA53_CORE_AUTO_GATE_EN);
+#else
+ sci_glb_clr(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG,BIT_CA7_CORE_AUTO_GATE_EN);
+#endif
+
+ return ret;
+}
+
+#define DEVICE_AHB (0x1UL << 20)
+#define DEVICE_APB (0x1UL << 21)
+#define DEVICE_VIR (0x1UL << 22)
+#define DEVICE_AWAKE (0x1UL << 23)
+#define LIGHT_SLEEP (0x1UL << 24)
+#define DEVICE_TEYP_MASK (DEVICE_AHB | DEVICE_APB | DEVICE_VIR | DEVICE_AWAKE | LIGHT_SLEEP)
+
+int sprd_cpu_deep_sleep(unsigned int cpu)
+{
+ int status, ret = 0;
+ unsigned long flags, time;
+
+#ifdef CONFIG_SPRD_PM_DEBUG
+ __raw_writel(0xfdffbfff, SPRD_INTC0_BASE + 0xc);//intc0
+ __raw_writel(0x02004000, SPRD_INTC0_BASE + 0x8);//intc0
+ __raw_writel(0xffffffff, SPRD_INTC0_BASE + 0x100c);//intc1
+#endif
+
+ time = get_sys_cnt();
+ if (!hw_irqs_disabled()) {
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ flags = read_cpsr();
+#endif
+ printk("##: Error(%s): IRQ is enabled(%08lx)!\n",
+ "wakelock_suspend", flags);
+ }
+ /*TODO:
+ * we need to known clock status in modem side
+ */
+#ifdef FORCE_DISABLE_DSP
+ status = 0;
+#else
+ /*
+ * TODO: get clock status in native version, force deep sleep now
+ */
+ status = 0;
+#endif
+ if (status & DEVICE_AHB) {
+ printk("###### %s, DEVICE_AHB ###\n", __func__ );
+ set_sleep_mode(SLP_MODE_ARM);
+ arm_sleep();
+ } else if (status & DEVICE_APB) {
+ printk("###### %s, DEVICE_APB ###\n", __func__ );
+ set_sleep_mode(SLP_MODE_MCU);
+ mcu_sleep();
+ } else if (status & LIGHT_SLEEP) {
+ printk("###### %s, DEVICE_APB ###\n", __func__ );
+ set_sleep_mode(SLP_MODE_LIT);
+ light_sleep();
+ } else {
+ /*printk("###### %s, DEEP ###\n", __func__ );*/
+ set_sleep_mode(SLP_MODE_DEP);
+#if 1
+
+ ret = deep_sleep(0);
+#else
+ //pm_debug_set_wakeup_timer();
+ ret = 0;
+ arm_sleep();
+#endif
+ flush_cache_all();
+ }
+
+ time_add(get_sys_cnt() - time, ret);
+ print_hard_irq_inloop(ret);
+
+ return ret;
+}
+static void init_gr(void){}
+void sc_default_idle(void)
+{
+ cpu_do_idle();
+ local_irq_enable();
+ return;
+}
+
+/*config dcdc core deep sleep voltage*/
+static void dcdc_core_ds_config(void)
+{
+ u32 dcdc_core_ctl_adi = 0;
+ u32 val = 0;
+ u32 dcdc_core_ctl_ds = -1;
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+#ifdef CONFIG_ARCH_SCX30G
+ static struct dcdc_core_ds_step_info step_info[5]={
+ {0, 0,0, 0},
+ {0, 3,0, 5},
+ {0, 6,0,10},
+ {0, 9,0, 0},
+ {0,12,0, 5}
+ };
+ step_info[0].ctl_reg = ANA_REG_GLB_MP_PWR_CTRL1;
+ step_info[1].ctl_reg = ANA_REG_GLB_MP_PWR_CTRL1;
+ step_info[2].ctl_reg = ANA_REG_GLB_MP_PWR_CTRL1;
+ step_info[3].ctl_reg = ANA_REG_GLB_MP_PWR_CTRL1;
+ step_info[4].ctl_reg = ANA_REG_GLB_MP_PWR_CTRL1;
+ step_info[0].cal_reg = ANA_REG_GLB_MP_PWR_CTRL2;
+ step_info[1].cal_reg = ANA_REG_GLB_MP_PWR_CTRL2;
+ step_info[2].cal_reg = ANA_REG_GLB_MP_PWR_CTRL2;
+ step_info[3].cal_reg = ANA_REG_GLB_MP_PWR_CTRL3;
+ step_info[4].cal_reg = ANA_REG_GLB_MP_PWR_CTRL3;
+
+ static u8 dcdc_core_down_volt[]={4,1,1,2,3,5,0,6};
+ static u8 dcdc_core_up_volt[]={6,2,3,4,0,1,7,7};
+ u32 dcdc_core_cal_adi,i;
+ /*1100,700,800,900,1000,650,1200,1300*/
+ static u32 step_ratio[]={10,10,6,3,3};
+ dcdc_core_ctl_adi = (sci_adi_read(ANA_REG_GLB_MP_MISC_CTRL) >> 3) & 0x7;
+ if((ana_chip_id == 0x2713ca00)||(ana_chip_id == 0x2723a000)) {
+ dcdc_core_ctl_ds = dcdc_core_ctl_adi;
+ } else {
+ dcdc_core_ctl_ds = dcdc_core_down_volt[dcdc_core_ctl_adi];
+ }
+ printk("ana_chip_id:%x, dcdc_core_ctl_adi = %d, dcdc_core_ctl_ds = %d\n"
+ ,ana_chip_id, dcdc_core_ctl_adi, dcdc_core_ctl_ds);
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ val = sci_adi_read(ANA_REG_GLB_DCDC_SLP_CTRL);
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ val = sci_adi_read(ANA_REG_GLB_DCDC_SLP_CTRL0);
+#endif
+ val &= ~0x7;
+ val |= dcdc_core_ctl_ds;
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ sci_adi_write(ANA_REG_GLB_DCDC_SLP_CTRL, val, 0xffff);
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ sci_adi_write(ANA_REG_GLB_DCDC_SLP_CTRL0, val, 0xffff);
+#endif
+
+ if(dcdc_core_ctl_ds < dcdc_core_ctl_adi){
+ dcdc_core_cal_adi = sci_adi_read(ANA_REG_GLB_DCDC_CORE_ADI) & 0x1F;
+ /*last step must equel function mode */
+ sci_adi_write(step_info[4].ctl_reg,dcdc_core_ctl_adi<<step_info[4].ctl_sht,0x07 << step_info[4].ctl_sht);
+ sci_adi_write(step_info[4].cal_reg,dcdc_core_cal_adi<<step_info[4].cal_sht,0x1F << step_info[4].cal_sht);
+
+ for(i=0;i<4;i++) {
+ val = dcdc_core_cal_adi + step_ratio[i];
+ if(val <= 0x1F) {
+ sci_adi_write(step_info[i].ctl_reg,dcdc_core_ctl_ds<<step_info[i].ctl_sht,0x07<<step_info[i].ctl_sht);
+ sci_adi_write(step_info[i].cal_reg,val<<step_info[i].cal_sht,0x1F << step_info[i].cal_sht);
+ dcdc_core_cal_adi = val;
+ } else {
+ sci_adi_write(step_info[i].ctl_reg,dcdc_core_up_volt[dcdc_core_ctl_ds]<<step_info[i].ctl_sht,
+ 0x07 << step_info[i].ctl_sht);
+ sci_adi_write(step_info[i].cal_reg,(val-0x1F)<<step_info[i].cal_sht,0x1F << step_info[i].cal_sht);
+ dcdc_core_ctl_ds = dcdc_core_up_volt[dcdc_core_ctl_ds];
+ dcdc_core_cal_adi = val - 0x1F;
+ }
+ }
+ } else {
+ dcdc_core_cal_adi = sci_adi_read(ANA_REG_GLB_DCDC_CORE_ADI) & 0x1F;
+ for(i=0;i<5;i++){
+ sci_adi_write(step_info[i].ctl_reg,dcdc_core_ctl_adi<<step_info[i].ctl_sht,0x07<<step_info[i].ctl_sht);
+ sci_adi_write(step_info[i].cal_reg,dcdc_core_cal_adi<<step_info[i].cal_sht,0x1F<<step_info[i].cal_sht);
+ }
+ }
+#else
+ dcdc_core_ctl_adi = sci_adi_read(ANA_REG_GLB_DCDC_CORE_ADI);
+ dcdc_core_ctl_adi = dcdc_core_ctl_adi >> 0x5;
+ dcdc_core_ctl_adi = dcdc_core_ctl_adi & 0x7;
+ /*only support dcdc_core_ctl_adi 0.9v,1.0v, 1.1v, 1.2v, 1.3v*/
+ switch(dcdc_core_ctl_adi) {
+ case 0://1.1v
+ dcdc_core_ctl_ds = 0x4; //1.0v
+ break;
+ case 4: //1.0v
+ dcdc_core_ctl_ds = 0x3;//0.9v
+ break;
+ case 6: //1.2v
+ dcdc_core_ctl_ds = 0x0;//1.1v
+ break;
+ case 7: //1.3v
+ dcdc_core_ctl_ds = 0x6;//1.2v
+ break;
+ case 3: //0.9v
+ dcdc_core_ctl_ds = 0x2;//0.8v
+ break;
+ case 1: //0.7v
+ case 2: //0.8v
+ case 5: //0.65v
+ //unvalid value
+ break;
+ }
+ printk("dcdc_core_ctl_adi = %d, dcdc_core_ctl_ds = %d\n", dcdc_core_ctl_adi, dcdc_core_ctl_ds);
+ /*valid value*/
+ if(dcdc_core_ctl_ds != -1) {
+#ifndef CONFIG_ARCH_SCX15
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ val = sci_adi_read(ANA_REG_GLB_DCDC_SLP_CTRL);
+ val &= ~(0x7);
+ val |= dcdc_core_ctl_ds;
+ sci_adi_write(ANA_REG_GLB_DCDC_SLP_CTRL, val, 0xffff);
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ val = sci_adi_read(ANA_REG_GLB_DCDC_SLP_CTRL0);
+ val &= ~(0x7);
+ val |= dcdc_core_ctl_ds;
+ sci_adi_write(ANA_REG_GLB_DCDC_SLP_CTRL0, val, 0xffff);
+#endif
+#if defined(CONFIG_ADIE_SC2723)
+ val = sci_adi_read(ANA_REG_GLB_DCDC_SLP_CTRL1);
+ val &= ~(0xF);
+ val |= dcdc_core_ctl_ds;
+ sci_adi_write(ANA_REG_GLB_DCDC_SLP_CTRL1, val, 0xffff);
+#endif
+#else
+ val = sci_adi_read(ANA_REG_GLB_DCDC_SLP_CTRL0);
+ val &= ~(0x7 << 4);
+ val |= (dcdc_core_ctl_ds << 4);
+ sci_adi_write(ANA_REG_GLB_DCDC_SLP_CTRL0, val, 0xffff);
+#endif
+ }
+#endif
+#endif
+}
+
+void pm_ana_ldo_config(void)
+{
+#ifdef CONFIG_ARCH_SCX30G
+ ana_chip_id = (sci_adi_read(ANA_REG_GLB_CHIP_ID_HIGH) & 0xFFFF) << 16;
+ ana_chip_id |= (sci_adi_read(ANA_REG_GLB_CHIP_ID_LOW) & 0xFFFF);
+#endif
+
+#if defined(CONFIG_ARCH_SCX15) || defined(CONFIG_ADIE_SC2723)
+ dcdc_core_ds_config();
+#else
+ /*set vddcore deep sleep voltage to 0.9v*/
+ //sci_adi_set(ANA_REG_GLB_DCDC_SLP_CTRL, BITS_DCDC_CORE_CTL_DS(3));
+ dcdc_core_ds_config();
+ /*open vddcore lp VDDMEM, DCDCGEN mode*/
+ //sci_adi_set(ANA_REG_GLB_LDO_SLP_CTRL2, BIT_SLP_DCDCCORE_LP_EN | BIT_SLP_DCDCMEM_LP_EN | BIT_SLP_DCDCGEN_LP_EN);
+ /*open vdd28, vdd18 lp mode*/
+ //sci_adi_set(ANA_REG_GLB_LDO_SLP_CTRL3, BIT_SLP_LDOVDD28_LP_EN | BIT_SLP_LDOVDD18_LP_EN);
+ /*ddr2_buf quiesent curretn set to 4-5uA in deep sleep mode*/
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ sci_adi_clr(ANA_REG_GLB_DDR2_CTRL, BITS_DDR2_BUF_S_DS(0x3));
+#endif
+#endif
+}
+static void init_led(void){}
+
+void sprd_pbint_7s_reset_disable(void)
+{
+ sci_adi_set(ANA_REG_GLB_POR_7S_CTRL, BIT_PBINT_7S_RST_DISABLE);
+}
+
+#ifdef CONFIG_SPRD_EXT_IC_POWER
+// EXT IC should disable otg mode when power off
+ extern void sprd_extic_otg_power(int enable);
+#endif
+
+static void sc8830_power_off(void)
+{
+ u32 reg_val;
+ /*turn off all modules's ldo*/
+
+#ifdef CONFIG_SPRD_EXT_IC_POWER
+ sprd_extic_otg_power(0);
+#endif
+ sci_adi_raw_write(ANA_REG_GLB_LDO_PD_CTRL, 0x1fff);
+
+#if defined(CONFIG_ARCH_SCX15) || defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S)
+ sci_adi_raw_write(ANA_REG_GLB_PWR_WR_PROT_VALUE,BITS_PWR_WR_PROT_VALUE(0x6e7f));
+ do{
+ reg_val = (sci_adi_read(ANA_REG_GLB_PWR_WR_PROT_VALUE) & BIT_PWR_WR_PROT);
+ }while(reg_val == 0);
+ sci_adi_raw_write(ANA_REG_GLB_LDO_PD_CTRL,0xfff);
+ sci_adi_raw_write(ANA_REG_GLB_LDO_DCDC_PD,0x7fff);
+#endif
+
+#if !defined(CONFIG_64BIT)
+#if defined(CONFIG_ADIE_SC2713S) || defined(CONFIG_ADIE_SC2713)
+ /*turn off system core's ldo*/
+ sci_adi_raw_write(ANA_REG_GLB_LDO_DCDC_PD_RTCCLR, 0x0);
+ sci_adi_raw_write(ANA_REG_GLB_LDO_DCDC_PD_RTCSET, 0X7fff);
+#endif
+#endif
+
+}
+
+
+static void sc8830_machine_restart(char mode, const char *cmd)
+{
+ local_irq_disable();
+ local_fiq_disable();
+
+ arch_reset(mode, cmd);
+
+ mdelay(1000);
+
+ printk("reboot failed!\n");
+
+ while (1);
+}
+void resume_code_remap(void)
+{
+ int ret;
+ ret = ioremap_page_range(SPRD_IRAM0_PHYS, SPRD_IRAM0_PHYS+SZ_8K, SPRD_IRAM0_PHYS, PAGE_KERNEL_EXEC);
+ if(ret){
+ printk("resume_code_remap err %d\n", ret);
+ BUG();
+ }
+}
+void __init sc_pm_init(void)
+{
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ init_reset_vector();
+ resume_code_remap();
+#endif
+ pm_power_off = sc8830_power_off;
+ arm_pm_restart = sc8830_machine_restart;
+ pr_info("power off %pf, restart %pf\n", pm_power_off, arm_pm_restart);
+ init_gr();
+ setup_autopd_mode();
+ pm_ana_ldo_config();
+ init_led();
+ /* disable all sleep mode */
+ sci_glb_clr(REG_AP_AHB_MCU_PAUSE, BIT_MCU_DEEP_SLEEP_EN | BIT_MCU_LIGHT_SLEEP_EN | \
+ BIT_MCU_SYS_SLEEP_EN | BIT_MCU_CORE_SLEEP);
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ set_reset_vector();
+#endif
+#ifdef CONFIG_DDR_VALIDITY_TEST
+ test_memory();
+#endif
+#ifndef CONFIG_SPRD_PM_DEBUG
+ pm_debug_init();
+#endif
+
+ /* enable arm clock auto gating*/
+ //sci_glb_set(REG_AHB_AHB_CTL1, BIT_ARM_AUTO_GATE_EN);
+#ifdef CONFIG_CPU_IDLE
+#ifndef CONFIG_64BIT
+ /* Used to armv7 */
+ sc_cpuidle_init();
+#endif
+#endif
+/*
+ wake_lock_init(&pm_debug_lock, WAKE_LOCK_SUSPEND, "pm_not_ready");
+ wake_lock(&pm_debug_lock);
+*/
+#if defined(CONFIG_SPRD_DEBUG)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ sprd_debug_init();
+#endif
+#endif
+}
diff --git a/drivers/platform/sprd/pm.c b/drivers/platform/sprd/pm.c
new file mode 100644
index 00000000..97bb899a
--- /dev/null
+++ b/drivers/platform/sprd/pm.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+extern void sc_pm_init(void);
+
+#include <asm/irqflags.h>
+#include <soc/sprd/pm_debug.h>
+#include <soc/sprd/globalregs.h>
+#if defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)
+#include <soc/sprd/sprd_persistent_clock.h>
+#endif
+
+extern unsigned int sprd_irq_pending(void);
+extern int sprd_cpu_deep_sleep(unsigned int cpu);
+extern void sc_pm_init(void);
+extern void check_ldo(void);
+extern void check_pd(void);
+
+#if 0 /*comment for bug 179358 */
+/*for battery*/
+#define BATTERY_CHECK_INTERVAL 30000
+extern int battery_updata(void);
+
+static int __used sprd_check_battery(void)
+{
+ int ret_val = 0;
+ if (battery_updata()) {
+ ret_val = 1;
+ }
+ return ret_val;
+}
+#endif
+
+static void sprd_pm_standby(void)
+{
+ cpu_do_idle();
+}
+
+static int sprd_pm_deepsleep(suspend_state_t state)
+{
+ int ret_val = 0;
+ unsigned long flags;
+ unsigned int cpu = smp_processor_id();
+ u32 battery_time, cur_time;
+ battery_time = cur_time = get_sys_cnt();
+
+ /* add for debug & statisic*/
+ clr_sleep_mode();
+ time_statisic_begin();
+
+ /*
+ * when we get here, only boot cpu still alive
+ */
+ if( cpu ){
+ __WARN();
+ goto enter_exit;
+ }
+ printk("cpu%d, enter %s\n", cpu, __func__ );
+
+ while(1){
+ local_fiq_disable();
+ local_irq_save(flags);
+ WARN_ONCE(!irqs_disabled(), "#####: Interrupts enabled in pm_enter()!\n");
+
+ sprd_cpu_deep_sleep(cpu);
+
+ if( sprd_irq_pending() ){
+ irq_wakeup_set();
+ local_irq_restore(flags);
+ local_fiq_enable();
+ break;
+ }
+
+ /*TODO: charging in power down
+ cur_time = get_sys_cnt();
+ if ((cur_time - battery_time) > BATTERY_CHECK_INTERVAL) {
+ battery_time = cur_time;
+ if (sprd_check_battery()) {
+ printk("###: battery low!\n");
+ break;
+ }
+ }
+ */
+
+ }/*end while*/
+
+ time_statisic_end();
+
+enter_exit:
+ return ret_val;
+}
+
+
+static int sprd_pm_enter(suspend_state_t state)
+{
+ int rval = 0;
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ sprd_pm_standby( );
+ break;
+ case PM_SUSPEND_MEM:
+ rval = sprd_pm_deepsleep(state);
+ break;
+ default:
+ break;
+ }
+
+ return rval;
+}
+
+static int sprd_pm_valid(suspend_state_t state)
+{
+ pr_debug("pm_valid: %d\n", state);
+ switch (state) {
+ case PM_SUSPEND_ON:
+ case PM_SUSPEND_STANDBY:
+ case PM_SUSPEND_MEM:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int sprd_pm_prepare(void)
+{
+ pr_debug("enter %s\n", __func__);
+ check_ldo();
+ check_pd();
+ return 0;
+}
+
+static void sprd_pm_finish(void)
+{
+ pr_debug("enter %s\n", __func__);
+ print_statisic();
+}
+
+static struct platform_suspend_ops sprd_pm_ops = {
+ .valid = sprd_pm_valid,
+ .enter = sprd_pm_enter,
+ .prepare = sprd_pm_prepare,
+ .prepare_late = NULL,
+ .finish = sprd_pm_finish,
+};
+
+static int __init sprd_pm_init(void)
+{
+ sc_pm_init();
+
+#ifdef CONFIG_SUSPEND
+ suspend_set_ops(&sprd_pm_ops);
+#endif
+
+ return 0;
+}
+
+subsys_initcall(sprd_pm_init);
diff --git a/drivers/platform/sprd/pm_debug.c b/drivers/platform/sprd/pm_debug.c
new file mode 100644
index 00000000..8037ced4
--- /dev/null
+++ b/drivers/platform/sprd/pm_debug.c
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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.
+ */
+
+#if 0
+#include <asm/irqflags.h>
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/kthread.h>
+#include <linux/debugfs.h>
+#include <mach/pm_debug.h>
+#include <mach/globalregs.h>
+#include <mach/regs_glb.h>
+#include <mach/regs_ahb.h>
+#include <mach/regs_ana_glb.h>
+#include <mach/sci.h>
+#include <mach/adi.h>
+
+struct dentry * dentry_debug_root = NULL;
+static int is_print_sleep_mode = 0;
+int is_print_linux_clock = 1;
+int is_print_modem_clock = 1;
+static int is_print_irq = 1;
+static int is_print_wakeup = 1;
+static int is_print_irq_runtime = 0;
+static int is_print_time = 1;
+static unsigned int core_time = 0;
+static unsigned int mcu_time = 0;
+static unsigned int deep_time_successed = 0;
+static unsigned int deep_time_failed = 0;
+static unsigned int sleep_time = 0;
+#define SPRD_INTC_NUM 2
+#define SPRD_HARD_INTERRUPT_NUM 64
+#define SPRD_HARD_INT_NUM_EACH_INTC 32
+#define SPRD_IRQ_NUM 1024
+static u32 sprd_hard_irq[SPRD_HARD_INTERRUPT_NUM]= {0, };
+static u32 sprd_irqs[SPRD_IRQ_NUM] = {0, };
+static u32 sprd_irqs_sts[SPRD_INTC_NUM] = {0, };
+static int is_wakeup = 0;
+static int irq_status = 0;
+static int hard_irq_status[SPRD_INTC_NUM] = {0, };
+static int sleep_mode = SLP_MODE_NON;
+static char * sleep_mode_str[] = {
+ "[ARM]",
+ "[MCU]",
+ "[DEP]",
+ "[NON]"
+};
+#define INT_REG(off) (SPRD_INTC0_BASE + (off))
+#define INTC1_REG(off) (SPRD_INTC0_BASE + 0x1000 + (off))
+#define INT_IRQ_STS INT_REG(0x0000)
+#define INT_IRQ_RAW INT_REG(0x0004)
+#define INT_IRQ_ENB INT_REG(0x0008)
+#define INT_IRQ_DIS INT_REG(0x000c)
+#define INT_FIQ_STS INT_REG(0x0020)
+#define INTCV1_IRQ_MSKSTS INTC1_REG(0x0000)
+#define INTCV1_IRQ_RAW INTC1_REG(0x0004)
+#define INTCV1_IRQ_EN INTC1_REG(0x0008)
+#define INTCV1_IRQ_DIS INTC1_REG(0x000C)
+#define INTCV1_FIQ_STS INTC1_REG(0x0020)
+#define INT_IRQ_MASK (1<<3)
+
+void pm_debug_dump_ahb_glb_regs(void);
+
+static void hard_irq_reset(void)
+{
+ int i = SPRD_HARD_INTERRUPT_NUM - 1;
+ do{
+ sprd_hard_irq[i] = 0;
+ }while(--i >= 0);
+}
+
+static void parse_hard_irq(unsigned long val, unsigned long intc)
+{
+ int i;
+ if(intc == 0){
+ for (i = 0; i < SPRD_HARD_INT_NUM_EACH_INTC; i++) {
+ if (test_and_clear_bit(i, &val)) sprd_hard_irq[i]++;
+ }
+ }
+ if(intc == 1){
+ for (i = 32; i < SPRD_HARD_INT_NUM_EACH_INTC; i++) {
+ if (test_and_clear_bit(i, &val)) sprd_hard_irq[i]++;
+ }
+ }
+}
+
+void hard_irq_set(void)
+{
+ sprd_irqs_sts[0] = __raw_readl(INT_IRQ_STS);
+ sprd_irqs_sts[1] = __raw_readl(INT_FIQ_STS);
+ irq_status = __raw_readl(INT_IRQ_STS);
+ parse_hard_irq(irq_status, 0);
+ irq_status = __raw_readl(INTCV1_IRQ_MSKSTS);
+ parse_hard_irq(irq_status, 1);
+}
+
+#define GPIO_GROUP_NUM 16
+#define IRQ_GPIO (1<<10)
+#define IRQ_ADIE (1<<25)
+#define IRQ_DSP0 (1<<26)
+#define IRQ_DSP1 (1<<27)
+#define IRQ_TIMER0 (1<<6)
+#define IRQ_SIM0 (1<<15)
+#define IRQ_SIM1 (1<<16)
+
+#define REG_GPIO_MIS (0x0020)
+#define ANA_REG_INT_MASK_STATUS (SPRD_MISC_BASE + 0x380 +0x0000)
+void print_hard_irq_inloop(int ret)
+{
+ unsigned int i, j, val;
+ unsigned int gpio_irq[GPIO_GROUP_NUM];
+
+ if(!((sprd_irqs_sts[0]&IRQ_DSP0) ||
+ (sprd_irqs_sts[0]&IRQ_DSP1) ||
+ (sprd_irqs_sts[0]&IRQ_TIMER0) ||
+ (sprd_irqs_sts[0]&IRQ_SIM0) ||
+ (sprd_irqs_sts[0]&IRQ_SIM1) ) ){
+
+ if(sprd_irqs_sts[0] != 0)
+ printk("%c#:INTC0: %08x\n", ret?'S':'F', sprd_irqs_sts[0]);
+ if(sprd_irqs_sts[1] != 0)
+ printk("%c#:INTC0 FIQ: %08x\n", ret?'S':'F', sprd_irqs_sts[1]);
+ }
+
+ if(sprd_irqs_sts[0] & IRQ_GPIO){
+ for(i=0; i<(GPIO_GROUP_NUM/2); i++){
+ j = 2*i;
+ gpio_irq[j]= __raw_readl(SPRD_GPIO_BASE + 0x100*i + REG_GPIO_MIS);
+ gpio_irq[j+1]= __raw_readl(SPRD_GPIO_BASE + 0x100*i + 0x80 + REG_GPIO_MIS);
+ printk("gpio_irq[%d]:0x%x, gpio_irq[%d]:0x%x \n", j, gpio_irq[j], j+1, gpio_irq[j+1]);
+ }
+ for(i=0; i<GPIO_GROUP_NUM; i++){
+ if(gpio_irq[i] != 0){
+ val = gpio_irq[i];
+ while(val){
+ j = __ffs(val);
+ printk("gpio irq number : %d \n", (j+16*(i+1)) );
+ val &= ~(1<<j);
+ }
+ }
+ }
+ }
+ if(sprd_irqs_sts[0] & IRQ_ADIE){
+ printk("adie, irq status:0x%x \n", sci_adi_read(ANA_REG_INT_MASK_STATUS));
+ }
+
+}
+
+static void print_hard_irq(void)
+{
+ int i = SPRD_HARD_INTERRUPT_NUM -1;
+ if(!is_print_irq)
+ return;
+ do{
+ if(0 != sprd_hard_irq[i])
+ printk("##: sprd_hard_irq[%d] = %d.\n",
+ i, sprd_hard_irq[i]);
+ }while(--i >= 0);
+}
+
+static void irq_reset(void)
+{
+ int i = SPRD_IRQ_NUM - 1;
+ do{
+ sprd_irqs[i] = 0;
+ }while(--i >= 0);
+}
+
+
+void inc_irq(int irq)
+{
+ if(is_wakeup){
+ if (irq >= SPRD_IRQ_NUM) {
+ printk("## bad irq number %d.\n", irq);
+ return;
+ }
+ sprd_irqs[irq]++;
+ if(is_print_wakeup)
+ printk("\n#####: wakeup irq = %d.\n", irq);
+ is_wakeup = 0;
+ }
+}
+EXPORT_SYMBOL(inc_irq);
+
+void irq_wakeup_set(void)
+{
+ is_wakeup = 1;
+}
+
+static void print_irq(void)
+{
+ int i = SPRD_IRQ_NUM - 1;
+ if(!is_print_irq)
+ return;
+ do{
+ if(0 != sprd_irqs[i])
+ printk("##: sprd_irqs[%d] = %d.\n",
+ i, sprd_irqs[i]);
+ }while(--i >= 0);
+}
+
+void time_add(unsigned int time, int ret)
+{
+ switch(sleep_mode){
+ case SLP_MODE_ARM:
+ core_time += time;
+ break;
+ case SLP_MODE_MCU:
+ mcu_time += time;
+ break;
+ case SLP_MODE_DEP:
+ if(ret)
+ deep_time_successed += time;
+ else
+ deep_time_failed += time;
+ break;
+ default:
+ break;
+ }
+}
+
+void time_statisic_begin(void)
+{
+ core_time = 0;
+ mcu_time = 0;
+ deep_time_successed = 0;
+ deep_time_failed = 0;
+ sleep_time = get_sys_cnt();
+ hard_irq_reset();
+}
+
+void time_statisic_end(void)
+{
+ sleep_time = get_sys_cnt() - sleep_time;
+}
+
+void print_time(void)
+{
+ if(!is_print_time)
+ return;
+ printk("time statisics : sleep_time=%d, core_time=%d, mcu_time=%d, deep_sus=%d, dep_fail=%d\n",
+ sleep_time, core_time, mcu_time, deep_time_successed, deep_time_failed);
+}
+
+void set_sleep_mode(int sm){
+ int is_print = (sm == sleep_mode);
+ sleep_mode = sm;
+ if(is_print_sleep_mode == 0 || is_print )
+ return;
+ switch(sm){
+ case SLP_MODE_ARM:
+ printk("\n[ARM]\n");
+ break;
+ case SLP_MODE_MCU:
+ printk("\n[MCU]\n");
+ break;
+ case SLP_MODE_DEP:
+ printk("\n[DEP]\n");
+ break;
+ default:
+ printk("\nNONE\n");
+ }
+}
+void clr_sleep_mode(void)
+{
+ sleep_mode = SLP_MODE_NON;
+}
+void print_statisic(void)
+{
+ print_time();
+ print_hard_irq();
+ print_irq();
+ pm_debug_dump_ahb_glb_regs();
+ if(is_print_wakeup){
+ printk("###wake up form %s : %08x\n", sleep_mode_str[sleep_mode], sprd_irqs_sts[0]);
+ printk("###wake up form %s : %08x\n", sleep_mode_str[sleep_mode], sprd_irqs_sts[1]);
+ }
+}
+#ifdef PM_PRINT_ENABLE
+static struct wake_lock messages_wakelock;
+#endif
+#ifdef PM_PRINT_ENABLE
+/* save pm message for debug when enter deep sleep*/
+unsigned int debug_status[10];
+void pm_debug_save_ahb_glb_regs(void)
+{
+ debug_status[0] = sci_glb_read(REG_AHB_AHB_STATUS, -1UL);
+ debug_status[1] = sci_glb_read(REG_AHB_AHB_CTL0, -1UL);
+ debug_status[2] = sci_glb_read(REG_AHB_AHB_CTL1, -1UL);
+ debug_status[3] = sci_glb_read(REG_AHB_AHB_STATUS, -1UL);
+ debug_status[4] = sci_glb_read(REG_GLB_GEN0, -1UL);
+ debug_status[5] = sci_glb_read(REG_GLB_GEN1, -1UL);
+ debug_status[6] = sci_glb_read(REG_GLB_STC_DSP_ST, -1UL);
+ debug_status[7] = sci_glb_read(REG_GLB_BUSCLK, -1UL);
+ debug_status[8] = sci_glb_read(REG_GLB_CLKDLY, -1UL);
+}
+void pm_debug_dump_ahb_glb_regs(void)
+{
+ printk("***** ahb and globle registers before last deep sleep **********\n");
+
+ printk("*** AHB_CTL0: 0x%x ***\n", debug_status[1] );
+ printk("*** AHB_CTL1: 0x%x ***\n", debug_status[2] );
+ printk("*** GR_GEN0: 0x%x ***\n", debug_status[4] );
+ printk("*** GR_GEN1: 0x%x ***\n", debug_status[5] );
+ printk("*** GR_BUSCLK: 0x%x ***\n", debug_status[7] );
+
+ printk("*** AHB_STS: 0x%x ***\n", debug_status[3] );
+ printk("*** GR_STC_STATE: 0x%x ***\n", debug_status[6] );
+ printk("*** GR_CLK_DLY: 0x%x ***\n", debug_status[8] );
+}
+
+
+static void print_ahb(void)
+{
+ u32 val = sci_glb_read(REG_AHB_AHB_CTL0, -1UL);
+ printk("##: REG_AHB_AHB_CTL0 = %08x.\n", val);
+ if (val & AHB_CTL0_DCAM_EN) printk("AHB_CTL0_DCAM_EN =1.\n");
+ if (val & AHB_CTL0_CCIR_IN_EN) printk("AHB_CTL0_CCIR_IN_EN =1. CCIR enable\n");
+ if (val & AHB_CTL0_LCDC_EN) printk("AHB_CTL0_LCDC_EN =1.\n");
+ if (val & AHB_CTL0_SDIO0_EN) printk("AHB_CTL0_SDIO0_EN =1.\n");
+ if (val & AHB_CTL0_USBD_EN) printk("AHB_CTL0_USBD_EN =1.\n");
+ if (val & AHB_CTL0_DMA_EN) printk("AHB_CTL0_DMA_EN =1.\n");
+ if (val & AHB_CTL0_BM0_EN) printk("AHB_CTL0_BM0_EN =1.\n");
+ if (val & AHB_CTL0_NFC_EN) printk("AHB_CTL0_NFC_EN =1.\n");
+ if (val & AHB_CTL0_CCIR_EN) printk("AHB_CTL0_CCIR_EN =1. CCIR clock enable\n");
+ if (val & AHB_CTL0_DCAM_MIPI_EN) printk("AHB_CTL0_DCAM_MIPI_EN =1.\n");
+ if (val & AHB_CTL0_BM1_EN) printk("AHB_CTL0_BM1_EN =1.\n");
+ if (val & AHB_CTL0_ISP_EN) printk("AHB_CTL0_ISP_EN =1.\n");
+ if (val & AHB_CTL0_VSP_EN) printk("AHB_CTL0_VSP_EN =1.\n");
+ if (val & AHB_CTL0_ROT_EN) printk("AHB_CTL0_ROT_EN =1.\n");
+ if (val & AHB_CTL0_BM2_EN) printk("AHB_CTL0_BM2_EN =1.\n");
+ if (val & AHB_CTL0_BM3_EN) printk("AHB_CTL0_BM3_EN =1.\n");
+ if (val & AHB_CTL0_BM4_EN) printk("AHB_CTL0_BM4_EN =1.\n");
+ if (val & AHB_CTL0_SDIO1_EN) printk("AHB_CTL0_SDIO1_EN =1.\n");
+ if (val & AHB_CTL0_G2D_EN) printk("AHB_CTL0_G2D_EN =1.\n");
+ if (val & AHB_CTL0_G3D_EN) printk("AHB_CTL0_G3D_EN =1.\n");
+ if (val & AHB_CTL0_DISPC_EN) printk("AHB_CTL0_DISPC_EN =1.\n");
+ if (val & AHB_CTL0_EMMC_EN) printk("AHB_CTL0_EMMC_EN =1.\n");
+ if (val & AHB_CTL0_SDIO2_EN) printk("AHB_CTL0_SDIO2_EN =1.\n");
+ if (val & AHB_CTL0_SPINLOCK_EN) printk("AHB_CTL0_SPINLOCK_EN =1.\n");
+ if (val & AHB_CTL0_AHB_ARCH_EB) printk("AHB_CTL0_AHB_ARCH_EB =1. AHB bus HCLK enable\n");
+ if (val & AHB_CTL0_EMC_EN) printk("AHB_CTL0_EMC_EN =1.\n");
+ if (val & AHB_CTL0_AXIBUSMON0_EN) printk("AHB_CTL0_AXIBUSMON0_EN =1.\n");
+ if (val & AHB_CTL0_AXIBUSMON1_EN) printk("AHB_CTL0_AXIBUSMON1_EN =1.\n");
+ if (val & AHB_CTL0_AXIBUSMON2_EN) printk("AHB_CTL0_AXIBUSMON2_EN =1.\n");
+
+ val = sci_glb_read(REG_AHB_AHB_CTL1, -1UL);
+ printk("##: REG_AHB_AHB_CTL1 = %08x.\n", val);
+
+ val = sci_glb_read(REG_AHB_AHB_CTL2, -1UL);
+ printk("##: REG_AHB_AHB_CTL2 = %08x.\n", val);
+ if (val & AHB_CTL2_DISPMTX_CLK_EN ) printk("AHB_CTL2_DISPMTX_CLK_EN =1.\n");
+ if (val & AHB_CTL2_MMMTX_CLK_EN ) printk("AHB_CTL2_MMMTX_CLK_EN =1.\n");
+ if (val & AHB_CTL2_DISPC_CORE_CLK_EN) printk("AHB_CTL2_DISPC_CORE_CLK_EN=1.\n");
+ if (val & AHB_CTL2_LCDC_CORE_CLK_EN) printk("AHB_CTL2_LCDC_CORE_CLK_EN=1.\n");
+ if (val & AHB_CTL2_ISP_CORE_CLK_EN) printk("AHB_CTL2_ISP_CORE_CLK_EN=1.\n");
+ if (val & AHB_CTL2_VSP_CORE_CLK_EN) printk("AHB_CTL2_VSP_CORE_CLK_EN=1.\n");
+ if (val & AHB_CTL2_DCAM_CORE_CLK_EN) printk("AHB_CTL2_DCAM_CORE_CLK_EN=1.\n");
+
+ val = sci_glb_read(REG_AHB_AHB_CTL3, -1UL);
+ printk("##: REG_AHB_AHB_CTL3 = %08x.\n", val);
+
+ val = sci_glb_read(REG_AHB_MIPI_PHY_CTRL, -1UL);
+ printk("##: REG_AHB_MIPI_PHY_CTRL= %08x.\n", val);
+
+ val = sci_glb_read(REG_AHB_AHB_STATUS, -1UL);
+ printk("##:REG_AHB_AHB_STATUS = %08x.\n", val);
+}
+static void print_gr(void)
+{
+ u32 val = sci_glb_read(REG_GLB_GEN0, -1UL);
+ printk("##: GR_GEN0 = %08x.\n", val);
+ if (val & GEN0_UART3_EN) printk("GEN0_UART3_EN =1.\n");
+ if (val & GEN0_SPI2_EN) printk("GEN0_SPI2_EN =1.\n");
+ if (val & GEN0_TIMER_EN) printk("GEN0_TIMER_EN =1.\n");
+ if (val & GEN0_SIM0_EN) printk("GEN0_SIM0_EN =1.\n");
+ if (val & GEN0_I2C_EN) printk("GEN0_I2C_EN =1.\n");
+ if (val & GEN0_GPIO_EN) printk("GEN0_GPIO_EN =1.\n");
+ if (val & GEN0_ADI_EN) printk("GEN0_ADI_EN =1.\n");
+ if (val & GEN0_EFUSE_EN) printk("GEN0_EFUSE_EN =1.\n");
+ if (val & GEN0_KPD_EN) printk("GEN0_KPD_EN =1.\n");
+ if (val & GEN0_EIC_EN) printk("GEN0_EIC_EN =1.\n");
+ if (val & GEN0_I2S0_EN) printk("GEN0_I2S0_EN =1.\n");
+ if (val & GEN0_PIN_EN) printk("GEN0_PIN_EN =1.\n");
+ if (val & GEN0_CCIR_MCLK_EN) printk("GEN0_CCIR_MCLK_EN =1.\n");
+ if (val & GEN0_EPT_EN) printk("GEN0_EPT_EN =1.\n");
+ if (val & GEN0_SIM1_EN) printk("GEN0_SIM1_EN =1.\n");
+ if (val & GEN0_SPI0_EN) printk("GEN0_SPI0_EN =1.\n");
+ if (val & GEN0_SPI1_EN) printk("GEN0_SPI1_EN =1.\n");
+ if (val & GEN0_SYST_EN) printk("GEN0_SYST_EN =1.\n");
+ if (val & GEN0_UART0_EN) printk("GEN0_UART0_EN =1.\n");
+ if (val & GEN0_UART1_EN) printk("GEN0_UART1_EN =1.\n");
+ if (val & GEN0_UART2_EN) printk("GEN0_UART2_EN =1.\n");
+ if (val & GEN0_VB_EN) printk("GEN0_VB_EN =1.\n");
+ if (val & GEN0_EIC_RTC_EN) printk("GEN0_EIC_RTC_EN =1.\n");
+ if (val & GEN0_I2S1_EN) printk("GEN0_I2S1_EN =1.\n");
+ if (val & GEN0_KPD_RTC_EN) printk("GEN0_KPD_RTC_EN =1.\n");
+ if (val & GEN0_SYST_RTC_EN) printk("GEN0_SYST_RTC_EN =1.\n");
+ if (val & GEN0_TMR_RTC_EN) printk("GEN0_TMR_RTC_EN =1.\n");
+ if (val & GEN0_I2C1_EN) printk("GEN0_I2C1_EN =1.\n");
+ if (val & GEN0_I2C2_EN) printk("GEN0_I2C2_EN =1.\n");
+ if (val & GEN0_I2C3_EN) printk("GEN0_I2C3_EN =1.\n");
+
+ val = sci_glb_read(REG_GLB_GEN1, -1UL);
+ printk("##: REG_GLB_GEN1 = %08x.\n", val);
+ if (val & BIT_CLK_AUX0_EN ) printk(" CLK_AUX0_EN =1.\n");
+ if (val & BIT_CLK_AUX1_EN ) printk(" CLK_AUX1_EN =1.\n");
+ if (val & BIT_AUD_IF_EB ) printk(" AUD_IF_EB =1.\n");
+ if (val & BIT_AUD_TOP_EB ) printk(" AUD_TOP_EB =1.\n");
+ if (val & BIT_VBC_EN ) printk(" VBC_EN =1.\n");
+ if (val & BIT_AUDIF_AUTO_EN ) printk(" AUDIF_AUTO_EN =1.\n");
+
+ val = sci_glb_read(REG_GLB_CLK_EN, -1UL);
+ printk("##: GR_CLK_EN = %08x.\n", val);
+ if (val & CLK_PWM0_EN) printk("CLK_PWM0_EN =1.\n");
+ if (val & CLK_PWM1_EN) printk("CLK_PWM1_EN = 1.\n");
+ if (val & CLK_PWM2_EN) printk("CLK_PWM2_EN = 1.\n");
+ if (val & CLK_PWM3_EN) printk("CLK_PWM3_EN = 1.\n");
+
+ val = sci_glb_read(REG_GLB_BUSCLK, -1UL);
+ printk("##: GR_BUSCLK_ALM = %08x.\n", val);
+ if (val & ARM_VB_MCLKON) printk("ARM_VB_MCLKON =1.\n");
+ if (val & ARM_VB_DA0ON) printk("ARM_VB_DA0ON = 1.\n");
+ if (val & ARM_VB_DA1ON) printk("ARM_VB_DA1ON = 1.\n");
+ if (val & ARM_VB_ADCON) printk("ARM_VB_ADCON = 1.\n");
+ if (val & ARM_VB_ANAON) printk("ARM_VB_ANAON = 1.\n");
+ if (val & ARM_VB_ACC) printk("ARM_VB_ACC = 1.\n");
+
+ val = sci_glb_read(REG_GLB_CLK_GEN5, -1UL);
+ printk("##: GR_CLK_GEN5 = %08x.\n", val);
+ if (val & BIT(9)) printk("LDO_USB_PD =1.\n");
+
+ val = sci_glb_raw_read(REG_GLB_PCTRL);
+ printk("##: GLB_PCTL = %08x.\n", val);
+ if (val & BIT_MCU_MPLL_EN) printk("MCU_MPLL = 1.\n");
+ if (val & BIT_MCU_GPLL_EN) printk("MCU_GPLL = 1.\n");
+ if (val & BIT_MCU_DPLL_EN) printk("MCU_DPLL = 1.\n");
+ if (val & BIT_MCU_TDPLL_EN) printk("MCU_TDPLL = 1.\n");
+
+ val = sci_glb_raw_read(REG_GLB_TD_PLL_CTL);
+ printk("##: GLB_TD_PLL_CTL = %08x.\n", val);
+ if (!(val & BIT_TDPLL_DIV2OUT_FORCE_PD)) printk("clk_384m = 1.\n");
+ if (!(val & BIT_TDPLL_DIV3OUT_FORCE_PD)) printk("clk_256m = 1.\n");
+ if (!(val & BIT_TDPLL_DIV4OUT_FORCE_PD)) printk("clk_192m = 1.\n");
+ if (!(val & BIT_TDPLL_DIV5OUT_FORCE_PD)) printk("clk_153p6m = 1.\n");
+
+ val = sci_glb_read(REG_GLB_POWCTL1, -1UL);
+ printk("##: GR_POWCTL1 = %08x.\n", val);
+
+ val = sci_glb_read(REG_GLB_M_PLL_CTL0, -1UL);
+ printk("##: REG_GLB_M_PLL_CTL0 = %08x.\n", val);
+
+ val = sci_glb_read(REG_GLB_MM_PWR_CTL, -1UL);
+ printk("##: REG_GLB_MM_PWR_CTL = %08x.\n", val);
+ val = sci_glb_read(REG_GLB_CEVA_L1RAM_PWR_CTL, -1UL);
+ printk("##: REG_GLB_CEVA_L1RAM_PWR_CTL = %08x.\n", val);
+ val = sci_glb_read(REG_GLB_GSM_PWR_CTL, -1UL);
+ printk("##: REG_GLB_GSM_PWR_CTL = %08x.\n", val);
+ val = sci_glb_read(REG_GLB_TD_PWR_CTL, -1UL);
+ printk("##: GR_TD_PWR_CTRL = %08x.\n", val);
+ val = sci_glb_read(REG_GLB_PERI_PWR_CTL, -1UL);
+ printk("##: GR_PERI_PWR_CTRL = %08x.\n", val);
+ val = sci_glb_read(REG_GLB_ARM_SYS_PWR_CTL, -1UL);
+ printk("##: GR_ARM_SYS_PWR_CTRL = %08x.\n", val);
+ val = sci_glb_read(REG_GLB_G3D_PWR_CTL, -1UL);
+ printk("##: GR_G3D_PWR_CTRL = %08x.\n", val);
+}
+#define LDO_USB_CTL 0x2
+#define LDO_SIM0_CTL 0x20
+#define LDO_SIM1_CTL 0x80
+#define LDO_BPCAMCORE_CTL 0x200
+#define LDO_BPCAMIO_CTL 0x800
+#define LDO_BPCAMA_CTL 0x2000
+#define LDO_BPCAMMOT_CTL 0x8000
+#define LDO_SDIO0_CTL 0x2
+#define LDO_SDIO1_CTL 0x8
+#define LDO_SDIO3_CTL 0x20
+#define LDO_VDD3V_CTL 0x80
+#define LDO_CMMB1P8_CTL 0x200
+#define LDO_CMMB1V2_CTL 0x800
+#define AUDIO_PA_ENABLE 0x1
+#define AUDIO_PA_ENABLE_RST 0x2
+#define AUDIO_PA_LDO_ENABLE 0x100
+#define AUDIO_PA_LDO_ENABLE_RST 0x200
+#define VIBR_PD_RST 0x8
+#define VIBR_PD 0x4
+#define LDO_REG_BASE (SPRD_MISC_BASE + 0x600)
+#define ANA_LDO_PD_CTL0 (LDO_REG_BASE + 0x10)
+#define ANA_LDO_PD_CTL1 (LDO_REG_BASE + 0x14)
+#define ANA_LED_CTL (LDO_REG_BASE + 0x70)
+#define ANA_VIBRATOR_CTRL0 (LDO_REG_BASE + 0x74)
+#define ANA_AUD_CLK_RST (LDO_REG_BASE + 0x7C)
+
+static int check_ana(void)
+{
+ static u16 tag = 0x555;
+ int ret = 0;
+ u32 val, reg = SCI_ADDR(SPRD_MISC_BASE, 0x0144);
+
+ /* check adi fifo r/w address */
+ val = __raw_readl(SCI_ADDR(SPRD_ADI_BASE, 0x002c));
+ ret |= (val >> 4 & 3) == (val >> 8 & 3);
+ ret <<= 1;
+
+ /* check ana reg w/r consistency */
+ sci_adi_raw_write(reg, tag);
+ ret |= sci_adi_read(reg) == tag;
+ tag = ~tag & 0xfff;
+
+ return ret;
+}
+
+static void print_ana(void)
+{
+ u32 val;
+ val = sci_adi_read(ANA_REG_GLB_ANA_APB_CLK_EN);
+ printk("##%d: ANA_REG_GLB_ANA_APB_CLK_EN = %08x.\n", check_ana(), val);
+
+ val = sci_adi_read(ANA_REG_GLB_LDO_PD_CTRL0);
+ printk("##: ANA_REG_GLB_LDO_PD_CTRL0 = %04x.\n", val);
+ if ((val & BIT_LDO_BP_CAMMOT_RST)) printk("##:LDO_CAMMOT is on.\n");
+ else if((val & BIT_LDO_BPCAMMOT)) printk("##: LDO_CAMMOT is off.\n");
+ if ((val & BIT_LDO_BPCAMA_RST)) printk("## LDO_BPCAMA:is on.\n");
+ else if((val & BIT_LDO_BPCAMA)) printk("##: LDO_BPCAMA is off.\n");
+ if ((val & BIT_LDO_BPCAMIO_RST)) printk("## LDO_BPCAMIO :is on.\n");
+ else if((val & BIT_LDO_BPCAMIO)) printk("## LDO_BPCAMIO : is off.\n");
+ if ((val & BIT_LDO_BPCAMCORE_RST)) printk("## LDO_BPCAMCORE :is on.\n");
+ else if((val & BIT_LDO_BPCAMCORE)) printk("## LDO_BPCAMCORE : is off.\n");
+ if ((val & BIT_LDO_BPSIM1_RST)) printk("## LDO_BPSIM1 :is on.\n");
+ else if((val & BIT_LDO_BPSIM1)) printk("## LDO_BPSIM1 : is off.\n");
+ if ((val & BIT_LDO_BPSIM0_RST)) printk("## LDO_BPSIM0 :is on.\n");
+ else if((val & BIT_LDO_BPSIM0)) printk("## LDO_BPSIM0 : is off.\n");
+ if ((val & BIT_LDO_BPUSB_RST)) printk("## LDO_BPUSB :is on.\n");
+ else if((val & BIT_LDO_BPUSB)) printk("## LDO_BPUSB : is off.\n");
+
+ val = sci_adi_read(ANA_REG_GLB_LDO_PD_CTRL1);
+ printk("##:ANA_REG_GLB_LDO_PD_CTRL1 = %04x.\n", val);
+ if ((val & BIT_LDO_BPCMMB1V2_RST)) printk("## LDO_BPCMMB1V2: is on.\n");
+ else if((val & BIT_LDO_BPCMMB1V2)) printk("## LDO_BPCMMB1V2: is off.\n");
+ if ((val & BIT_LDO_BPCMMB1P8_RST)) printk("## LDO_BPCMMB1P8: is on.\n");
+ else if((val & BIT_LDO_BPCMMB1P8)) printk("## LDO_BPCMMB1P8: is off.\n");
+ if ((val & BIT_LDO_BPVDD3V_RST)) printk("## LDO_BPVDD3V: is on.\n");
+ else if((val & BIT_LDO_BPVDD3V)) printk("## LDO_BPVDD3V: is off.\n");
+ if ((val & BIT_LDO_BPSD3_RST)) printk("## LDO_BPSD3: is on.\n");
+ else if((val & BIT_LDO_BPSD3)) printk("## LDO_BPSD3: is off.\n");
+ if ((val & BIT_LDO_BPSD1_RST)) printk("## LDO_BPSD1: is on.\n");
+ else if((val & BIT_LDO_BPSD1)) printk("## LDO_BPSD1: is off.\n");
+ if ((val & BIT_LDO_BPSD0_RST)) printk("## LDO_BPSD0: is on.\n");
+ else if((val & BIT_LDO_BPSD0)) printk("## LDO_BPSD0: is off.\n");
+
+ val = sci_adi_read(ANA_LED_CTL);
+ printk("##: ANA_LED_CTL = 0x%x.\n", val);
+
+ val = sci_adi_read(ANA_VIBRATOR_CTRL0);
+ printk("##: ANA_VIBRATOR_CTRL0 = 0x%x.\n", val);
+ if (val & VIBR_PD_RST) printk("##: vibrator is power on.\n");
+ else if (val & VIBR_PD) printk("##: vibrator is power off.\n");
+
+ val = sci_adi_read(ANA_AUD_CLK_RST);
+ printk("##: ANA_AUD_CLK_RST = 0x%x.\n", val);
+
+ val = sci_adi_read(ANA_REG_GLB_DCDCARM_CTRL0);
+ printk("##: ANA_REG_GLB_DCDCARM_CTRL0 = 0x%x.\n", val);
+
+}
+static int is_dsp_sleep(void)
+{
+ u32 val;
+ val = sci_glb_read(REG_GLB_STC_DSP_ST, -1UL);
+ if (GR_EMC_STOP & val)
+ printk("#####: REG_GLB_STC_DSP_ST[GR_EMC_STOP] is set!\n");
+ else
+ printk("#####: REG_GLB_STC_DSP_ST[GR_EMC_STOP] is NOT set!\n");
+ if (GR_MCU_STOP & val)
+ printk("#####: REG_GLB_STC_DSP_ST[GR_MCU_STOP] is set!\n");
+ else
+ printk("#####: REG_GLB_STC_DSP_ST[GR_MCU_STOP] is NOT set!\n");
+ if (GR_DSP_STOP & val)
+ printk("#####: REG_GLB_STC_DSP_ST[DSP_STOP] is set!\n");
+ else
+ printk("#####: REG_GLB_STC_DSP_ST[DSP_STOP] is NOT set!\n");
+ return 0;
+}
+extern void sci_clock_dump_active(void);
+static int print_thread(void * data)
+{
+ while(1){
+ wake_lock(&messages_wakelock);
+ print_ahb();
+ print_gr();
+ print_ana();
+ is_dsp_sleep();
+ has_wake_lock(WAKE_LOCK_SUSPEND);
+ msleep(100);
+ wake_unlock(&messages_wakelock);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(30 * HZ);
+ }
+ return 0;
+}
+void pm_debug_set_wakeup_timer()
+{
+ u32 val = get_sys_cnt();
+ val = val + 2000;
+ __raw_writel(val, SYSCNT_REG(0) );
+ __raw_writel(1, SYSCNT_REG(0X8) );
+}
+static void debugfs_init(void)
+{
+ dentry_debug_root = debugfs_create_dir("power", NULL);
+ if (IS_ERR(dentry_debug_root) || !dentry_debug_root) {
+ printk("!!!powermanager Failed to create debugfs directory\n");
+ dentry_debug_root = NULL;
+ return;
+ }
+ debugfs_create_u32("print_sleep_mode", 0644, dentry_debug_root,
+ &is_print_sleep_mode);
+ debugfs_create_u32("print_linux_clock", 0644, dentry_debug_root,
+ &is_print_linux_clock);
+ debugfs_create_u32("print_modem_clock", 0644, dentry_debug_root,
+ &is_print_modem_clock);
+ debugfs_create_u32("print_irq", 0644, dentry_debug_root,
+ &is_print_irq);
+ debugfs_create_u32("print_wakeup", 0644, dentry_debug_root,
+ &is_print_wakeup);
+ debugfs_create_u32("print_irq_runtime", 0644, dentry_debug_root,
+ &is_print_irq_runtime);
+ debugfs_create_u32("print_time", 0644, dentry_debug_root,
+ &is_print_time);
+}
+void pm_debug_init(void)
+{
+ struct task_struct * task;
+#ifdef PM_PRINT_ENABLE
+ wake_lock_init(&messages_wakelock, WAKE_LOCK_SUSPEND,
+ "pm_message_wakelock");
+ task = kthread_create(print_thread, NULL, "pm_print");
+ if (task == 0) {
+ printk("Can't crate power manager print thread!\n");
+ }else
+ wake_up_process(task);
+#endif
+ debugfs_init();
+}
+void pm_debug_clr(void)
+{
+ if(dentry_debug_root != NULL)
+ debugfs_remove_recursive(dentry_debug_root);
+}
+#endif
+#else
+void pm_debug_init(void)
+{
+}
+void pm_debug_clr(void)
+{
+}
+void clr_sleep_mode(void)
+{
+}
+void time_statisic_begin(void){}
+void irq_wakeup_set(void){}
+void time_statisic_end(void){}
+void pm_debug_save_ahb_glb_regs(void){}
+void time_add(unsigned int time, int ret){}
+void print_hard_irq_inloop(int ret){}
+void print_statisic(void){}
+void set_sleep_mode(int sm){}
+#endif
+#if 0
+void inc_irq(int irq)
+{
+
+}
+
+#include <linux/regulator/consumer.h>
+int __init sc_ldo_slp_init(void)
+{
+ int i;
+
+ static struct {
+ const char *vdd_name;
+ /*
+ * 0: slp pd disable, very light loads when in STANDBY mode
+ * 1: slp pd enable, this is chip default config for most of LDOs
+ */
+ bool pd_en;
+ } ldo_slp_config[] __initdata = {
+ {"vddsim0", 0},
+ {"vddsim1", 0},
+ {"avddvb", 0},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(ldo_slp_config); i++) {
+ if (0 == ldo_slp_config[i].pd_en) {
+ struct regulator *ldo =
+ regulator_get(NULL, ldo_slp_config[i].vdd_name);
+ if (!WARN_ON(IS_ERR(ldo))) {
+ regulator_set_mode(ldo, REGULATOR_MODE_STANDBY);
+ regulator_put(ldo);
+ }
+ pr_info("%s slp pd disable\n", ldo_slp_config[i].vdd_name);
+ }
+ }
+ return 0;
+}
+
+late_initcall(sc_ldo_slp_init);
+#endif
diff --git a/drivers/platform/sprd/pm_debug_scx35.c b/drivers/platform/sprd/pm_debug_scx35.c
new file mode 100644
index 00000000..280b34bc
--- /dev/null
+++ b/drivers/platform/sprd/pm_debug_scx35.c
@@ -0,0 +1,982 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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/irqflags.h>
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+#include <linux/kthread.h>
+#include <linux/debugfs.h>
+#include <soc/sprd/pm_debug.h>
+#if defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8)
+#include <soc/sprd/sprd_persistent_clock.h>
+#endif
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/adi.h>
+#include <linux/interrupt.h>
+#include <soc/sprd/irqs.h>
+#include <soc/sprd/gpio.h>
+#include <soc/sprd/iomap.h>
+
+struct dentry * dentry_debug_root = NULL;
+static int is_print_sleep_mode = 0;
+int is_print_linux_clock = 1;
+int is_print_modem_clock = 1;
+static int is_print_irq = 1;
+static int is_print_wakeup = 1;
+static int is_print_irq_runtime = 0;
+static int is_print_time = 1;
+static int print_thread_enable = 1;
+static int print_thread_interval = 30;
+static unsigned int core_time = 0;
+static unsigned int mcu_time = 0;
+static unsigned int lit_time = 0;
+static unsigned int deep_time_successed = 0;
+static unsigned int deep_time_failed = 0;
+static unsigned int sleep_time = 0;
+#define SPRD_INTC_NUM 4
+#define SPRD_HARD_INTERRUPT_NUM 128
+#define SPRD_HARD_INT_NUM_EACH_INTC 32
+#define SPRD_IRQ_NUM 1024
+static u32 sprd_hard_irq[SPRD_HARD_INTERRUPT_NUM]= {0, };
+//static u32 sprd_irqs[SPRD_IRQ_NUM] = {0, };
+static u32 sprd_irqs_sts[SPRD_INTC_NUM] = {0, };
+static int is_wakeup = 0;
+static int irq_status = 0;
+//static int hard_irq_status[SPRD_INTC_NUM] = {0, };
+static int sleep_mode = SLP_MODE_NON;
+static char * sleep_mode_str[] = {
+ "[ARM]",
+ "[MCU]",
+ "[DEP]",
+ "[LIT]",
+ "[NON]"
+};
+#define INT_REG(off) (SPRD_INT_BASE + (off))
+#define INTC0_REG(off) (SPRD_INTC0_BASE + (off))
+#define INTC1_REG(off) (SPRD_INTC1_BASE + (off))
+#define INTC2_REG(off) (SPRD_INTC2_BASE + (off))
+#define INTC3_REG(off) (SPRD_INTC3_BASE + (off))
+#define INT_IRQ_STS INT_REG(0x0000)
+#define INT_IRQ_RAW INT_REG(0x0004)
+#define INT_IRQ_ENB INT_REG(0x0008)
+#define INT_IRQ_DIS INT_REG(0x000c)
+#define INT_FIQ_STS INT_REG(0x0020)
+#define INTCV0_IRQ_MSKSTS INTC0_REG(0x0000)
+#define INTCV0_IRQ_RAW INTC0_REG(0x0004)
+#define INTCV0_IRQ_EN INTC0_REG(0x0008)
+#define INTCV0_IRQ_DIS INTC0_REG(0x000C)
+#define INTCV0_FIQ_STS INTC0_REG(0x0020)
+#define INTCV1_IRQ_MSKSTS INTC1_REG(0x0000)
+#define INTCV1_IRQ_RAW INTC1_REG(0x0004)
+#define INTCV1_IRQ_EN INTC1_REG(0x0008)
+#define INTCV1_IRQ_DIS INTC1_REG(0x000C)
+#define INTCV1_FIQ_STS INTC1_REG(0x0020)
+#define INTCV2_IRQ_MSKSTS INTC2_REG(0x0000)
+#define INTCV2_IRQ_RAW INTC2_REG(0x0004)
+#define INTCV2_IRQ_EN INTC2_REG(0x0008)
+#define INTCV2_IRQ_DIS INTC2_REG(0x000C)
+#define INTCV2_FIQ_STS INTC2_REG(0x0020)
+#define INTCV3_IRQ_MSKSTS INTC3_REG(0x0000)
+#define INTCV3_IRQ_RAW INTC3_REG(0x0004)
+#define INTCV3_IRQ_EN INTC3_REG(0x0008)
+#define INTCV3_IRQ_DIS INTC3_REG(0x000C)
+#define INTCV3_FIQ_STS INTC3_REG(0x0020)
+#define INT_IRQ_MASK (1<<3)
+
+#define ANA_REG_INT_MASK_STATUS (ANA_CTL_INT_BASE + 0x0000)
+#define ANA_REG_INT_RAW_STATUS (ANA_CTL_INT_BASE + 0x0004)
+#define ANA_REG_INT_EN (ANA_CTL_INT_BASE + 0x0008)
+#define ANA_REG_INT_MASK_STATUS_SYNC (ANA_CTL_INT_BASE + 0x000c)
+
+void pm_debug_dump_ahb_glb_regs(void);
+
+static void hard_irq_reset(void)
+{
+ int i = SPRD_HARD_INTERRUPT_NUM - 1;
+ do{
+ sprd_hard_irq[i] = 0;
+ }while(--i >= 0);
+}
+
+static void parse_hard_irq(unsigned long val, unsigned long intc)
+{
+ int i;
+ if(intc == 0){
+ for (i = 0; i < SPRD_HARD_INT_NUM_EACH_INTC; i++) {
+ if (test_and_clear_bit(i, &val)) sprd_hard_irq[i]++;
+ }
+ }
+ if(intc == 1){
+ for (i = 0; i < SPRD_HARD_INT_NUM_EACH_INTC; i++) {
+ if (test_and_clear_bit(i, &val)) sprd_hard_irq[32+i]++;
+ }
+ }
+ if(intc == 2){
+ for (i = 0; i < SPRD_HARD_INT_NUM_EACH_INTC; i++) {
+ if (test_and_clear_bit(i, &val)) sprd_hard_irq[64+i]++;
+ }
+ }
+ if(intc == 3){
+ for (i = 0; i < SPRD_HARD_INT_NUM_EACH_INTC; i++) {
+ if (test_and_clear_bit(i, &val)) sprd_hard_irq[96+i]++;
+ }
+ }
+}
+
+void hard_irq_set(void)
+{
+ sprd_irqs_sts[0] = __raw_readl(INT_IRQ_STS);
+ sprd_irqs_sts[1] = __raw_readl(INT_FIQ_STS);
+ irq_status = __raw_readl(INTCV0_IRQ_RAW);
+ parse_hard_irq(irq_status, 0);
+ irq_status = __raw_readl(INTCV1_IRQ_RAW);
+ parse_hard_irq(irq_status, 1);
+ irq_status = __raw_readl(INTCV2_IRQ_RAW);
+ parse_hard_irq(irq_status, 2);
+ irq_status = __raw_readl(INTCV3_IRQ_RAW);
+ parse_hard_irq(irq_status, 3);
+}
+void print_int_status(void)
+{
+ printk("APB_EB 0x%08x\n", __raw_readl(REG_AP_APB_APB_EB));
+ printk("INTC0 mask:0x%08x raw:0x%08x en:0x%08x\n", __raw_readl(INTCV0_IRQ_MSKSTS),__raw_readl(INTCV0_IRQ_RAW), __raw_readl(INTCV0_IRQ_EN));
+ printk("INTC1 mask:0x%08x raw:0x%08x en:0x%08x\n", __raw_readl(INTCV1_IRQ_MSKSTS),__raw_readl(INTCV1_IRQ_RAW), __raw_readl(INTCV1_IRQ_EN));
+ printk("INTC2 mask:0x%08x raw:0x%08x en:0x%08x\n", __raw_readl(INTCV2_IRQ_MSKSTS),__raw_readl(INTCV2_IRQ_RAW), __raw_readl(INTCV2_IRQ_EN));
+ printk("INTC3 mask:0x%08x raw:0x%08x en:0x%08x\n", __raw_readl(INTCV3_IRQ_MSKSTS),__raw_readl(INTCV3_IRQ_RAW), __raw_readl(INTCV3_IRQ_EN));
+ printk("INT mask:0x%08x raw:0x%08x en:0x%08x\n", __raw_readl(INT_IRQ_STS),__raw_readl(INT_IRQ_RAW), __raw_readl(INT_IRQ_ENB));
+ printk("ANA INT mask:0x%08x raw:0x%08x en:0x%08x\n", sci_adi_read(ANA_REG_INT_MASK_STATUS), sci_adi_read(ANA_REG_INT_RAW_STATUS), sci_adi_read(ANA_REG_INT_EN));
+ printk("ANA EIC MODULE_EN 0x%08x eic bit(3)\n", sci_adi_read(ANA_REG_GLB_ARM_MODULE_EN));
+ printk("ANA EIC int en 0x%08x\n", sci_adi_read(ANA_CTL_EIC_BASE + 0x18));
+ printk("ANA EIC int status 0x%08x, 0x%08x\n", sci_adi_read(ANA_CTL_EIC_BASE + 0x20),sci_adi_read(ANA_CTL_EIC_BASE + 0x14));
+}
+
+#define GPIO_GROUP_NUM 16
+#define IRQ_EIC (1<<14)
+#define IRQ_BUSMON (1<<13)
+#define IRQ_WDG (1<<11)
+#define IRQ_CP2 (1<<10)
+#define IRQ_CP1 (1<<9)
+#define IRQ_CP0 (1<<8)
+#define IRQ_CP_WDG (1<<7)
+#define IRQ_GPU (1<<6)
+#define IRQ_MM (1<<5)
+#define IRQ_4 (1<<4)
+#define IRQ_3 (1<<3)
+#define IRQ_2 (1<<2)
+
+#define REG_GPIO_MIS (0x0020)
+void print_hard_irq_inloop(int ret)
+{
+ unsigned int i, j, val;
+ unsigned int ana_sts;
+ unsigned int gpio_irq[GPIO_GROUP_NUM];
+
+ if(sprd_irqs_sts[0] != 0)
+ printk("%c#:INTC0: %08x\n", ret?'S':'F', sprd_irqs_sts[0]);
+ if(sprd_irqs_sts[1] != 0)
+ printk("%c#:INTC0 FIQ: %08x\n", ret?'S':'F', sprd_irqs_sts[1]);
+ if(sprd_irqs_sts[0]&IRQ_EIC){
+ printk("wake up by eic\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_BUSMON){
+ printk("wake up by busmoniter\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_WDG){
+ printk("wake up by ca7_wdg or ap_wdg\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_CP2){
+ printk("wake up by cp2\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_CP1){
+ printk("wake up by cp1\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_CP0){
+ printk("wake up by cp0\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_GPU){
+ printk("wake up by gpu\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_MM){
+ printk("wake up by mm\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_4){
+ if(sprd_hard_irq[34])
+ printk("wake up by i2c\n");
+ if(sprd_hard_irq[20])
+ printk("wake up by audio\n");
+ if(sprd_hard_irq[27])
+ printk("wake up by fm\n");
+ if(sprd_hard_irq[25]){
+ printk("wake up by adi\n");
+ }
+ if(sprd_hard_irq[38]){
+ printk("wake up by ana ");
+ ana_sts = sci_adi_read(ANA_REG_INT_RAW_STATUS);
+ if(ana_sts & BIT(0))
+ printk("adc\n");
+ if(ana_sts & BIT(1)){
+ printk("gpio\n");
+ printk("gpio 0 ~ 16 0x%08x\n", sci_adi_read(SPRD_MISC_BASE + 0x0480 + 0x1c));
+ printk("gpio 17 ~ 31 0x%08x\n", sci_adi_read(SPRD_MISC_BASE + 0x0480 + 0x40 + 0x1c));
+ }
+ if(ana_sts & BIT(2))
+ printk("rtc\n");
+ if(ana_sts & BIT(3))
+ printk("wdg\n");
+ if(ana_sts & BIT(4))
+ printk("fgu\n");
+ if(ana_sts & BIT(5)){
+ printk("eic\n");
+ printk("ana eic 0x%08x\n", sci_adi_read(ANA_EIC_BASE + 0x1c));
+ }
+ if(ana_sts & BIT(6))
+ printk("aud head button\n");
+ if(ana_sts & BIT(7))
+ printk("aud protect\n");
+ if(ana_sts & BIT(8))
+ printk("thermal\n");
+ if(ana_sts & BIT(10))
+ printk("dcdc otp\n");
+ printk("ANA INT 0x%08x\n", ana_sts);
+ }
+ if(sprd_hard_irq[36])
+ printk("wake up by kpd\n");
+ if(sprd_hard_irq[35]){
+ printk("wake up by gpio\n");
+ }
+ }
+ if(sprd_irqs_sts[0]&IRQ_3){
+ if(sprd_hard_irq[23])
+ printk("wake up by vbc_ad01\n");
+ if(sprd_hard_irq[24])
+ printk("wake up by vbc_ad23\n");
+ if(sprd_hard_irq[22])
+ printk("wake up by vbc_da\n");
+ if(sprd_hard_irq[21])
+ printk("wake up by afifi_error\n");
+ }
+ if(sprd_irqs_sts[0]&IRQ_2){
+ if(sprd_hard_irq[29]){
+ printk("wake up by ap_tmr0\n");
+ }
+ if(sprd_hard_irq[118]){
+ printk("wake up by ap_tmr1\n");
+ }
+ if(sprd_hard_irq[119]){
+ printk("wake up by ap_tmr2\n");
+ }
+ if(sprd_hard_irq[120]){
+ printk("wake up by ap_tmr3\n");
+ }
+ if(sprd_hard_irq[121]){
+ printk("wake up by ap_tmr4\n");
+ }
+ if(sprd_hard_irq[31]){
+ printk("wake up by ap_syst\n");
+ }
+ if(sprd_hard_irq[30]){
+ printk("wake up by aon_syst\n");
+ }
+ if(sprd_hard_irq[28]){
+ printk("wake up by aon_tmr\n");
+ }
+ }
+
+ if (sprd_hard_irq[35]) {
+ for(i=0; i<(GPIO_GROUP_NUM/2); i++){
+ j = 2*i;
+ gpio_irq[j]= __raw_readl(SPRD_GPIO_BASE + 0x100*i + REG_GPIO_MIS);
+ gpio_irq[j+1]= __raw_readl(SPRD_GPIO_BASE + 0x100*i + 0x80 + REG_GPIO_MIS);
+ // printk("gpio_irq[%d]:0x%x, gpio_irq[%d]:0x%x \n", j, gpio_irq[j], j+1, gpio_irq[j+1]);
+ }
+ for(i=0; i<GPIO_GROUP_NUM; i++){
+ if(gpio_irq[i] != 0){
+ val = gpio_irq[i];
+ while(val){
+ j = __ffs(val);
+ printk("irq from gpio%d\n", j + 16*i);
+ val &= ~(1<<j);
+ }
+ }
+ }
+ }
+}
+
+static void print_hard_irq(void)
+{
+ int i = SPRD_HARD_INTERRUPT_NUM -1;
+ if(!is_print_irq)
+ return;
+ do{
+ if(0 != sprd_hard_irq[i])
+ printk("##: sprd_hard_irq[%d] = %d.\n",
+ i, sprd_hard_irq[i]);
+ }while(--i >= 0);
+}
+
+#if 0
+static void irq_reset(void)
+{
+ int i = SPRD_IRQ_NUM - 1;
+ do{
+ sprd_irqs[i] = 0;
+ }while(--i >= 0);
+}
+
+
+void inc_irq(int irq)
+{
+ if(is_wakeup){
+ if (irq >= SPRD_IRQ_NUM) {
+ printk("## bad irq number %d.\n", irq);
+ return;
+ }
+ sprd_irqs[irq]++;
+ if(is_print_wakeup)
+ printk("\n#####: wakeup irq = %d.\n", irq);
+ is_wakeup = 0;
+ }
+}
+EXPORT_SYMBOL(inc_irq);
+
+
+static void print_irq(void)
+{
+ int i = SPRD_IRQ_NUM - 1;
+ if(!is_print_irq)
+ return;
+ do{
+ if(0 != sprd_irqs[i])
+ printk("##: sprd_irqs[%d] = %d.\n",
+ i, sprd_irqs[i]);
+ }while(--i >= 0);
+}
+#else
+static void print_irq(void){}
+#endif
+void irq_wakeup_set(void)
+{
+ is_wakeup = 1;
+}
+
+void time_add(unsigned int time, int ret)
+{
+ switch(sleep_mode){
+ case SLP_MODE_ARM:
+ core_time += time;
+ break;
+ case SLP_MODE_MCU:
+ mcu_time += time;
+ break;
+ case SLP_MODE_LIT:
+ lit_time += time;
+ break;
+ case SLP_MODE_DEP:
+ if(ret)
+ deep_time_successed += time;
+ else
+ deep_time_failed += time;
+ break;
+ default:
+ break;
+ }
+}
+
+void time_statisic_begin(void)
+{
+ core_time = 0;
+ mcu_time = 0;
+ lit_time = 0;
+ deep_time_successed = 0;
+ deep_time_failed = 0;
+ sleep_time = get_sys_cnt();
+ hard_irq_reset();
+}
+
+void time_statisic_end(void)
+{
+ sleep_time = get_sys_cnt() - sleep_time;
+}
+
+void print_time(void)
+{
+ if(!is_print_time)
+ return;
+ printk("time statisics : sleep_time=%d, core_time=%d, mcu_time=%d, lit_time=%d, deep_sus=%d, dep_fail=%d\n",
+ sleep_time, core_time, mcu_time, lit_time, deep_time_successed, deep_time_failed);
+}
+
+void set_sleep_mode(int sm){
+ int is_print = (sm == sleep_mode);
+ sleep_mode = sm;
+ if(is_print_sleep_mode == 0 || is_print )
+ return;
+ switch(sm){
+ case SLP_MODE_ARM:
+ printk("\n[ARM]\n");
+ break;
+ case SLP_MODE_MCU:
+ printk("\n[MCU]\n");
+ break;
+ case SLP_MODE_LIT:
+ printk("\n[LIT]\n");
+ break;
+ case SLP_MODE_DEP:
+ printk("\n[DEP]\n");
+ break;
+ default:
+ printk("\nNONE\n");
+ }
+}
+void clr_sleep_mode(void)
+{
+ sleep_mode = SLP_MODE_NON;
+}
+void print_statisic(void)
+{
+ print_time();
+ print_hard_irq();
+ print_irq();
+ pm_debug_dump_ahb_glb_regs();
+ if(is_print_wakeup){
+ printk("###wake up form %s : %08x\n", sleep_mode_str[sleep_mode], sprd_irqs_sts[0]);
+ printk("###wake up form %s : %08x\n", sleep_mode_str[sleep_mode], sprd_irqs_sts[1]);
+ }
+}
+#ifdef PM_PRINT_ENABLE
+static struct wake_lock messages_wakelock;
+#endif
+
+#define PM_PRINT_ENABLE
+#if defined(CONFIG_ARCH_SCX35L)
+void cp0_power_domain_debug(void)
+{
+ unsigned long cp0_arm9_0_cfg = __raw_readl(REG_PMU_APB_PD_CP0_ARM9_0_CFG);
+ unsigned long cp0_arm9_1_cfg = __raw_readl(REG_PMU_APB_PD_CP0_ARM9_1_CFG);
+ unsigned long cp0_hu3ge_cfg = __raw_readl(REG_PMU_APB_PD_CP0_HU3GE_CFG);
+ unsigned long cp0_gsm_0_cfg = __raw_readl(REG_PMU_APB_PD_CP0_GSM_0_CFG);
+ unsigned long cp0_gsm_1_cfg = __raw_readl(REG_PMU_APB_PD_CP0_GSM_1_CFG);
+ unsigned long cp0_td_cfg = __raw_readl(REG_PMU_APB_PD_CP0_TD_CFG);
+ unsigned long cp0_ceva_0_cfg = __raw_readl(REG_PMU_APB_PD_CP0_CEVA_0_CFG);
+ unsigned long cp0_ceva_1_cfg = __raw_readl(REG_PMU_APB_PD_CP0_CEVA_1_CFG);
+
+ printk("###---- REG_PMU_APB_PD_CP0_ARM9_0_CFG : 0x%08x\n", cp0_arm9_0_cfg);
+ printk("###---- REG_PMU_APB_PD_CP0_ARM9_1_CFG : 0x%08x\n", cp0_arm9_1_cfg);
+ printk("###---- REG_PMU_APB_PD_CP0_HU3GE_CFG : 0x%08x\n", cp0_hu3ge_cfg);
+ printk("###---- REG_PMU_APB_PD_CP0_GSM_0_CFG : 0x%08x\n", cp0_gsm_0_cfg);
+ printk("###---- REG_PMU_APB_PD_CP0_GSM_1_CFG : 0x%08x\n", cp0_gsm_1_cfg);
+ printk("###---- REG_PMU_APB_PD_CP0_TD_CFG : 0x%08x\n", cp0_td_cfg);
+ printk("###---- REG_PMU_APB_PD_CP0_CEVA_0_CFG : 0x%08x\n", cp0_ceva_0_cfg);
+ printk("###---- REG_PMU_APB_PD_CP0_CEVA_1_CFG : 0x%08x\n", cp0_ceva_1_cfg);
+}
+
+void cp1_power_domain_debug(void)
+{
+ unsigned long cp1_ca5_cfg = __raw_readl(REG_PMU_APB_PD_CP1_CA5_CFG);
+ unsigned long cp1_lte_p1_cfg = __raw_readl(REG_PMU_APB_PD_CP1_LTE_P1_CFG);
+ unsigned long cp1_lte_p2_cfg = __raw_readl(REG_PMU_APB_PD_CP1_LTE_P2_CFG);
+ unsigned long cp1_ceva_cfg = __raw_readl(REG_PMU_APB_PD_CP1_CEVA_CFG);
+ /*unsigned long cp1_comwrap_cfg = __raw_readl(REG_PMU_APB_PD_COMWRAP_CFG);*/
+
+ printk("###---- REG_PMU_APB_PD_CP1_CA5_CFG : 0x%08x\n", cp1_ca5_cfg);
+ printk("###---- REG_PMU_APB_PD_CP1_LTE_P1_CFG : 0x%08x\n", cp1_lte_p1_cfg);
+ printk("###---- REG_PMU_APB_PD_CP1_LTE_P2_CFG : 0x%08x\n", cp1_lte_p2_cfg);
+ printk("###---- REG_PMU_APB_PD_CP1_CEVA_CFG : 0x%08x\n", cp1_ceva_cfg);
+ /*printk("REG_PMU_APB_PD_COMWRAP_CFG ------ 0x%08x\n", cp1_comwrap_cfg);*/
+}
+#endif
+
+static void print_debug_info(void)
+{
+ unsigned int ahb_eb, apb_eb0, cp_slp_status0, cp_slp_status1, ldo_pd_ctrl,
+ ap_apb_eb, apb_pwrstatus0, apb_pwrstatus1, apb_pwrstatus2,
+ apb_pwrstatus3, mpll_cfg, dpll_cfg, emc_clk_cfg,
+ apb_slp_status, ap_sys_auto_sleep_cfg, ca5_lte_status;
+#if defined(CONFIG_ARCH_SCX15)
+ unsigned int ldo_dcdc_pd_ctrl;
+#endif
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L)
+ unsigned int mpll_cfg1, dpll_cfg1, aon_apb_eb1;
+#endif
+#if defined(CONFIG_ARCH_SCX35L)
+ unsigned int mpll_cfg2, dpll_cfg2;
+#endif
+ ahb_eb = sci_glb_read(REG_AP_AHB_AHB_EB, -1UL);
+ ap_sys_auto_sleep_cfg = sci_glb_read(REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG, -1UL);
+ ap_apb_eb = sci_glb_read(REG_AP_APB_APB_EB, -1UL);
+ apb_eb0 = sci_glb_read(REG_AON_APB_APB_EB0, -1UL);
+ cp_slp_status0 = sci_glb_read(REG_PMU_APB_CP_SLP_STATUS_DBG0, -1UL);
+#if !defined(CONFIG_ARCH_SCX35L)
+ cp_slp_status1 = sci_glb_read(REG_PMU_APB_CP_SLP_STATUS_DBG1, -1UL);
+#endif
+ ca5_lte_status = sci_glb_read(REG_PUB_APB_DDR_ID2QOS_RCFG9, -1UL);
+ apb_pwrstatus0 = sci_glb_read(REG_PMU_APB_PWR_STATUS0_DBG, -1UL);
+ apb_pwrstatus1 = sci_glb_read(REG_PMU_APB_PWR_STATUS1_DBG, -1UL);
+ apb_pwrstatus2 = sci_glb_read(REG_PMU_APB_PWR_STATUS2_DBG, -1UL);
+#if !defined(CONFIG_ARCH_SCX35L)
+ apb_pwrstatus3 = sci_glb_read(REG_PMU_APB_PWR_STATUS3_DBG, -1UL);
+#endif
+#if defined(CONFIG_ARCH_SCX35LT8)
+ apb_pwrstatus3 = sci_glb_read(REG_PMU_APB_PWR_STATUS3_DBG, -1UL);
+#endif
+ apb_slp_status = __raw_readl(REG_PMU_APB_SLEEP_STATUS);
+#if defined(CONFIG_ARCH_SCX15)
+ mpll_cfg = sci_glb_read(REG_AON_APB_MPLL_CFG, -1UL);
+ dpll_cfg = sci_glb_read(REG_AON_APB_DPLL_CFG, -1UL);
+#endif
+ emc_clk_cfg = sci_glb_read(REG_AON_CLK_EMC_CFG, -1UL);
+ ldo_pd_ctrl = sci_adi_read(ANA_REG_GLB_LDO_PD_CTRL);
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L)
+ mpll_cfg1 = sci_glb_read(REG_AON_APB_MPLL_CFG1, -1UL);
+ dpll_cfg1 = sci_glb_read(REG_AON_APB_DPLL_CFG1, -1UL);
+ aon_apb_eb1 = sci_glb_read(REG_AON_APB_APB_EB1, -1UL);
+#endif
+#if defined(CONFIG_ARCH_SCX35L)
+ mpll_cfg2 = sci_glb_read(REG_AON_APB_MPLL_CFG2, -1UL);
+ dpll_cfg2 = sci_glb_read(REG_AON_APB_DPLL_CFG2, -1UL);
+#endif
+#if defined(CONFIG_ARCH_SCX15)
+ ldo_dcdc_pd_ctrl = sci_adi_read(ANA_REG_GLB_LDO_DCDC_PD);
+#endif
+ unsigned long sleep_ctrl = __raw_readl(REG_PMU_APB_SLEEP_CTRL);
+ printk("###---- REG_PMU_APB_SLEEP_CTRL : 0x%08x\n", sleep_ctrl);
+ printk("###---- REG_AP_AHB_AHB_EB : 0x%08x\n", ahb_eb);
+ printk("###---- REG_AP_AHB_AP_SYS_AUTO_SLEEP_CFG : 0x%08x\n", ap_sys_auto_sleep_cfg);
+ printk("###---- REG_AP_APB_APB_EB : 0x%08x\n", ap_apb_eb);
+ printk("###---- REG_AON_APB_APB_EB0 : 0x%08x\n", apb_eb0);
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L)
+ printk("###---- REG_AON_APB_APB_EB1 : 0x%08x\n", aon_apb_eb1);
+#endif
+ printk("###---- REG_PMU_APB_CP_SLP_STATUS_DBG0 : 0x%08x\n", cp_slp_status0);
+#if !defined(CONFIG_ARCH_SCX35L)
+ printk("###---- REG_PMU_APB_CP_SLP_STATUS_DBG1 : 0x%08x\n", cp_slp_status1);
+#endif
+ printk("###---- REG_PMU_APB_PWR_STATUS0_DBG : 0x%08x\n", apb_pwrstatus0);
+ printk("###---- REG_PMU_APB_PWR_STATUS1_DBG : 0x%08x\n", apb_pwrstatus1);
+ printk("###---- REG_PMU_APB_PWR_STATUS2_DBG : 0x%08x\n", apb_pwrstatus2);
+ printk("###---- REG_PMU_APB_PWR_STATUS3_DBG : 0x%08x\n", apb_pwrstatus3);
+ printk("###---- REG_PMU_APB_SLEEP_STATUS : 0x%08x\n", apb_slp_status);
+ printk("###---- REG_PUB_APB_DDR_ID2QOS_RCFG9 : 0x%08x\n", ca5_lte_status);
+#if defined(CONFIG_ARCH_SCX15) || defined(CONFIG_ARCH_SCX35) || defined(CONFIG_ARCH_SCX30G)
+ printk("###---- REG_AON_APB_MPLL_CFG : 0x%08x\n", mpll_cfg);
+ printk("###---- REG_AON_APB_DPLL_CFG : 0x%08x\n", dpll_cfg);
+#endif
+ printk("###---- REG_AON_CLK_EMC_CFG : 0x%08x\n", emc_clk_cfg);
+ printk("###---- ANA_REG_GLB_LDO_PD_CTRL : 0x%08x\n", ldo_pd_ctrl);
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L)
+ printk("###---- REG_AON_APB_MPLL_CFG1 : 0x%08x\n", mpll_cfg1);
+ printk("###---- REG_AON_APB_DPLL_CFG1 : 0x%08x\n", dpll_cfg1);
+ printk("###---- REG_PMU_APB_DDR_SLEEP_CTRL : 0x%08x\n",
+ sci_glb_read(REG_PMU_APB_DDR_SLEEP_CTRL, -1UL) );
+#endif
+#if defined(CONFIG_ARCH_SCX35L)
+ printk("###---- REG_AON_APB_MPLL_CFG2 : 0x%08x\n", mpll_cfg2);
+ printk("###---- REG_AON_APB_DPLL_CFG2 : 0x%08x\n", dpll_cfg2);
+#endif
+#if defined(CONFIG_ARCH_SCX15)
+ printk("###---- ANA_REG_GLB_LDO_DCDC_PD_CTRL : 0x%08x\n", ldo_dcdc_pd_ctrl);
+#endif
+ if (apb_eb0 & BIT_GPU_EB)
+ printk("###---- BIT_GPU_EB still set ----###\n");
+ if (apb_eb0 & BIT_MM_EB)
+ printk("###---- BIT_MM_EB still set ----###\n");
+ if (apb_eb0 & BIT_CA7_DAP_EB)
+ printk("###---- BIT_CA7_DAP_EB still set ----###\n");
+
+ if (ahb_eb & BIT_GSP_EB)
+ printk("###---- BIT_GSP_EB still set ----###\n");
+#if defined(CONFIG_ARCH_SCX15) || defined(CONFIG_ARCH_SCX30G)
+ if (ahb_eb & BIT_DISPC1_EB)
+ printk("###---- BIT_DISPC1_EB still set ----###\n");
+#endif
+#if defined(CONFIG_ARCH_SCX35L)
+ if (ahb_eb & BIT_HSIC_EB)
+ printk("###---- BIT_HSIC_EB still set ----###\n");
+ if (ahb_eb & BIT_DISPC_EB)
+ printk("###---- BIT_DISPC_EB still set ----###\n");
+#else
+ if (ahb_eb & BIT_DISPC0_EB)
+ printk("###---- BIT_DISPC0_EB still set ----###\n");
+#endif
+ if (ahb_eb & BIT_SDIO0_EB)
+ printk("###---- SDIO0_EB still set ----###\n");
+ if (ahb_eb & BIT_SDIO1_EB)
+ printk("###---- SDIO1_EB still set ----###\n");
+ if (ahb_eb & BIT_SDIO2_EB)
+ printk("###---- BIT_SDIO2_EB still set ----###\n");
+#if defined(CONFIG_ARCH_SCX35L)
+ if (ahb_eb & BIT_OTG_EB)
+ printk("###---- BIT_OTG_EB still set ----###\n");
+#else
+ if (ahb_eb & BIT_USB_EB)
+ printk("###---- BIT_USB_EB still set ----###\n");
+#endif
+ if (ahb_eb & BIT_DMA_EB)
+ printk("###---- BIT_DMA_EB still set ----###\n");
+ if (ahb_eb & BIT_NFC_EB)
+ printk("###---- BIT_NFC_EB still set ----###\n");
+ if (ahb_eb & BIT_EMMC_EB)
+ printk("###---- BIT_EMMC_EB still set ----###\n");
+#if defined(CONFIG_ARCH_SCX15) || defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_64BIT)
+ if (ahb_eb & BIT_ZIPDEC_EB)
+ printk("###---- BIT_ZIPDEC_EB still set ----###\n");
+ if (ahb_eb & BIT_ZIPENC_EB)
+ printk("###---- BIT_ZIPENC_EB still set ----###\n");
+ if (ahb_eb & BIT_NANDC_ECC_EB)
+ printk("###---- BIT_NANDC_ECC_EB still set ----###\n");
+ if (ahb_eb & BIT_NANDC_2X_EB)
+ printk("###---- BIT_NANDC_2X_EB still set ----###\n");
+ if (ahb_eb & BIT_NANDC_EB)
+ printk("###---- BIT_NANDC_EB still set ----###\n");
+#endif
+#if defined(CONFIG_ARCH_SCX30G)
+ if (aon_apb_eb1 & BIT_GSP_EMC_EB )
+ printk("###---- BIT_GSP_EMC_EB set ----###\n");
+ if (aon_apb_eb1 & BIT_ZIP_EMC_EB )
+ printk("###---- BIT_ZIP_EMC_EB set ----###\n");
+ if (aon_apb_eb1 & BIT_DISP_EMC_EB )
+ printk("###---- BIT_DISP_EMC_EB set ----###\n");
+#endif
+
+ /*A-die*/
+ printk("---------- A-die power status -----------\n");
+ if (!(ldo_pd_ctrl & BIT_DCDC_WPA_PD))
+ printk("###---- BIT_DCDC_WPA_PD power on! ----###\n");
+#if defined(CONFIG_ADIE_SC2713S)
+ if (!(ldo_pd_ctrl & BIT_LDO_CLSG_PD))
+ printk("###---- BIT_LDO_CLSG_PD power on! ----###\n");
+#endif
+ if (!(ldo_pd_ctrl & BIT_LDO_USB_PD))
+ printk("###---- BIT_LDO_USB_PD power on! ----###\n");
+ if (!(ldo_pd_ctrl & BIT_LDO_CAMMOT_PD))
+ printk("###---- BIT_LDO_CAMMOT_PD power on! ----###\n");
+ if (!(ldo_pd_ctrl & BIT_LDO_CAMIO_PD))
+ printk("###---- BIT_LDO_CAMIO_PD power on! ----###\n");
+ if (!(ldo_pd_ctrl & BIT_LDO_CAMD_PD))
+ printk("###---- BIT_LDO_CAMD_PD power on! ----###\n");
+ if (!(ldo_pd_ctrl & BIT_LDO_CAMA_PD))
+ printk("###---- BIT_LDO_CAMA_PD power on! ----###\n");
+ if (!(ldo_pd_ctrl & BIT_LDO_SIM2_PD))
+ printk("###---- BIT_LDO_SIM2_PD power on! ----###\n");
+ if (!(ldo_pd_ctrl & BIT_LDO_SIM1_PD))
+ printk("###---- BIT_LDO_SIM1_PD power on! ----###\n");
+ if (!(ldo_pd_ctrl & BIT_LDO_SIM0_PD))
+ printk("###---- BIT_LDO_SIM0_PD power on! ----###\n");
+#if defined(CONFIG_ADIE_SC2713S)
+ if (!(ldo_pd_ctrl & BIT_LDO_SD_PD))
+ printk("###---- BIT_LDO_SD_PD power on! ----###\n");
+#endif
+#if defined(CONFIG_ADIE_SC2723S)
+ if (!(ldo_pd_ctrl & BIT_LDO_SDIO_PD))
+ printk("###---- BIT_LDO_SD_PD power on! ----###\n");
+#endif
+#if defined(CONFIG_ADIE_SC2723)
+ if (!(ldo_pd_ctrl & BIT_LDO_SDIO_PD))
+ printk("###---- BIT_LDO_SD_PD power on! ----###\n");
+#endif
+#if defined(CONFIG_ARCH_SCX15)
+ if (!(ldo_dcdc_pd_ctrl & BIT_BG_PD))
+ printk("###---- BIT_BG_PD power on! ----###\n");
+ if (!(ldo_dcdc_pd_ctrl & BIT_LDO_CON_PD))
+ printk("###---- BIT_LDO_CON_PD power on! ----###\n");
+ if (!(ldo_dcdc_pd_ctrl & BIT_LDO_DCXO_PD))
+ printk("###---- BIT_LDO_DCXO_PD power on! ----###\n");
+ if (!(ldo_dcdc_pd_ctrl & BIT_LDO_EMMCIO_PD))
+ printk("###---- BIT_LDO_EMMCIO_PD power on! ----###\n");
+ if (!(ldo_dcdc_pd_ctrl & BIT_LDO_EMMCCORE_PD))
+ printk("###---- BIT_LDO_EMMCCORE_PD power on! ----###\n");
+#endif
+
+#if defined(CONFIG_ARCH_SCX15)
+ if (ap_apb_eb & BIT_UART4_EB)
+ printk("###---- BIT_UART4_EB set! ----###\n");
+ if (ap_apb_eb & BIT_UART3_EB)
+ printk("###---- BIT_UART3_EB set! ----###\n");
+ if (ap_apb_eb & BIT_UART2_EB)
+ printk("###---- BIT_UART2_EB set! ----###\n");
+ if (ap_apb_eb & BIT_UART1_EB)
+ printk("###---- BIT_UART1_EB set! ----###\n");
+ if (ap_apb_eb & BIT_UART0_EB)
+ printk("###---- BIT_UART0_EB set! ----###\n");
+ if (ap_apb_eb & BIT_I2C4_EB)
+ printk("###---- BIT_I2C4_EB set! ----###\n");
+ if (ap_apb_eb & BIT_I2C3_EB)
+ printk("###---- BIT_I2C3_EB set! ----###\n");
+ if (ap_apb_eb & BIT_I2C2_EB)
+ printk("###---- BIT_I2C2_EB set! ----###\n");
+ if (ap_apb_eb & BIT_I2C1_EB)
+ printk("###---- BIT_I2C1_EB set! ----###\n");
+ if (ap_apb_eb & BIT_I2C0_EB)
+ printk("###---- BIT_I2C0_EB set! ----###\n");
+ if (ap_apb_eb & BIT_IIS3_EB)
+ printk("###---- BIT_IIS3_EB set! ----###\n");
+ if (ap_apb_eb & BIT_IIS2_EB)
+ printk("###---- BIT_IIS2_EB set! ----###\n");
+ if (ap_apb_eb & BIT_IIS1_EB)
+ printk("###---- BIT_IIS1_EB set! ----###\n");
+ if (ap_apb_eb & BIT_IIS0_EB)
+ printk("###---- BIT_IIS0_EB set! ----###\n");
+#endif
+
+#if defined(CONFIG_ARCH_SCX35LT8)
+ if (sleep_ctrl & BIT_VCP1_FORCE_LIGHT_SLEEP)
+ printk("###---- force VCP1 in light sleep ----###\n");
+ if (sleep_ctrl & BIT_VCP0_FORCE_LIGHT_SLEEP)
+ printk("###---- force VCP0 in light sleep ----###\n");
+ if (sleep_ctrl & BIT_CP1_FORCE_LIGHT_SLEEP)
+ printk("###---- force CP1 in light sleep ----###\n");
+ if (sleep_ctrl & BIT_CP0_FORCE_LIGHT_SLEEP)
+ printk("###---- force CP0 in light sleep ----###\n");
+ if (sleep_ctrl & BIT_AP_FORCE_LIGHT_SLEEP)
+ printk("###---- force AP in light sleep ----###\n");
+ if (sleep_ctrl & BIT_VCP1_LIGHT_SLEEP)
+ printk("###---- VCP1 is in light sleep status ----###\n");
+ if (sleep_ctrl & BIT_VCP0_LIGHT_SLEEP)
+ printk("###--- VCP0 is in light sleep status ----###\n");
+ if (sleep_ctrl & BIT_CP1_LIGHT_SLEEP)
+ printk("###---- CP1 is in light sleep status ----###\n");
+ if (sleep_ctrl & BIT_CP0_LIGHT_SLEEP)
+ printk("###---- CP0 is in light sleep status ----###\n");
+ if (sleep_ctrl & BIT_AP_LIGHT_SLEEP)
+ printk("###---- AP is in light sleep status ----###\n");
+
+ if (apb_pwrstatus0){
+ printk("status of power domain 'MM_TOP' 0x%08x\n", (apb_pwrstatus0 & BITS_PD_MM_TOP_STATE(15)) >> 28);
+ printk("status of power domain 'GPU_TOP' 0x%08x\n", (apb_pwrstatus0 & BITS_PD_GPU_TOP_STATE(15)) >> 24);
+ printk("status of power domain 'AP_SYS' 0x%08x\n", (apb_pwrstatus0 & BITS_PD_AP_SYS_STATE(15)) >> 20);
+ printk("status of power domain 'CA53_LIT_C3' 0x%08x\n", (apb_pwrstatus0 & BITS_PD_CA53_LIT_C3_STATE(15)) >> 16);
+ printk("status of power domain 'CA53_LIT_C2' 0x%08x\n", (apb_pwrstatus0 & BITS_PD_CA53_LIT_C2_STATE(15)) >> 12);
+ printk("status of power domain 'CA53_LIT_C1' 0x%08x\n", (apb_pwrstatus0 & BITS_PD_CA53_LIT_C1_STATE(15)) >> 8);
+ printk("status of power domain 'CA53_LIT_C0' 0x%08x\n", (apb_pwrstatus0 & BITS_PD_CA53_LIT_C0_STATE(15)) >> 4);
+ printk("status of power domain 'CA53_TOP' 0x%08x\n", apb_pwrstatus0 & BITS_PD_CA53_TOP_STATE(15));
+ }
+ if (apb_pwrstatus1){
+ printk("status of power domain 'CP0_CEVA_1' 0x%08x\n", (apb_pwrstatus1 & BITS_PD_CP0_CEVA_1_STATE(15)) >> 28);
+ printk("status of power domain 'CP_CEVA_0' 0x%08x\n", (apb_pwrstatus1 & BITS_PD_CP0_CEVA_0_STATE(15)) >> 24);
+ printk("status of power domain 'CP0_GSM_0' 0x%08x\n", (apb_pwrstatus1 & BITS_PD_CP0_GSM_0_STATE(15)) >> 20);
+ printk("status of power domain 'CP0_GSM_1' 0x%08x\n", (apb_pwrstatus1 & BITS_PD_CP0_GSM_1_STATE(15)) >> 16);
+ printk("status of power domain 'CP0_HU3GE' 0x%08x\n", (apb_pwrstatus1 & BITS_PD_CP0_HU3GE_STATE(15)) >> 12);
+ printk("status of power domain 'CP0_ARM9_1' 0x%08x\n", (apb_pwrstatus1 & BITS_PD_CP0_ARM9_1_STATE(15)) >> 8);
+ printk("status of power domain 'CP0_ARM9_0' 0x%08x\n", (apb_pwrstatus1 & BITS_PD_CP0_ARM9_0_STATE(15)) >> 4);
+ printk("status of power domain 'CP0_TD' 0x%08x\n", apb_pwrstatus1 & BITS_PD_CP0_TD_STATE(15));
+ }
+ if (apb_pwrstatus2){
+ printk("status of power domain 'CP0_PUB_SYS' 0x%08x\n", (apb_pwrstatus2 & BITS_PD_PUB_SYS_STATE(15)) >> 24);
+ printk("status of power domain 'CP1_COMWRAP' 0x%08x\n", (apb_pwrstatus2 & BITS_PD_CP1_COMWRAP_STATE(15)) >> 20);
+ printk("status of power domain 'CP1_LTE_P2' 0x%08x\n", (apb_pwrstatus2 & BITS_PD_CP1_LTE_P2_STATE(15)) >> 16);
+ printk("status of power domain 'CP1_LTE_P1' 0x%08x\n", (apb_pwrstatus2 & BITS_PD_CP1_LTE_P1_STATE(15)) >> 12);
+ printk("status of power domain 'CP1_CEVA' 0x%08x\n", (apb_pwrstatus2 & BITS_PD_CP1_CEVA_STATE(15)) >> 8);
+ printk("status of power domain 'CP1_CA5' 0x%08x\n", (apb_pwrstatus2 & BITS_PD_CP1_CA5_STATE(15)) >> 4);
+ printk("status of power domain 'CA53_LIT_MP4' 0x%08x\n", apb_pwrstatus2 & BITS_PD_CA53_LIT_MP4_STATE(15));
+ }
+ if (apb_pwrstatus3){
+ printk("###---- status of power domain 'MM_CODEC' 0x%08x----###\n", (apb_pwrstatus3 & BITS_PD_MM_CODEC_STATE(15)) >> 28);
+ printk("###---- status of power domain 'CA53_BIG_C3' 0x%08x----###\n", (apb_pwrstatus3 & BITS_PD_CA53_BIG_C3_STATE(15)) >> 24);
+ printk("###---- status of power domain 'CA53_BIG_C2' 0x%08x----###\n", (apb_pwrstatus3 & BITS_PD_CA53_BIG_C2_STATE(15)) >> 20);
+ printk("###---- status of power domain 'CA53_BIG_C1' 0x%08x----###\n", (apb_pwrstatus3 & BITS_PD_CA53_BIG_C1_STATE(15)) >> 16);
+ printk("###---- status of power domain 'CA53_BIG_C0' 0x%08x----###\n", (apb_pwrstatus3 & BITS_PD_CA53_BIG_C0_STATE(15)) >> 12);
+ printk("###---- status of power domain 'CA53_BIG_MP4' 0x%08x----###\n", (apb_pwrstatus3 & BITS_PD_CA53_BIG_MP4_STATE(15)) >> 8);
+ printk("###---- status of power domain 'GPU_C1' 0x%08x----###\n", (apb_pwrstatus3 & BITS_PD_GPU_C1_STATE(15)) >> 4);
+ printk("###---- status of power domain 'GPU_C0' 0x%08x----###\n", apb_pwrstatus3 & BITS_PD_GPU_C0_STATE(15));
+ }
+
+ if (mpll_cfg1 & BIT_MPLL_LOCK_DONE)
+ printk("###---- MPLL lock_none bit is set ----###\n");
+ if (mpll_cfg1 & BIT_MPLL_DIV_S)
+ printk("###---- MPLL feedback is fractional divider ----###");
+ if (mpll_cfg1 & BIT_MPLL_MOD_EN)
+ printk("###---- MPLL modulator MOD enable ----###\n");
+ if (mpll_cfg1 & BIT_MPLL_SDM_EN)
+ printk("###---- MPLL modulator SDM enable ----###\n");
+
+ if (dpll_cfg1 & BIT_DPLL_LOCK_DONE)
+ printk("###---- DPLL lock_node bit is set ----###\n");
+ if (dpll_cfg1 & BIT_DPLL_DIV_S)
+ printk("###---- DPLL feedback is fractional divider ----###\n");
+ if (dpll_cfg1 & BIT_DPLL_MOD_EN)
+ printk("###---- DPLL modulator MOD enable ----###\n");
+ if (dpll_cfg1 & BIT_DPLL_SDM_EN)
+ printk("###---- DPLL modulator SDM enable ----###\n");
+#endif
+
+
+}
+
+static int print_thread(void * data)
+{
+ while(1){
+ wake_lock(&messages_wakelock);
+ if (print_thread_enable)
+ print_debug_info();
+ has_wake_lock(WAKE_LOCK_SUSPEND);
+ msleep(100);
+ wake_unlock(&messages_wakelock);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(print_thread_interval * HZ);
+ }
+ return 0;
+}
+static void debugfs_init(void)
+{
+ dentry_debug_root = debugfs_create_dir("power", NULL);
+ if (IS_ERR(dentry_debug_root) || !dentry_debug_root) {
+ printk("!!!powermanager Failed to create debugfs directory\n");
+ dentry_debug_root = NULL;
+ return;
+ }
+ debugfs_create_u32("print_sleep_mode", 0644, dentry_debug_root,
+ &is_print_sleep_mode);
+ debugfs_create_u32("print_linux_clock", 0644, dentry_debug_root,
+ &is_print_linux_clock);
+ debugfs_create_u32("print_modem_clock", 0644, dentry_debug_root,
+ &is_print_modem_clock);
+ debugfs_create_u32("print_irq", 0644, dentry_debug_root,
+ &is_print_irq);
+ debugfs_create_u32("print_wakeup", 0644, dentry_debug_root,
+ &is_print_wakeup);
+ debugfs_create_u32("print_irq_runtime", 0644, dentry_debug_root,
+ &is_print_irq_runtime);
+ debugfs_create_u32("print_time", 0644, dentry_debug_root,
+ &is_print_time);
+ debugfs_create_u32("print_thread_enable", 0644, dentry_debug_root,
+ &print_thread_enable);
+ debugfs_create_u32("print_thread_interval", 0644, dentry_debug_root,
+ &print_thread_interval);
+}
+void pm_debug_init(void)
+{
+#ifdef PM_PRINT_ENABLE
+ struct task_struct * task;
+ wake_lock_init(&messages_wakelock, WAKE_LOCK_SUSPEND,
+ "pm_message_wakelock");
+ task = kthread_create(print_thread, NULL, "pm_print");
+ if (task == 0) {
+ printk("Can't crate power manager print thread!\n");
+ }else
+ wake_up_process(task);
+#endif
+ debugfs_init();
+}
+void pm_debug_clr(void)
+{
+ if(dentry_debug_root != NULL)
+ debugfs_remove_recursive(dentry_debug_root);
+}
+void pm_debug_dump_ahb_glb_regs(void){}
+void pm_debug_save_ahb_glb_regs(void){}
+#define WDG_BASE (ANA_WDG_BASE)
+#define SPRD_ANA_BASE (ANA_CTL_GLB_BASE)
+
+#define WDG_LOAD_LOW (WDG_BASE + 0x00)
+#define WDG_LOAD_HIGH (WDG_BASE + 0x04)
+#define WDG_CTRL (WDG_BASE + 0x08)
+#define WDG_INT_CLR (WDG_BASE + 0x0C)
+#define WDG_INT_RAW (WDG_BASE + 0x10)
+#define WDG_INT_MSK (WDG_BASE + 0x14)
+#define WDG_CNT_LOW (WDG_BASE + 0x18)
+#define WDG_CNT_HIGH (WDG_BASE + 0x1C)
+#define WDG_LOCK (WDG_BASE + 0x20)
+#define WDG_IRQ_LOW (WDG_BASE + 0x2c)
+#define WDG_IRQ_HIGH (WDG_BASE + 0x30)
+
+#define WDG_INT_EN_BIT BIT(0)
+#define WDG_CNT_EN_BIT BIT(1)
+#define WDG_NEW_VER_EN BIT(2)
+#define WDG_INT_CLEAR_BIT BIT(0)
+#define WDG_RST_CLEAR_BIT BIT(3)
+#define WDG_LD_BUSY_BIT BIT(4)
+#define WDG_RST_EN_BIT BIT(3)
+
+
+#define ANA_REG_BASE SPRD_ANA_BASE /* 0x82000600 */
+
+
+#define WDG_NEW_VERSION
+#define ANA_RST_STATUS (ANA_REG_BASE + 0xE8)
+#define ANA_AGEN (ANA_REG_BASE + 0x00)
+#define ANA_RTC_CLK_EN (ANA_REG_BASE + 0x08)
+
+#define AGEN_WDG_EN BIT(2)
+#define AGEN_RTC_WDG_EN BIT(2)
+
+#define WDG_CLK 32768
+#define WDG_UNLOCK_KEY 0xE551
+
+#define ANA_WDG_LOAD_TIMEOUT_NUM (10000)
+
+#ifdef WDG_NEW_VERSION
+#define WDG_LOAD_TIMER_VALUE(value) \
+ do{\
+ sci_adi_raw_write( WDG_LOAD_HIGH, (uint16_t)(((value) >> 16 ) & 0xffff));\
+ sci_adi_raw_write( WDG_LOAD_LOW , (uint16_t)((value) & 0xffff) );\
+ }while(0)
+#define WDG_LOAD_TIMER_INT_VALUE(value) \
+ do{\
+ sci_adi_raw_write( WDG_IRQ_HIGH, (uint16_t)(((value) >> 16 ) & 0xffff));\
+ sci_adi_raw_write( WDG_IRQ_LOW , (uint16_t)((value) & 0xffff) );\
+ }while(0)
+
+#else
+
+#define WDG_LOAD_TIMER_VALUE(value) \
+ do{\
+ uint32_t cnt = 0;\
+ sci_adi_raw_write( WDG_LOAD_HIGH, (uint16_t)(((value) >> 16 ) & 0xffff));\
+ sci_adi_raw_write( WDG_LOAD_LOW , (uint16_t)((value) & 0xffff) );\
+ while((sci_adi_read(WDG_INT_RAW) & WDG_LD_BUSY_BIT) && ( cnt < ANA_WDG_LOAD_TIMEOUT_NUM )) cnt++;\
+ }while(0)
+#endif
+/* use ana watchdog to wake up */
+void pm_debug_set_apwdt(void)
+{
+ uint32_t cnt = 0;
+ uint32_t ms = 5000;
+ cnt = (ms * WDG_CLK) / 1000;
+ sci_glb_set(INT_IRQ_ENB, BIT(11));
+ /*enable interface clk*/
+ sci_adi_set(ANA_AGEN, AGEN_WDG_EN);
+ /*enable work clk*/
+ sci_adi_set(ANA_RTC_CLK_EN, AGEN_RTC_WDG_EN);
+ sci_adi_raw_write(WDG_LOCK, WDG_UNLOCK_KEY);
+ sci_adi_set(WDG_CTRL, WDG_NEW_VER_EN);
+ WDG_LOAD_TIMER_VALUE(0x80000);
+ WDG_LOAD_TIMER_INT_VALUE(0x40000);
+ sci_adi_set(WDG_CTRL, WDG_CNT_EN_BIT | WDG_INT_EN_BIT| WDG_RST_EN_BIT);
+ sci_adi_raw_write(WDG_LOCK, (uint16_t) (~WDG_UNLOCK_KEY));
+ sci_adi_set(ANA_REG_INT_EN, BIT(3));
+#if 0
+ do{
+ udelay(1000);
+ printk("INTC1 mask:0x%08x raw:0x%08x en:0x%08x\n", __raw_readl(INTCV1_IRQ_MSKSTS),__raw_readl(INTCV1_IRQ_RAW), __raw_readl(INTCV1_IRQ_EN));
+ printk("INT mask:0x%08x raw:0x%08x en:0x%08x ana 0x%08x\n", __raw_readl(INT_IRQ_STS),__raw_readl(INT_IRQ_RAW), __raw_readl(INT_IRQ_ENB), sci_adi_read(ANA_REG_INT_MASK_STATUS));
+ printk("ANA mask:0x%08x raw:0x%08x en:0x%08x\n", sci_adi_read(ANA_REG_INT_MASK_STATUS), sci_adi_read(ANA_REG_INT_RAW_STATUS), sci_adi_read(ANA_REG_INT_EN));
+ printk("wdg cnt low 0x%08x high 0x%08x\n", sci_adi_read(WDG_CNT_LOW), sci_adi_read(WDG_CNT_HIGH));
+ }while(0);
+#endif
+}
+void pm_debug_clr_apwdt(void)
+{
+ //sci_adi_raw_write(WDG_LOCK, WDG_UNLOCK_KEY);
+ printk("watchdog int raw status: 0x%x\n", sci_adi_read(WDG_INT_RAW));
+ printk("watchdog int msk status: 0x%x\n", sci_adi_read(WDG_INT_MSK));
+ sci_adi_set(WDG_INT_CLR, WDG_INT_CLEAR_BIT | WDG_RST_CLEAR_BIT);
+ printk("watchdog int raw status: 0x%x\n", sci_adi_read(WDG_INT_RAW));
+ printk("watchdog int msk status: 0x%x\n", sci_adi_read(WDG_INT_MSK));
+ sci_adi_clr(WDG_CTRL, WDG_CNT_EN_BIT | WDG_RST_EN_BIT);
+ //sci_adi_raw_write(WDG_LOCK, (uint16_t) (~WDG_UNLOCK_KEY));
+}
diff --git a/drivers/platform/sprd/reserve.c b/drivers/platform/sprd/reserve.c
new file mode 100755
index 00000000..14412be5
--- /dev/null
+++ b/drivers/platform/sprd/reserve.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/init.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/board.h>
+#include <asm/memory.h>
+#include <linux/memblock.h>
+#include <linux/dma-contiguous.h>
+#include <linux/platform_device.h>
+//#include "devices.h"
+
+#include <asm/setup.h>
+
+static int __init __iomem_reserve_memblock(void)
+{
+ int ret;
+
+#ifndef CONFIG_CMA
+ if (memblock_is_region_reserved(SPRD_ION_MEM_BASE, SPRD_ION_MEM_SIZE))
+ return -EBUSY;
+ if (memblock_reserve(SPRD_ION_MEM_BASE, SPRD_ION_MEM_SIZE))
+ return -ENOMEM;
+#else
+#ifndef CONFIG_OF
+ ret = dma_declare_contiguous_reserved(&sprd_ion_dev.dev, SPRD_ION_MEM_SIZE, SPRD_ION_MEM_BASE, 0, CMA_RESERVE, CMA_THRESHOLD);
+ if (unlikely(ret))
+ {
+ pr_err("reserve CMA area(base:%x size:%x) for ION failed!!!\n", SPRD_ION_MEM_BASE, SPRD_ION_MEM_SIZE);
+ return -ENOMEM;
+ }
+ pr_info("reserve CMA area(base:%x size:%x) for ION\n", SPRD_ION_MEM_BASE, SPRD_ION_MEM_SIZE);
+#endif
+#endif
+ return 0;
+}
+
+#ifdef CONFIG_PSTORE_RAM
+int __init __ramconsole_reserve_memblock(void)
+{
+ if (memblock_is_region_reserved(SPRD_RAM_CONSOLE_START, SPRD_RAM_CONSOLE_SIZE))
+ return -EBUSY;
+ if (memblock_reserve(SPRD_RAM_CONSOLE_START, SPRD_RAM_CONSOLE_SIZE))
+ return -ENOMEM;
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_FB_LCD_RESERVE_MEM
+static int __init __fbmem_reserve_memblock(void)
+{
+ pr_err("__fbmem_reserve_memblock,SPRD_FB_MEM_BASE:%x,SPRD_FB_MEM_SIZE:%x\n",SPRD_FB_MEM_BASE,SPRD_FB_MEM_SIZE);
+ if (memblock_is_region_reserved(SPRD_FB_MEM_BASE, SPRD_FB_MEM_SIZE))
+ return -EBUSY;
+ if (memblock_reserve(SPRD_FB_MEM_BASE, SPRD_FB_MEM_SIZE))
+ return -ENOMEM;
+ pr_err("__fbmem_reserve_memblock-end,\n");
+ return 0;
+}
+#endif
+
+#if defined(CONFIG_SIPC) && !defined(CONFIG_ARCH_SC8825)
+int __init __sipc_reserve_memblock(void)
+{
+ uint32_t smem_size = 0;
+
+#ifdef CONFIG_SIPC_TD
+ if (memblock_reserve(CPT_START_ADDR, CPT_TOTAL_SIZE))
+ return -ENOMEM;
+ smem_size += CPT_SMEM_SIZE;
+#endif
+
+#ifdef CONFIG_SIPC_WCDMA
+ if (memblock_reserve(CPW_START_ADDR, CPW_TOTAL_SIZE))
+ return -ENOMEM;
+ smem_size += CPW_SMEM_SIZE;
+#endif
+
+#ifdef CONFIG_SIPC_WCN
+ if (memblock_reserve(WCN_START_ADDR, WCN_TOTAL_SIZE))
+ return -ENOMEM;
+ smem_size += WCN_SMEM_SIZE;
+#endif
+
+#ifdef CONFIG_SIPC_GGE
+ if (memblock_reserve(GGE_START_ADDR, GGE_TOTAL_SIZE))
+ return -ENOMEM;
+ smem_size += GGE_SMEM_SIZE;
+#endif
+
+#ifdef CONFIG_SIPC_LTE
+ if (memblock_reserve(LTE_START_ADDR, LTE_TOTAL_SIZE))
+ return -ENOMEM;
+ smem_size += LTE_SMEM_SIZE;
+#endif
+
+ if (memblock_reserve(SIPC_SMEM_ADDR, smem_size))
+ return -ENOMEM;
+
+ return 0;
+}
+#endif
+
+#ifdef SPRD_ION_BASE_USE_VARIABLE
+phys_addr_t sprd_reserve_limit;
+extern phys_addr_t arm_lowmem_limit;
+#endif
+static int dram_cs_num = 0;
+static u32 dram_cs0_size = 0x0;
+static int __init mem_cs_num_get(char *str)
+{
+ dram_cs_num=simple_strtoul(str,0, 16);
+ printk("mem_cs_get dram_cs_num=%d\n",dram_cs_num);
+ return 0;
+}
+static int __init mem_cs0_size_get(char *str)
+{
+ dram_cs0_size=simple_strtoul(str,0, 16);
+ printk("mem_cs0_size_get dram_cs0_size =0x%08x\n",dram_cs0_size);
+ return 0;
+}
+early_param("mem_cs", mem_cs_num_get);
+early_param("mem_cs0_sz", mem_cs0_size_get);
+static int __ddr_training_memblock(void)
+{
+ memblock_reserve(CONFIG_PHYS_OFFSET, PAGE_SIZE);
+ if(2 == dram_cs_num) {
+ if(0==dram_cs0_size){
+ pr_err("dram_cs0_size = 0, error, please check the dram size\n");
+ return -ENOMEM;
+ }
+ memblock_reserve(CONFIG_PHYS_OFFSET+dram_cs0_size, PAGE_SIZE);
+ }
+ return 0;
+}
+void __init sci_reserve(void)
+{
+ int ret;
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L)
+ __ddr_training_memblock();
+#endif
+#ifdef SPRD_ION_BASE_USE_VARIABLE
+ /*sprd_reserve_limit is used save arm_lowmem_limit,will be use by ION*/
+ sprd_reserve_limit = arm_lowmem_limit;
+#endif
+
+#ifndef CONFIG_OF
+ ret = __iomem_reserve_memblock();
+ if (ret != 0)
+ pr_err("Fail to reserve mem for iomem. errno=%d\n", ret);
+
+#if defined(CONFIG_SIPC) && !defined(CONFIG_ARCH_SC8825)
+ ret = __sipc_reserve_memblock();
+ if (ret != 0)
+ pr_err("Fail to reserve mem for sipc. errno=%d\n", ret);
+#endif
+
+#ifdef CONFIG_PSTORE_RAM
+ ret = __ramconsole_reserve_memblock();
+ if (ret != 0)
+ pr_err("Fail to reserve mem for ram_console. errno=%d\n", ret);
+#endif
+
+#ifdef CONFIG_FB_LCD_RESERVE_MEM
+ ret = __fbmem_reserve_memblock();
+ if (ret != 0)
+ pr_err("Fail to reserve mem for framebuffer . errno=%d\n", ret);
+#endif
+#endif
+}
diff --git a/drivers/platform/sprd/sec_gps_s5n6420.c b/drivers/platform/sprd/sec_gps_s5n6420.c
new file mode 100755
index 00000000..61d91ef9
--- /dev/null
+++ b/drivers/platform/sprd/sec_gps_s5n6420.c
@@ -0,0 +1,140 @@
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <soc/sprd/gpio.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <soc/sprd/regulator.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+
+static struct device *gps_dev;
+extern struct class *sec_class;
+
+static void gps_clk_init(void)
+{
+ struct clk *gps_clk;
+ struct clk *clk_parent;
+ const char *clk_32k= "clk_aux0";
+
+ gps_clk = clk_get(NULL, clk_32k);
+ if (IS_ERR(gps_clk)) {
+ printk("clock: failed to get clk_aux0\n");
+ }
+ clk_parent = clk_get(NULL, "ext_32k");
+ if (IS_ERR(clk_parent)) {
+ printk("failed to get parent ext_32k\n");
+ }
+
+ clk_set_parent(gps_clk, clk_parent);
+ clk_set_rate(gps_clk, 32000);
+ if(!clk_prepare_enable(gps_clk))
+ printk("%s: %s enabled!!\n", __func__, clk_32k);
+ //clk_enable(gps_clk);
+}
+
+static int gps_regulator_on(void)
+{
+ int ret = 0;
+ const char *gps_node = "lsi,s5n6420";
+ struct device_node *root_node = NULL;
+ static struct regulator *gps_regulator = NULL;
+ const char *reg = NULL;
+
+ root_node = of_find_compatible_node(NULL, NULL, gps_node);
+ if(!root_node) {
+ WARN(1, "[GPS] failed to get device node of %s\n", gps_node);
+ return -ENODEV;
+ }
+ /* regulator enable from dts */
+ if(of_property_read_string(root_node, "gps-regulator", &reg)) {
+ pr_err("%s: main(1.8v) regulator field is not exsit\n", __func__);
+ ret = -ENODEV;
+ goto err_find_node;
+ } else {
+ gps_regulator = regulator_get(NULL, reg);
+ if (IS_ERR(gps_regulator)){
+ gps_regulator = NULL;
+ return -EIO;
+ } else {
+ regulator_set_voltage(gps_regulator, 1800000, 1800000);
+ if(!regulator_enable(gps_regulator))
+ printk("%s: (%s) main(1.8v) regulator turned ON!!\n", __func__, reg);
+ }
+ }
+ return 0;
+
+err_find_node:
+ of_node_put(root_node);
+ return ret;
+}
+
+static unsigned int gps_pwr_on = 0;
+static unsigned int gps_reset = 0;
+
+static int __init gps_s5n6420_init(void)
+{
+ pr_info("%s\n", __func__);
+ gps_regulator_on();
+ gps_clk_init();
+
+ int ret = 0;
+ const char *gps_node = "lsi,s5n6420";
+ const char *gps_pwr_en = "gps-pwr-en";
+ const char *gps_nRst = "gps-reset";
+ struct device_node *root_node = NULL;
+
+ BUG_ON(!sec_class);
+ gps_dev = device_create(sec_class, NULL, 0, NULL, "gps");
+ BUG_ON(!gps_dev);
+
+ root_node = of_find_compatible_node(NULL, NULL, gps_node);
+ if(!root_node) {
+ WARN(1, "failed to get device node of bcm4752\n");
+ ret = -ENODEV;
+ goto err_sec_device_create;
+ }
+
+ gps_pwr_on = of_get_named_gpio(root_node, gps_pwr_en, 0);
+ if(!gpio_is_valid(gps_pwr_on)) {
+ WARN(1, "Invalid gpio pin : %d\n", gps_pwr_on);
+ ret = -ENODEV;
+ goto err_find_node;
+ }
+ if (gpio_request(gps_pwr_on, "GPS_PWR_EN")) {
+ WARN(1, "fail to request gpio(GPS_PWR_EN)\n");
+ ret = -ENODEV;
+ goto err_find_node;
+ }
+ gps_reset= of_get_named_gpio(root_node, gps_nRst, 0);
+ if(!gpio_is_valid(gps_reset)) {
+ WARN(1, "Invalid gpio pin : %d\n", gps_reset);
+ ret = -ENODEV;
+ goto err_find_node;
+ }
+ if (gpio_request(gps_reset, "GPS_RESET")) {
+ WARN(1, "fail to request gpio(GPS_RESET)\n");
+ ret = -ENODEV;
+ goto err_find_node;
+ }
+
+ gpio_direction_output(gps_pwr_on, 0);
+ gpio_export(gps_pwr_on, 1);
+ gpio_export_link(gps_dev, "GPS_PWR_EN", gps_pwr_on);
+
+ gpio_direction_output(gps_reset, 1);
+ gpio_export(gps_reset, 1);
+ gpio_export_link(gps_dev, "GPS_RESET", gps_reset);
+
+ return 0;
+
+err_find_node:
+ of_node_put(root_node);
+err_sec_device_create:
+ device_destroy(gps_dev, gps_dev->devt);
+ return ret;
+}
+
+device_initcall(gps_s5n6420_init);
diff --git a/drivers/platform/sprd/sprd_debug.c b/drivers/platform/sprd/sprd_debug.c
new file mode 100644
index 00000000..1025c20e
--- /dev/null
+++ b/drivers/platform/sprd/sprd_debug.c
@@ -0,0 +1,249 @@
+/*
+ * sprd_debug.c
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/ctype.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/sysrq.h>
+//#include <mach/regs-pmu.h>
+
+/* FIXME: This is temporary solution come from Midas */
+#ifndef CONFIG_64BIT
+#include <asm/outercache.h>
+#endif
+#include <asm/cacheflush.h>
+#include <asm/io.h>
+#include <linux/sched.h>
+#include <linux/smp.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/swiotlb.h>
+#include <soc/sprd/pm_debug.h>
+#include <soc/sprd/sprd_persistent_clock.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/system.h>
+#include <soc/sprd/sprd_debug.h>
+#include <linux/fs.h>
+#include <linux/seq_file.h>
+#include <linux/regs_debug.h>
+
+#define MY_DEBUG 0
+
+struct sprd_debug_regs_access *sprd_debug_last_regs_access;
+EXPORT_SYMBOL(sprd_debug_last_regs_access);
+
+/* enable/disable sprd_debug feature
+ * level = 0 when enable = 0 && enable_user = 0
+ * level = 1 when enable = 1 && enable_user = 0
+ * level = 0x10001 when enable = 1 && enable_user = 1
+ * The other cases are not considered
+ */
+union sprd_debug_level_t sprd_debug_level = { .en.kernel_fault = 1, };
+
+module_param_named(enable, sprd_debug_level.en.kernel_fault, ushort, 0644);
+module_param_named(enable_user, sprd_debug_level.en.user_fault, ushort, 0644);
+module_param_named(level, sprd_debug_level.uint_val, uint, 0644);
+
+
+static const char *gkernel_sprd_build_info_date_time[] = {
+ __DATE__,
+ __TIME__
+};
+
+static char gkernel_sprd_build_info[100];
+
+/* klaatu - schedule log */
+#ifdef CONFIG_SPRD_DEBUG_SCHED_LOG
+static struct sched_log sprd_debug_log __cacheline_aligned;
+static atomic_t task_log_idx[NR_CPUS] = { ATOMIC_INIT(-1), ATOMIC_INIT(-1) };
+static atomic_t irq_log_idx[NR_CPUS] = { ATOMIC_INIT(-1), ATOMIC_INIT(-1) };
+static atomic_t work_log_idx[NR_CPUS] = { ATOMIC_INIT(-1), ATOMIC_INIT(-1) };
+static atomic_t hrtimer_log_idx[NR_CPUS] = { ATOMIC_INIT(-1), ATOMIC_INIT(-1) };
+struct sched_log (*psprd_debug_log) = (&sprd_debug_log);
+#endif
+
+
+
+void sprd_debug_save_pte(void *pte, int task_addr)
+{
+
+// memcpy(&per_cpu(sprd_debug_fault_status,smp_processor_id()), pte,sizeof(struct sprd_debug_fault_status_t));
+// per_cpu(sprd_debug_fault_status,smp_processor_id()).cur_process_magic =task_addr;
+}
+void sprd_debug_check_crash_key(unsigned int code, int value)
+{
+ static bool volup_p;
+ static bool voldown_p;
+ static int loopcount;
+
+ if (!sprd_debug_level.en.kernel_fault)
+ return;
+
+#if 0
+ /* Must be deleted later */
+ pr_info("Test %s:key code(%d) value(%d),(up:%d,down:%d)\n", __func__, code, value, volup_p, voldown_p);
+#endif
+
+ /* Enter Force Upload
+ * Hold volume down key first
+ * and then press power key twice
+ * and volume up key should not be pressed
+ */
+ if (value) {
+ if (code == KEY_VOLUMEUP)
+ volup_p = true;
+ if (code == KEY_VOLUMEDOWN)
+ voldown_p = true;
+ if (volup_p && voldown_p) {
+ static unsigned long stack_dump_jiffies = 0;
+
+ if (!stack_dump_jiffies || jiffies > stack_dump_jiffies + HZ * 10) {
+ pr_info("trigger sysrq w & m\n");
+
+ /* show blocked thread */
+ handle_sysrq('w');
+
+ /* show memory */
+ handle_sysrq('m');
+ stack_dump_jiffies = jiffies;
+ }
+ if (code == KEY_POWER) {
+ pr_info("%s: Crash key count : %d\n", __func__, ++loopcount);
+ if (loopcount == 2)
+ panic("Crash Key");
+ }
+ }
+ } else {
+ if (code == KEY_VOLUMEUP)
+ volup_p = false;
+ if (code == KEY_VOLUMEDOWN) {
+ loopcount = 0;
+ voldown_p = false;
+ }
+ }
+}
+
+static void sprd_debug_set_build_info(void)
+{
+ char *p = gkernel_sprd_build_info;
+ sprintf(p, "Kernel Build Info : ");
+ strcat(p, " Date:");
+ strncat(p, gkernel_sprd_build_info_date_time[0], 12);
+ strcat(p, " Time:");
+ strncat(p, gkernel_sprd_build_info_date_time[1], 9);
+}
+
+__init int sprd_debug_init(void)
+{
+ dma_addr_t addr;
+
+ if (!sprd_debug_level.en.kernel_fault)
+ return -1;
+
+ sprd_debug_set_build_info();
+#ifndef CONFIG_64BIT
+ sprd_debug_last_regs_access = (struct sprd_debug_regs_access*)dma_alloc_coherent(
+ NULL, sizeof(struct sprd_debug_regs_access)*NR_CPUS, &addr, GFP_KERNEL);
+#else
+ sprd_debug_last_regs_access = (struct sprd_debug_regs_access*)swiotlb_alloc_coherent(
+ NULL, sizeof(struct sprd_debug_regs_access)*NR_CPUS, &addr, GFP_KERNEL);
+#endif
+ printk("*** %s, size:%u, sprd_debug_last_regs_access:%p *** \n",
+ __func__, sizeof(struct sprd_debug_regs_access)*NR_CPUS, sprd_debug_last_regs_access);
+
+ return 0;
+}
+subsys_initcall(sprd_debug_init);
+
+int get_sprd_debug_level(void)
+{
+ return sprd_debug_level.uint_val;
+}
+
+/* klaatu - schedule log */
+#ifdef CONFIG_SPRD_DEBUG_SCHED_LOG
+void __sprd_debug_task_log(int cpu, struct task_struct *task)
+{
+ unsigned i;
+
+ i = atomic_inc_return(&task_log_idx[cpu]) &
+ (ARRAY_SIZE(psprd_debug_log->task[0]) - 1);
+ psprd_debug_log->task[cpu][i].time = cpu_clock(cpu);
+#ifdef CP_DEBUG
+ psprd_debug_log->task[cpu][i].sys_cnt = get_sys_cnt();
+#endif
+ strcpy(psprd_debug_log->task[cpu][i].comm, task->comm);
+ psprd_debug_log->task[cpu][i].pid = task->pid;
+}
+
+void __sprd_debug_irq_log(unsigned int irq, void *fn, int en)
+{
+ int cpu = raw_smp_processor_id();
+ unsigned i;
+
+ i = atomic_inc_return(&irq_log_idx[cpu]) &
+ (ARRAY_SIZE(psprd_debug_log->irq[0]) - 1);
+ psprd_debug_log->irq[cpu][i].time = cpu_clock(cpu);
+#ifdef CP_DEBUG
+ psprd_debug_log->irq[cpu][i].sys_cnt = get_sys_cnt();
+#endif
+ psprd_debug_log->irq[cpu][i].irq = irq;
+ psprd_debug_log->irq[cpu][i].fn = (void *)fn;
+ psprd_debug_log->irq[cpu][i].en = en;
+}
+
+void __sprd_debug_work_log(struct worker *worker,
+ struct work_struct *work, work_func_t f)
+{
+ int cpu = raw_smp_processor_id();
+ unsigned i;
+
+ i = atomic_inc_return(&work_log_idx[cpu]) &
+ (ARRAY_SIZE(psprd_debug_log->work[0]) - 1);
+ psprd_debug_log->work[cpu][i].time = cpu_clock(cpu);
+#ifdef CP_DEBUG
+ psprd_debug_log->work[cpu][i].sys_cnt = get_sys_cnt();
+#endif
+ psprd_debug_log->work[cpu][i].worker = worker;
+ psprd_debug_log->work[cpu][i].work = work;
+ psprd_debug_log->work[cpu][i].f = f;
+}
+
+void __sprd_debug_hrtimer_log(struct hrtimer *timer,
+ enum hrtimer_restart (*fn) (struct hrtimer *), int en)
+{
+ int cpu = raw_smp_processor_id();
+ unsigned i;
+
+ i = atomic_inc_return(&hrtimer_log_idx[cpu]) &
+ (ARRAY_SIZE(psprd_debug_log->hrtimers[0]) - 1);
+ psprd_debug_log->hrtimers[cpu][i].time = cpu_clock(cpu);
+#ifdef CP_DEBUG
+ psprd_debug_log->hrtimers[cpu][i].sys_cnt = get_sys_cnt();
+#endif
+ psprd_debug_log->hrtimers[cpu][i].timer = timer;
+ psprd_debug_log->hrtimers[cpu][i].fn = fn;
+ psprd_debug_log->hrtimers[cpu][i].en = en;
+}
+
+int get_task_log_idx(int cpu)
+{
+ return atomic_inc_return(&task_log_idx[cpu]) & (ARRAY_SIZE(psprd_debug_log->task[0]) - 1);
+}
+
+int get_irq_log_idx(int cpu)
+{
+ return atomic_inc_return(&irq_log_idx[cpu]) & (ARRAY_SIZE(psprd_debug_log->irq[0]) - 1);
+}
+
+#endif /* CONFIG_SPRD_DEBUG_SCHED_LOG */
+
diff --git a/drivers/platform/sprd/sprd_mem_pool.c b/drivers/platform/sprd/sprd_mem_pool.c
new file mode 100644
index 00000000..dcfa658d
--- /dev/null
+++ b/drivers/platform/sprd/sprd_mem_pool.c
@@ -0,0 +1,470 @@
+/* linux/arch/arm/mach-sc8810/sprd_mem_pool.c
+ *
+ * Copyright (C) 2010 Spreadtrum
+ *
+ * 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/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/mm.h>
+#include <linux/mmzone.h>
+#include <linux/workqueue.h>
+#include <linux/sched.h>
+#include <linux/pid.h>
+#include <linux/mutex.h>
+#include <asm/current.h>
+#include <asm/uaccess.h>
+#include <linux/pagemap.h>
+#include <linux/pfn.h>
+#include <linux/memory.h>
+#include <linux/slab.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+
+/**
+ * add another order pool, plz modify:
+ * 1.PAGE_POOL_MAX_COUNT: +1
+ * 2.page_pool struct: +node
+ * */
+
+/*Magic Numbers*/
+#define DEBUG_PRINT 1 /*used for debug*/
+#define UNSIGNED_LONG_MAX 0xffffffff
+#define PAGE_POOL_MAX_COUNT 5
+
+/*status record struct declare*/
+struct status {
+ unsigned long used;
+ unsigned long free;
+ unsigned long peak;
+ unsigned long total;
+};
+
+/*page pool struct definition*/
+static struct {
+ unsigned long order;
+ unsigned long threshold;
+ unsigned long node_count;
+ unsigned long *buffer;
+ struct mutex lock;
+ struct status stat;
+ struct workqueue_struct *sprd_alloc_wq;
+ struct work_struct sprd_alloc_work;
+} page_pool[PAGE_POOL_MAX_COUNT] = {
+ {
+ .order = 0,
+ .buffer = NULL,
+ .sprd_alloc_wq = NULL,
+ }, {
+ .order = 1,
+ .threshold = 2,
+ .node_count = 15,
+ .buffer = NULL,
+ .lock = __MUTEX_INITIALIZER(page_pool[1].lock),
+ .stat.used = 0,
+ .stat.free = 0,
+ .stat.peak = 0,
+ .stat.total = 0,
+ .sprd_alloc_wq = NULL,
+ }, {
+ .order = 2,
+ .threshold = 2,
+ .node_count = 10,
+ .buffer = NULL,
+ .lock = __MUTEX_INITIALIZER(page_pool[2].lock),
+ .stat.used = 0,
+ .stat.free = 0,
+ .stat.peak = 0,
+ .stat.total = 0,
+ .sprd_alloc_wq = NULL,
+ }, {
+ .order = 3,
+ .threshold = 1,
+ .node_count = 3,
+ .buffer = NULL,
+ .lock = __MUTEX_INITIALIZER(page_pool[3].lock),
+ .stat.used = 0,
+ .stat.free = 0,
+ .stat.peak = 0,
+ .stat.total = 0,
+ .sprd_alloc_wq = NULL,
+ }, {
+ .order = 4,
+ .threshold = 1,
+ .node_count = 2,
+ .buffer = NULL,
+ .lock = __MUTEX_INITIALIZER(page_pool[4].lock),
+ .stat.used = 0,
+ .stat.free = 0,
+ .stat.peak = 0,
+ .stat.total = 0,
+ .sprd_alloc_wq = NULL,
+ }
+};
+
+/*global variables*/
+static unsigned long sys_buddy_info[MAX_ORDER];
+static unsigned long alloc_flag = 1;
+static int sprd_alloc_workqueue_pid = -1;
+
+/*extern functions*/
+extern void msleep(unsigned int msecs);
+extern void dump_stack(void);
+struct mutex sprd_mem_stat_lock;
+static int sprd_mem_pool_stat = 0;
+static unsigned long long sprd_mem_alloc_count = 0;
+
+/*internal functions*/
+static unsigned long pow2(unsigned long x, unsigned long y)
+{
+ unsigned long result = 0;
+
+ /*x == 0*/
+ if(!x) return 0;
+
+ /*y == 0*/
+ if(!y) return 1;
+
+ /*x > 0, y > 0*/
+ result = x * pow2(x, (y - 1));
+
+ if(result > UNSIGNED_LONG_MAX) {
+ result = UNSIGNED_LONG_MAX;
+ printk("__SPRD__INIT__: call pow2(%lu, %lu) error!!!\n", x, y);
+ }
+
+ return result;
+}
+
+static void dumpstack(void)
+{
+ printk("Process Name: %s, Process Pid: %d, Parent Name: %s, Parent Pid: %d\n",
+ current->comm, current->pid, current->parent->comm, current->parent->pid);
+ dump_stack();
+}
+
+static struct page *address_to_pages(unsigned long address)
+{
+ if(!address) return NULL;
+
+#if defined(WANT_PAGE_VIRTUAL)
+ return container_of((void *)(address), struct page, virtual);
+#else
+ return virt_to_page(address);
+#endif
+}
+
+static void get_buddy_info(void)
+{
+ struct zone *zone;
+ unsigned int order = 0;
+
+ /*normal zone only*/
+ for_each_zone(zone) {
+ if(is_normal(zone)) break;
+ }
+
+ /*global buddyinfo*/
+ for(order = 0; order < MAX_ORDER; order++) {
+ sys_buddy_info[order] = zone->free_area[order].nr_free;
+ }
+}
+
+static unsigned long get_page_count(unsigned int entry_order)
+{
+ struct zone *zone;
+ unsigned int order = 0;
+ unsigned long page_count = 0;
+
+ /*normal zone only*/
+ for_each_zone(zone) {
+ if(is_normal(zone)) break;
+ }
+
+ /*forall buddy pages*/
+ for(order = 0; order < MAX_ORDER; order++) {
+ /*get page counts*/
+ if(order < entry_order) continue;
+
+ page_count += (zone->free_area[order].nr_free << (order - entry_order));
+ }
+
+ return page_count;
+}
+
+static unsigned long sprd_alloc_one(unsigned long order)
+{
+ unsigned long sub = 0;
+ unsigned long address = 0;
+
+ /*lock*/
+ mutex_lock(&(page_pool[order].lock));
+
+ /*for order page pool*/
+ for(sub = 0; sub < page_pool[order].node_count; sub++) {
+
+ /*jump over null page*/
+ if(!page_pool[order].buffer[sub]) continue;
+
+ /*get first !null page*/
+ address = page_pool[order].buffer[sub];
+ page_pool[order].buffer[sub] = 0;
+
+ /*status record*/
+ page_pool[order].stat.free--;
+ page_pool[order].stat.used++;
+
+ mutex_lock(&sprd_mem_stat_lock);
+ sprd_mem_alloc_count++;
+ sprd_mem_pool_stat = 1;
+ mutex_unlock(&sprd_mem_stat_lock);
+
+ if(strncmp(current->comm, "sprd-page-alloc", 13)) {
+ if(page_pool[order].stat.peak < page_pool[order].stat.used)
+ page_pool[order].stat.peak = page_pool[order].stat.used;
+ if(page_pool[order].stat.total < UNSIGNED_LONG_MAX)
+ page_pool[order].stat.total++;
+ }
+ goto end;
+ }
+
+end:
+ /*unlock*/
+ mutex_unlock(&(page_pool[order].lock));
+
+#if DEBUG_PRINT
+ printk("__SPRD__ALLOC__ONE__: buffer for order = %lu empty; free = %lu, used = %lu, sys = %lu; peak = %lu, total = %lu; Call Stack:\n",
+ order, page_pool[order].stat.free, page_pool[order].stat.used,
+ get_page_count(order), page_pool[order].stat.peak, page_pool[order].stat.total);
+ dumpstack();
+#endif
+ return address;
+}
+
+int sprd_page_mask_check(int pid)
+{
+ if(pid == sprd_alloc_workqueue_pid) return -1;
+
+ return 0;
+}
+
+struct page *sprd_page_alloc(gfp_t gfp_mask, unsigned int order, unsigned long zoneidx)
+{
+ unsigned long address = 0;
+ struct page *page = NULL;
+
+ /*check some flags*/
+ if((!alloc_flag) || (GFP_KERNEL != gfp_mask) || (ZONE_NORMAL != zoneidx) || (order > PAGE_POOL_MAX_COUNT - 1) || (order < 1))
+ goto Failed;
+
+ /*alloc*/
+ address = sprd_alloc_one((unsigned long)order);
+ if(!address) goto Failed;
+
+ /*convert address to page*/
+ page = address_to_pages(address);
+
+ return page;
+
+Failed:
+ return NULL;
+}
+
+static int sprd_show_pages_info(struct seq_file *m, void *v)
+{
+ unsigned long i = 0;
+ unsigned long start = 0;
+
+ seq_printf(m,
+ "sprd pool summery:\n"
+ " status: %s\n",
+ (alloc_flag ? "open" : "closed"));
+ for(start = 1; start < PAGE_POOL_MAX_COUNT; start++) {
+ seq_printf(m,
+ " free node (order=%lu) = %lu, used node (order=%lu) = %lu\n",
+ start, page_pool[start].stat.free, start, page_pool[start].stat.used);
+ }
+ for(start = 1; start < PAGE_POOL_MAX_COUNT; start++) {
+ seq_printf(m,
+ " system memory count (order=%lu) = %lu\n",
+ start, get_page_count(start));
+ }
+ for(start = 1; start < PAGE_POOL_MAX_COUNT; start++) {
+ seq_printf(m,
+ " alloc peak (order=%lu) = %lu, alloc total (order=%lu) = %lu\n",
+ start, page_pool[start].stat.peak, start, page_pool[start].stat.total);
+ }
+
+ seq_printf(m, "buddy info:\n");
+ get_buddy_info();
+ for(i = 0; i < MAX_ORDER; i++) {
+ seq_printf(m, " orders = %lu, number = %lu\n", i, sys_buddy_info[i]);
+ }
+
+ for(start = 1; start < PAGE_POOL_MAX_COUNT; start++) {
+ seq_printf(m, "pool node (order=%lu) details:\n", start);
+ for(i = 0; i < page_pool[start].node_count; i++) {
+ struct page *page = NULL;
+ page = address_to_pages(page_pool[start].buffer[i]);
+ seq_printf(m, " subscript = %lu, value = %p, page = %p, page->flags %lx\n",
+ i, (void *)(page_pool[start].buffer[i]), page, (page ? page->flags : 0x00));
+ }
+ }
+
+ seq_printf(m, "sprd_mem_alloc_count %d\n", sprd_mem_alloc_count);
+ seq_printf(m, "sprd_mem_pool_stat %d\n", sprd_mem_pool_stat);
+ sprd_alloc_one(3);
+ return 0;
+}
+
+static int sprd_pages_info_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sprd_show_pages_info, NULL);
+}
+
+static int sprd_pages_info_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos)
+{
+ char flag;
+
+ if (copy_from_user(&flag, buf, 1)) return -EFAULT;
+
+ switch (flag) {
+ case '0': alloc_flag = 0; break;
+ case '1': alloc_flag = 1; break;
+ default: break;
+ }
+
+ return 1;
+}
+
+static const struct file_operations sprd_page_info_fops = {
+ .open = sprd_pages_info_open,
+ .read = seq_read,
+ .write = sprd_pages_info_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int sprd_mem_pool_daemon(void *arg)
+{
+ while (1) {
+ int start = 0;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(1000));
+ set_current_state(TASK_RUNNING);
+
+ if (!sprd_mem_pool_stat)
+ continue;
+
+ mutex_lock(&sprd_mem_stat_lock);
+ sprd_mem_pool_stat = 0;
+ mutex_unlock(&sprd_mem_stat_lock);
+
+ for(start = PAGE_POOL_MAX_COUNT - 1; start > 0; start--) {
+ unsigned long sub = 0;
+
+ if (page_pool[start].node_count == page_pool[start].stat.free)
+ continue;
+ else {
+ mutex_lock(&sprd_mem_stat_lock);
+ if (!sprd_mem_pool_stat)
+ sprd_mem_pool_stat = 1;
+ mutex_unlock(&sprd_mem_stat_lock);
+ }
+
+ /*for single page buffer*/
+ for(sub = 0; sub < page_pool[start].node_count; sub++) {
+ mutex_lock(&(page_pool[start].lock));
+ gfp_t gfp_mask = GFP_KERNEL;
+
+ /*jump !null node*/
+ if(page_pool[start].buffer[sub]) {
+ mutex_unlock(&(page_pool[start].lock));
+ continue;
+ }
+
+ /*check if sys pages enough*/
+ if(page_pool[start].threshold > get_page_count(page_pool[start].order)) {
+ mutex_unlock(&(page_pool[start].lock));
+ break;
+ }
+
+ /*alloc page from sys for null node*/
+ page_pool[start].buffer[sub] = __get_free_pages(gfp_mask, page_pool[start].order);
+ if(!page_pool[start].buffer[sub]) {
+ mutex_unlock(&(page_pool[start].lock));
+ break;
+ }
+
+ /*stats record*/
+ page_pool[start].stat.free++;
+ page_pool[start].stat.used--;
+ mutex_unlock(&(page_pool[start].lock));
+ }
+ }
+
+ mutex_lock(&sprd_mem_stat_lock);
+ if(!sprd_mem_pool_stat)
+ sprd_mem_alloc_count = 0;
+ mutex_unlock(&sprd_mem_stat_lock);
+ }
+}
+
+static int __init sprd_pages_init(void)
+{
+ int start = 0, mem_sum = 0;
+ gfp_t gfp_mask = GFP_KERNEL;
+ struct workqueue_struct *sprd_alloc_wq = NULL;
+ struct task_struct *p = NULL;
+
+ /*init global*/
+ memset(sys_buddy_info, 0, sizeof(sys_buddy_info));
+ mutex_init(&sprd_mem_stat_lock);
+
+ /*init page pool*/
+ for(start = 1; start < PAGE_POOL_MAX_COUNT; start++) {
+
+ /*node buffer*/
+ unsigned long sub = 0;
+ page_pool[start].buffer = kmalloc((page_pool[start].node_count * sizeof(unsigned long)), gfp_mask);
+ memset(page_pool[start].buffer, 0, (page_pool[start].node_count * sizeof(unsigned long)));
+
+ for(sub = 0; sub < page_pool[start].node_count; sub++) {
+ /*alloc page from sys*/
+ page_pool[start].buffer[sub] = __get_free_pages(gfp_mask, page_pool[start].order);
+ /*stats record*/
+ page_pool[start].buffer[sub] ? page_pool[start].stat.free++ : page_pool[start].stat.used++;
+ }
+
+ /*alloc total size*/
+ mem_sum += page_pool[start].node_count * 4 * pow2(2, page_pool[start].order);
+
+#if DEBUG_PRINT
+ printk("__SPRD__INIT__: page_pool[%d].node_count = %lu, page_pool[%d].order = %lu, mem_sum = %d KiB\n",
+ start, page_pool[start].node_count, start, page_pool[start].order, mem_sum);
+#endif
+ }
+ p = kthread_create(sprd_mem_pool_daemon, NULL, "%s", "sprdmempool");
+ wake_up_process(p);
+ sprd_alloc_workqueue_pid = p->pid;
+
+ printk("__SPRD__INIT__: Sprd memory pool initialized, %d KiB memory allocated.\n", mem_sum);
+
+ /*create proc file*/
+ proc_create("sprd_pages", 0, NULL, &sprd_page_info_fops);
+ return 0;
+}
+
+module_init(sprd_pages_init);
+
diff --git a/drivers/platform/sprd/sprd_vibrator_2723.c b/drivers/platform/sprd/sprd_vibrator_2723.c
new file mode 100644
index 00000000..e44db15c
--- /dev/null
+++ b/drivers/platform/sprd/sprd_vibrator_2723.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/hrtimer.h>
+#include <../../../drivers/staging/android/timed_output.h>
+#include <linux/sched.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/adc.h>
+
+#ifdef CONFIG_SC_VIBRATOR_GPIO
+#include <linux/gpio.h>
+#include <soc/sprd/board.h>
+#elif defined(CONFIG_SC_VIBRATOR_POWER)
+#include <linux/regulator/consumer.h>
+#endif
+
+#include <soc/sprd/sci_glb_regs.h>
+#define ANA_VIBRATOR_CTRL0 (ANA_REGS_GLB_BASE + 0xf8)
+
+#define CUR_DRV_CAL_SEL (0x0 << 12)
+#define SLP_LDOVIBR_PD_EN (0x1 << 9)
+#define LDO_VIBR_PD (0x1 << 8)
+#define LDO_VIBR_V (0xB4)
+
+static struct work_struct vibrator_work;
+static struct hrtimer vibe_timer;
+static int vibe_state = 0;
+
+static inline uint32_t vibrator_read(unsigned long reg)
+{
+ return sci_adi_read(reg);
+}
+
+static void set_vibrator(int on)
+{
+ if (on){
+ sci_adi_clr(ANA_VIBRATOR_CTRL0, LDO_VIBR_PD);
+ sci_adi_clr(ANA_VIBRATOR_CTRL0, SLP_LDOVIBR_PD_EN);
+ printk("v_reg:0x%08x\n",ANA_VIBRATOR_CTRL0);
+ printk("v_reg_value:0x%08x\n",vibrator_read(ANA_VIBRATOR_CTRL0));
+
+ }
+ else{
+ sci_adi_write(ANA_VIBRATOR_CTRL0, LDO_VIBR_PD, LDO_VIBR_PD);
+ sci_adi_write(ANA_VIBRATOR_CTRL0, SLP_LDOVIBR_PD_EN, SLP_LDOVIBR_PD_EN);
+ }
+}
+
+static void vibrator_hw_init(void)
+{
+ sci_adi_write(ANA_REG_GLB_RTC_CLK_EN, BIT_RTC_VIBR_EN, BIT_RTC_VIBR_EN);
+ sci_adi_write(ANA_VIBRATOR_CTRL0,CUR_DRV_CAL_SEL,CUR_DRV_CAL_SEL);
+ sci_adi_clr(ANA_VIBRATOR_CTRL0, 0xFF);
+ sci_adi_write(ANA_VIBRATOR_CTRL0,LDO_VIBR_V,LDO_VIBR_V);
+}
+
+static void update_vibrator(struct work_struct *work)
+{
+ set_vibrator(vibe_state);
+}
+
+static void vibrator_enable(struct timed_output_dev *dev, int value)
+{
+
+ hrtimer_cancel(&vibe_timer);
+
+ if (value == 0)
+ vibe_state = 0;
+ else {
+ value = (value > 15000) ? 15000 : value;
+ vibe_state = 1;
+ hrtimer_start(&vibe_timer,
+ ktime_set(value / 1000, (value % 1000) * 1000000),
+ HRTIMER_MODE_REL);
+ printk("vibrator_enable:value = %d\n",value);
+ printk("vibrator_enable:vibe_state = %d\n",vibe_state);
+
+ }
+
+ schedule_work(&vibrator_work);
+}
+
+static int vibrator_get_time(struct timed_output_dev *dev)
+{
+ ktime_t re;
+
+ if (hrtimer_active(&vibe_timer)) {
+ re = hrtimer_get_remaining(&vibe_timer);
+ return ktime_to_ns(re);
+ } else
+ return 0;
+}
+
+static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer)
+{
+ vibe_state = 0;
+ schedule_work(&vibrator_work);
+
+ return HRTIMER_NORESTART;
+}
+
+static struct timed_output_dev sprd_vibrator = {
+ .name = "vibrator",
+ .get_time = vibrator_get_time,
+ .enable = vibrator_enable,
+};
+
+static int __init sprd_init_vibrator(void)
+{
+ vibrator_hw_init();
+
+ printk("locate in sprd_init_vibrator!\n");
+ INIT_WORK(&vibrator_work, update_vibrator);
+ vibe_state = 0;
+ hrtimer_init(&vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ vibe_timer.function = vibrator_timer_func;
+
+ timed_output_dev_register(&sprd_vibrator);
+
+ return 0;
+}
+
+module_init(sprd_init_vibrator);
+MODULE_DESCRIPTION("vibrator driver for spreadtrum Processors");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/platform/sprd/standby-scx35.S b/drivers/platform/sprd/standby-scx35.S
new file mode 100644
index 00000000..1b014171
--- /dev/null
+++ b/drivers/platform/sprd/standby-scx35.S
@@ -0,0 +1,848 @@
+/*
+ * sc8830 standby mode
+ *
+ * Author: Wang Liwei
+ *
+ */
+
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci_glb_regs.h>
+
+#ifndef CONFIG_NR_CPUS
+#define CONFIG_NR_CPUS 1
+#endif
+
+#define CPU_SAVED_STATE_SIZE (4 * 11 + 4 * 11)
+#define REG_AON_APB_EB0_VIR reg_aon_apb_eb0_vir
+/* #define REG_AON_APB_EB0_PHY reg_aon_apb_eb0_phy */
+#define REG_AON_APB_EB0_PHY 0x402e0000
+#define REG_UART1_ENABLE_VIR reg_uart1_enable_vir
+#define REG_UART1_ENABLE_PHY reg_uart1_enable_phy
+#define REG_UART1_ENABLE_BIT (0x1 << 14)
+#define REG_GLB_GEN0_PHY_ADDR (SPRD_APBREG_PHYS + 0x8)
+#define REG_GLB_GEN0_VIR_ADDR (SPRD_APBREG_BASE + 0x8)
+#define UART1_EN (0X1 << 14)
+#define JTAG_DAP_EN (0X1 << 30)
+#define UART1_CKD_PHY (SPRD_UART1_PHYS + 0x24)
+#define UART1_CTR0_PHY (SPRD_UART1_PHYS + 0x18)
+#define UART1_CKD_VIR (SPRD_UART1_BASE + 0x24)
+#define UART1_CTR0_VIR (SPRD_UART1_BASE + 0x18)
+#define SPRD_LPDDR2_PHY_PHYS 0X30010000
+#define SPRD_PUB_PHYS 0X30020000
+#define LPDDR2_PHY_STATUS (SPRD_LPDDR2_PHY_PHYS+0x0184)
+#define PUB_ACCESS_SET (SPRD_PUB_PHYS+0x1064)
+#define PUB_ACCESS_CLR (SPRD_PUB_PHYS+0x2064)
+#define AON_APB_RES_REG1 (SPRD_AONAPB_PHYS+0x3048)
+#define SLEEP_RESUME_CODE_PHYS 0X400
+
+ .macro addruart,rx
+ @ see if the MMU is enabled and select appropriate base address
+ mrc p15, 0, \rx, c1, c0
+ tst \rx, #1
+ ldreq \rx, =SPRD_UART1_PHYS
+ ldrne \rx, =SPRD_UART1_BASE
+ .endm
+
+ .macro senduart,rd,rx
+ and \rd,\rd,#0xFF
+ str \rd, [\rx, #0x00]
+ .endm
+
+ .macro waituart,rd,rx
+ @ wait for TX_READY
+1: ldr \rd, [\rx, #0x0C]
+ mov \rd,\rd,lsr #8
+ and \rd,\rd,#0xFF
+ teq \rd, #0x00
+ bne 1b
+ .endm
+
+ .macro busyuart,rd,rx
+ @ wait for TX_READY
+2: ldr \rd, [\rx, #0x0C]
+ mov \rd,\rd,lsr #8
+ and \rd,\rd,#0xFF
+ teq \rd, #0x00
+ bne 2b
+ .endm
+
+ .text
+ .align 12
+ENTRY(sc8830_standby_iram)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+sprd_pm_mapped_va:
+ /* disable uart1 for idle deep sleep */
+ adr r0, REG_UART1_ENABLE_PHY
+ ldr r0, [r0]
+ ldr r1, [r0]
+ bic r1, #REG_UART1_ENABLE_BIT
+ str r1, [r0]
+/*
+ ldr r0, =sprd_pm_va_to_iram
+ ldr r1, =sc8830_standby_iram
+ ldr r2, =iram_start
+ ldr r3, [r2, #0]
+ sub r0, r0, r1
+ add r0, r0, r3
+ mov pc, r0
+sprd_pm_va_to_iram:
+*/
+ nop
+ nop
+ nop
+ dsb
+ isb
+#if 0
+4: b 4b
+#endif
+ nop
+ nop
+ nop
+ dsb
+ isb
+#if !(defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L))
+ wfi
+ mov r0, #0x400
+ /*wait 60us for clk is safely closed*/
+5: sub r0, r0, #0x1
+ cmp r0, #0x0
+ bne 5b
+#else
+#ifndef CONFIG_ARCH_SCX20
+// Pike doesn't have this register
+ ldr r0, =PUB_ACCESS_SET
+ mov r1, #0x1
+ str r1, [r0]
+ dsb
+ isb
+ nop
+ nop
+ nop
+#endif
+
+ /*enable icache*/
+ mrc p15, 0, r0, c1, c0, 0
+ orr r0, r0, #0x1000
+ mcr p15, 0, r0, c1, c0, 0
+
+#ifdef CONFIG_ARCH_SCX35L
+check_pub_fsm_wfi0:
+ ldr r0,=0x402b00c4
+check_pub_fsm_wfi:
+ ldr r2,[r0]
+ ldr r1,[r0]
+ and r2,r2,#0x0f000000
+ and r1,r1,#0x0f000000
+ cmp r2,#0x0
+ bne check_pub_fsm_wfi
+ cmp r1,#0x0
+ bne check_pub_fsm_wfi
+
+ ldr r0,=LPDDR2_PHY_STATUS
+ ldr r1,[r0]
+ tst r1,#0x1
+ beq check_pub_fsm_wfi0
+ dsb
+ isb
+#endif
+
+ /* NOTE: here is some trick code, please pay more attention.
+ * Put some code which before "wfi" executed into I-cache to avoid
+ * some hang-up problems caused by FSM of chip-sleep.
+ * When code after "wfi" is executing, it means that cpu can not be
+ * powerred off. So we delay some time(must longer than 6*RTC_cycle,
+ * wait for FSM) in I-cache to avoid some access out of cpu.
+ */
+ mov r1, #0x0
+9: cmp r1, #0x1
+ wfieq
+ movne r0, #0x1
+ moveq r0, #0x30000
+ /*wait 250us for clk is safely closed*/
+5: sub r0, r0, #0x1
+ cmp r0, #0x0
+ bne 5b
+
+ add r1, r1, #0x1
+ cmp r1, #0x1
+ beq 9b
+ dsb
+ isb
+
+#ifdef CONFIG_ARCH_SCX35L
+check_pub_fsm_wake0:
+ ldr r0,=0x402b00c4
+check_pub_fsm_wake:
+ ldr r2,[r0]
+ ldr r1,[r0]
+ and r2,r2,#0x0f000000
+ and r1,r1,#0x0f000000
+ cmp r2,#0x0
+ bne check_pub_fsm_wake
+ cmp r1,#0x0
+ bne check_pub_fsm_wake
+ ldr r0,=LPDDR2_PHY_STATUS
+ ldr r1,[r0]
+ tst r1,#0x1
+ beq check_pub_fsm_wake0
+
+ ldr r0, =PUB_ACCESS_CLR
+ mov r1, #0x1
+ str r1, [r0]
+#endif
+
+ /*disable icache*/
+ mrc p15, 0, r0, c1, c0, 0
+ bic r0, r0, #0x1000
+ mcr p15, 0, r0, c1, c0, 0
+#endif
+#if 0
+6: b 6b
+#endif
+ ldr r0, =REG_AON_APB_EB0_PHY
+ ldr r1, [r0]
+ orr r1, #JTAG_DAP_EN
+ str r1, [r0]
+ adr r0, REG_UART1_ENABLE_PHY
+ ldr r0, [r0]
+ ldr r1, [r0]
+ orr r1, #REG_UART1_ENABLE_BIT
+ str r1, [r0]
+#if 0
+ adr r0, REG_UART1_ENABLE_VIR
+ ldr r0, [r0]
+ ldr r1, [r0]
+ orr r1, r1, #REG_UART1_ENABLE_BIT
+ str r1, [r0]
+ ldr r0, =UART1_CKD_VIR
+ ldr r1, =0xe2
+ str r1, [r0]
+ ldr r0, =UART1_CTR0_VIR
+ ldr r1, =0x1c
+ str r1, [r0]
+loopq:
+ mov r0, #'B'
+ bl printch
+ b loopq
+
+#endif
+ mov r0, #0
+ mov pc, lr
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+#endif
+ENDPROC(sc8830_standby_iram)
+
+ENTRY(sc8830_standby_exit_iram)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ /*delay on 26MHZ in IRAM*/
+ mov r0, #0x100
+delay_after_reset:
+ sub r0, r0, #0x1
+ cmp r0, #0x0
+ bne delay_after_reset
+
+ bl uart1_en
+
+ /*enable DAP for debug*/
+ ldr r0, =REG_AON_APB_EB0_PHY
+ ldr r1, [r0]
+ orr r1, #JTAG_DAP_EN
+ str r1, [r0]
+
+ mrs r0, cpsr
+ and r0, r0, #0xff
+ mov r6, r0
+ ldr r1, =0x00d3
+ cmp r0, r1
+ msr cpsr_c, r1
+
+ ldr r0, =0x20
+ bl my_delay
+#ifdef CONFIG_ARCH_SCX20
+/* pub_reinit_info */
+ ldr r0, =0x50002400
+ ldr r7, [r0, #0x4] // ap_access_reg_addr
+ ldr r1, [r0, #0x18] // sw_reinit_enable
+ ldr r6, [r0, #0x14] // complete_flag
+
+/* PWR_STATUS3_DBG */
+ ldr r2, =0x402b00c8
+ ldr r3, =0x000000f0
+
+ cmp r1, #1
+ bne pub_reinit_complete // This is hw_reinit and we directly go to pub_reinit_complete
+
+/* sw_reinit */
+1:
+ ldr r4, [r2]
+ and r4, r3
+ ldr r5, [r2]
+ and r5, r3
+ cmp r4, r5
+ bne 1b
+
+ cmp r4, #0
+ bne send_ipi
+ ldr r5, [r7]
+ cmp r5, r6
+ beq next
+
+send_ipi:
+ /*send ipi irq1 to cp0*/
+ ldr r4,=0x402c0000
+ mov r5,#0x4
+ str r5,[r4]
+
+/* Polling if pub reinit completed */
+pub_reinit_complete:
+1:
+ ldr r4, [r2]
+ and r4, r3
+ ldr r5, [r2]
+ and r5, r3
+ cmp r4, r5
+ bne 1b
+
+ cmp r4, #0
+ bne 1b
+ ldr r5, [r7]
+ cmp r5, r6
+ bne 1b
+
+next:
+#else
+#ifdef CONFIG_ARCH_SCX30G
+ mov r2, #0x3
+ ldr r0,=0x402b00c8
+check_phy_fsm:
+ ldr r1,[r0]
+ and r1,r1,#0xf000
+ cmp r1,#0x0
+ bne check_phy_fsm
+check_pub_fsm:
+ ldr r1,[r0]
+ and r1,r1,#0x00f0
+ cmp r1,#0x0
+ bne check_pub_fsm
+ sub r2,r2,#0x1
+ cmp r2,#0x0
+ bne check_phy_fsm
+ ldr r0,=0x50002404
+ ldr r0,[r0]
+ ldr r1,[r0]
+ tst r1,#0x1
+ bne next
+ /*send ipi irq1 to cp0*/
+ ldr r0,=0x402c0000
+ mov r1,#0x4
+ str r1,[r0]
+
+1: ldr r0,=0x50002404
+ ldr r0,[r0]
+ ldr r1,[r0]
+ tst r1,#0x1
+ beq 1b
+next:
+#endif
+#endif
+
+#ifdef CONFIG_ARCH_SCX35L
+check_pub_fsm0:
+ ldr r0,=0x402b00c4
+check_pub_fsm:
+ ldr r2,[r0]
+ ldr r1,[r0]
+ and r2,r2,#0x0f000000
+ and r1,r1,#0x0f000000
+ cmp r2,#0x0
+ bne check_pub_fsm
+ cmp r1,#0x0
+ bne check_pub_fsm
+
+ ldr r0,=LPDDR2_PHY_STATUS
+ ldr r1,[r0]
+ tst r1,#0x1
+ beq check_pub_fsm0
+
+ ldr r0, =PUB_ACCESS_CLR
+ mov r1, #0x1
+ str r1, [r0]
+#endif
+
+/* DLL calibration */
+#if 0
+ mov r0,#0x30000004
+ ldr r1,[r0]
+ and r1,r1,#0x7
+ cmp r1,#0x3
+ bne recover_context
+
+ ldr r0,=0x300001b0
+ ldr r1,[r0]
+ bic r1,r1,#0x1
+ str r1,[r0]
+
+ ldr r0,=0x30010004
+ mov r1,#0x21
+ str r1,[r0]
+
+ ldr r0,=0x30010018
+1: ldr r1,[r0]
+ tst r1,#0x1
+ beq 1b
+
+ ldr r0,=0x300001b0
+ ldr r1,[r0]
+ orr r1,r1,#0x1
+ str r1,[r0]
+
+ mov r0,#0x100
+2: sub r0,r0,#0x1
+ cmp r0,#0x0
+ bne 2b
+recover_context:
+#endif
+/* DLL calibration end */
+
+#ifdef CONFIG_DDR_VALIDITY_TEST
+ bl ddr_test
+#endif
+#if 0
+5: b 5b
+#endif
+ #blne dead
+/*
+ adr r0, sleep_asm_counter
+ ldr r1, [r0]
+ add r1, r1, #1
+ str r1, [r0]
+ cmp r1, #32
+ bleq dead
+*/
+
+loopp:
+ adr r0, str_deep
+ bl printascii_phy
+ /*
+ b loopp
+ */
+
+ /*
+ * convert to physical address,
+ * need to find a more elegent way.
+ */
+context_restore:
+ /*
+ ldr r0, =sp_pm_collapse_exit
+ ldr r1, =0xc0000000
+ sub r0, r0, r1
+ ldr r1, =(CONFIG_PHYS_OFFSET)
+ add r0, r0, r1
+ */
+ mov r0, #0x104 /*sp_pm_collapse_exit phy address pointer*/
+ ldr r0, [r0] /*sp_pm_collapse_exit phy address*/
+
+ mov pc, r0
+#endif
+ENDPROC(sc8830_standby_exit_iram)
+
+#ifdef CONFIG_DDR_VALIDITY_TEST
+#ifndef CONFIG_ARCH_SCX30G
+ENTRY(ddr_test)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ddr_start:
+ /* we will test 64kB ddr */
+ mov r8, lr
+ ldr r4, =0x10000
+ ldr r3, =0x12345678
+ ldr r5, =0x87654321
+ mov r0, #0x100
+ ldr r1, [r0]
+ cmp r1, #0x0
+ beq finish
+ add r4, r1
+loop:
+ ldr r2, [r1]
+ teq r2, r3
+ bne bad
+ str r5, [r1]
+ ldr r2, [r1]
+ teq r2, r5
+ str r3, [r1]
+ bne bad
+ add r1, #0x4
+ cmp r1, r4
+ blt loop
+ adr r0, str_y
+ bl printascii_phy
+ b finish
+bad:
+ str r1, [r0]
+ str r2, [r0, #0x4]
+ adr r0, str_n
+ bl printascii_phy
+#b ddr_start
+ b dead
+finish:
+ mov pc, r8
+#endif
+ENDPROC(ddr_test)
+#else
+ENTRY(ddr_test)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+start:
+ /*ddr test, always read till it's right*/
+ ldr r7, =0x12345678
+ mov r8, lr
+ mov r5, #0x100
+ ldr r0, [r5]
+ cmp r0, #0x0
+ beq finish
+ add r6, r0
+ mov r2, #0x0
+loop:
+ ldr r1, [r0]
+ teq r7, r1
+ bne bad
+ mov r6, r2
+ mov r7, r0
+ adr r0, str_y
+ bl printascii_phy
+ b finish
+bad:
+ add r2, #0x1
+ ldr r1, [r0]
+ teq r7, r1
+ bne bad
+ mov r6, r2
+ mov r7, r0
+ adr r0, str_n
+ bl printascii_phy
+finish:
+ str r6, [r7, #0x4]
+ mov pc, r8
+#endif
+ENDPROC(ddr_test)
+#endif
+#endif //CONFIG_DDR_VALIDITY_TEST
+
+ENTRY(uart1_en)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ adr r0, REG_UART1_ENABLE_PHY
+ ldr r0, [r0]
+ ldr r1, [r0]
+ orr r1, r1, #REG_UART1_ENABLE_BIT
+ str r1, [r0]
+ ldr r0, =UART1_CKD_PHY
+ ldr r1, =0xe2
+ str r1, [r0]
+ ldr r0, =UART1_CTR0_PHY
+ ldr r1, =0x1c
+ str r1, [r0]
+ mov pc, lr
+#endif
+ENDPROC(uart1_en)
+
+ENTRY(printascii_phy)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ addruart r3
+ b 2f
+1: waituart r2, r3
+ senduart r1, r3
+ busyuart r2, r3
+ teq r1, #'\n'
+ moveq r1, #'\r'
+ beq 1b
+2: teq r0, #0
+ ldrneb r1, [r0], #1
+ teqne r1, #0
+ bne 1b
+ mov pc, lr
+#endif
+ENDPROC(printascii_phy)
+
+printch:
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ addruart r3
+ mov r1, r0
+ mov r0, #0
+ b 1b
+#endif
+ENTRY(my_delay)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+1: sub r0, r0, #0x1
+ cmp r0, #0x0
+ bne 1b
+ mov pc, lr
+#endif
+ENDPROC(my_delay)
+
+ENTRY(dead)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+1: adr r0, str_err
+ bl printascii_phy
+ ldr r0, =0x000fffff
+ bl my_delay
+
+ b 1b
+#endif
+ENDPROC(dead)
+
+ .globl sleep_asm_counter
+sleep_asm_counter:
+ .long 0x0
+
+str_y: .asciz "Y!\n"
+str_n: .asciz "N!\n"
+str_deep: .asciz "from deep!"
+str_err: .asciz "DDR ERR!"
+
+reg_aon_apb_eb0_vir: .long 0
+reg_aon_apb_eb0_phy: .long 0
+reg_uart1_enable_vir: .long 0
+reg_uart1_enable_phy: .long 0
+
+.globl reg_aon_apb_eb0_vir
+.globl reg_aon_apb_eb0_phy
+.globl reg_uart1_enable_vir
+.globl reg_uart1_enable_phy
+
+#if 0
+ENTRY(sc8830_standby_iram_end)
+
+ENTRY(sp_arch_idle)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ wfi
+ nop
+ nop
+ nop
+ bx lr
+#endif
+ENDPROC(sp_arch_idle)
+#endif
+
+ENTRY(sp_pm_collapse)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ mov r3, r1
+ ldr r0, =sp_saved_state /* address of sp_saved_state ptr */
+#if (CONFIG_NR_CPUS >= 2)
+ mrc p15, 0, r1, c0, c0, 5 /* MPIDR */
+ ands r1, r1, #15 /* What CPU am I */
+ mov r2, #CPU_SAVED_STATE_SIZE
+ mul r1, r1, r2
+ add r0, r0, r1
+#endif
+
+ stmia r0!, {r4-r14}
+#if 0
+ mov r5, r0
+ cmp r3, #1
+ bne real_deep
+ adr r0, str_a6
+ bl printascii_phy
+ b go_on
+real_deep:
+ adr r0, str_a7
+ bl printascii_phy
+go_on:
+ mov r0, r5
+#endif
+#if 0
+2: b 2b
+#endif
+ mrc p15, 0, r1, c1, c0, 0 /* MMU control */
+ mrc p15, 0, r2, c2, c0, 0 /* TTBR0 */
+ mrc p15, 0, r3, c3, c0, 0 /* dacr */
+ mrc p15, 0, r5, c10, c2, 0 /* PRRR */
+ mrc p15, 0, r6, c10, c2, 1 /* NMRR */
+ mrc p15, 0, r7, c1, c0, 1 /* ACTLR */
+ mrc p15, 0, r8, c2, c0, 1 /* TTBR1 */
+ mrc p15, 0, r9, c13, c0, 3 /* TPIDRURO */
+ mrc p15, 0, r10, c13, c0, 4 /* TPIDRURW */
+ mrc p15, 0, ip, c13, c0, 1 /* context ID */
+ stmia r0!, {r1-r10, ip}
+
+ ldr r0, =v7_flush_dcache_all
+ blx r0
+#if 0
+ blx outer_flush_all
+ blx outer_disable
+#endif
+ dmb
+
+ mrc p15, 0, r0, c0, c0, 5 /* MPIDR */
+ and r0, r0, #15 /* what CPU am I */
+
+ mrc p15, 0, r4, c1, c0, 0 /* read current CR */
+ bic r0, r4, #(1 << 2) /* clear dcache bit */
+ bic r0, r0, #(1 << 12) /* clear icache bit */
+ mcr p15, 0, r0, c1, c0, 0 /* disable d/i cache */
+ isb
+
+#if (defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L))
+ ldr r1, =v7_flush_icache_all
+ blx r1
+#endif
+ mrc p15, 0, r4, c1, c0, 0 /* read current CR */
+ bic r0, r4, #(1 << 0) /* disable MMU */
+ mcr p15, 0, r0, c1, c0, 0 /* disable MMU */
+
+ ldr r1, =SLEEP_RESUME_CODE_PHYS /*jump to sc8830_standby_iram*/
+ blx r1
+
+ dsb
+ isb
+ mrc p15, 0, r1, c1, c0, 0 /* read current CR */
+ orr r0, r1, #(1 << 0) /* enable MMU */
+ mcr p15, 0, r0, c1, c0, 0 /* enable MMU */
+ isb
+
+ mcr p15, 0, r4, c1, c0, 0 /* restore d/i cache */
+ isb
+
+ mrc p15, 0, r0, c0, c0, 5 /* MPIDR */
+ and r0, r0, #15 /* what CPU am I */
+
+#if 0
+ blx outer_resume
+#endif
+ dmb
+
+ ldr r0, =sp_saved_state /* address of sp_saved_state ptr */
+#if (CONFIG_NR_CPUS >= 2)
+ mrc p15, 0, r1, c0, c0, 5 /* MPIDR */
+ ands r1, r1, #15 /* What CPU am I */
+ mov r2, #CPU_SAVED_STATE_SIZE
+ mul r2, r2, r1
+ add r0, r0, r2
+#endif
+ ldmfd r0, {r4-r14} /* restore registers */
+ mov r0, #0 /* return power collapse failed */
+ bx lr
+#endif
+ENDPROC(sp_pm_collapse)
+str_a6: .asciz "idle deep\n"
+str_a7: .asciz "real deep\n"
+ENTRY(sp_pm_collapse_exit)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ mov r0, #'Z'
+ bl printch
+ ldr r1, =sp_saved_state
+ ldr r2, =sp_pm_collapse_exit
+ adr r3, sp_pm_collapse_exit
+ add r1, r1, r3
+ sub r1, r1, r2
+ add r1, r1, #CPU_SAVED_STATE_SIZE
+#if (CONFIG_NR_CPUS >= 2)
+ mrc p15, 0, r2, c0, c0, 5 /* MPIDR */
+ ands r2, r2, #15 /* What CPU am I */
+ mov r3, #CPU_SAVED_STATE_SIZE
+ mul r2, r2, r3
+ add r1, r1, r2
+#endif
+
+ ldmdb r1!, {r2-r12}
+ mcr p15, 0, r4, c3, c0, 0 /* dacr */
+ mcr p15, 0, r3, c2, c0, 0 /* TTBR0 */
+ mcr p15, 0, r6, c10, c2, 0 /* PRRR */
+ mcr p15, 0, r7, c10, c2, 1 /* NMRR */
+ mcr p15, 0, r8, c1, c0, 1 /* ACTLR */
+ mcr p15, 0, r9, c2, c0, 1 /* TTBR1 */
+ mcr p15, 0, r10, c13, c0, 3 /* TPIDRURO */
+ mcr p15, 0, r11, c13, c0, 4 /* TPIDRURW */
+ mcr p15, 0, r12, c13, c0, 1 /* context ID */
+ isb
+ ldmdb r1!, {r4-r14}
+
+#if 0
+3: b 3b
+ mov r0, #'Y'
+ bl printch
+#endif
+
+ /* Add 1:1 map in the PMD to allow smooth switch when turning on MMU */
+ and r3, r3, #~0x7F /* mask off lower 7 bits of TTB */
+ adr r0, sp_pm_mapped_pa /* get address of the mapped instr */
+ lsr r1, r0, #20 /* get the addr range of addr in MB */
+ lsl r1, r1, #2 /* multiply by 4 to get to the pg index */
+ add r3, r3, r1 /* pgd + pgd_index(addr) */
+ ldr r1, [r3] /* save current entry to r1 */
+ lsr r0, #20 /* align current addr to 1MB boundary */
+ lsl r0, #20
+ /* Create new entry for this 1MB page */
+ orr r0, r0, #0x4 /* PMD_SECT_BUFFERED */
+ orr r0, r0, #0x400 /* PMD_SECT_AP_WRITE */
+ orr r0, r0, #0x2 /* PMD_TYPE_SECT|PMD_DOMAIN(DOMAIN_KERNEL) */
+ str r0, [r3] /* put new entry into the MMU table */
+ mcr p15, 0, r3, c7, c10, 1 /* flush_pmd */
+ dsb
+ isb
+ mcr p15, 0, r2, c1, c0, 0 /* MMU control */
+ isb
+sp_pm_mapped_pa:
+ /* Switch to virtual */
+ adr r2, sp_pm_pa_to_va
+ ldr r0, =sp_pm_pa_to_va
+ mov pc, r0
+sp_pm_pa_to_va:
+ sub r0, r0, r2
+ /* Restore r1 in MMU table */
+ add r3, r3, r0
+ str r1, [r3]
+ mcr p15, 0, r3, c7, c10, 1 /* flush_pmd */
+ dsb
+ isb
+ mcr p15, 0, r3, c8, c7, 0 /* UTLBIALL */
+ mcr p15, 0, r3, c7, c5, 6 /* BPIALL */
+ dsb
+ isb
+#ifdef CONFIG_FIX_V7TAGRAM_BUG
+ stmfd sp!, {lr}
+ ldr r0, =physical_from_idle
+ mov r1, #0
+ str r1, [r0]
+ bl fix_tag_ram_bug
+ ldmfd sp!, {lr}
+#endif
+
+#if 0
+ stmfd sp!, {lr}
+ blx outer_resume
+ dmb
+ ldmfd sp!, {lr}
+#endif
+#if 0
+2: b 2b
+#endif
+ mov r0, #1
+ bx lr
+ nop
+ nop
+ nop
+ nop
+ nop
+1: b 1b
+#endif
+ENDPROC(sp_pm_collapse_exit)
+ENTRY(sc8830_standby_iram_end)
+
+ .data
+sp_saved_state:
+ .space CPU_SAVED_STATE_SIZE * CONFIG_NR_CPUS
+
+ENTRY(sp_arch_idle)
+#if !(defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+ wfi
+ nop
+ nop
+ nop
+ bx lr
+#endif
+ENDPROC(sp_arch_idle)
diff --git a/drivers/platform/sprd/sys_reset.c b/drivers/platform/sprd/sys_reset.c
new file mode 100755
index 00000000..1f6bc437
--- /dev/null
+++ b/drivers/platform/sprd/sys_reset.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <soc/sprd/sys_reset.h>
+#include <linux/string.h>
+#include <asm/bitops.h>
+
+void sprd_set_reboot_mode(const char *cmd)
+{
+ if(cmd)
+ printk("sprd_set_reboot_mode:cmd=%s\n",cmd);
+ if (cmd && !(strncmp(cmd, "recovery", 8))) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_RECOVERY);
+ } else if (cmd && !strncmp(cmd, "alarm", 5)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_ALARM);
+ } else if (cmd && !strncmp(cmd, "fastsleep", 9)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_SLEEP);
+ } else if (cmd && !strncmp(cmd, "bootloader", 10)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_FASTBOOT);
+ } else if (cmd && !strncmp(cmd, "panic", 5)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_PANIC);
+ } else if (cmd && !strncmp(cmd, "special", 7)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_SPECIAL);
+ } else if (cmd && !strncmp(cmd, "cftreboot", 9)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_CFTREBOOT);
+ } else if (cmd && !strncmp(cmd, "autodloader", 11)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_AUTODLOADER);
+ } else if (cmd && !strncmp(cmd, "iqmode", 6)) {
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_IQMODE);
+ } else if(cmd){
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_NORMAL);
+ }else{
+ sci_adi_raw_write(ANA_RST_STATUS, HWRST_STATUS_SPECIAL);
+ }
+}
+
+void sprd_turnon_watchdog(unsigned int ms)
+{
+ uint32_t cnt;
+
+ cnt = (ms * 1000) / WDG_CLK;
+
+ /*enable interface clk*/
+ sci_adi_set(ANA_AGEN, AGEN_WDG_EN);
+ /*enable work clk*/
+ sci_adi_set(ANA_RTC_CLK_EN, AGEN_RTC_WDG_EN);
+ sci_adi_raw_write(WDG_LOCK, WDG_UNLOCK_KEY);
+ sci_adi_set(WDG_CTRL, WDG_NEW_VER_EN);
+ WDG_LOAD_TIMER_VALUE(cnt);
+ sci_adi_set(WDG_CTRL, WDG_CNT_EN_BIT | WDG_RST_EN_BIT);
+ sci_adi_raw_write(WDG_LOCK, (uint16_t) (~WDG_UNLOCK_KEY));
+}
+
+void sprd_turnoff_watchdog(void)
+{
+ sci_adi_raw_write(WDG_LOCK, WDG_UNLOCK_KEY);
+ /*wdg counter stop*/
+ sci_adi_clr(WDG_CTRL, WDG_CNT_EN_BIT);
+ /*disable the reset mode*/
+ sci_adi_clr(WDG_CTRL, WDG_RST_EN_BIT);
+ sci_adi_raw_write(WDG_LOCK, (uint16_t) (~WDG_UNLOCK_KEY));
+ /*stop the interface and work clk*/
+ sci_adi_clr(ANA_AGEN, AGEN_WDG_EN);
+ sci_adi_clr(ANA_RTC_CLK_EN, AGEN_RTC_WDG_EN);
+}
diff --git a/drivers/platform/sprd/sysdump64.c b/drivers/platform/sprd/sysdump64.c
new file mode 100644
index 00000000..fdb207e7
--- /dev/null
+++ b/drivers/platform/sprd/sysdump64.c
@@ -0,0 +1,741 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/elf.h>
+#include <linux/elfcore.h>
+#include <linux/init.h>
+#include <linux/highuid.h>
+#include <linux/sched.h>
+#include <linux/sysctl.h>
+#include <asm/memory.h>
+#include <asm/cacheflush.h>
+
+#include <soc/sprd/sci_glb_regs.h>
+#include "sysdump64.h"
+
+#define CORE_STR "CORE"
+#ifndef ELF_CORE_EFLAGS
+#define ELF_CORE_EFLAGS 0
+#endif
+
+#define SYSDUMP_MAGIC "SPRD_SYSDUMP_119"
+/* FIXME: come from u-boot */
+#define SPRD_SYSDUMP_MAGIC (0x80000000 + 0x20000000 - (1024<<10))
+
+#define SYSDUMP_NOTE_BYTES (ALIGN(sizeof(struct elf_note), 4) + \
+ ALIGN(sizeof(CORE_STR), 4) + \
+ ALIGN(sizeof(struct elf_prstatus), 4))
+
+typedef char note_buf_t[SYSDUMP_NOTE_BYTES];
+
+note_buf_t __percpu *crash_notes;
+
+/* An ELF note in memory */
+struct memelfnote
+{
+ const char *name;
+ int type;
+ unsigned int datasz;
+ void *data;
+};
+
+struct sysdump_info {
+ char magic[16];
+ char time[32];
+ char dump_path[128];
+ int elfhdr_size;
+ int mem_num;
+ unsigned long dump_mem_paddr;
+ int crash_key;
+};
+
+struct sysdump_extra {
+ int enter_id;
+ int enter_cpu;
+ char reason[256];
+ struct pt_regs cpu_context[CONFIG_NR_CPUS];
+};
+
+struct sysdump_config {
+ int enable;
+ int crashkey_only;
+ int dump_modem;
+ int reboot;
+ char dump_path[128];
+};
+
+static struct sysdump_info *sprd_sysdump_info = NULL;
+
+/* must be global to let gdb know */
+struct sysdump_extra sprd_sysdump_extra = {
+ .enter_id = -1,
+ .enter_cpu = -1,
+ .reason = {0},
+};
+
+static struct sysdump_config sysdump_conf = {
+ .enable = 1,
+ .crashkey_only = 0,
+ .dump_modem = 1,
+ .reboot = 1,
+ .dump_path = "",
+};
+
+#ifdef CONFIG_SPRD_SYSDUMP
+/*FIXME:
+ the file nodes related to resered memory from DT has not been created,
+ so we just use hard code here temporarily.
+*/
+struct sysdump_mem sprd_dump_mem[] = {
+ {
+ .paddr = 0x80000000,
+ .soff = 0xffffffff,
+ .size = 128*1024*1024,
+ .type = SYSDUMP_RAM,
+ },
+ { /* GGE modem */
+ .paddr = 0x80000000 + 128*1024*1024,
+ .soff = 0xffffffff,
+ .size = 22*1024*1024,
+ .type = SYSDUMP_RAM,
+ },
+ { /* LTE modem */
+ .paddr = 0x80000000 + 150*1024*1024,
+ .soff = 0xffffffff,
+ .size = 80*1024*1024,
+ .type = SYSDUMP_RAM,
+ },
+ { /* LEFT mem */
+ .paddr = 0x80000000 + 230*1024*1024,
+ .soff = 0xffffffff,
+ .size = 0,
+ .type = SYSDUMP_RAM,
+ },
+};
+int sprd_dump_mem_num = ARRAY_SIZE(sprd_dump_mem);
+#endif /* CONFIG_SPRD_SYSDUMP */
+
+static size_t get_elfhdr_size(int nphdr)
+{
+ size_t elfhdr_len;
+
+ elfhdr_len = sizeof(struct elfhdr) +
+ (nphdr + 1)*sizeof(struct elf_phdr) +
+ ((sizeof(struct elf_note)) * 3 +
+ roundup(sizeof(CORE_STR), 4)) +
+ roundup(sizeof(struct elf_prstatus), 4) +
+ roundup(sizeof(struct elf_prpsinfo), 4) +
+ roundup(sizeof(struct task_struct), 4);
+ elfhdr_len = PAGE_ALIGN(elfhdr_len);
+
+ return elfhdr_len;
+}
+
+static int notesize(struct memelfnote *en)
+{
+ int sz;
+
+ sz = sizeof(struct elf_note);
+ sz += roundup((strlen(en->name) + 1), 4);
+ sz += roundup(en->datasz, 4);
+
+ return sz;
+}
+
+static char *storenote(struct memelfnote *men, char *bufp)
+{
+ struct elf_note en;
+
+#define DUMP_WRITE(addr,nr) do { memcpy(bufp,addr,nr); bufp += nr; } while(0)
+
+ en.n_namesz = strlen(men->name) + 1;
+ en.n_descsz = men->datasz;
+ en.n_type = men->type;
+
+ DUMP_WRITE(&en, sizeof(en));
+ DUMP_WRITE(men->name, en.n_namesz);
+
+ /* XXX - cast from long long to long to avoid need for libgcc.a */
+ bufp = (char*) roundup((unsigned long)bufp,4);
+ DUMP_WRITE(men->data, men->datasz);
+ bufp = (char*) roundup((unsigned long)bufp,4);
+
+#undef DUMP_WRITE
+
+ return bufp;
+}
+
+/*
+ * fill up all the fields in prstatus from the given task struct, except
+ * registers which need to be filled up separately.
+ */
+static void fill_prstatus(struct elf_prstatus *prstatus,
+ struct task_struct *p, long signr)
+{
+ prstatus->pr_info.si_signo = prstatus->pr_cursig = signr;
+ prstatus->pr_sigpend = p->pending.signal.sig[0];
+ prstatus->pr_sighold = p->blocked.sig[0];
+ rcu_read_lock();
+ prstatus->pr_ppid = task_pid_vnr(rcu_dereference(p->real_parent));
+ rcu_read_unlock();
+ prstatus->pr_pid = task_pid_vnr(p);
+ prstatus->pr_pgrp = task_pgrp_vnr(p);
+ prstatus->pr_sid = task_session_vnr(p);
+ if ( 0 /* thread_group_leader(p) */) {
+ struct task_cputime cputime;
+
+ /*
+ * This is the record for the group leader. It shows the
+ * group-wide total, not its individual thread total.
+ */
+ /* thread_group_cputime(p, &cputime); */
+ cputime_to_timeval(cputime.utime, &prstatus->pr_utime);
+ cputime_to_timeval(cputime.stime, &prstatus->pr_stime);
+ } else {
+ cputime_to_timeval(p->utime, &prstatus->pr_utime);
+ cputime_to_timeval(p->stime, &prstatus->pr_stime);
+ }
+ cputime_to_timeval(p->signal->cutime, &prstatus->pr_cutime);
+ cputime_to_timeval(p->signal->cstime, &prstatus->pr_cstime);
+}
+
+static int fill_psinfo(struct elf_prpsinfo *psinfo, struct task_struct *p,
+ struct mm_struct *mm)
+{
+ const struct cred *cred;
+ unsigned int i, len;
+
+ /* first copy the parameters from user space */
+ memset(psinfo, 0, sizeof(struct elf_prpsinfo));
+
+ if (mm) {
+ len = mm->arg_end - mm->arg_start;
+ if (len >= ELF_PRARGSZ)
+ len = ELF_PRARGSZ-1;
+ if (copy_from_user(&psinfo->pr_psargs,
+ (const char __user *)mm->arg_start, len))
+ return -EFAULT;
+ for(i = 0; i < len; i++)
+ if (psinfo->pr_psargs[i] == 0)
+ psinfo->pr_psargs[i] = ' ';
+ psinfo->pr_psargs[len] = 0;
+ }
+
+ rcu_read_lock();
+ psinfo->pr_ppid = task_pid_vnr(rcu_dereference(p->real_parent));
+ rcu_read_unlock();
+ psinfo->pr_pid = task_pid_vnr(p);
+ psinfo->pr_pgrp = task_pgrp_vnr(p);
+ psinfo->pr_sid = task_session_vnr(p);
+
+ i = p->state ? ffz(~p->state) + 1 : 0;
+ psinfo->pr_state = i;
+ psinfo->pr_sname = (i > 5) ? '.' : "RSDTZW"[i];
+ psinfo->pr_zomb = psinfo->pr_sname == 'Z';
+ psinfo->pr_nice = task_nice(p);
+ psinfo->pr_flag = p->flags;
+
+ rcu_read_lock();
+ cred = __task_cred(p);
+ SET_UID(psinfo->pr_uid, cred->uid);
+ SET_GID(psinfo->pr_gid, cred->gid);
+ rcu_read_unlock();
+ strncpy(psinfo->pr_fname, p->comm, sizeof(psinfo->pr_fname));
+
+ return 0;
+}
+
+/**
+ * crash_setup_regs() - save registers for the panic kernel
+ * @newregs: registers are saved here
+ * @oldregs: registers to be saved (may be %NULL)
+ *
+ * Function copies machine registers from @oldregs to @newregs. If @oldregs is
+ * %NULL then current registers are stored there.
+ */
+static inline void crash_setup_regs(struct pt_regs *newregs,
+ struct pt_regs *oldregs)
+{
+ unsigned long tmp = 0;
+ if (oldregs) {
+ memcpy(newregs, oldregs, sizeof(*newregs));
+ } else {
+ __asm__ __volatile__ (
+ "stp x0, x1, [%[regs_base], #0x00]\n\t"
+ "stp x2, x3, [%[regs_base], #0x10]\n\t"
+ "stp x4, x5, [%[regs_base], #0x20]\n\t"
+ "stp x6, x7, [%[regs_base], #0x30]\n\t"
+ "stp x8, x9, [%[regs_base], #0x40]\n\t"
+ "stp x10, x11, [%[regs_base], #0x50]\n\t"
+ "stp x12, x13, [%[regs_base], #0x60]\n\t"
+ "stp x14, x15, [%[regs_base], #0x70]\n\t"
+ "stp x16, x17, [%[regs_base], #0x80]\n\t"
+ "stp x18, x19, [%[regs_base], #0x90]\n\t"
+ "stp x20, x21, [%[regs_base], #0x100]\n\t"
+ "stp x22, x23, [%[regs_base], #0x110]\n\t"
+ "stp x24, x25, [%[regs_base], #0x120]\n\t"
+ "stp x26, x27, [%[regs_base], #0x130]\n\t"
+ "stp x28, x29, [%[regs_base], #0x140]\n\t"
+ "mov %[tmp], sp\n\t"
+ "stp x30, %[tmp], [%[regs_base], #0x150]\n\t"
+ "adr %[pc], 1f\n\t"
+ "mrs %[cpsr], nzcv\n\t"
+ "mrs %[tmp], daif\n\t"
+ "orr %[cpsr], %[cpsr], %[tmp]\n\t"
+ "mrs %[tmp], CurrentEL\n\t"
+ "orr %[cpsr], %[cpsr], %[tmp]\n\t"
+ "mrs %[tmp], SPSel\n\t"
+ "orr %[cpsr], %[cpsr], %[tmp]\n\t"
+ "1:"
+ : [pc] "=r" (newregs->pc),
+ [cpsr] "=r" (newregs->pstate),
+ [tmp] "=r" (tmp)
+ : [regs_base] "r" (&newregs->regs)
+ : "memory"
+ );
+ }
+}
+
+void crash_note_save_cpu(struct pt_regs *regs, int cpu)
+{
+ struct elf_prstatus prstatus;
+ struct memelfnote notes;
+
+ notes.name = CORE_STR;
+ notes.type = NT_PRSTATUS;
+ notes.datasz = sizeof(struct elf_prstatus);
+ notes.data = &prstatus;
+
+ memset(&prstatus, 0, sizeof(struct elf_prstatus));
+ fill_prstatus(&prstatus, current, 0);
+ memcpy(&prstatus.pr_reg, regs, sizeof(struct pt_regs));
+ storenote(&notes, per_cpu_ptr(crash_notes, cpu));
+}
+
+static void sysdump_fill_core_hdr(struct pt_regs *regs,
+ struct sysdump_mem *sysmem, int mem_num,
+ char *bufp, int nphdr, int dataoff)
+{
+ struct elf_prstatus prstatus; /* NT_PRSTATUS */
+ struct elf_prpsinfo prpsinfo; /* NT_PRPSINFO */
+ struct elf_phdr *nhdr, *phdr;
+ struct elfhdr *elf;
+ struct memelfnote notes;
+ off_t offset = 0;
+ int i;
+
+ /* setup ELF header */
+ elf = (struct elfhdr *) bufp;
+ bufp += sizeof(struct elfhdr);
+ offset += sizeof(struct elfhdr);
+ memcpy(elf->e_ident, ELFMAG, SELFMAG);
+ elf->e_ident[EI_CLASS] = ELF_CLASS;
+ elf->e_ident[EI_DATA] = ELF_DATA;
+ elf->e_ident[EI_VERSION]= EV_CURRENT;
+ elf->e_ident[EI_OSABI] = ELF_OSABI;
+ memset(elf->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);
+ elf->e_type = ET_CORE;
+ elf->e_machine = ELF_ARCH;
+ elf->e_version = EV_CURRENT;
+ elf->e_entry = 0;
+ elf->e_phoff = sizeof(struct elfhdr);
+ elf->e_shoff = 0;
+ elf->e_flags = ELF_CORE_EFLAGS;
+ elf->e_ehsize = sizeof(struct elfhdr);
+ elf->e_phentsize= sizeof(struct elf_phdr);
+ elf->e_phnum = nphdr;
+ elf->e_shentsize= 0;
+ elf->e_shnum = 0;
+ elf->e_shstrndx = 0;
+
+ /* setup ELF PT_NOTE program header */
+ nhdr = (struct elf_phdr *) bufp;
+ bufp += sizeof(struct elf_phdr);
+ offset += sizeof(struct elf_phdr);
+ nhdr->p_type = PT_NOTE;
+ nhdr->p_offset = 0;
+ nhdr->p_vaddr = 0;
+ nhdr->p_paddr = 0;
+ nhdr->p_filesz = 0;
+ nhdr->p_memsz = SYSDUMP_NOTE_BYTES*NR_CPUS;
+ nhdr->p_flags = 0;
+ nhdr->p_align = 0;
+
+ /* setup ELF PT_LOAD program header for every area */
+ for (i = 0; i < mem_num; i++) {
+ phdr = (struct elf_phdr *) bufp;
+ bufp += sizeof(struct elf_phdr);
+ offset += sizeof(struct elf_phdr);
+
+ phdr->p_type = PT_LOAD;
+ phdr->p_flags = PF_R|PF_W|PF_X;
+ phdr->p_offset = dataoff;
+ phdr->p_vaddr = sysmem[i].vaddr;
+ phdr->p_paddr = sysmem[i].paddr;
+ phdr->p_filesz = phdr->p_memsz = sysmem[i].size;
+ phdr->p_align = 0;//PAGE_SIZE;
+ dataoff += sysmem[i].size;
+ }
+
+ /*
+ * Set up the notes in similar form to SVR4 core dumps made
+ * with info from their /proc.
+ */
+ nhdr->p_offset = offset;
+ nhdr->p_filesz = 0;
+
+ return;
+} /* end elf_kcore_store_hdr() */
+
+static void sysdump_prepare_info(int enter_id, const char *reason,
+ struct pt_regs *regs)
+{
+ unsigned long long t;
+ unsigned long nanosec_rem;
+ int iocnt, i;
+ char *iomem;
+
+ strncpy(sprd_sysdump_extra.reason,
+ reason, sizeof(sprd_sysdump_extra.reason));
+ sprd_sysdump_extra.enter_id = enter_id;
+
+ sprd_sysdump_info = (struct sysdump_info *)phys_to_virt(SPRD_SYSDUMP_MAGIC);
+
+ printk("vaddr is %p,paddr is %p\n",sprd_sysdump_info, (void *)SPRD_SYSDUMP_MAGIC);
+ memcpy(sprd_sysdump_info->magic, SYSDUMP_MAGIC,
+ sizeof(sprd_sysdump_info->magic));
+
+ if (reason != NULL && !strcmp(reason, "Crash Key")) {
+ sprd_sysdump_info->crash_key = 1;
+ } else {
+ sprd_sysdump_info->crash_key = 0;
+ }
+
+ printk("reason: %s, sprd_sysdump_info->crash_key: %d\n", reason, sprd_sysdump_info->crash_key);
+
+ t = cpu_clock(smp_processor_id());
+ nanosec_rem = do_div(t, 1000000000);
+ sprintf(sprd_sysdump_info->time, "%lu.%06lu", (unsigned long)t,
+ nanosec_rem / 1000);
+
+ memcpy(sprd_sysdump_info->dump_path, sysdump_conf.dump_path,
+ sizeof(sprd_sysdump_info->dump_path));
+
+ sprd_sysdump_info->dump_mem_paddr = virt_to_phys(sprd_dump_mem);
+ sprd_sysdump_info->mem_num = sprd_dump_mem_num;
+ sprd_sysdump_info->elfhdr_size = get_elfhdr_size(sprd_sysdump_info->mem_num);
+
+ /* this must before sysdump_fill_core_hdr, it needs size */
+ for (i = 0; i < sprd_dump_mem_num; i++) {
+ if (SYSDUMP_RAM == sprd_dump_mem[i].type && 0 == sprd_dump_mem[i].size)
+ sprd_dump_mem[i].size = PHYS_OFFSET + (num_physpages << PAGE_SHIFT) - sprd_dump_mem[i].paddr;
+
+ if (!sysdump_conf.dump_modem && SYSDUMP_MODEM == sprd_dump_mem[i].type)
+ sprd_dump_mem[i].size = 0;
+ }
+
+ sysdump_fill_core_hdr(regs,
+ sprd_dump_mem,
+ sprd_dump_mem_num,
+ (char *)sprd_sysdump_info + sizeof(*sprd_sysdump_info),
+ sprd_sysdump_info->mem_num + 1,
+ sprd_sysdump_info->elfhdr_size);
+
+ iocnt = 0;
+ for (i = 0; i < sprd_dump_mem_num; i++) {
+ /* save iomem(regiters) to ram, cause they will change while rebooting */
+ if (0xffffffff != sprd_dump_mem[i].soff) {
+ sprd_dump_mem[i].soff = iocnt;
+ iomem = (char *)sprd_sysdump_info + sizeof(*sprd_sysdump_info) +
+ sprd_sysdump_info->elfhdr_size + iocnt;
+ memcpy(iomem,
+ (void const *)(sprd_dump_mem[i].vaddr),
+ sprd_dump_mem[i].size);
+ iocnt += sprd_dump_mem[i].size;
+ }
+ }
+
+ return;
+}
+
+
+struct sprd_debug_mmu_reg_t {
+ unsigned long sctlr_el1;
+ unsigned long ttbr0_el1;
+ unsigned long ttbr1_el1;
+ unsigned long tcr_el1;
+ unsigned long mair_el1;
+ unsigned long amair_el1;
+ unsigned long contextidr_el1;
+};
+
+/* ARM CORE regs mapping structure */
+struct sprd_debug_core_t {
+ /* COMMON */
+ unsigned long x0, x1, x2, x3, x4, x5, x6, x7, x8, x9;
+ unsigned long x10, x11, x12, x13, x14, x15, x16, x17, x18, x19;
+ unsigned long x20, x21, x22, x23, x24, x25, x26, x27, x28, x29;
+ unsigned long x30;
+
+ /* PC & CPSR */
+ unsigned long pc;
+ unsigned long pstate;
+};
+
+DEFINE_PER_CPU(struct sprd_debug_core_t, sprd_debug_core_reg);
+DEFINE_PER_CPU(struct sprd_debug_mmu_reg_t, sprd_debug_mmu_reg);
+
+
+/* core reg dump function*/
+static inline void sprd_debug_save_core_reg(struct sprd_debug_core_t *core_reg)
+{
+ unsigned long tmp;
+ __asm__ __volatile__ (
+ "stp x0, x1, [%[regs_base], #0x00]\n\t"
+ "stp x2, x3, [%[regs_base], #0x10]\n\t"
+ "stp x4, x5, [%[regs_base], #0x20]\n\t"
+ "stp x6, x7, [%[regs_base], #0x30]\n\t"
+ "stp x8, x9, [%[regs_base], #0x40]\n\t"
+ "stp x10, x11, [%[regs_base], #0x50]\n\t"
+ "stp x12, x13, [%[regs_base], #0x60]\n\t"
+ "stp x14, x15, [%[regs_base], #0x70]\n\t"
+ "stp x16, x17, [%[regs_base], #0x80]\n\t"
+ "stp x18, x19, [%[regs_base], #0x90]\n\t"
+ "stp x20, x21, [%[regs_base], #0x100]\n\t"
+ "stp x22, x23, [%[regs_base], #0x110]\n\t"
+ "stp x24, x25, [%[regs_base], #0x120]\n\t"
+ "stp x26, x27, [%[regs_base], #0x130]\n\t"
+ "stp x28, x29, [%[regs_base], #0x140]\n\t"
+ "mov %[tmp], sp\n\t"
+ "stp x30, %[tmp], [%[regs_base], #0x150]\n\t"
+ "adr %[pc], 1f\n\t"
+ "mrs %[cpsr], nzcv\n\t"
+ "mrs %[tmp], daif\n\t"
+ "orr %[cpsr], %[cpsr], %[tmp]\n\t"
+ "mrs %[tmp], CurrentEL\n\t"
+ "orr %[cpsr], %[cpsr], %[tmp]\n\t"
+ "mrs %[tmp], SPSel\n\t"
+ "orr %[cpsr], %[cpsr], %[tmp]\n\t"
+ "stp %[pc], %[cpsr], [%[regs_base], #0x160]\n\t"
+ "1:"
+ : [pc] "=r" (core_reg->pc),
+ [cpsr] "=r" (core_reg->pstate),
+ [tmp] "=r" (tmp)
+ : [regs_base] "r" (core_reg)
+ : "memory"
+ );
+
+ return;
+}
+
+static inline void sprd_debug_save_mmu_reg(struct sprd_debug_mmu_reg_t *mmu_reg)
+{
+ unsigned long tmp = 0;
+
+ asm volatile ("mrs %1, sctlr_el1\n\t"
+ "str %1, [%0]\n\t"
+ "mrs %1, ttbr0_el1\n\t"
+ "str %1, [%0, #8]\n\t"
+ "mrs %1, ttbr1_el1\n\t"
+ "str %1, [%0, #0x10]\n\t"
+ "mrs %1, tcr_el1\n\t"
+ "str %1, [%0, #0x18]\n\t"
+ "mrs %1, mair_el1\n\t"
+ "str %1, [%0, #0x20]\n\t"
+ "mrs %1, amair_el1\n\t"
+ "str %1, [%0, #0x28]\n\t"
+ "mrs %1, contextidr_el1\n\t"
+ "str %1, [%0, #0x30]\n\t"
+ :
+ : "r"(mmu_reg), "r"(tmp)
+ : "%0", "%1"
+ );
+}
+
+static inline void sprd_debug_save_context(void)
+{
+ unsigned long flags;
+ local_irq_save(flags);
+ sprd_debug_save_mmu_reg(&per_cpu(sprd_debug_mmu_reg, smp_processor_id()));
+ sprd_debug_save_core_reg(&per_cpu
+ (sprd_debug_core_reg, smp_processor_id()));
+
+ pr_emerg("(%s) context saved(CPU:%d)\n", __func__, smp_processor_id());
+ local_irq_restore(flags);
+
+ flush_cache_all();
+}
+
+
+extern void emergency_restart(void);
+void sysdump_enter(int enter_id, const char *reason, struct pt_regs *regs)
+{
+ struct pt_regs * pregs;
+ if (!sysdump_conf.enable)
+ return;
+
+ if ((sysdump_conf.crashkey_only) && !(reason != NULL && !strcmp(reason, "Crash Key")))
+ return;
+
+ /* this should before smp_send_stop() to make sysdump_ipi enable */
+ sprd_sysdump_extra.enter_cpu = smp_processor_id();
+
+ pregs = &sprd_sysdump_extra.cpu_context[sprd_sysdump_extra.enter_cpu];
+ if (regs)
+ memcpy(pregs, regs, sizeof(*regs));
+ else
+ crash_setup_regs((struct pt_regs *)pregs, NULL);
+
+ crash_note_save_cpu(pregs, sprd_sysdump_extra.enter_cpu);
+ sprd_debug_save_context();
+
+ smp_send_stop();
+ mdelay(1000);
+
+ printk("\n");
+ printk("*****************************************************\n");
+ printk("* *\n");
+ printk("* Sysdump enter, preparing debug info to dump ... *\n");
+ printk("* *\n");
+ printk("*****************************************************\n");
+ printk("\n");
+
+ flush_cache_all();
+ mdelay(1000);
+
+ sysdump_prepare_info(enter_id, reason, regs);
+
+ flush_cache_all();
+ mdelay(1000);
+
+ printk("\n");
+ printk("*****************************************************\n");
+ printk("* *\n");
+ printk("* Try to reboot system ... *\n");
+ printk("* *\n");
+ printk("*****************************************************\n");
+ printk("\n");
+
+ emergency_restart();
+
+
+ return;
+}
+
+void sysdump_ipi(struct pt_regs *regs)
+{
+ int cpu = smp_processor_id();
+
+ if (sprd_sysdump_extra.enter_cpu != -1)
+ {
+ memcpy((void *)&(sprd_sysdump_extra.cpu_context[cpu]),
+ (void *)regs, sizeof(struct pt_regs));
+ crash_note_save_cpu(regs, cpu);
+ sprd_debug_save_context();
+ }
+ return;
+}
+
+
+static ctl_table sysdump_sysctl_table[] = {
+ {
+ .procname = "sysdump_enable",
+ .data = &sysdump_conf.enable,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+ {
+ .procname = "sysdump_crashkey_only",
+ .data = &sysdump_conf.crashkey_only,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+ {
+ .procname = "sysdump_dump_modem",
+ .data = &sysdump_conf.dump_modem,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+ {
+ .procname = "sysdump_reboot",
+ .data = &sysdump_conf.reboot,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
+ {
+ .procname = "sysdump_dump_path",
+ .data = sysdump_conf.dump_path,
+ .maxlen = sizeof(sysdump_conf.dump_path),
+ .mode = 0644,
+ .proc_handler = proc_dostring,
+ },
+ {}
+};
+
+static ctl_table sysdump_sysctl_root[] = {
+ {
+ .procname = "kernel",
+ .mode = 0555,
+ .child = sysdump_sysctl_table,
+ },
+ {}
+};
+
+
+static struct ctl_table_header *sysdump_sysctl_hdr = NULL;
+
+int sysdump_sysctl_init(void)
+{
+ char *phy_sprd_sysdump_magic;
+ int i;
+
+ for (i = 0; i < sprd_dump_mem_num; i++)
+ sprd_dump_mem[i].vaddr = __va(sprd_dump_mem[i].paddr);
+
+ sysdump_sysctl_hdr = register_sysctl_table(sysdump_sysctl_root);
+ if (!sysdump_sysctl_hdr)
+ return -ENOMEM;
+
+ crash_notes = alloc_percpu(note_buf_t);
+ if (crash_notes == NULL)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void sysdump_sysctl_exit(void)
+{
+ if (sysdump_sysctl_hdr)
+ unregister_sysctl_table(sysdump_sysctl_hdr);
+}
+
+module_init(sysdump_sysctl_init);
+module_exit(sysdump_sysctl_exit);
+
+MODULE_AUTHOR("Jianjun.He <jianjun.he@spreadtrum.com>");
+MODULE_DESCRIPTION("kernel core dump for Spreadtrum");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/sprd/sysdump64.h b/drivers/platform/sprd/sysdump64.h
new file mode 100644
index 00000000..bb78d413
--- /dev/null
+++ b/drivers/platform/sprd/sysdump64.h
@@ -0,0 +1,21 @@
+#ifndef CONFIG_SYSDUMP64_H
+#define CONFIG_SYSDUMP64_H
+
+struct sysdump_mem {
+ unsigned long paddr;
+ unsigned long vaddr;
+ unsigned long soff;
+ size_t size;
+ int type;
+};
+
+enum sysdump_type {
+ SYSDUMP_RAM,
+ SYSDUMP_MODEM,
+ SYSDUMP_IOMEM,
+};
+
+extern int sprd_dump_mem_num;
+extern struct sysdump_mem sprd_dump_mem[];
+
+#endif
diff --git a/drivers/platform/sprd/timer_sc8830.c b/drivers/platform/sprd/timer_sc8830.c
new file mode 100755
index 00000000..fa8b9213
--- /dev/null
+++ b/drivers/platform/sprd/timer_sc8830.c
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/clockchips.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/clocksource.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <asm/sched_clock.h>
+#include <asm/localtimer.h>
+#include <asm/mach/time.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <mach/irqs.h>
+#include <asm/mach/time.h>
+#include <soc/sprd/sprd_persistent_clock.h>
+
+static __iomem void *base_gptimer[4] = {
+ 0,
+};
+
+static int irq_nr[4] = {
+ 0,
+};
+static struct clock_event_device *local_evt[4] = { 0 };
+
+static int e_cpu = 0;
+#define TIMER_LOAD(ind, id) (base_gptimer[ind] + 0x20 * (id) + 0x0000)
+#define TIMER_VALUE(ind, id) (base_gptimer[ind] + 0x20 * (id) + 0x0004)
+#define TIMER_CTL(ind, id) (base_gptimer[ind] + 0x20 * (id) + 0x0008)
+#define TIMER_INT(ind, id) (base_gptimer[ind] + 0x20 * (id) + 0x000C)
+#define TIMER_CNT_RD(ind, id) (base_gptimer[ind] + 0x20 * (id) + 0x0010)
+
+#define ONETIME_MODE (0 << 6)
+#define PERIOD_MODE (1 << 6)
+
+#define TIMER_DISABLE (0 << 7)
+#define TIMER_ENABLE (1 << 7)
+
+#define TIMER_INT_EN (1 << 0)
+#define TIMER_INT_CLR (1 << 3)
+#define TIMER_INT_BUSY (1 << 4)
+#define TIMER_NEW (1 << 8)
+/**
+ * timer0 is used as clockevent,
+ * timer1 of aptimer0 is used as broadcast timer,
+ * timer1 is used as clocksource
+ */
+#define EVENT_TIMER 0
+#define BC_TIMER 1
+#define SOURCE_TIMER 1
+
+#define BC_CPU 1
+static int BC_IRQ = 0;
+
+static __iomem void *base_syscnt = (__iomem void *)NULL;
+
+#define SYSCNT_ALARM (base_syscnt + 0x0000)
+#define SYSCNT_COUNT (base_syscnt + 0x0004)
+#define SYSCNT_CTL (base_syscnt + 0x0008)
+#define SYSCNT_SHADOW_CNT (base_syscnt + 0x000C)
+
+static int sched_clock_source_freq;
+static int gptimer_clock_source_freq;
+
+/*only for 2713 charger*/
+int sprd_request_timer(int timer_id, int sub_id, unsigned long *base)
+{
+ if (1 == timer_id && 1 == sub_id) {
+ *base = (unsigned long)(base_gptimer[2] + 0x20);
+ return 0;
+ } else
+ return -1;
+}
+
+static inline void __gptimer_ctl(int cpu, int timer_id, int enable, int mode)
+{
+ __raw_writel(enable | mode, TIMER_CTL(cpu, timer_id));
+}
+
+static int __gptimer_set_next_event(unsigned long cycles,
+ struct clock_event_device *c)
+{
+ int cpu = smp_processor_id();
+
+ while (TIMER_INT_BUSY & __raw_readl(TIMER_INT(cpu, EVENT_TIMER))) ;
+ __gptimer_ctl(cpu, EVENT_TIMER, TIMER_DISABLE, ONETIME_MODE);
+ __raw_writel(cycles, TIMER_LOAD(cpu, EVENT_TIMER));
+ __gptimer_ctl(cpu, EVENT_TIMER, TIMER_ENABLE, ONETIME_MODE);
+ return 0;
+}
+
+static void __gptimer_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *c)
+{
+ unsigned int saved;
+ int cpu = smp_processor_id();
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ __gptimer_ctl(cpu, EVENT_TIMER, TIMER_DISABLE, PERIOD_MODE);
+ __raw_writel(LATCH, TIMER_LOAD(cpu, EVENT_TIMER));
+ __gptimer_ctl(cpu, EVENT_TIMER, TIMER_ENABLE, PERIOD_MODE);
+ __raw_writel(TIMER_INT_EN, TIMER_INT(cpu, EVENT_TIMER));
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ __raw_writel(LATCH, TIMER_LOAD(cpu, EVENT_TIMER));
+ __gptimer_ctl(cpu, EVENT_TIMER, TIMER_ENABLE, ONETIME_MODE);
+ __raw_writel(TIMER_INT_EN, TIMER_INT(cpu, EVENT_TIMER));
+ break;
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_UNUSED:
+ __raw_writel(TIMER_INT_CLR, TIMER_INT(cpu, EVENT_TIMER));
+ saved = __raw_readl(TIMER_CTL(cpu, EVENT_TIMER)) & PERIOD_MODE;
+ __gptimer_ctl(cpu, EVENT_TIMER, TIMER_DISABLE, saved);
+ break;
+ case CLOCK_EVT_MODE_RESUME:
+ saved = __raw_readl(TIMER_CTL(cpu, EVENT_TIMER)) & PERIOD_MODE;
+ __gptimer_ctl(cpu, EVENT_TIMER, TIMER_ENABLE, saved);
+ break;
+ }
+}
+
+static int __bctimer_set_next_event(unsigned long cycles,
+ struct clock_event_device *c)
+{
+ while (TIMER_INT_BUSY & __raw_readl(TIMER_INT(BC_CPU, BC_TIMER))) ;
+ __gptimer_ctl(BC_CPU, BC_TIMER, TIMER_DISABLE, ONETIME_MODE);
+ __raw_writel(cycles, TIMER_LOAD(BC_CPU, BC_TIMER));
+ __gptimer_ctl(BC_CPU, BC_TIMER, TIMER_ENABLE, ONETIME_MODE);
+ return 0;
+}
+
+static void __bctimer_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *c)
+{
+ unsigned int saved;
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ __gptimer_ctl(BC_CPU, BC_TIMER, TIMER_DISABLE, PERIOD_MODE);
+ __raw_writel(LATCH, TIMER_LOAD(BC_CPU, BC_TIMER));
+ __gptimer_ctl(BC_CPU, BC_TIMER, TIMER_ENABLE, PERIOD_MODE);
+ __raw_writel(TIMER_INT_EN, TIMER_INT(BC_CPU, BC_TIMER));
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ __raw_writel(LATCH, TIMER_LOAD(BC_CPU, BC_TIMER));
+ __gptimer_ctl(BC_CPU, BC_TIMER, TIMER_ENABLE, ONETIME_MODE);
+ __raw_writel(TIMER_INT_EN, TIMER_INT(BC_CPU, BC_TIMER));
+ break;
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_UNUSED:
+ __raw_writel(TIMER_INT_CLR, TIMER_INT(BC_CPU, BC_TIMER));
+ saved = __raw_readl(TIMER_CTL(BC_CPU, BC_TIMER)) & PERIOD_MODE;
+ __gptimer_ctl(BC_CPU, BC_TIMER, TIMER_DISABLE, saved);
+ break;
+ case CLOCK_EVT_MODE_RESUME:
+ saved = __raw_readl(TIMER_CTL(BC_CPU, BC_TIMER)) & PERIOD_MODE;
+ __gptimer_ctl(BC_CPU, BC_TIMER, TIMER_ENABLE, saved);
+ break;
+ }
+}
+
+static struct clock_event_device bctimer_event = {
+ .features = CLOCK_EVT_FEAT_ONESHOT,
+ .shift = 32,
+ .rating = 150,
+ .set_next_event = __bctimer_set_next_event,
+ .set_mode = __bctimer_set_mode,
+};
+
+static irqreturn_t __gptimer_interrupt(int irq, void *dev_id);
+#ifdef CONFIG_LOCAL_TIMERS
+#if !defined (CONFIG_HAVE_ARM_ARCH_TIMER)
+static int __cpuinit sprd_local_timer_setup(struct clock_event_device *evt)
+{
+ int cpu = smp_processor_id();
+
+ evt->irq = irq_nr[cpu];
+ evt->name = "local_timer";
+ evt->features = CLOCK_EVT_FEAT_ONESHOT;
+ evt->rating = 200;
+ evt->set_mode = __gptimer_set_mode;
+ evt->set_next_event = __gptimer_set_next_event;
+ evt->shift = 32;
+ evt->mult = div_sc(32768, NSEC_PER_SEC, evt->shift);
+ evt->max_delta_ns = clockevent_delta2ns(0xf0000000, evt);
+ evt->min_delta_ns = clockevent_delta2ns(4, evt);
+
+ local_evt[cpu] = evt;
+ irq_set_affinity(evt->irq, cpumask_of(cpu));
+
+ clockevents_register_device(evt);
+ return 0;
+}
+
+static void sprd_local_timer_stop(struct clock_event_device *evt)
+{
+ evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt);
+}
+
+static struct local_timer_ops sprd_local_timer_ops __cpuinitdata = {
+ .setup = sprd_local_timer_setup,
+ .stop = sprd_local_timer_stop,
+};
+#endif
+#endif /* CONFIG_LOCAL_TIMERS */
+
+static irqreturn_t __gptimer_interrupt(int irq, void *dev_id)
+{
+ unsigned int value;
+ int cpu = smp_processor_id();
+ struct clock_event_device **evt = dev_id;
+
+ value = __raw_readl(TIMER_INT(cpu, EVENT_TIMER));
+ value |= TIMER_INT_CLR;
+ __raw_writel(value, TIMER_INT(cpu, EVENT_TIMER));
+
+ if (evt[cpu]->event_handler)
+ evt[cpu]->event_handler(evt[cpu]);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t __bctimer_interrupt(int irq, void *dev_id)
+{
+ unsigned int value;
+ struct clock_event_device *evt = dev_id;
+
+ value = __raw_readl(TIMER_INT(BC_CPU, BC_TIMER));
+ value |= TIMER_INT_CLR;
+ __raw_writel(value, TIMER_INT(BC_CPU, BC_TIMER));
+
+ if (evt->event_handler)
+ evt->event_handler(evt);
+
+ return IRQ_HANDLED;
+}
+
+static struct irqaction bctimer_irq = {
+ .name = "bctimer",
+ .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
+ .handler = __bctimer_interrupt,
+ .dev_id = &bctimer_event,
+};
+
+static void sprd_gptimer_clockevent_init(unsigned int irq, const char *name,
+ unsigned long hz)
+{
+ struct clock_event_device *evt = &bctimer_event;
+ int ret = 0;
+
+ __raw_writel(TIMER_DISABLE, TIMER_CTL(BC_CPU, BC_TIMER));
+ __raw_writel(TIMER_INT_CLR, TIMER_INT(BC_CPU, BC_TIMER));
+
+ evt->name = name;
+ evt->irq = irq;
+ evt->mult = div_sc(hz, NSEC_PER_SEC, evt->shift);
+ evt->max_delta_ns = clockevent_delta2ns(ULONG_MAX, evt);
+ evt->min_delta_ns = clockevent_delta2ns(2, evt);
+ evt->cpumask = cpu_all_mask;
+
+ ret = setup_irq(irq, &bctimer_irq);
+ if (ret)
+ BUG_ON(1);
+ clockevents_register_device(evt);
+}
+
+/* ****************************************************************** */
+void __gptimer_clocksource_resume(struct clocksource *cs)
+{
+ __gptimer_ctl(e_cpu, SOURCE_TIMER, TIMER_ENABLE | TIMER_NEW,
+ PERIOD_MODE);
+ pr_debug("%s: timer_val=0x%x\n", __FUNCTION__,
+ __raw_readl(TIMER_CNT_RD(e_cpu, SOURCE_TIMER)));
+}
+
+void __gptimer_clocksource_suspend(struct clocksource *cs)
+{
+ __gptimer_ctl(e_cpu, SOURCE_TIMER, TIMER_DISABLE, PERIOD_MODE);
+ pr_debug("%s: timer_val=0x%x\n", __FUNCTION__,
+ __raw_readl(TIMER_CNT_RD(e_cpu, SOURCE_TIMER)));
+}
+
+cycle_t __gptimer_clocksource_read(struct clocksource *cs)
+{
+ return ~readl_relaxed(TIMER_CNT_RD(e_cpu, SOURCE_TIMER));
+}
+
+struct clocksource clocksource_sprd = {
+ .name = "aon_timer_timer1",
+ .rating = 300,
+ .read = __gptimer_clocksource_read,
+ .mask = CLOCKSOURCE_MASK(32),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .resume = __gptimer_clocksource_resume,
+ .suspend = __gptimer_clocksource_suspend,
+};
+
+static void __gptimer_clocksource_init(void)
+{
+ /* disalbe irq since it's just a read source */
+ __raw_writel(0, TIMER_INT(e_cpu, SOURCE_TIMER));
+
+ __gptimer_ctl(e_cpu, SOURCE_TIMER, TIMER_DISABLE | TIMER_NEW,
+ PERIOD_MODE);
+ __raw_writel(ULONG_MAX, TIMER_LOAD(e_cpu, SOURCE_TIMER));
+ __gptimer_ctl(e_cpu, SOURCE_TIMER, TIMER_NEW, PERIOD_MODE);
+ __gptimer_ctl(e_cpu, SOURCE_TIMER, TIMER_ENABLE | TIMER_NEW,
+ PERIOD_MODE);
+
+ if (clocksource_register_hz
+ (&clocksource_sprd, gptimer_clock_source_freq))
+ printk("%s: can't register clocksource\n",
+ clocksource_sprd.name);
+}
+
+static void __syscnt_clocksource_init(const char *name, unsigned long hz)
+{
+ /* disable irq for syscnt */
+ __raw_writel(0, SYSCNT_CTL);
+
+ clocksource_mmio_init(SYSCNT_SHADOW_CNT, name,
+ hz, 200, 32, clocksource_mmio_readw_up);
+}
+
+/* ****************************************************************** */
+static u32 notrace __update_sched_clock(void)
+{
+ return ~(readl_relaxed(TIMER_CNT_RD(0, SOURCE_TIMER)));
+}
+
+static void __init __sched_clock_init(unsigned long rate)
+{
+ setup_sched_clock(__update_sched_clock, 32, rate);
+}
+
+void __init sci_enable_timer_early(void)
+{
+ /* enable timer & syscnt in global regs */
+ int i = 0, j = 0;
+ sci_glb_set(REG_AON_APB_APB_EB0,
+ BIT_AON_TMR_EB | BIT_AP_SYST_EB | BIT_AP_TMR0_EB);
+#if defined CONFIG_LOCAL_TIMERS && !defined CONFIG_HAVE_ARM_ARCH_TIMER
+ sci_glb_set(REG_AON_APB_APB_EB1, BIT_AP_TMR2_EB | BIT_AP_TMR1_EB);
+ for (i = 0; i < 4; i++) {
+#else
+ sci_glb_clr(REG_AON_APB_APB_EB1, BIT_AP_TMR2_EB | BIT_AP_TMR1_EB);
+ for (i = 0; i < 2; i++) {
+#endif
+ for (j = 0; j < 3; j++) {
+ __gptimer_ctl(i, j, TIMER_DISABLE, 0);
+ __raw_writel(TIMER_INT_CLR, TIMER_INT(i, j));
+ }
+ }
+
+#if defined(CONFIG_ARCH_SCX30G) || defined(CONFIG_ARCH_SCX35L)
+ /*timer1 fixed 32768 clk */
+ sched_clock_source_freq = 32768;
+#else /*timer2 clk source is from apb clk */
+ val = sci_glb_read(REG_AON_CLK_AON_APB_CFG, -1) & 0x3;
+ if (val == 0x1)
+ sched_clock_source_freq = 76800000;
+ else if (val == 0x2)
+ sched_clock_source_freq = 96000000;
+ else if (val == 0x3)
+ sched_clock_source_freq = 128000000;
+ else
+ sched_clock_source_freq = 26000000; /*default setting */
+#endif
+
+ gptimer_clock_source_freq = sched_clock_source_freq;
+#if !defined (CONFIG_HAVE_ARM_ARCH_TIMER)
+ __sched_clock_init(sched_clock_source_freq);
+#endif
+}
+
+u32 get_sys_cnt(void)
+{
+ u32 val = 0;
+ val = __raw_readl(SYSCNT_SHADOW_CNT);
+ return val;
+}
+
+void set_ap_system_timer_expires(u32 expires_ms)
+{
+ u32 val = get_sys_cnt();
+ val = val + expires_ms;
+ __raw_writel(val, SYSCNT_ALARM);
+ __raw_writel(1, SYSCNT_CTL);
+}
+
+static irqreturn_t sys_cnt_isr(int irq, void *dev_id)
+{
+ __raw_writel(8, SYSCNT_CTL);
+ return IRQ_HANDLED;
+}
+
+static struct timespec persistent_ts;
+static u64 persistent_ms, last_persistent_ms;
+static void sprd_read_persistent_clock(struct timespec *ts)
+{
+ u64 delta;
+ struct timespec *tsp = &persistent_ts;
+
+ last_persistent_ms = persistent_ms;
+ persistent_ms = get_sys_cnt();
+ delta = persistent_ms - last_persistent_ms;
+
+ timespec_add_ns(tsp, delta * NSEC_PER_MSEC);
+ *ts = *tsp;
+}
+
+void __init sci_timer_init(void)
+{
+#ifdef CONFIG_LOCAL_TIMERS
+#if !defined (CONFIG_HAVE_ARM_ARCH_TIMER)
+ int i = 0, ret = 0;
+ local_timer_register(&sprd_local_timer_ops);
+ for (i = 0; i < CONFIG_NR_CPUS; i++) {
+ ret = request_irq(irq_nr[i], __gptimer_interrupt,
+ IRQF_TIMER | IRQF_NOBALANCING | IRQF_DISABLED
+ | IRQF_PERCPU, "local_timer", local_evt);
+ if (ret) {
+ printk(KERN_ERR "request local timer irq %d failed\n",
+ irq_nr[i]);
+ }
+ }
+#endif
+#endif
+ /* setup aon timer timer1 and syscnt as clocksource */
+ __gptimer_clocksource_init();
+ __syscnt_clocksource_init("syscnt", 1000);
+ /* setup timer1 of aon timer as clockevent. */
+ sprd_gptimer_clockevent_init(BC_IRQ, "bctimer", 32768);
+ register_persistent_clock(NULL, sprd_read_persistent_clock);
+
+ printk(KERN_INFO "sci_timer_init\n");
+}
+
+#define LOCAL_TIMER_CNT 4
+static void __init sprd_init_timer(struct device_node *np)
+{
+ struct resource res;
+ int i, ret;
+ int syscnt_irqnr = 0;
+
+ printk("%s \n", __func__);
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret < 0)
+ panic("Can't get syscnt registers!\n");
+
+ base_syscnt = ioremap_nocache(res.start, res.end - res.start);
+ if (!base_syscnt)
+ panic("ioremap_nocache failed!");
+
+ syscnt_irqnr = irq_of_parse_and_map(np, 5);
+ if (syscnt_irqnr < 0)
+ panic("Can't map ap system timer irq!\n");
+
+ ret = request_irq(syscnt_irqnr,
+ sys_cnt_isr, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
+ "sys_cnt", NULL);
+ if (ret)
+ panic("sys cnt isr register failed\n");
+
+ for (i = 0; i < LOCAL_TIMER_CNT; i++) {
+ void *vaddr;
+ ret = of_address_to_resource(np, i + 1, &res);
+ if (ret < 0)
+ panic("Can't get timer %d registers for local timer",
+ i + 1);
+
+ vaddr = ioremap_nocache(res.start, resource_size(&res));
+ if (vaddr)
+ base_gptimer[i] = vaddr;
+ else
+ panic("ioremap_nocache failed!");
+ }
+ BC_IRQ = irq_of_parse_and_map(np, 0);
+ if (BC_IRQ < 0)
+ panic("Can't map bc irq");
+
+ for (i = 0; i < LOCAL_TIMER_CNT; i++) {
+ *(irq_nr + i) = irq_of_parse_and_map(np, i + 1);
+ if (*(irq_nr + i) < 0)
+ panic("Can't map bc irq");
+ }
+ of_node_put(np);
+ sci_enable_timer_early();
+ sci_timer_init();
+}
+
+CLOCKSOURCE_OF_DECLARE(scx35_timer, "sprd,scx35-timer", sprd_init_timer);
diff --git a/drivers/platform/sprd/timer_sharkl64.c b/drivers/platform/sprd/timer_sharkl64.c
new file mode 100755
index 00000000..e27ed793
--- /dev/null
+++ b/drivers/platform/sprd/timer_sharkl64.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/clockchips.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/clocksource.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/sprd_persistent_clock.h>
+struct sprd_ap_system_timer sprd_apsystimer;
+static __iomem void *base_bctimer = NULL;
+static int bc_irqnr = 0;
+
+#define TIMER_LOAD (base_bctimer + 0x0000)
+#define TIMER_VALUE (base_bctimer + 0x0004)
+#define TIMER_CTL (base_bctimer + 0x0008)
+#define TIMER_INT (base_bctimer + 0x000C)
+#define TIMER_CNT_RD (base_bctimer + 0x0010)
+
+#define ONETIME_MODE (0 << 6)
+#define PERIOD_MODE (1 << 6)
+#define TIMER_DISABLE (0 << 7)
+#define TIMER_ENABLE (1 << 7)
+#define TIMER_NEW (1 << 8)
+
+#define TIMER_INT_EN (1 << 0)
+#define TIMER_INT_CLR (1 << 3)
+#define TIMER_INT_BUSY (1 << 4)
+
+#define SYST_ALARM (sprd_apsystimer.base + 0x0000)
+#define SYST_VALUE (sprd_apsystimer.base + 0x0004)
+#define SYST_INT (sprd_apsystimer.base + 0x0008)
+#define SYST_VALUE_SHDW (sprd_apsystimer.base + 0x000C)
+
+u32 get_sys_cnt(void)
+{
+ u32 val = 0;
+ val = __raw_readl(SYST_VALUE_SHDW);
+ return val;
+}
+
+void set_ap_system_timer_expires(u32 expires_ms)
+{
+ u32 val = get_sys_cnt();
+ val = val + expires_ms;
+ __raw_writel(val, SYST_ALARM);
+ __raw_writel(1, SYST_INT);
+}
+
+static irqreturn_t sys_cnt_isr(int irq, void *dev_id)
+{
+ __raw_writel(8, SYST_INT);
+ return IRQ_HANDLED;
+}
+/*
+static struct timespec persistent_ts;
+static u64 persistent_ms, last_persistent_ms;
+static void sprd_read_persistent_clock(struct timespec *ts)
+{
+ u64 delta;
+ struct timespec *tsp = &persistent_ts;
+
+ last_persistent_ms = persistent_ms;
+ persistent_ms = get_sys_cnt();
+ delta = persistent_ms - last_persistent_ms;
+
+ timespec_add_ns(tsp, delta * NSEC_PER_MSEC);
+ *ts = *tsp;
+}
+*/
+static int sprd_ap_system_timer_init(void)
+{
+ struct resource res;
+ int ret = 0;
+ struct device_node *np = NULL;
+
+ np = of_find_node_by_name(NULL, "sprd_ap_system_timer");
+ if (!np) {
+ panic("Can't get the sprd_ap_system_timer node!\n");
+ }
+
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret < 0) {
+ panic("Can't get syscnt registers!\n");
+ }
+ sprd_apsystimer.base = ioremap_nocache(res.start, resource_size(&res));
+ if (!sprd_apsystimer.base) {
+ panic("ioremap_nocache failed!\n");
+ }
+
+ sprd_apsystimer.irqnr = irq_of_parse_and_map(np, 0);
+ if (sprd_apsystimer.irqnr < 0) {
+ panic("Can't map ap system timer irq!\n");
+ }
+
+ ret = request_irq(sprd_apsystimer.irqnr,
+ sys_cnt_isr,IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
+ "sys_cnt", NULL);
+ if(ret){
+ panic("sys cnt isr register failed\n");
+ }
+
+ of_node_put(np);
+
+ sci_glb_set(REG_AON_APB_APB_EB0, BIT_AP_SYST_EB);
+
+ /* disable irq for syscnt */
+ __raw_writel(0, SYST_INT);
+ /*register_persistent_clock(NULL, sprd_read_persistent_clock); */
+ printk("sprd ap system timer init ok!\n");
+ return 0;
+}
+
+arch_initcall(sprd_ap_system_timer_init);
+
+static inline void bctimer_ctl(int enable, int mode)
+{
+ __raw_writel(enable | mode, TIMER_CTL);
+}
+
+static int bctimer_set_next_event(unsigned long cycles,
+ struct clock_event_device *c)
+{
+ while (TIMER_INT_BUSY & __raw_readl(TIMER_INT)) ;
+ bctimer_ctl(TIMER_DISABLE, ONETIME_MODE);
+ __raw_writel(cycles, TIMER_LOAD);
+ bctimer_ctl(TIMER_ENABLE, ONETIME_MODE);
+ return 0;
+}
+
+static void bctimer_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *c)
+{
+ unsigned int saved;
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ bctimer_ctl(TIMER_DISABLE, PERIOD_MODE);
+ __raw_writel(TIMER_INT_EN, TIMER_INT);
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ bctimer_ctl(TIMER_DISABLE, ONETIME_MODE);
+ __raw_writel(TIMER_INT_EN, TIMER_INT);
+ break;
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_UNUSED:
+ __raw_writel(TIMER_INT_CLR, TIMER_INT);
+ saved = __raw_readl(TIMER_CTL) & PERIOD_MODE;
+ bctimer_ctl(TIMER_DISABLE, saved);
+ break;
+ case CLOCK_EVT_MODE_RESUME:
+ saved = __raw_readl(TIMER_CTL) & PERIOD_MODE;
+ bctimer_ctl(TIMER_ENABLE, saved);
+ break;
+ }
+}
+
+static struct clock_event_device bctimer_event = {
+ .features = CLOCK_EVT_FEAT_ONESHOT,
+ .shift = 32,
+ .rating = 150,
+ .set_next_event = bctimer_set_next_event,
+ .set_mode = bctimer_set_mode,
+};
+
+static irqreturn_t bctimer_interrupt(int irq, void *dev_id)
+{
+ unsigned int value;
+ struct clock_event_device *evt = dev_id;
+
+ value = __raw_readl(TIMER_INT);
+ value |= TIMER_INT_CLR;
+ __raw_writel(value, TIMER_INT);
+ if (evt->event_handler)
+ evt->event_handler(evt);
+
+ return IRQ_HANDLED;
+}
+
+static struct irqaction bctimer_irq = {
+ .name = "bctimer",
+ .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
+ .handler = bctimer_interrupt,
+ .dev_id = &bctimer_event,
+};
+
+static void sprd_gptimer_clockevent_init(unsigned int irq, const char *name,
+ unsigned long hz)
+{
+ struct clock_event_device *evt = &bctimer_event;
+ int ret = 0;
+
+ __raw_writel(TIMER_DISABLE, TIMER_CTL);
+ __raw_writel(TIMER_INT_CLR, TIMER_INT);
+
+ evt->name = name;
+ evt->irq = irq;
+ evt->mult = div_sc(hz, NSEC_PER_SEC, evt->shift);
+ evt->max_delta_ns = clockevent_delta2ns(ULONG_MAX, evt);
+ evt->min_delta_ns = clockevent_delta2ns(2, evt);
+ evt->cpumask = cpu_all_mask;
+
+ ret = setup_irq(irq, &bctimer_irq);
+ if (ret)
+ BUG_ON(1);
+ clockevents_register_device(evt);
+}
+
+static void sci_enable_timer_early(void)
+{
+ /* enable timer, need modify */
+ sci_glb_set(REG_AON_APB_APB_EB0,
+ BIT_AON_TMR_EB | BIT_AP_SYST_EB | BIT_AP_TMR0_EB);
+ sci_glb_set(REG_AON_APB_APB_EB1, BIT_AP_TMR2_EB | BIT_AP_TMR1_EB);
+}
+
+static int sprd_init_timer_sharkl64(void)
+{
+ struct resource res;
+ int ret = 0;
+ struct device_node *np = NULL;
+ unsigned int bc_timer_rate = 0;
+
+ np = of_find_node_by_name(NULL, "sprd_timer");
+ if (!np) {
+ printk("Can't get the sprd_sharkl64_timer node!\n");
+ return -ENODEV;
+ }
+
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret < 0) {
+ panic("Can't get syscnt registers!\n");
+ }
+ base_bctimer = ioremap_nocache(res.start, resource_size(&res));
+ if (!base_bctimer) {
+ panic("ioremap_nocache failed!");
+ }
+
+ bc_irqnr = irq_of_parse_and_map(np, 0);
+ if (bc_irqnr < 0) {
+ panic("Can't map bc irq");
+ }
+
+ of_property_read_u32(np, "clock-frequency", &bc_timer_rate);
+ if (bc_timer_rate == 0) {
+ printk("bc timer frequency not available\n");
+ of_node_put(np);
+ return -ENODEV;
+ }
+
+ of_node_put(np);
+ printk("%s,bc_irqnr = %d,clock-frequency is %d\n", __func__, bc_irqnr,
+ bc_timer_rate);
+ sci_enable_timer_early();
+ sprd_gptimer_clockevent_init(bc_irqnr, "bctimer", bc_timer_rate);
+ return 0;
+}
+
+subsys_initcall(sprd_init_timer_sharkl64);
diff --git a/drivers/platform/sprd/vibrator.c b/drivers/platform/sprd/vibrator.c
new file mode 100644
index 00000000..680d72ea
--- /dev/null
+++ b/drivers/platform/sprd/vibrator.c
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/hrtimer.h>
+#include <../../../drivers/staging/android/timed_output.h>
+#include <linux/sched.h>
+#if (defined(CONFIG_ARCH_SCX35L64)||defined(CONFIG_ARCH_SCX35LT8))
+#include <soc/sprd/adi.h>
+#include <soc/sprd/adc.h>
+#else
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/adc.h>
+#endif
+
+#ifdef CONFIG_SC_VIBRATOR_GPIO
+#include <linux/gpio.h>
+#include <soc/sprd/board.h>
+#elif defined(CONFIG_SC_VIBRATOR_POWER)
+#include <linux/regulator/consumer.h>
+#endif
+
+#ifdef CONFIG_ARCH_SCX35
+#include <soc/sprd/sci_glb_regs.h>
+
+#define ANA_VIBRATOR_CTRL0 (ANA_REG_GLB_VIBR_CTRL0)
+#if defined(CONFIG_ADIE_SC2723)
+#define BIT_VIBRATOR_PD BIT_LDO_VIBR_PD
+#else
+#define BIT_VIBRATOR_PD BIT_VIBR_PON
+#define ANA_VIBRATOR_CTRL1 (ANA_REG_GLB_VIBR_CTRL1)
+#define ANA_VIBRATOR_CTRL2 (ANA_REG_GLB_VIBR_CTRL2)
+#define ANA_VIBR_WR_PROT (ANA_REG_GLB_VIBR_WR_PROT_VALUE)
+#endif
+#define VIBRATOR_REG_UNLOCK (0x1A2B)
+#else
+#define SPRD_ANA_BASE (SPRD_MISC_BASE + 0x600)
+#define ANA_REG_BASE (SPRD_ANA_BASE)
+#define ANA_VIBR_WR_PROT (ANA_REG_BASE + 0x90)
+#define ANA_VIBRATOR_CTRL0 (ANA_REG_BASE + 0x74)
+#define ANA_VIBRATOR_CTRL1 (ANA_REG_BASE + 0x78)
+#define VIBRATOR_REG_UNLOCK (0xA1B2)
+#endif
+#define VIBRATOR_REG_LOCK ((~VIBRATOR_REG_UNLOCK) & 0xffff)
+#define VIBRATOR_STABLE_LEVEL (4)
+#ifdef CONFIG_ARCH_SCX15
+#define VIBRATOR_INIT_LEVEL (0x0f)
+#define VIBRATOR_INIT_STATE_CNT (0x600)
+#else
+#define VIBRATOR_INIT_LEVEL (4)
+#define VIBRATOR_INIT_STATE_CNT (10)
+#endif
+
+#define VIBR_STABLE_V_SHIFT (12)
+#define VIBR_STABLE_V_MSK (0x0f << VIBR_STABLE_V_SHIFT)
+#define VIBR_INIT_V_SHIFT (8)
+#define VIBR_INIT_V_MSK (0x0f << VIBR_INIT_V_SHIFT)
+#define VIBR_V_BP_SHIFT (4)
+#define VIBR_V_BP_MSK (0x0f << VIBR_V_BP_SHIFT)
+#define VIBR_PD_RST (1 << 3)
+#define VIBR_PD_SET (1 << 2)
+#define VIBR_BP_EN (1 << 1)
+#define VIBR_RTC_EN (1 << 0)
+
+#ifdef CONFIG_SS_FUNCTION
+#define VIBR_PULLDOWN_EN (1 << 1)
+#define LDO_VIBR_PD (1 << 8)
+#endif
+
+#ifdef CONFIG_SC_VIBRATOR_POWER
+static struct regulator *vibrator_regulator = NULL;
+#endif
+
+static struct work_struct vibrator_work;
+static struct hrtimer vibe_timer;
+static int vibe_state = 0;
+
+#ifdef CONFIG_SS_FUNCTION
+static void set_vibrator(int on)
+{
+ if (on)
+ {
+ printk("###############vibrator on##################\n");
+ sci_adi_set((unsigned long)ANA_REG_GLB_BA_CTRL3, VIBR_PULLDOWN_EN);
+ sci_adi_clr((unsigned long)ANA_REG_GLB_BA_CTRL2, LDO_VIBR_PD);
+ }
+ else
+ {
+ printk("###############vibrator off##################\n");
+ sci_adi_set((unsigned long)ANA_REG_GLB_BA_CTRL2, LDO_VIBR_PD);
+ sci_adi_clr((unsigned long)ANA_REG_GLB_BA_CTRL3, VIBR_PULLDOWN_EN);
+ }
+}
+static void vibrator_hw_init(void)
+{
+ return;
+}
+#else
+static void set_vibrator(int on)
+{
+#ifdef CONFIG_SC_VIBRATOR_GPIO
+ gpio_set_value(GPIO_VIBRATOR_INT, on);
+#elif defined(CONFIG_SC_VIBRATOR_POWER)
+ if (on) {
+ regulator_enable(vibrator_regulator);
+ }
+ else {
+ regulator_disable(vibrator_regulator);
+ }
+#else
+#if !defined(CONFIG_ADIE_SC2723)
+ /* unlock vibrator registor */
+ sci_adi_write((unsigned long)ANA_VIBR_WR_PROT, VIBRATOR_REG_UNLOCK, 0xffff);
+#endif
+#ifdef CONFIG_ARCH_SCX35
+ if (on)
+ sci_adi_write((unsigned long)ANA_VIBRATOR_CTRL0, BIT_VIBRATOR_PD, BIT_VIBRATOR_PD);
+ else
+ sci_adi_write((unsigned long)ANA_VIBRATOR_CTRL0, 0, BIT_VIBRATOR_PD);
+#else
+ sci_adi_clr((unsigned long)ANA_VIBRATOR_CTRL0, VIBR_PD_SET | VIBR_PD_RST);
+ if (on)
+ sci_adi_set((unsigned long)ANA_VIBRATOR_CTRL0, VIBR_PD_RST);
+ else
+ sci_adi_set((unsigned long)ANA_VIBRATOR_CTRL0, VIBR_PD_SET);
+#endif
+#if !defined(CONFIG_ADIE_SC2723)
+ /* lock vibrator registor */
+ sci_adi_write((unsigned long)ANA_VIBR_WR_PROT, VIBRATOR_REG_LOCK, 0xffff);
+#endif
+#endif
+}
+
+static void vibrator_hw_init(void)
+{
+#ifdef CONFIG_SC_VIBRATOR_GPIO
+ gpio_request(GPIO_VIBRATOR_INT, "vibrator");
+ gpio_direction_output(GPIO_VIBRATOR_INT, 0);
+#elif defined(CONFIG_SC_VIBRATOR_POWER)
+ vibrator_regulator = regulator_get(NULL, "vddcammot");
+ regulator_set_voltage(vibrator_regulator, 3300000, 3300000);
+#else
+#if !defined(CONFIG_ADIE_SC2723)
+ sci_adi_write((unsigned long)ANA_VIBR_WR_PROT, VIBRATOR_REG_UNLOCK, 0xffff);
+#endif
+#ifdef CONFIG_ARCH_SCX35
+ sci_adi_write((unsigned long)ANA_REG_GLB_RTC_CLK_EN, BIT_RTC_VIBR_EN, BIT_RTC_VIBR_EN);
+#else
+ sci_adi_set((unsigned long)ANA_VIBRATOR_CTRL0, VIBR_RTC_EN);
+ sci_adi_clr((unsigned long)ANA_VIBRATOR_CTRL0, VIBR_BP_EN);
+#endif
+ /* set init current level */
+ sci_adi_write((unsigned long)ANA_VIBRATOR_CTRL0,
+ (VIBRATOR_INIT_LEVEL << VIBR_INIT_V_SHIFT),
+ VIBR_INIT_V_MSK);
+ sci_adi_write((unsigned long)ANA_VIBRATOR_CTRL0,
+ (VIBRATOR_STABLE_LEVEL << VIBR_STABLE_V_SHIFT),
+ VIBR_STABLE_V_MSK);
+#if !defined(CONFIG_ADIE_SC2723)
+ /* set stable current level */
+ sci_adi_write((unsigned long)ANA_VIBRATOR_CTRL1, VIBRATOR_INIT_STATE_CNT, 0xffff);
+ sci_adi_write((unsigned long)ANA_VIBR_WR_PROT, VIBRATOR_REG_LOCK, 0xffff);
+#endif
+#endif
+}
+
+#endif
+
+static void update_vibrator(struct work_struct *work)
+{
+ set_vibrator(vibe_state);
+}
+
+static void vibrator_enable(struct timed_output_dev *dev, int value)
+{
+
+ hrtimer_cancel(&vibe_timer);
+
+ if (value == 0)
+ vibe_state = 0;
+ else {
+ value = (value > 15000) ? 15000 : value;
+ vibe_state = 1;
+ hrtimer_start(&vibe_timer,
+ ktime_set(value / 1000, (value % 1000) * 1000000),
+ HRTIMER_MODE_REL);
+ }
+
+ schedule_work(&vibrator_work);
+}
+
+static int vibrator_get_time(struct timed_output_dev *dev)
+{
+ ktime_t re;
+
+ if (hrtimer_active(&vibe_timer)) {
+ re = hrtimer_get_remaining(&vibe_timer);
+ return ktime_to_ns(re);
+ } else
+ return 0;
+}
+
+static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer)
+{
+ vibe_state = 0;
+ schedule_work(&vibrator_work);
+
+ return HRTIMER_NORESTART;
+}
+
+static struct timed_output_dev sprd_vibrator = {
+ .name = "vibrator",
+ .get_time = vibrator_get_time,
+ .enable = vibrator_enable,
+};
+
+static int __init sprd_init_vibrator(void)
+{
+ vibrator_hw_init();
+
+ INIT_WORK(&vibrator_work, update_vibrator);
+ vibe_state = 0;
+ hrtimer_init(&vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ vibe_timer.function = vibrator_timer_func;
+
+ timed_output_dev_register(&sprd_vibrator);
+
+ return 0;
+}
+#ifdef CONFIG_OF
+late_initcall(sprd_init_vibrator);
+#else
+module_init(sprd_init_vibrator);
+#endif
+MODULE_DESCRIPTION("vibrator driver for spreadtrum Processors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
new file mode 100644
index 00000000..85772616
--- /dev/null
+++ b/drivers/platform/x86/Kconfig
@@ -0,0 +1,792 @@
+#
+# X86 Platform Specific Drivers
+#
+
+menuconfig X86_PLATFORM_DEVICES
+ bool "X86 Platform Specific Device Drivers"
+ default y
+ depends on X86
+ ---help---
+ Say Y here to get to see options for device drivers for various
+ x86 platforms, including vendor-specific laptop extension drivers.
+ This option alone does not add any kernel code.
+
+ If you say N, all options in this submenu will be skipped and disabled.
+
+if X86_PLATFORM_DEVICES
+
+config ACER_WMI
+ tristate "Acer WMI Laptop Extras"
+ depends on ACPI
+ select LEDS_CLASS
+ select NEW_LEDS
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on SERIO_I8042
+ depends on INPUT
+ depends on RFKILL || RFKILL = n
+ depends on ACPI_WMI
+ select INPUT_SPARSEKMAP
+ # Acer WMI depends on ACPI_VIDEO when ACPI is enabled
+ # but for select to work, need to select ACPI_VIDEO's dependencies, ick
+ select VIDEO_OUTPUT_CONTROL if ACPI
+ select ACPI_VIDEO if ACPI
+ ---help---
+ This is a driver for newer Acer (and Wistron) laptops. It adds
+ wireless radio and bluetooth control, and on some laptops,
+ exposes the mail LED and LCD backlight.
+
+ If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
+ here.
+
+config ACERHDF
+ tristate "Acer Aspire One temperature and fan driver"
+ depends on THERMAL && ACPI
+ ---help---
+ This is a driver for Acer Aspire One netbooks. It allows to access
+ the temperature sensor and to control the fan.
+
+ After loading this driver the BIOS is still in control of the fan.
+ To let the kernel handle the fan, do:
+ echo -n enabled > /sys/class/thermal/thermal_zone0/mode
+
+ For more information about this driver see
+ <http://piie.net/files/acerhdf_README.txt>
+
+ If you have an Acer Aspire One netbook, say Y or M
+ here.
+
+config ASUS_LAPTOP
+ tristate "Asus Laptop Extras"
+ depends on ACPI
+ select LEDS_CLASS
+ select NEW_LEDS
+ select BACKLIGHT_CLASS_DEVICE
+ depends on INPUT
+ depends on RFKILL || RFKILL = n
+ select INPUT_SPARSEKMAP
+ select INPUT_POLLDEV
+ ---help---
+ This is a driver for Asus laptops, Lenovo SL and the Pegatron
+ Lucid tablet. It may also support some MEDION, JVC or VICTOR
+ laptops. It makes all the extra buttons generate standard
+ ACPI events and input events, and on the Lucid the built-in
+ accelerometer appears as an input device. It also adds
+ support for video output switching, LCD backlight control,
+ Bluetooth and Wlan control, and most importantly, allows you
+ to blink those fancy LEDs.
+
+ For more information see <http://acpi4asus.sf.net>.
+
+ If you have an ACPI-compatible ASUS laptop, say Y or M here.
+
+config CHROMEOS_LAPTOP
+ tristate "Chrome OS Laptop"
+ depends on I2C
+ depends on DMI
+ ---help---
+ This driver instantiates i2c and smbus devices such as
+ light sensors and touchpads.
+
+ If you have a supported Chromebook, choose Y or M here.
+ The module will be called chromeos_laptop.
+
+config DELL_LAPTOP
+ tristate "Dell Laptop Extras"
+ depends on X86
+ depends on DCDBAS
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on RFKILL || RFKILL = n
+ depends on SERIO_I8042
+ select POWER_SUPPLY
+ select LEDS_CLASS
+ select NEW_LEDS
+ default n
+ ---help---
+ This driver adds support for rfkill and backlight control to Dell
+ laptops.
+
+config DELL_WMI
+ tristate "Dell WMI extras"
+ depends on ACPI_WMI
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ ---help---
+ Say Y here if you want to support WMI-based hotkeys on Dell laptops.
+
+ To compile this driver as a module, choose M here: the module will
+ be called dell-wmi.
+
+config DELL_WMI_AIO
+ tristate "WMI Hotkeys for Dell All-In-One series"
+ depends on ACPI_WMI
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ ---help---
+ Say Y here if you want to support WMI-based hotkeys on Dell
+ All-In-One machines.
+
+ To compile this driver as a module, choose M here: the module will
+ be called dell-wmi-aio.
+
+
+config FUJITSU_LAPTOP
+ tristate "Fujitsu Laptop Extras"
+ depends on ACPI
+ depends on INPUT
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on LEDS_CLASS || LEDS_CLASS=n
+ ---help---
+ This is a driver for laptops built by Fujitsu:
+
+ * P2xxx/P5xxx/S6xxx/S7xxx series Lifebooks
+ * Possibly other Fujitsu laptop models
+ * Tested with S6410 and S7020
+
+ It adds support for LCD brightness control and some hotkeys.
+
+ If you have a Fujitsu laptop, say Y or M here.
+
+config FUJITSU_LAPTOP_DEBUG
+ bool "Verbose debug mode for Fujitsu Laptop Extras"
+ depends on FUJITSU_LAPTOP
+ default n
+ ---help---
+ Enables extra debug output from the fujitsu extras driver, at the
+ expense of a slight increase in driver size.
+
+ If you are not sure, say N here.
+
+config FUJITSU_TABLET
+ tristate "Fujitsu Tablet Extras"
+ depends on ACPI
+ depends on INPUT
+ ---help---
+ This is a driver for tablets built by Fujitsu:
+
+ * Lifebook P1510/P1610/P1620/Txxxx
+ * Stylistic ST5xxx
+ * Possibly other Fujitsu tablet models
+
+ It adds support for the panel buttons, docking station detection,
+ tablet/notebook mode detection for convertible and
+ orientation detection for docked slates.
+
+ If you have a Fujitsu convertible or slate, say Y or M here.
+
+config AMILO_RFKILL
+ tristate "Fujitsu-Siemens Amilo rfkill support"
+ depends on RFKILL
+ ---help---
+ This is a driver for enabling wifi on some Fujitsu-Siemens Amilo
+ laptops.
+
+config TC1100_WMI
+ tristate "HP Compaq TC1100 Tablet WMI Extras"
+ depends on !X86_64
+ depends on ACPI
+ depends on ACPI_WMI
+ ---help---
+ This is a driver for the WMI extensions (wireless and bluetooth power
+ control) of the HP Compaq TC1100 tablet.
+
+config HP_ACCEL
+ tristate "HP laptop accelerometer"
+ depends on INPUT && ACPI
+ select SENSORS_LIS3LV02D
+ select NEW_LEDS
+ select LEDS_CLASS
+ help
+ This driver provides support for the "Mobile Data Protection System 3D"
+ or "3D DriveGuard" feature of HP laptops. On such systems the driver
+ should load automatically (via ACPI alias).
+
+ Support for a led indicating disk protection will be provided as
+ hp::hddprotect. For more information on the feature, refer to
+ Documentation/misc-devices/lis3lv02d.
+
+ To compile this driver as a module, choose M here: the module will
+ be called hp_accel.
+
+config HP_WMI
+ tristate "HP WMI extras"
+ depends on ACPI_WMI
+ depends on INPUT
+ depends on RFKILL || RFKILL = n
+ select INPUT_SPARSEKMAP
+ help
+ Say Y here if you want to support WMI-based hotkeys on HP laptops and
+ to read data from WMI such as docking or ambient light sensor state.
+
+ To compile this driver as a module, choose M here: the module will
+ be called hp-wmi.
+
+config MSI_LAPTOP
+ tristate "MSI Laptop Extras"
+ depends on ACPI
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on RFKILL
+ depends on INPUT && SERIO_I8042
+ select INPUT_SPARSEKMAP
+ ---help---
+ This is a driver for laptops built by MSI (MICRO-STAR
+ INTERNATIONAL):
+
+ MSI MegaBook S270 (MS-1013)
+ Cytron/TCM/Medion/Tchibo MD96100/SAM2000
+
+ It adds support for Bluetooth, WLAN and LCD brightness control.
+
+ More information about this driver is available at
+ <http://0pointer.de/lennart/tchibo.html>.
+
+ If you have an MSI S270 laptop, say Y or M here.
+
+config PANASONIC_LAPTOP
+ tristate "Panasonic Laptop Extras"
+ depends on INPUT && ACPI
+ depends on BACKLIGHT_CLASS_DEVICE
+ select INPUT_SPARSEKMAP
+ ---help---
+ This driver adds support for access to backlight control and hotkeys
+ on Panasonic Let's Note laptops.
+
+ If you have a Panasonic Let's note laptop (such as the R1(N variant),
+ R2, R3, R5, T2, W2 and Y2 series), say Y.
+
+config COMPAL_LAPTOP
+ tristate "Compal Laptop Extras"
+ depends on ACPI
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on RFKILL
+ depends on HWMON
+ depends on POWER_SUPPLY
+ ---help---
+ This is a driver for laptops built by Compal:
+
+ Compal FL90/IFL90
+ Compal FL91/IFL91
+ Compal FL92/JFL92
+ Compal FT00/IFT00
+
+ It adds support for Bluetooth, WLAN and LCD brightness control.
+
+ If you have an Compal FL9x/IFL9x/FT00 laptop, say Y or M here.
+
+config SONY_LAPTOP
+ tristate "Sony Laptop Extras"
+ depends on ACPI
+ select BACKLIGHT_CLASS_DEVICE
+ depends on INPUT
+ depends on RFKILL
+ ---help---
+ This mini-driver drives the SNC and SPIC devices present in the ACPI
+ BIOS of the Sony Vaio laptops.
+
+ It gives access to some extra laptop functionalities like Bluetooth,
+ screen brightness control, Fn keys and allows powering on/off some
+ devices.
+
+ Read <file:Documentation/laptops/sony-laptop.txt> for more information.
+
+config SONYPI_COMPAT
+ bool "Sonypi compatibility"
+ depends on SONY_LAPTOP
+ ---help---
+ Build the sonypi driver compatibility code into the sony-laptop driver.
+
+config IDEAPAD_LAPTOP
+ tristate "Lenovo IdeaPad Laptop Extras"
+ depends on ACPI
+ depends on RFKILL && INPUT
+ depends on SERIO_I8042
+ depends on BACKLIGHT_CLASS_DEVICE
+ select INPUT_SPARSEKMAP
+ help
+ This is a driver for Lenovo IdeaPad netbooks contains drivers for
+ rfkill switch, hotkey, fan control and backlight control.
+
+config THINKPAD_ACPI
+ tristate "ThinkPad ACPI Laptop Extras"
+ depends on ACPI
+ depends on INPUT
+ depends on RFKILL || RFKILL = n
+ select BACKLIGHT_LCD_SUPPORT
+ select BACKLIGHT_CLASS_DEVICE
+ select HWMON
+ select NVRAM
+ select NEW_LEDS
+ select LEDS_CLASS
+ ---help---
+ This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
+ support for Fn-Fx key combinations, Bluetooth control, video
+ output switching, ThinkLight control, UltraBay eject and more.
+ For more information about this driver see
+ <file:Documentation/laptops/thinkpad-acpi.txt> and
+ <http://ibm-acpi.sf.net/> .
+
+ This driver was formerly known as ibm-acpi.
+
+ Extra functionality will be available if the rfkill (CONFIG_RFKILL)
+ and/or ALSA (CONFIG_SND) subsystems are available in the kernel.
+ Note that if you want ThinkPad-ACPI to be built-in instead of
+ modular, ALSA and rfkill will also have to be built-in.
+
+ If you have an IBM or Lenovo ThinkPad laptop, say Y or M here.
+
+config THINKPAD_ACPI_ALSA_SUPPORT
+ bool "Console audio control ALSA interface"
+ depends on THINKPAD_ACPI
+ depends on SND
+ depends on SND = y || THINKPAD_ACPI = SND
+ default y
+ ---help---
+ Enables monitoring of the built-in console audio output control
+ (headphone and speakers), which is operated by the mute and (in
+ some ThinkPad models) volume hotkeys.
+
+ If this option is enabled, ThinkPad-ACPI will export an ALSA card
+ with a single read-only mixer control, which should be used for
+ on-screen-display feedback purposes by the Desktop Environment.
+
+ Optionally, the driver will also allow software control (the
+ ALSA mixer will be made read-write). Please refer to the driver
+ documentation for details.
+
+ All IBM models have both volume and mute control. Newer Lenovo
+ models only have mute control (the volume hotkeys are just normal
+ keys and volume control is done through the main HDA mixer).
+
+config THINKPAD_ACPI_DEBUGFACILITIES
+ bool "Maintainer debug facilities"
+ depends on THINKPAD_ACPI
+ default n
+ ---help---
+ Enables extra stuff in the thinkpad-acpi which is completely useless
+ for normal use. Read the driver source to find out what it does.
+
+ Say N here, unless you were told by a kernel maintainer to do
+ otherwise.
+
+config THINKPAD_ACPI_DEBUG
+ bool "Verbose debug mode"
+ depends on THINKPAD_ACPI
+ default n
+ ---help---
+ Enables extra debugging information, at the expense of a slightly
+ increase in driver size.
+
+ If you are not sure, say N here.
+
+config THINKPAD_ACPI_UNSAFE_LEDS
+ bool "Allow control of important LEDs (unsafe)"
+ depends on THINKPAD_ACPI
+ default n
+ ---help---
+ Overriding LED state on ThinkPads can mask important
+ firmware alerts (like critical battery condition), or misled
+ the user into damaging the hardware (undocking or ejecting
+ the bay while buses are still active), etc.
+
+ LED control on the ThinkPad is write-only (with very few
+ exceptions on very ancient models), which makes it
+ impossible to know beforehand if important information will
+ be lost when one changes LED state.
+
+ Users that know what they are doing can enable this option
+ and the driver will allow control of every LED, including
+ the ones on the dock stations.
+
+ Never enable this option on a distribution kernel.
+
+ Say N here, unless you are building a kernel for your own
+ use, and need to control the important firmware LEDs.
+
+config THINKPAD_ACPI_VIDEO
+ bool "Video output control support"
+ depends on THINKPAD_ACPI
+ default y
+ ---help---
+ Allows the thinkpad_acpi driver to provide an interface to control
+ the various video output ports.
+
+ This feature often won't work well, depending on ThinkPad model,
+ display state, video output devices in use, whether there is a X
+ server running, phase of the moon, and the current mood of
+ Schroedinger's cat. If you can use X.org's RandR to control
+ your ThinkPad's video output ports instead of this feature,
+ don't think twice: do it and say N here to save memory and avoid
+ bad interactions with X.org.
+
+ NOTE: access to this feature is limited to processes with the
+ CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms
+ where it interacts badly with X.org.
+
+ If you are not sure, say Y here but do try to check if you could
+ be using X.org RandR instead.
+
+config THINKPAD_ACPI_HOTKEY_POLL
+ bool "Support NVRAM polling for hot keys"
+ depends on THINKPAD_ACPI
+ default y
+ ---help---
+ Some thinkpad models benefit from NVRAM polling to detect a few of
+ the hot key press events. If you know your ThinkPad model does not
+ need to do NVRAM polling to support any of the hot keys you use,
+ unselecting this option will save about 1kB of memory.
+
+ ThinkPads T40 and newer, R52 and newer, and X31 and newer are
+ unlikely to need NVRAM polling in their latest BIOS versions.
+
+ NVRAM polling can detect at most the following keys: ThinkPad/Access
+ IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute,
+ Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12).
+
+ If you are not sure, say Y here. The driver enables polling only if
+ it is strictly necessary to do so.
+
+config SENSORS_HDAPS
+ tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
+ depends on INPUT && X86
+ select INPUT_POLLDEV
+ default n
+ help
+ This driver provides support for the IBM Hard Drive Active Protection
+ System (hdaps), which provides an accelerometer and other misc. data.
+ ThinkPads starting with the R50, T41, and X40 are supported. The
+ accelerometer data is readable via sysfs.
+
+ This driver also provides an absolute input class device, allowing
+ the laptop to act as a pinball machine-esque joystick.
+
+ If your ThinkPad is not recognized by the driver, please update to latest
+ BIOS. This is especially the case for some R52 ThinkPads.
+
+ Say Y here if you have an applicable laptop and want to experience
+ the awesome power of hdaps.
+
+config INTEL_MENLOW
+ tristate "Thermal Management driver for Intel menlow platform"
+ depends on ACPI_THERMAL
+ select THERMAL
+ ---help---
+ ACPI thermal management enhancement driver on
+ Intel Menlow platform.
+
+ If unsure, say N.
+
+config EEEPC_LAPTOP
+ tristate "Eee PC Hotkey Driver"
+ depends on ACPI
+ depends on INPUT
+ depends on RFKILL || RFKILL = n
+ depends on HOTPLUG_PCI
+ select BACKLIGHT_CLASS_DEVICE
+ select HWMON
+ select LEDS_CLASS
+ select NEW_LEDS
+ select INPUT_SPARSEKMAP
+ ---help---
+ This driver supports the Fn-Fx keys on Eee PC laptops.
+
+ It also gives access to some extra laptop functionalities like
+ Bluetooth, backlight and allows powering on/off some other
+ devices.
+
+ If you have an Eee PC laptop, say Y or M here. If this driver
+ doesn't work on your Eee PC, try eeepc-wmi instead.
+
+config ASUS_WMI
+ tristate "ASUS WMI Driver"
+ depends on ACPI_WMI
+ depends on INPUT
+ depends on HWMON
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on RFKILL || RFKILL = n
+ depends on HOTPLUG_PCI
+ select INPUT_SPARSEKMAP
+ select LEDS_CLASS
+ select NEW_LEDS
+ ---help---
+ Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new
+ Asus Notebooks).
+
+ To compile this driver as a module, choose M here: the module will
+ be called asus-wmi.
+
+config ASUS_NB_WMI
+ tristate "Asus Notebook WMI Driver"
+ depends on ASUS_WMI
+ ---help---
+ This is a driver for newer Asus notebooks. It adds extra features
+ like wireless radio and bluetooth control, leds, hotkeys, backlight...
+
+ For more informations, see
+ <file:Documentation/ABI/testing/sysfs-platform-asus-wmi>
+
+ If you have an ACPI-WMI compatible Asus Notebook, say Y or M
+ here.
+
+config EEEPC_WMI
+ tristate "Eee PC WMI Driver"
+ depends on ASUS_WMI
+ ---help---
+ This is a driver for newer Eee PC laptops. It adds extra features
+ like wireless radio and bluetooth control, leds, hotkeys, backlight...
+
+ For more informations, see
+ <file:Documentation/ABI/testing/sysfs-platform-asus-wmi>
+
+ If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M
+ here.
+
+config ACPI_WMI
+ tristate "WMI"
+ depends on ACPI
+ help
+ This driver adds support for the ACPI-WMI (Windows Management
+ Instrumentation) mapper device (PNP0C14) found on some systems.
+
+ ACPI-WMI is a proprietary extension to ACPI to expose parts of the
+ ACPI firmware to userspace - this is done through various vendor
+ defined methods and data blocks in a PNP0C14 device, which are then
+ made available for userspace to call.
+
+ The implementation of this in Linux currently only exposes this to
+ other kernel space drivers.
+
+ This driver is a required dependency to build the firmware specific
+ drivers needed on many machines, including Acer and HP laptops.
+
+ It is safe to enable this driver even if your DSDT doesn't define
+ any ACPI-WMI devices.
+
+config MSI_WMI
+ tristate "MSI WMI extras"
+ depends on ACPI_WMI
+ depends on INPUT
+ depends on BACKLIGHT_CLASS_DEVICE
+ select INPUT_SPARSEKMAP
+ help
+ Say Y here if you want to support WMI-based hotkeys on MSI laptops.
+
+ To compile this driver as a module, choose M here: the module will
+ be called msi-wmi.
+
+config TOPSTAR_LAPTOP
+ tristate "Topstar Laptop Extras"
+ depends on ACPI
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ ---help---
+ This driver adds support for hotkeys found on Topstar laptops.
+
+ If you have a Topstar laptop, say Y or M here.
+
+config ACPI_TOSHIBA
+ tristate "Toshiba Laptop Extras"
+ depends on ACPI
+ depends on ACPI_WMI
+ select LEDS_CLASS
+ select NEW_LEDS
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on INPUT
+ depends on RFKILL || RFKILL = n
+ select INPUT_POLLDEV
+ select INPUT_SPARSEKMAP
+ ---help---
+ This driver adds support for access to certain system settings
+ on "legacy free" Toshiba laptops. These laptops can be recognized by
+ their lack of a BIOS setup menu and APM support.
+
+ On these machines, all system configuration is handled through the
+ ACPI. This driver is required for access to controls not covered
+ by the general ACPI drivers, such as LCD brightness, video output,
+ etc.
+
+ This driver differs from the non-ACPI Toshiba laptop driver (located
+ under "Processor type and features") in several aspects.
+ Configuration is accessed by reading and writing text files in the
+ /proc tree instead of by program interface to /dev. Furthermore, no
+ power management functions are exposed, as those are handled by the
+ general ACPI drivers.
+
+ More information about this driver is available at
+ <http://memebeam.org/toys/ToshibaAcpiDriver>.
+
+ If you have a legacy free Toshiba laptop (such as the Libretto L1
+ series), say Y.
+
+config TOSHIBA_BT_RFKILL
+ tristate "Toshiba Bluetooth RFKill switch support"
+ depends on ACPI
+ ---help---
+ This driver adds support for Bluetooth events for the RFKill
+ switch on modern Toshiba laptops with full ACPI support and
+ an RFKill switch.
+
+ This driver handles RFKill events for the TOS6205 Bluetooth,
+ and re-enables it when the switch is set back to the 'on'
+ position.
+
+ If you have a modern Toshiba laptop with a Bluetooth and an
+ RFKill switch (such as the Portege R500), say Y.
+
+config ACPI_CMPC
+ tristate "CMPC Laptop Extras"
+ depends on X86 && ACPI
+ depends on RFKILL || RFKILL=n
+ select INPUT
+ select BACKLIGHT_CLASS_DEVICE
+ default n
+ help
+ Support for Intel Classmate PC ACPI devices, including some
+ keys as input device, backlight device, tablet and accelerometer
+ devices.
+
+config INTEL_SCU_IPC
+ bool "Intel SCU IPC Support"
+ depends on X86_INTEL_MID
+ default y
+ ---help---
+ IPC is used to bridge the communications between kernel and SCU on
+ some embedded Intel x86 platforms. This is not needed for PC-type
+ machines.
+
+config INTEL_SCU_IPC_UTIL
+ tristate "Intel SCU IPC utility driver"
+ depends on INTEL_SCU_IPC
+ default y
+ ---help---
+ The IPC Util driver provides an interface with the SCU enabling
+ low level access for debug work and updating the firmware. Say
+ N unless you will be doing this on an Intel MID platform.
+
+config GPIO_INTEL_PMIC
+ bool "Intel PMIC GPIO support"
+ depends on INTEL_SCU_IPC && GPIOLIB
+ ---help---
+ Say Y here to support GPIO via the SCU IPC interface
+ on Intel MID platforms.
+
+config INTEL_MID_POWER_BUTTON
+ tristate "power button driver for Intel MID platforms"
+ depends on INTEL_SCU_IPC && INPUT
+ help
+ This driver handles the power button on the Intel MID platforms.
+
+ If unsure, say N.
+
+config INTEL_MFLD_THERMAL
+ tristate "Thermal driver for Intel Medfield platform"
+ depends on MFD_INTEL_MSIC && THERMAL
+ help
+ Say Y here to enable thermal driver support for the Intel Medfield
+ platform.
+
+config INTEL_IPS
+ tristate "Intel Intelligent Power Sharing"
+ depends on ACPI
+ ---help---
+ Intel Calpella platforms support dynamic power sharing between the
+ CPU and GPU, maximizing performance in a given TDP. This driver,
+ along with the CPU frequency and i915 drivers, provides that
+ functionality. If in doubt, say Y here; it will only load on
+ supported platforms.
+
+config IBM_RTL
+ tristate "Device driver to enable PRTL support"
+ depends on X86 && PCI
+ ---help---
+ Enable support for IBM Premium Real Time Mode (PRTM).
+ This module will allow you the enter and exit PRTM in the BIOS via
+ sysfs on platforms that support this feature. System in PRTM will
+ not receive CPU-generated SMIs for recoverable errors. Use of this
+ feature without proper support may void your hardware warranty.
+
+ If the proper BIOS support is found the driver will load and create
+ /sys/devices/system/ibm_rtl/. The "state" variable will indicate
+ whether or not the BIOS is in PRTM.
+ state = 0 (BIOS SMIs on)
+ state = 1 (BIOS SMIs off)
+
+config XO1_RFKILL
+ tristate "OLPC XO-1 software RF kill switch"
+ depends on OLPC
+ depends on RFKILL
+ ---help---
+ Support for enabling/disabling the WLAN interface on the OLPC XO-1
+ laptop.
+
+config XO15_EBOOK
+ tristate "OLPC XO-1.5 ebook switch"
+ depends on ACPI && INPUT
+ ---help---
+ Support for the ebook switch on the OLPC XO-1.5 laptop.
+
+ This switch is triggered as the screen is rotated and folded down to
+ convert the device into ebook form.
+
+config SAMSUNG_LAPTOP
+ tristate "Samsung Laptop driver"
+ depends on X86
+ depends on RFKILL || RFKILL = n
+ depends on BACKLIGHT_CLASS_DEVICE
+ select LEDS_CLASS
+ select NEW_LEDS
+ ---help---
+ This module implements a driver for a wide range of different
+ Samsung laptops. It offers control over the different
+ function keys, wireless LED, LCD backlight level.
+
+ It may also provide some sysfs files described in
+ <file:Documentation/ABI/testing/sysfs-platform-samsung-laptop>
+
+ To compile this driver as a module, choose M here: the module
+ will be called samsung-laptop.
+
+config MXM_WMI
+ tristate "WMI support for MXM Laptop Graphics"
+ depends on ACPI_WMI
+ ---help---
+ MXM is a standard for laptop graphics cards, the WMI interface
+ is required for switchable nvidia graphics machines
+
+config INTEL_OAKTRAIL
+ tristate "Intel Oaktrail Platform Extras"
+ depends on ACPI
+ depends on RFKILL && BACKLIGHT_CLASS_DEVICE && ACPI
+ ---help---
+ Intel Oaktrail platform need this driver to provide interfaces to
+ enable/disable the Camera, WiFi, BT etc. devices. If in doubt, say Y
+ here; it will only load on supported platforms.
+
+config SAMSUNG_Q10
+ tristate "Samsung Q10 Extras"
+ depends on SERIO_I8042
+ select BACKLIGHT_CLASS_DEVICE
+ ---help---
+ This driver provides support for backlight control on Samsung Q10
+ and related laptops, including Dell Latitude X200.
+
+config APPLE_GMUX
+ tristate "Apple Gmux Driver"
+ depends on ACPI
+ depends on PNP
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on BACKLIGHT_APPLE=n || BACKLIGHT_APPLE
+ depends on ACPI_VIDEO=n || ACPI_VIDEO
+ ---help---
+ This driver provides support for the gmux device found on many
+ Apple laptops, which controls the display mux for the hybrid
+ graphics as well as the backlight. Currently only backlight
+ control is supported by the driver.
+
+config PVPANIC
+ tristate "pvpanic device support"
+ depends on ACPI
+ ---help---
+ This driver provides support for the pvpanic device. pvpanic is
+ a paravirtualized device provided by QEMU; it lets a virtual machine
+ (guest) communicate panic events to the host.
+
+endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
new file mode 100644
index 00000000..ef0ec746
--- /dev/null
+++ b/drivers/platform/x86/Makefile
@@ -0,0 +1,55 @@
+#
+# Makefile for linux/drivers/platform/x86
+# x86 Platform-Specific Drivers
+#
+obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
+obj-$(CONFIG_ASUS_WMI) += asus-wmi.o
+obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o
+obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
+obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
+obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
+obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o
+obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
+obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o
+obj-$(CONFIG_DELL_WMI) += dell-wmi.o
+obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
+obj-$(CONFIG_ACER_WMI) += acer-wmi.o
+obj-$(CONFIG_ACERHDF) += acerhdf.o
+obj-$(CONFIG_HP_ACCEL) += hp_accel.o
+obj-$(CONFIG_HP_WMI) += hp-wmi.o
+obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o
+obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
+obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o
+obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
+obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
+obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
+obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o
+obj-$(CONFIG_FUJITSU_TABLET) += fujitsu-tablet.o
+obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o
+obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
+obj-$(CONFIG_ACPI_WMI) += wmi.o
+obj-$(CONFIG_MSI_WMI) += msi-wmi.o
+obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
+
+# toshiba_acpi must link after wmi to ensure that wmi devices are found
+# before toshiba_acpi initializes
+obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
+
+obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o
+obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
+obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
+obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
+obj-$(CONFIG_INTEL_IPS) += intel_ips.o
+obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o
+obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o
+obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o
+obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
+obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o
+obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
+obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o
+obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o
+obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o
+obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
+obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
+
+obj-$(CONFIG_PVPANIC) += pvpanic.o
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
new file mode 100644
index 00000000..59a8d325
--- /dev/null
+++ b/drivers/platform/x86/acer-wmi.c
@@ -0,0 +1,2336 @@
+/*
+ * Acer WMI Laptop Extras
+ *
+ * Copyright (C) 2007-2009 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ * Based on acer_acpi:
+ * Copyright (C) 2005-2007 E.M. Smith
+ * Copyright (C) 2007-2008 Carlos Corbacho <cathectic@gmail.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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/dmi.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/i8042.h>
+#include <linux/rfkill.h>
+#include <linux/workqueue.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+
+#include <acpi/acpi_drivers.h>
+#include <acpi/video.h>
+
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Magic Number
+ * Meaning is unknown - this number is required for writing to ACPI for AMW0
+ * (it's also used in acerhk when directly accessing the BIOS)
+ */
+#define ACER_AMW0_WRITE 0x9610
+
+/*
+ * Bit masks for the AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK 0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK 0x31
+
+/*
+ * Method IDs for WMID interface
+ */
+#define ACER_WMID_GET_WIRELESS_METHODID 1
+#define ACER_WMID_GET_BLUETOOTH_METHODID 2
+#define ACER_WMID_GET_BRIGHTNESS_METHODID 3
+#define ACER_WMID_SET_WIRELESS_METHODID 4
+#define ACER_WMID_SET_BLUETOOTH_METHODID 5
+#define ACER_WMID_SET_BRIGHTNESS_METHODID 6
+#define ACER_WMID_GET_THREEG_METHODID 10
+#define ACER_WMID_SET_THREEG_METHODID 11
+
+/*
+ * Acer ACPI method GUIDs
+ */
+#define AMW0_GUID1 "67C3371D-95A3-4C37-BB61-DD47B491DAAB"
+#define AMW0_GUID2 "431F16ED-0C2B-444C-B267-27DEB140CF9C"
+#define WMID_GUID1 "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3"
+#define WMID_GUID2 "95764E09-FB56-4E83-B31A-37761F60994A"
+#define WMID_GUID3 "61EF69EA-865C-4BC3-A502-A0DEBA0CB531"
+
+/*
+ * Acer ACPI event GUIDs
+ */
+#define ACERWMID_EVENT_GUID "676AA15E-6A47-4D9F-A2CC-1E6D18D14026"
+
+MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB");
+MODULE_ALIAS("wmi:6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3");
+MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026");
+
+enum acer_wmi_event_ids {
+ WMID_HOTKEY_EVENT = 0x1,
+ WMID_ACCEL_EVENT = 0x5,
+};
+
+static const struct key_entry acer_wmi_keymap[] = {
+ {KE_KEY, 0x01, {KEY_WLAN} }, /* WiFi */
+ {KE_KEY, 0x03, {KEY_WLAN} }, /* WiFi */
+ {KE_KEY, 0x04, {KEY_WLAN} }, /* WiFi */
+ {KE_KEY, 0x12, {KEY_BLUETOOTH} }, /* BT */
+ {KE_KEY, 0x21, {KEY_PROG1} }, /* Backup */
+ {KE_KEY, 0x22, {KEY_PROG2} }, /* Arcade */
+ {KE_KEY, 0x23, {KEY_PROG3} }, /* P_Key */
+ {KE_KEY, 0x24, {KEY_PROG4} }, /* Social networking_Key */
+ {KE_KEY, 0x29, {KEY_PROG3} }, /* P_Key for TM8372 */
+ {KE_IGNORE, 0x41, {KEY_MUTE} },
+ {KE_IGNORE, 0x42, {KEY_PREVIOUSSONG} },
+ {KE_IGNORE, 0x4d, {KEY_PREVIOUSSONG} },
+ {KE_IGNORE, 0x43, {KEY_NEXTSONG} },
+ {KE_IGNORE, 0x4e, {KEY_NEXTSONG} },
+ {KE_IGNORE, 0x44, {KEY_PLAYPAUSE} },
+ {KE_IGNORE, 0x4f, {KEY_PLAYPAUSE} },
+ {KE_IGNORE, 0x45, {KEY_STOP} },
+ {KE_IGNORE, 0x50, {KEY_STOP} },
+ {KE_IGNORE, 0x48, {KEY_VOLUMEUP} },
+ {KE_IGNORE, 0x49, {KEY_VOLUMEDOWN} },
+ {KE_IGNORE, 0x4a, {KEY_VOLUMEDOWN} },
+ {KE_IGNORE, 0x61, {KEY_SWITCHVIDEOMODE} },
+ {KE_IGNORE, 0x62, {KEY_BRIGHTNESSUP} },
+ {KE_IGNORE, 0x63, {KEY_BRIGHTNESSDOWN} },
+ {KE_KEY, 0x64, {KEY_SWITCHVIDEOMODE} }, /* Display Switch */
+ {KE_IGNORE, 0x81, {KEY_SLEEP} },
+ {KE_KEY, 0x82, {KEY_TOUCHPAD_TOGGLE} }, /* Touch Pad Toggle */
+ {KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} },
+ {KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },
+ {KE_IGNORE, 0x83, {KEY_TOUCHPAD_TOGGLE} },
+ {KE_KEY, 0x85, {KEY_TOUCHPAD_TOGGLE} },
+ {KE_END, 0}
+};
+
+static struct input_dev *acer_wmi_input_dev;
+static struct input_dev *acer_wmi_accel_dev;
+
+struct event_return_value {
+ u8 function;
+ u8 key_num;
+ u16 device_state;
+ u32 reserved;
+} __attribute__((packed));
+
+/*
+ * GUID3 Get Device Status device flags
+ */
+#define ACER_WMID3_GDS_WIRELESS (1<<0) /* WiFi */
+#define ACER_WMID3_GDS_THREEG (1<<6) /* 3G */
+#define ACER_WMID3_GDS_WIMAX (1<<7) /* WiMAX */
+#define ACER_WMID3_GDS_BLUETOOTH (1<<11) /* BT */
+#define ACER_WMID3_GDS_TOUCHPAD (1<<1) /* Touchpad */
+
+struct lm_input_params {
+ u8 function_num; /* Function Number */
+ u16 commun_devices; /* Communication type devices default status */
+ u16 devices; /* Other type devices default status */
+ u8 lm_status; /* Launch Manager Status */
+ u16 reserved;
+} __attribute__((packed));
+
+struct lm_return_value {
+ u8 error_code; /* Error Code */
+ u8 ec_return_value; /* EC Return Value */
+ u16 reserved;
+} __attribute__((packed));
+
+struct wmid3_gds_set_input_param { /* Set Device Status input parameter */
+ u8 function_num; /* Function Number */
+ u8 hotkey_number; /* Hotkey Number */
+ u16 devices; /* Set Device */
+ u8 volume_value; /* Volume Value */
+} __attribute__((packed));
+
+struct wmid3_gds_get_input_param { /* Get Device Status input parameter */
+ u8 function_num; /* Function Number */
+ u8 hotkey_number; /* Hotkey Number */
+ u16 devices; /* Get Device */
+} __attribute__((packed));
+
+struct wmid3_gds_return_value { /* Get Device Status return value*/
+ u8 error_code; /* Error Code */
+ u8 ec_return_value; /* EC Return Value */
+ u16 devices; /* Current Device Status */
+ u32 reserved;
+} __attribute__((packed));
+
+struct hotkey_function_type_aa {
+ u8 type;
+ u8 length;
+ u16 handle;
+ u16 commun_func_bitmap;
+ u16 application_func_bitmap;
+ u16 media_func_bitmap;
+ u16 display_func_bitmap;
+ u16 others_func_bitmap;
+ u8 commun_fn_key_number;
+} __attribute__((packed));
+
+/*
+ * Interface capability flags
+ */
+#define ACER_CAP_MAILLED (1<<0)
+#define ACER_CAP_WIRELESS (1<<1)
+#define ACER_CAP_BLUETOOTH (1<<2)
+#define ACER_CAP_BRIGHTNESS (1<<3)
+#define ACER_CAP_THREEG (1<<4)
+#define ACER_CAP_ACCEL (1<<5)
+#define ACER_CAP_ANY (0xFFFFFFFF)
+
+/*
+ * Interface type flags
+ */
+enum interface_flags {
+ ACER_AMW0,
+ ACER_AMW0_V2,
+ ACER_WMID,
+ ACER_WMID_v2,
+};
+
+#define ACER_DEFAULT_WIRELESS 0
+#define ACER_DEFAULT_BLUETOOTH 0
+#define ACER_DEFAULT_MAILLED 0
+#define ACER_DEFAULT_THREEG 0
+
+static int max_brightness = 0xF;
+
+static int mailled = -1;
+static int brightness = -1;
+static int threeg = -1;
+static int force_series;
+static bool ec_raw_mode;
+static bool has_type_aa;
+static u16 commun_func_bitmap;
+static u8 commun_fn_key_number;
+
+module_param(mailled, int, 0444);
+module_param(brightness, int, 0444);
+module_param(threeg, int, 0444);
+module_param(force_series, int, 0444);
+module_param(ec_raw_mode, bool, 0444);
+MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");
+MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");
+MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");
+MODULE_PARM_DESC(force_series, "Force a different laptop series");
+MODULE_PARM_DESC(ec_raw_mode, "Enable EC raw mode");
+
+struct acer_data {
+ int mailled;
+ int threeg;
+ int brightness;
+};
+
+struct acer_debug {
+ struct dentry *root;
+ struct dentry *devices;
+ u32 wmid_devices;
+};
+
+static struct rfkill *wireless_rfkill;
+static struct rfkill *bluetooth_rfkill;
+static struct rfkill *threeg_rfkill;
+static bool rfkill_inited;
+
+/* Each low-level interface must define at least some of the following */
+struct wmi_interface {
+ /* The WMI device type */
+ u32 type;
+
+ /* The capabilities this interface provides */
+ u32 capability;
+
+ /* Private data for the current interface */
+ struct acer_data data;
+
+ /* debugfs entries associated with this interface */
+ struct acer_debug debug;
+};
+
+/* The static interface pointer, points to the currently detected interface */
+static struct wmi_interface *interface;
+
+/*
+ * Embedded Controller quirks
+ * Some laptops require us to directly access the EC to either enable or query
+ * features that are not available through WMI.
+ */
+
+struct quirk_entry {
+ u8 wireless;
+ u8 mailled;
+ s8 brightness;
+ u8 bluetooth;
+};
+
+static struct quirk_entry *quirks;
+
+static void set_quirks(void)
+{
+ if (!interface)
+ return;
+
+ if (quirks->mailled)
+ interface->capability |= ACER_CAP_MAILLED;
+
+ if (quirks->brightness)
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+}
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+ quirks = dmi->driver_data;
+ return 1;
+}
+
+static struct quirk_entry quirk_unknown = {
+};
+
+static struct quirk_entry quirk_acer_aspire_1520 = {
+ .brightness = -1,
+};
+
+static struct quirk_entry quirk_acer_travelmate_2490 = {
+ .mailled = 1,
+};
+
+/* This AMW0 laptop has no bluetooth */
+static struct quirk_entry quirk_medion_md_98300 = {
+ .wireless = 1,
+};
+
+static struct quirk_entry quirk_fujitsu_amilo_li_1718 = {
+ .wireless = 2,
+};
+
+static struct quirk_entry quirk_lenovo_ideapad_s205 = {
+ .wireless = 3,
+};
+
+/* The Aspire One has a dummy ACPI-WMI interface - disable it */
+static struct dmi_system_id acer_blacklist[] = {
+ {
+ .ident = "Acer Aspire One (SSD)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AOA110"),
+ },
+ },
+ {
+ .ident = "Acer Aspire One (HDD)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AOA150"),
+ },
+ },
+ {}
+};
+
+static struct dmi_system_id acer_quirks[] = {
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 1360",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1360"),
+ },
+ .driver_data = &quirk_acer_aspire_1520,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 1520",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1520"),
+ },
+ .driver_data = &quirk_acer_aspire_1520,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 3100",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3100"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 3610",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3610"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 5100",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5100"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 5610",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5610"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 5630",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5630"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 5650",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5650"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 5680",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer Aspire 9110",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 9110"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer TravelMate 2490",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Acer TravelMate 4200",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4200"),
+ },
+ .driver_data = &quirk_acer_travelmate_2490,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Fujitsu Siemens Amilo Li 1718",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Li 1718"),
+ },
+ .driver_data = &quirk_fujitsu_amilo_li_1718,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Medion MD 98300",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MEDION"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "WAM2030"),
+ },
+ .driver_data = &quirk_medion_md_98300,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Lenovo Ideapad S205",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "10382LG"),
+ },
+ .driver_data = &quirk_lenovo_ideapad_s205,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Lenovo Ideapad S205 (Brazos)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Brazos"),
+ },
+ .driver_data = &quirk_lenovo_ideapad_s205,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Lenovo 3000 N200",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "0687A31"),
+ },
+ .driver_data = &quirk_fujitsu_amilo_li_1718,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Lenovo Ideapad S205-10382JG",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "10382JG"),
+ },
+ .driver_data = &quirk_lenovo_ideapad_s205,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Lenovo Ideapad S205-1038DPG",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "1038DPG"),
+ },
+ .driver_data = &quirk_lenovo_ideapad_s205,
+ },
+ {}
+};
+
+static int video_set_backlight_video_vendor(const struct dmi_system_id *d)
+{
+ interface->capability &= ~ACER_CAP_BRIGHTNESS;
+ pr_info("Brightness must be controlled by generic video driver\n");
+ return 0;
+}
+
+static const struct dmi_system_id video_vendor_dmi_table[] = {
+ {
+ .callback = video_set_backlight_video_vendor,
+ .ident = "Acer TravelMate 4750",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 4750"),
+ },
+ },
+ {
+ .callback = video_set_backlight_video_vendor,
+ .ident = "Acer Extensa 5235",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Extensa 5235"),
+ },
+ },
+ {
+ .callback = video_set_backlight_video_vendor,
+ .ident = "Acer TravelMate 5760",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 5760"),
+ },
+ },
+ {
+ .callback = video_set_backlight_video_vendor,
+ .ident = "Acer Aspire 5750",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5750"),
+ },
+ },
+ {
+ /*
+ * Note no video_set_backlight_video_vendor, we must use the
+ * acer interface, as there is no native backlight interface.
+ */
+ .ident = "Acer KAV80",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "KAV80"),
+ },
+ },
+ {}
+};
+
+/* Find which quirks are needed for a particular vendor/ model pair */
+static void find_quirks(void)
+{
+ if (!force_series) {
+ dmi_check_system(acer_quirks);
+ } else if (force_series == 2490) {
+ quirks = &quirk_acer_travelmate_2490;
+ }
+
+ if (quirks == NULL)
+ quirks = &quirk_unknown;
+
+ set_quirks();
+}
+
+/*
+ * General interface convenience methods
+ */
+
+static bool has_cap(u32 cap)
+{
+ if ((interface->capability & cap) != 0)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * AMW0 (V1) interface
+ */
+struct wmab_args {
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+};
+
+struct wmab_ret {
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+ u32 eex;
+};
+
+static acpi_status wmab_execute(struct wmab_args *regbuf,
+struct acpi_buffer *result)
+{
+ struct acpi_buffer input;
+ acpi_status status;
+ input.length = sizeof(struct wmab_args);
+ input.pointer = (u8 *)regbuf;
+
+ status = wmi_evaluate_method(AMW0_GUID1, 1, 1, &input, result);
+
+ return status;
+}
+
+static acpi_status AMW0_get_u32(u32 *value, u32 cap)
+{
+ int err;
+ u8 result;
+
+ switch (cap) {
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ default:
+ err = ec_read(0xA, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 7) & 0x1;
+ return AE_OK;
+ }
+ break;
+ case ACER_CAP_WIRELESS:
+ switch (quirks->wireless) {
+ case 1:
+ err = ec_read(0x7B, &result);
+ if (err)
+ return AE_ERROR;
+ *value = result & 0x1;
+ return AE_OK;
+ case 2:
+ err = ec_read(0x71, &result);
+ if (err)
+ return AE_ERROR;
+ *value = result & 0x1;
+ return AE_OK;
+ case 3:
+ err = ec_read(0x78, &result);
+ if (err)
+ return AE_ERROR;
+ *value = result & 0x1;
+ return AE_OK;
+ default:
+ err = ec_read(0xA, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 2) & 0x1;
+ return AE_OK;
+ }
+ break;
+ case ACER_CAP_BLUETOOTH:
+ switch (quirks->bluetooth) {
+ default:
+ err = ec_read(0xA, &result);
+ if (err)
+ return AE_ERROR;
+ *value = (result >> 4) & 0x1;
+ return AE_OK;
+ }
+ break;
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ err = ec_read(0x83, &result);
+ if (err)
+ return AE_ERROR;
+ *value = result;
+ return AE_OK;
+ }
+ break;
+ default:
+ return AE_ERROR;
+ }
+ return AE_OK;
+}
+
+static acpi_status AMW0_set_u32(u32 value, u32 cap)
+{
+ struct wmab_args args;
+
+ args.eax = ACER_AMW0_WRITE;
+ args.ebx = value ? (1<<8) : 0;
+ args.ecx = args.edx = 0;
+
+ switch (cap) {
+ case ACER_CAP_MAILLED:
+ if (value > 1)
+ return AE_BAD_PARAMETER;
+ args.ebx |= ACER_AMW0_MAILLED_MASK;
+ break;
+ case ACER_CAP_WIRELESS:
+ if (value > 1)
+ return AE_BAD_PARAMETER;
+ args.ebx |= ACER_AMW0_WIRELESS_MASK;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ if (value > 1)
+ return AE_BAD_PARAMETER;
+ args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+ break;
+ case ACER_CAP_BRIGHTNESS:
+ if (value > max_brightness)
+ return AE_BAD_PARAMETER;
+ switch (quirks->brightness) {
+ default:
+ return ec_write(0x83, value);
+ break;
+ }
+ default:
+ return AE_ERROR;
+ }
+
+ /* Actually do the set */
+ return wmab_execute(&args, NULL);
+}
+
+static acpi_status AMW0_find_mailled(void)
+{
+ struct wmab_args args;
+ struct wmab_ret ret;
+ acpi_status status = AE_OK;
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+
+ args.eax = 0x86;
+ args.ebx = args.ecx = args.edx = 0;
+
+ status = wmab_execute(&args, &out);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = (union acpi_object *) out.pointer;
+ if (obj && obj->type == ACPI_TYPE_BUFFER &&
+ obj->buffer.length == sizeof(struct wmab_ret)) {
+ ret = *((struct wmab_ret *) obj->buffer.pointer);
+ } else {
+ kfree(out.pointer);
+ return AE_ERROR;
+ }
+
+ if (ret.eex & 0x1)
+ interface->capability |= ACER_CAP_MAILLED;
+
+ kfree(out.pointer);
+
+ return AE_OK;
+}
+
+static int AMW0_set_cap_acpi_check_device_found;
+
+static acpi_status AMW0_set_cap_acpi_check_device_cb(acpi_handle handle,
+ u32 level, void *context, void **retval)
+{
+ AMW0_set_cap_acpi_check_device_found = 1;
+ return AE_OK;
+}
+
+static const struct acpi_device_id norfkill_ids[] = {
+ { "VPC2004", 0},
+ { "IBM0068", 0},
+ { "LEN0068", 0},
+ { "SNY5001", 0}, /* sony-laptop in charge */
+ { "", 0},
+};
+
+static int AMW0_set_cap_acpi_check_device(void)
+{
+ const struct acpi_device_id *id;
+
+ for (id = norfkill_ids; id->id[0]; id++)
+ acpi_get_devices(id->id, AMW0_set_cap_acpi_check_device_cb,
+ NULL, NULL);
+ return AMW0_set_cap_acpi_check_device_found;
+}
+
+static acpi_status AMW0_set_capabilities(void)
+{
+ struct wmab_args args;
+ struct wmab_ret ret;
+ acpi_status status;
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+
+ /*
+ * On laptops with this strange GUID (non Acer), normal probing doesn't
+ * work.
+ */
+ if (wmi_has_guid(AMW0_GUID2)) {
+ if ((quirks != &quirk_unknown) ||
+ !AMW0_set_cap_acpi_check_device())
+ interface->capability |= ACER_CAP_WIRELESS;
+ return AE_OK;
+ }
+
+ args.eax = ACER_AMW0_WRITE;
+ args.ecx = args.edx = 0;
+
+ args.ebx = 0xa2 << 8;
+ args.ebx |= ACER_AMW0_WIRELESS_MASK;
+
+ status = wmab_execute(&args, &out);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = out.pointer;
+ if (obj && obj->type == ACPI_TYPE_BUFFER &&
+ obj->buffer.length == sizeof(struct wmab_ret)) {
+ ret = *((struct wmab_ret *) obj->buffer.pointer);
+ } else {
+ status = AE_ERROR;
+ goto out;
+ }
+
+ if (ret.eax & 0x1)
+ interface->capability |= ACER_CAP_WIRELESS;
+
+ args.ebx = 2 << 8;
+ args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+
+ /*
+ * It's ok to use existing buffer for next wmab_execute call.
+ * But we need to kfree(out.pointer) if next wmab_execute fail.
+ */
+ status = wmab_execute(&args, &out);
+ if (ACPI_FAILURE(status))
+ goto out;
+
+ obj = (union acpi_object *) out.pointer;
+ if (obj && obj->type == ACPI_TYPE_BUFFER
+ && obj->buffer.length == sizeof(struct wmab_ret)) {
+ ret = *((struct wmab_ret *) obj->buffer.pointer);
+ } else {
+ status = AE_ERROR;
+ goto out;
+ }
+
+ if (ret.eax & 0x1)
+ interface->capability |= ACER_CAP_BLUETOOTH;
+
+ /*
+ * This appears to be safe to enable, since all Wistron based laptops
+ * appear to use the same EC register for brightness, even if they
+ * differ for wireless, etc
+ */
+ if (quirks->brightness >= 0)
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+
+ status = AE_OK;
+out:
+ kfree(out.pointer);
+ return status;
+}
+
+static struct wmi_interface AMW0_interface = {
+ .type = ACER_AMW0,
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+ .type = ACER_AMW0_V2,
+};
+
+/*
+ * New interface (The WMID interface)
+ */
+static acpi_status
+WMI_execute_u32(u32 method_id, u32 in, u32 *out)
+{
+ struct acpi_buffer input = { (acpi_size) sizeof(u32), (void *)(&in) };
+ struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ u32 tmp = 0;
+ acpi_status status;
+
+ status = wmi_evaluate_method(WMID_GUID1, 1, method_id, &input, &result);
+
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = (union acpi_object *) result.pointer;
+ if (obj) {
+ if (obj->type == ACPI_TYPE_BUFFER &&
+ (obj->buffer.length == sizeof(u32) ||
+ obj->buffer.length == sizeof(u64))) {
+ tmp = *((u32 *) obj->buffer.pointer);
+ } else if (obj->type == ACPI_TYPE_INTEGER) {
+ tmp = (u32) obj->integer.value;
+ }
+ }
+
+ if (out)
+ *out = tmp;
+
+ kfree(result.pointer);
+
+ return status;
+}
+
+static acpi_status WMID_get_u32(u32 *value, u32 cap)
+{
+ acpi_status status;
+ u8 tmp;
+ u32 result, method_id = 0;
+
+ switch (cap) {
+ case ACER_CAP_WIRELESS:
+ method_id = ACER_WMID_GET_WIRELESS_METHODID;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ method_id = ACER_WMID_GET_BLUETOOTH_METHODID;
+ break;
+ case ACER_CAP_BRIGHTNESS:
+ method_id = ACER_WMID_GET_BRIGHTNESS_METHODID;
+ break;
+ case ACER_CAP_THREEG:
+ method_id = ACER_WMID_GET_THREEG_METHODID;
+ break;
+ case ACER_CAP_MAILLED:
+ if (quirks->mailled == 1) {
+ ec_read(0x9f, &tmp);
+ *value = tmp & 0x1;
+ return 0;
+ }
+ default:
+ return AE_ERROR;
+ }
+ status = WMI_execute_u32(method_id, 0, &result);
+
+ if (ACPI_SUCCESS(status))
+ *value = (u8)result;
+
+ return status;
+}
+
+static acpi_status WMID_set_u32(u32 value, u32 cap)
+{
+ u32 method_id = 0;
+ char param;
+
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ if (value > max_brightness)
+ return AE_BAD_PARAMETER;
+ method_id = ACER_WMID_SET_BRIGHTNESS_METHODID;
+ break;
+ case ACER_CAP_WIRELESS:
+ if (value > 1)
+ return AE_BAD_PARAMETER;
+ method_id = ACER_WMID_SET_WIRELESS_METHODID;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ if (value > 1)
+ return AE_BAD_PARAMETER;
+ method_id = ACER_WMID_SET_BLUETOOTH_METHODID;
+ break;
+ case ACER_CAP_THREEG:
+ if (value > 1)
+ return AE_BAD_PARAMETER;
+ method_id = ACER_WMID_SET_THREEG_METHODID;
+ break;
+ case ACER_CAP_MAILLED:
+ if (value > 1)
+ return AE_BAD_PARAMETER;
+ if (quirks->mailled == 1) {
+ param = value ? 0x92 : 0x93;
+ i8042_lock_chip();
+ i8042_command(&param, 0x1059);
+ i8042_unlock_chip();
+ return 0;
+ }
+ break;
+ default:
+ return AE_ERROR;
+ }
+ return WMI_execute_u32(method_id, (u32)value, NULL);
+}
+
+static acpi_status wmid3_get_device_status(u32 *value, u16 device)
+{
+ struct wmid3_gds_return_value return_value;
+ acpi_status status;
+ union acpi_object *obj;
+ struct wmid3_gds_get_input_param params = {
+ .function_num = 0x1,
+ .hotkey_number = commun_fn_key_number,
+ .devices = device,
+ };
+ struct acpi_buffer input = {
+ sizeof(struct wmid3_gds_get_input_param),
+ &params
+ };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input, &output);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = output.pointer;
+
+ if (!obj)
+ return AE_ERROR;
+ else if (obj->type != ACPI_TYPE_BUFFER) {
+ kfree(obj);
+ return AE_ERROR;
+ }
+ if (obj->buffer.length != 8) {
+ pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return AE_ERROR;
+ }
+
+ return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer);
+ kfree(obj);
+
+ if (return_value.error_code || return_value.ec_return_value)
+ pr_warn("Get 0x%x Device Status failed: 0x%x - 0x%x\n",
+ device,
+ return_value.error_code,
+ return_value.ec_return_value);
+ else
+ *value = !!(return_value.devices & device);
+
+ return status;
+}
+
+static acpi_status wmid_v2_get_u32(u32 *value, u32 cap)
+{
+ u16 device;
+
+ switch (cap) {
+ case ACER_CAP_WIRELESS:
+ device = ACER_WMID3_GDS_WIRELESS;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ device = ACER_WMID3_GDS_BLUETOOTH;
+ break;
+ case ACER_CAP_THREEG:
+ device = ACER_WMID3_GDS_THREEG;
+ break;
+ default:
+ return AE_ERROR;
+ }
+ return wmid3_get_device_status(value, device);
+}
+
+static acpi_status wmid3_set_device_status(u32 value, u16 device)
+{
+ struct wmid3_gds_return_value return_value;
+ acpi_status status;
+ union acpi_object *obj;
+ u16 devices;
+ struct wmid3_gds_get_input_param get_params = {
+ .function_num = 0x1,
+ .hotkey_number = commun_fn_key_number,
+ .devices = commun_func_bitmap,
+ };
+ struct acpi_buffer get_input = {
+ sizeof(struct wmid3_gds_get_input_param),
+ &get_params
+ };
+ struct wmid3_gds_set_input_param set_params = {
+ .function_num = 0x2,
+ .hotkey_number = commun_fn_key_number,
+ .devices = commun_func_bitmap,
+ };
+ struct acpi_buffer set_input = {
+ sizeof(struct wmid3_gds_set_input_param),
+ &set_params
+ };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct acpi_buffer output2 = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &get_input, &output);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = output.pointer;
+
+ if (!obj)
+ return AE_ERROR;
+ else if (obj->type != ACPI_TYPE_BUFFER) {
+ kfree(obj);
+ return AE_ERROR;
+ }
+ if (obj->buffer.length != 8) {
+ pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return AE_ERROR;
+ }
+
+ return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer);
+ kfree(obj);
+
+ if (return_value.error_code || return_value.ec_return_value) {
+ pr_warn("Get Current Device Status failed: 0x%x - 0x%x\n",
+ return_value.error_code,
+ return_value.ec_return_value);
+ return status;
+ }
+
+ devices = return_value.devices;
+ set_params.devices = (value) ? (devices | device) : (devices & ~device);
+
+ status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &set_input, &output2);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = output2.pointer;
+
+ if (!obj)
+ return AE_ERROR;
+ else if (obj->type != ACPI_TYPE_BUFFER) {
+ kfree(obj);
+ return AE_ERROR;
+ }
+ if (obj->buffer.length != 4) {
+ pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return AE_ERROR;
+ }
+
+ return_value = *((struct wmid3_gds_return_value *)obj->buffer.pointer);
+ kfree(obj);
+
+ if (return_value.error_code || return_value.ec_return_value)
+ pr_warn("Set Device Status failed: 0x%x - 0x%x\n",
+ return_value.error_code,
+ return_value.ec_return_value);
+
+ return status;
+}
+
+static acpi_status wmid_v2_set_u32(u32 value, u32 cap)
+{
+ u16 device;
+
+ switch (cap) {
+ case ACER_CAP_WIRELESS:
+ device = ACER_WMID3_GDS_WIRELESS;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ device = ACER_WMID3_GDS_BLUETOOTH;
+ break;
+ case ACER_CAP_THREEG:
+ device = ACER_WMID3_GDS_THREEG;
+ break;
+ default:
+ return AE_ERROR;
+ }
+ return wmid3_set_device_status(value, device);
+}
+
+static void type_aa_dmi_decode(const struct dmi_header *header, void *dummy)
+{
+ struct hotkey_function_type_aa *type_aa;
+
+ /* We are looking for OEM-specific Type AAh */
+ if (header->type != 0xAA)
+ return;
+
+ has_type_aa = true;
+ type_aa = (struct hotkey_function_type_aa *) header;
+
+ pr_info("Function bitmap for Communication Button: 0x%x\n",
+ type_aa->commun_func_bitmap);
+ commun_func_bitmap = type_aa->commun_func_bitmap;
+
+ if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_WIRELESS)
+ interface->capability |= ACER_CAP_WIRELESS;
+ if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_THREEG)
+ interface->capability |= ACER_CAP_THREEG;
+ if (type_aa->commun_func_bitmap & ACER_WMID3_GDS_BLUETOOTH)
+ interface->capability |= ACER_CAP_BLUETOOTH;
+
+ commun_fn_key_number = type_aa->commun_fn_key_number;
+}
+
+static acpi_status WMID_set_capabilities(void)
+{
+ struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+ union acpi_object *obj;
+ acpi_status status;
+ u32 devices;
+
+ status = wmi_query_block(WMID_GUID2, 1, &out);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = (union acpi_object *) out.pointer;
+ if (obj) {
+ if (obj->type == ACPI_TYPE_BUFFER &&
+ (obj->buffer.length == sizeof(u32) ||
+ obj->buffer.length == sizeof(u64))) {
+ devices = *((u32 *) obj->buffer.pointer);
+ } else if (obj->type == ACPI_TYPE_INTEGER) {
+ devices = (u32) obj->integer.value;
+ } else {
+ kfree(out.pointer);
+ return AE_ERROR;
+ }
+ } else {
+ kfree(out.pointer);
+ return AE_ERROR;
+ }
+
+ pr_info("Function bitmap for Communication Device: 0x%x\n", devices);
+ if (devices & 0x07)
+ interface->capability |= ACER_CAP_WIRELESS;
+ if (devices & 0x40)
+ interface->capability |= ACER_CAP_THREEG;
+ if (devices & 0x10)
+ interface->capability |= ACER_CAP_BLUETOOTH;
+
+ if (!(devices & 0x20))
+ max_brightness = 0x9;
+
+ kfree(out.pointer);
+ return status;
+}
+
+static struct wmi_interface wmid_interface = {
+ .type = ACER_WMID,
+};
+
+static struct wmi_interface wmid_v2_interface = {
+ .type = ACER_WMID_v2,
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_u32(u32 *value, u32 cap)
+{
+ acpi_status status = AE_ERROR;
+
+ switch (interface->type) {
+ case ACER_AMW0:
+ status = AMW0_get_u32(value, cap);
+ break;
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED) {
+ status = AMW0_get_u32(value, cap);
+ break;
+ }
+ case ACER_WMID:
+ status = WMID_get_u32(value, cap);
+ break;
+ case ACER_WMID_v2:
+ if (cap & (ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH |
+ ACER_CAP_THREEG))
+ status = wmid_v2_get_u32(value, cap);
+ else if (wmi_has_guid(WMID_GUID2))
+ status = WMID_get_u32(value, cap);
+ break;
+ }
+
+ return status;
+}
+
+static acpi_status set_u32(u32 value, u32 cap)
+{
+ acpi_status status;
+
+ if (interface->capability & cap) {
+ switch (interface->type) {
+ case ACER_AMW0:
+ return AMW0_set_u32(value, cap);
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED)
+ return AMW0_set_u32(value, cap);
+
+ /*
+ * On some models, some WMID methods don't toggle
+ * properly. For those cases, we want to run the AMW0
+ * method afterwards to be certain we've really toggled
+ * the device state.
+ */
+ if (cap == ACER_CAP_WIRELESS ||
+ cap == ACER_CAP_BLUETOOTH) {
+ status = WMID_set_u32(value, cap);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ return AMW0_set_u32(value, cap);
+ }
+ case ACER_WMID:
+ return WMID_set_u32(value, cap);
+ case ACER_WMID_v2:
+ if (cap & (ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH |
+ ACER_CAP_THREEG))
+ return wmid_v2_set_u32(value, cap);
+ else if (wmi_has_guid(WMID_GUID2))
+ return WMID_set_u32(value, cap);
+ default:
+ return AE_BAD_PARAMETER;
+ }
+ }
+ return AE_BAD_PARAMETER;
+}
+
+static void __init acer_commandline_init(void)
+{
+ /*
+ * These will all fail silently if the value given is invalid, or the
+ * capability isn't available on the given interface
+ */
+ if (mailled >= 0)
+ set_u32(mailled, ACER_CAP_MAILLED);
+ if (!has_type_aa && threeg >= 0)
+ set_u32(threeg, ACER_CAP_THREEG);
+ if (brightness >= 0)
+ set_u32(brightness, ACER_CAP_BRIGHTNESS);
+}
+
+/*
+ * LED device (Mail LED only, no other LEDs known yet)
+ */
+static void mail_led_set(struct led_classdev *led_cdev,
+enum led_brightness value)
+{
+ set_u32(value, ACER_CAP_MAILLED);
+}
+
+static struct led_classdev mail_led = {
+ .name = "acer-wmi::mail",
+ .brightness_set = mail_led_set,
+};
+
+static int acer_led_init(struct device *dev)
+{
+ return led_classdev_register(dev, &mail_led);
+}
+
+static void acer_led_exit(void)
+{
+ set_u32(LED_OFF, ACER_CAP_MAILLED);
+ led_classdev_unregister(&mail_led);
+}
+
+/*
+ * Backlight device
+ */
+static struct backlight_device *acer_backlight_device;
+
+static int read_brightness(struct backlight_device *bd)
+{
+ u32 value;
+ get_u32(&value, ACER_CAP_BRIGHTNESS);
+ return value;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+ int intensity = bd->props.brightness;
+
+ if (bd->props.power != FB_BLANK_UNBLANK)
+ intensity = 0;
+ if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+ intensity = 0;
+
+ set_u32(intensity, ACER_CAP_BRIGHTNESS);
+
+ return 0;
+}
+
+static const struct backlight_ops acer_bl_ops = {
+ .get_brightness = read_brightness,
+ .update_status = update_bl_status,
+};
+
+static int acer_backlight_init(struct device *dev)
+{
+ struct backlight_properties props;
+ struct backlight_device *bd;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = max_brightness;
+ bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops,
+ &props);
+ if (IS_ERR(bd)) {
+ pr_err("Could not register Acer backlight device\n");
+ acer_backlight_device = NULL;
+ return PTR_ERR(bd);
+ }
+
+ acer_backlight_device = bd;
+
+ bd->props.power = FB_BLANK_UNBLANK;
+ bd->props.brightness = read_brightness(bd);
+ backlight_update_status(bd);
+ return 0;
+}
+
+static void acer_backlight_exit(void)
+{
+ backlight_device_unregister(acer_backlight_device);
+}
+
+/*
+ * Accelerometer device
+ */
+static acpi_handle gsensor_handle;
+
+static int acer_gsensor_init(void)
+{
+ acpi_status status;
+ struct acpi_buffer output;
+ union acpi_object out_obj;
+
+ output.length = sizeof(out_obj);
+ output.pointer = &out_obj;
+ status = acpi_evaluate_object(gsensor_handle, "_INI", NULL, &output);
+ if (ACPI_FAILURE(status))
+ return -1;
+
+ return 0;
+}
+
+static int acer_gsensor_open(struct input_dev *input)
+{
+ return acer_gsensor_init();
+}
+
+static int acer_gsensor_event(void)
+{
+ acpi_status status;
+ struct acpi_buffer output;
+ union acpi_object out_obj[5];
+
+ if (!has_cap(ACER_CAP_ACCEL))
+ return -1;
+
+ output.length = sizeof(out_obj);
+ output.pointer = out_obj;
+
+ status = acpi_evaluate_object(gsensor_handle, "RDVL", NULL, &output);
+ if (ACPI_FAILURE(status))
+ return -1;
+
+ if (out_obj->package.count != 4)
+ return -1;
+
+ input_report_abs(acer_wmi_accel_dev, ABS_X,
+ (s16)out_obj->package.elements[0].integer.value);
+ input_report_abs(acer_wmi_accel_dev, ABS_Y,
+ (s16)out_obj->package.elements[1].integer.value);
+ input_report_abs(acer_wmi_accel_dev, ABS_Z,
+ (s16)out_obj->package.elements[2].integer.value);
+ input_sync(acer_wmi_accel_dev);
+ return 0;
+}
+
+/*
+ * Rfkill devices
+ */
+static void acer_rfkill_update(struct work_struct *ignored);
+static DECLARE_DELAYED_WORK(acer_rfkill_work, acer_rfkill_update);
+static void acer_rfkill_update(struct work_struct *ignored)
+{
+ u32 state;
+ acpi_status status;
+
+ if (has_cap(ACER_CAP_WIRELESS)) {
+ status = get_u32(&state, ACER_CAP_WIRELESS);
+ if (ACPI_SUCCESS(status)) {
+ if (quirks->wireless == 3)
+ rfkill_set_hw_state(wireless_rfkill, !state);
+ else
+ rfkill_set_sw_state(wireless_rfkill, !state);
+ }
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ status = get_u32(&state, ACER_CAP_BLUETOOTH);
+ if (ACPI_SUCCESS(status))
+ rfkill_set_sw_state(bluetooth_rfkill, !state);
+ }
+
+ if (has_cap(ACER_CAP_THREEG) && wmi_has_guid(WMID_GUID3)) {
+ status = get_u32(&state, ACER_WMID3_GDS_THREEG);
+ if (ACPI_SUCCESS(status))
+ rfkill_set_sw_state(threeg_rfkill, !state);
+ }
+
+ schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ));
+}
+
+static int acer_rfkill_set(void *data, bool blocked)
+{
+ acpi_status status;
+ u32 cap = (unsigned long)data;
+
+ if (rfkill_inited) {
+ status = set_u32(!blocked, cap);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static const struct rfkill_ops acer_rfkill_ops = {
+ .set_block = acer_rfkill_set,
+};
+
+static struct rfkill *acer_rfkill_register(struct device *dev,
+ enum rfkill_type type,
+ char *name, u32 cap)
+{
+ int err;
+ struct rfkill *rfkill_dev;
+ u32 state;
+ acpi_status status;
+
+ rfkill_dev = rfkill_alloc(name, dev, type,
+ &acer_rfkill_ops,
+ (void *)(unsigned long)cap);
+ if (!rfkill_dev)
+ return ERR_PTR(-ENOMEM);
+
+ status = get_u32(&state, cap);
+
+ err = rfkill_register(rfkill_dev);
+ if (err) {
+ rfkill_destroy(rfkill_dev);
+ return ERR_PTR(err);
+ }
+
+ if (ACPI_SUCCESS(status))
+ rfkill_set_sw_state(rfkill_dev, !state);
+
+ return rfkill_dev;
+}
+
+static int acer_rfkill_init(struct device *dev)
+{
+ int err;
+
+ if (has_cap(ACER_CAP_WIRELESS)) {
+ wireless_rfkill = acer_rfkill_register(dev, RFKILL_TYPE_WLAN,
+ "acer-wireless", ACER_CAP_WIRELESS);
+ if (IS_ERR(wireless_rfkill)) {
+ err = PTR_ERR(wireless_rfkill);
+ goto error_wireless;
+ }
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ bluetooth_rfkill = acer_rfkill_register(dev,
+ RFKILL_TYPE_BLUETOOTH, "acer-bluetooth",
+ ACER_CAP_BLUETOOTH);
+ if (IS_ERR(bluetooth_rfkill)) {
+ err = PTR_ERR(bluetooth_rfkill);
+ goto error_bluetooth;
+ }
+ }
+
+ if (has_cap(ACER_CAP_THREEG)) {
+ threeg_rfkill = acer_rfkill_register(dev,
+ RFKILL_TYPE_WWAN, "acer-threeg",
+ ACER_CAP_THREEG);
+ if (IS_ERR(threeg_rfkill)) {
+ err = PTR_ERR(threeg_rfkill);
+ goto error_threeg;
+ }
+ }
+
+ rfkill_inited = true;
+
+ if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) &&
+ has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
+ schedule_delayed_work(&acer_rfkill_work,
+ round_jiffies_relative(HZ));
+
+ return 0;
+
+error_threeg:
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ rfkill_unregister(bluetooth_rfkill);
+ rfkill_destroy(bluetooth_rfkill);
+ }
+error_bluetooth:
+ if (has_cap(ACER_CAP_WIRELESS)) {
+ rfkill_unregister(wireless_rfkill);
+ rfkill_destroy(wireless_rfkill);
+ }
+error_wireless:
+ return err;
+}
+
+static void acer_rfkill_exit(void)
+{
+ if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) &&
+ has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
+ cancel_delayed_work_sync(&acer_rfkill_work);
+
+ if (has_cap(ACER_CAP_WIRELESS)) {
+ rfkill_unregister(wireless_rfkill);
+ rfkill_destroy(wireless_rfkill);
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ rfkill_unregister(bluetooth_rfkill);
+ rfkill_destroy(bluetooth_rfkill);
+ }
+
+ if (has_cap(ACER_CAP_THREEG)) {
+ rfkill_unregister(threeg_rfkill);
+ rfkill_destroy(threeg_rfkill);
+ }
+ return;
+}
+
+/*
+ * sysfs interface
+ */
+static ssize_t show_bool_threeg(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u32 result; \
+ acpi_status status;
+
+ pr_info("This threeg sysfs will be removed in 2012 - used by: %s\n",
+ current->comm);
+ status = get_u32(&result, ACER_CAP_THREEG);
+ if (ACPI_SUCCESS(status))
+ return sprintf(buf, "%u\n", result);
+ return sprintf(buf, "Read error\n");
+}
+
+static ssize_t set_bool_threeg(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ u32 tmp = simple_strtoul(buf, NULL, 10);
+ acpi_status status = set_u32(tmp, ACER_CAP_THREEG);
+ pr_info("This threeg sysfs will be removed in 2012 - used by: %s\n",
+ current->comm);
+ if (ACPI_FAILURE(status))
+ return -EINVAL;
+ return count;
+}
+static DEVICE_ATTR(threeg, S_IRUGO | S_IWUSR, show_bool_threeg,
+ set_bool_threeg);
+
+static ssize_t show_interface(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ pr_info("This interface sysfs will be removed in 2012 - used by: %s\n",
+ current->comm);
+ switch (interface->type) {
+ case ACER_AMW0:
+ return sprintf(buf, "AMW0\n");
+ case ACER_AMW0_V2:
+ return sprintf(buf, "AMW0 v2\n");
+ case ACER_WMID:
+ return sprintf(buf, "WMID\n");
+ case ACER_WMID_v2:
+ return sprintf(buf, "WMID v2\n");
+ default:
+ return sprintf(buf, "Error!\n");
+ }
+}
+
+static DEVICE_ATTR(interface, S_IRUGO, show_interface, NULL);
+
+static void acer_wmi_notify(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ struct event_return_value return_value;
+ acpi_status status;
+ u16 device_state;
+ const struct key_entry *key;
+ u32 scancode;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_warn("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+
+ if (!obj)
+ return;
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ pr_warn("Unknown response received %d\n", obj->type);
+ kfree(obj);
+ return;
+ }
+ if (obj->buffer.length != 8) {
+ pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return;
+ }
+
+ return_value = *((struct event_return_value *)obj->buffer.pointer);
+ kfree(obj);
+
+ switch (return_value.function) {
+ case WMID_HOTKEY_EVENT:
+ device_state = return_value.device_state;
+ pr_debug("device state: 0x%x\n", device_state);
+
+ key = sparse_keymap_entry_from_scancode(acer_wmi_input_dev,
+ return_value.key_num);
+ if (!key) {
+ pr_warn("Unknown key number - 0x%x\n",
+ return_value.key_num);
+ } else {
+ scancode = return_value.key_num;
+ switch (key->keycode) {
+ case KEY_WLAN:
+ case KEY_BLUETOOTH:
+ if (has_cap(ACER_CAP_WIRELESS))
+ rfkill_set_sw_state(wireless_rfkill,
+ !(device_state & ACER_WMID3_GDS_WIRELESS));
+ if (has_cap(ACER_CAP_THREEG))
+ rfkill_set_sw_state(threeg_rfkill,
+ !(device_state & ACER_WMID3_GDS_THREEG));
+ if (has_cap(ACER_CAP_BLUETOOTH))
+ rfkill_set_sw_state(bluetooth_rfkill,
+ !(device_state & ACER_WMID3_GDS_BLUETOOTH));
+ break;
+ case KEY_TOUCHPAD_TOGGLE:
+ scancode = (device_state & ACER_WMID3_GDS_TOUCHPAD) ?
+ KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF;
+ }
+ sparse_keymap_report_event(acer_wmi_input_dev, scancode, 1, true);
+ }
+ break;
+ case WMID_ACCEL_EVENT:
+ acer_gsensor_event();
+ break;
+ default:
+ pr_warn("Unknown function number - %d - %d\n",
+ return_value.function, return_value.key_num);
+ break;
+ }
+}
+
+static acpi_status
+wmid3_set_lm_mode(struct lm_input_params *params,
+ struct lm_return_value *return_value)
+{
+ acpi_status status;
+ union acpi_object *obj;
+
+ struct acpi_buffer input = { sizeof(struct lm_input_params), params };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ status = wmi_evaluate_method(WMID_GUID3, 0, 0x1, &input, &output);
+ if (ACPI_FAILURE(status))
+ return status;
+
+ obj = output.pointer;
+
+ if (!obj)
+ return AE_ERROR;
+ else if (obj->type != ACPI_TYPE_BUFFER) {
+ kfree(obj);
+ return AE_ERROR;
+ }
+ if (obj->buffer.length != 4) {
+ pr_warn("Unknown buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return AE_ERROR;
+ }
+
+ *return_value = *((struct lm_return_value *)obj->buffer.pointer);
+ kfree(obj);
+
+ return status;
+}
+
+static int acer_wmi_enable_ec_raw(void)
+{
+ struct lm_return_value return_value;
+ acpi_status status;
+ struct lm_input_params params = {
+ .function_num = 0x1,
+ .commun_devices = 0xFFFF,
+ .devices = 0xFFFF,
+ .lm_status = 0x00, /* Launch Manager Deactive */
+ };
+
+ status = wmid3_set_lm_mode(&params, &return_value);
+
+ if (return_value.error_code || return_value.ec_return_value)
+ pr_warn("Enabling EC raw mode failed: 0x%x - 0x%x\n",
+ return_value.error_code,
+ return_value.ec_return_value);
+ else
+ pr_info("Enabled EC raw mode\n");
+
+ return status;
+}
+
+static int acer_wmi_enable_lm(void)
+{
+ struct lm_return_value return_value;
+ acpi_status status;
+ struct lm_input_params params = {
+ .function_num = 0x1,
+ .commun_devices = 0xFFFF,
+ .devices = 0xFFFF,
+ .lm_status = 0x01, /* Launch Manager Active */
+ };
+
+ status = wmid3_set_lm_mode(&params, &return_value);
+
+ if (return_value.error_code || return_value.ec_return_value)
+ pr_warn("Enabling Launch Manager failed: 0x%x - 0x%x\n",
+ return_value.error_code,
+ return_value.ec_return_value);
+
+ return status;
+}
+
+static acpi_status __init acer_wmi_get_handle_cb(acpi_handle ah, u32 level,
+ void *ctx, void **retval)
+{
+ *(acpi_handle *)retval = ah;
+ return AE_OK;
+}
+
+static int __init acer_wmi_get_handle(const char *name, const char *prop,
+ acpi_handle *ah)
+{
+ acpi_status status;
+ acpi_handle handle;
+
+ BUG_ON(!name || !ah);
+
+ handle = NULL;
+ status = acpi_get_devices(prop, acer_wmi_get_handle_cb,
+ (void *)name, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ *ah = handle;
+ return 0;
+ } else {
+ return -ENODEV;
+ }
+}
+
+static int __init acer_wmi_accel_setup(void)
+{
+ int err;
+
+ err = acer_wmi_get_handle("SENR", "BST0001", &gsensor_handle);
+ if (err)
+ return err;
+
+ interface->capability |= ACER_CAP_ACCEL;
+
+ acer_wmi_accel_dev = input_allocate_device();
+ if (!acer_wmi_accel_dev)
+ return -ENOMEM;
+
+ acer_wmi_accel_dev->open = acer_gsensor_open;
+
+ acer_wmi_accel_dev->name = "Acer BMA150 accelerometer";
+ acer_wmi_accel_dev->phys = "wmi/input1";
+ acer_wmi_accel_dev->id.bustype = BUS_HOST;
+ acer_wmi_accel_dev->evbit[0] = BIT_MASK(EV_ABS);
+ input_set_abs_params(acer_wmi_accel_dev, ABS_X, -16384, 16384, 0, 0);
+ input_set_abs_params(acer_wmi_accel_dev, ABS_Y, -16384, 16384, 0, 0);
+ input_set_abs_params(acer_wmi_accel_dev, ABS_Z, -16384, 16384, 0, 0);
+
+ err = input_register_device(acer_wmi_accel_dev);
+ if (err)
+ goto err_free_dev;
+
+ return 0;
+
+err_free_dev:
+ input_free_device(acer_wmi_accel_dev);
+ return err;
+}
+
+static void acer_wmi_accel_destroy(void)
+{
+ input_unregister_device(acer_wmi_accel_dev);
+}
+
+static int __init acer_wmi_input_setup(void)
+{
+ acpi_status status;
+ int err;
+
+ acer_wmi_input_dev = input_allocate_device();
+ if (!acer_wmi_input_dev)
+ return -ENOMEM;
+
+ acer_wmi_input_dev->name = "Acer WMI hotkeys";
+ acer_wmi_input_dev->phys = "wmi/input0";
+ acer_wmi_input_dev->id.bustype = BUS_HOST;
+
+ err = sparse_keymap_setup(acer_wmi_input_dev, acer_wmi_keymap, NULL);
+ if (err)
+ goto err_free_dev;
+
+ status = wmi_install_notify_handler(ACERWMID_EVENT_GUID,
+ acer_wmi_notify, NULL);
+ if (ACPI_FAILURE(status)) {
+ err = -EIO;
+ goto err_free_keymap;
+ }
+
+ err = input_register_device(acer_wmi_input_dev);
+ if (err)
+ goto err_uninstall_notifier;
+
+ return 0;
+
+err_uninstall_notifier:
+ wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
+err_free_keymap:
+ sparse_keymap_free(acer_wmi_input_dev);
+err_free_dev:
+ input_free_device(acer_wmi_input_dev);
+ return err;
+}
+
+static void acer_wmi_input_destroy(void)
+{
+ wmi_remove_notify_handler(ACERWMID_EVENT_GUID);
+ sparse_keymap_free(acer_wmi_input_dev);
+ input_unregister_device(acer_wmi_input_dev);
+}
+
+/*
+ * debugfs functions
+ */
+static u32 get_wmid_devices(void)
+{
+ struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+ union acpi_object *obj;
+ acpi_status status;
+ u32 devices = 0;
+
+ status = wmi_query_block(WMID_GUID2, 1, &out);
+ if (ACPI_FAILURE(status))
+ return 0;
+
+ obj = (union acpi_object *) out.pointer;
+ if (obj) {
+ if (obj->type == ACPI_TYPE_BUFFER &&
+ (obj->buffer.length == sizeof(u32) ||
+ obj->buffer.length == sizeof(u64))) {
+ devices = *((u32 *) obj->buffer.pointer);
+ } else if (obj->type == ACPI_TYPE_INTEGER) {
+ devices = (u32) obj->integer.value;
+ }
+ }
+
+ kfree(out.pointer);
+ return devices;
+}
+
+/*
+ * Platform device
+ */
+static int acer_platform_probe(struct platform_device *device)
+{
+ int err;
+
+ if (has_cap(ACER_CAP_MAILLED)) {
+ err = acer_led_init(&device->dev);
+ if (err)
+ goto error_mailled;
+ }
+
+ if (has_cap(ACER_CAP_BRIGHTNESS)) {
+ err = acer_backlight_init(&device->dev);
+ if (err)
+ goto error_brightness;
+ }
+
+ err = acer_rfkill_init(&device->dev);
+ if (err)
+ goto error_rfkill;
+
+ return err;
+
+error_rfkill:
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ acer_backlight_exit();
+error_brightness:
+ if (has_cap(ACER_CAP_MAILLED))
+ acer_led_exit();
+error_mailled:
+ return err;
+}
+
+static int acer_platform_remove(struct platform_device *device)
+{
+ if (has_cap(ACER_CAP_MAILLED))
+ acer_led_exit();
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ acer_backlight_exit();
+
+ acer_rfkill_exit();
+ return 0;
+}
+
+static int acer_suspend(struct device *dev)
+{
+ u32 value;
+ struct acer_data *data = &interface->data;
+
+ if (!data)
+ return -ENOMEM;
+
+ if (has_cap(ACER_CAP_MAILLED)) {
+ get_u32(&value, ACER_CAP_MAILLED);
+ set_u32(LED_OFF, ACER_CAP_MAILLED);
+ data->mailled = value;
+ }
+
+ if (has_cap(ACER_CAP_BRIGHTNESS)) {
+ get_u32(&value, ACER_CAP_BRIGHTNESS);
+ data->brightness = value;
+ }
+
+ return 0;
+}
+
+static int acer_resume(struct device *dev)
+{
+ struct acer_data *data = &interface->data;
+
+ if (!data)
+ return -ENOMEM;
+
+ if (has_cap(ACER_CAP_MAILLED))
+ set_u32(data->mailled, ACER_CAP_MAILLED);
+
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ set_u32(data->brightness, ACER_CAP_BRIGHTNESS);
+
+ if (has_cap(ACER_CAP_ACCEL))
+ acer_gsensor_init();
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(acer_pm, acer_suspend, acer_resume);
+
+static void acer_platform_shutdown(struct platform_device *device)
+{
+ struct acer_data *data = &interface->data;
+
+ if (!data)
+ return;
+
+ if (has_cap(ACER_CAP_MAILLED))
+ set_u32(LED_OFF, ACER_CAP_MAILLED);
+}
+
+static struct platform_driver acer_platform_driver = {
+ .driver = {
+ .name = "acer-wmi",
+ .owner = THIS_MODULE,
+ .pm = &acer_pm,
+ },
+ .probe = acer_platform_probe,
+ .remove = acer_platform_remove,
+ .shutdown = acer_platform_shutdown,
+};
+
+static struct platform_device *acer_platform_device;
+
+static int remove_sysfs(struct platform_device *device)
+{
+ if (has_cap(ACER_CAP_THREEG))
+ device_remove_file(&device->dev, &dev_attr_threeg);
+
+ device_remove_file(&device->dev, &dev_attr_interface);
+
+ return 0;
+}
+
+static int create_sysfs(void)
+{
+ int retval = -ENOMEM;
+
+ if (has_cap(ACER_CAP_THREEG)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_threeg);
+ if (retval)
+ goto error_sysfs;
+ }
+
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_interface);
+ if (retval)
+ goto error_sysfs;
+
+ return 0;
+
+error_sysfs:
+ remove_sysfs(acer_platform_device);
+ return retval;
+}
+
+static void remove_debugfs(void)
+{
+ debugfs_remove(interface->debug.devices);
+ debugfs_remove(interface->debug.root);
+}
+
+static int create_debugfs(void)
+{
+ interface->debug.root = debugfs_create_dir("acer-wmi", NULL);
+ if (!interface->debug.root) {
+ pr_err("Failed to create debugfs directory");
+ return -ENOMEM;
+ }
+
+ interface->debug.devices = debugfs_create_u32("devices", S_IRUGO,
+ interface->debug.root,
+ &interface->debug.wmid_devices);
+ if (!interface->debug.devices)
+ goto error_debugfs;
+
+ return 0;
+
+error_debugfs:
+ remove_debugfs();
+ return -ENOMEM;
+}
+
+static int __init acer_wmi_init(void)
+{
+ int err;
+
+ pr_info("Acer Laptop ACPI-WMI Extras\n");
+
+ if (dmi_check_system(acer_blacklist)) {
+ pr_info("Blacklisted hardware detected - not loading\n");
+ return -ENODEV;
+ }
+
+ find_quirks();
+
+ /*
+ * Detect which ACPI-WMI interface we're using.
+ */
+ if (wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+ interface = &AMW0_V2_interface;
+
+ if (!wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+ interface = &wmid_interface;
+
+ if (wmi_has_guid(WMID_GUID3))
+ interface = &wmid_v2_interface;
+
+ if (interface)
+ dmi_walk(type_aa_dmi_decode, NULL);
+
+ if (wmi_has_guid(WMID_GUID2) && interface) {
+ if (!has_type_aa && ACPI_FAILURE(WMID_set_capabilities())) {
+ pr_err("Unable to detect available WMID devices\n");
+ return -ENODEV;
+ }
+ /* WMID always provides brightness methods */
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+ } else if (!wmi_has_guid(WMID_GUID2) && interface && !has_type_aa) {
+ pr_err("No WMID device detection method found\n");
+ return -ENODEV;
+ }
+
+ if (wmi_has_guid(AMW0_GUID1) && !wmi_has_guid(WMID_GUID1)) {
+ interface = &AMW0_interface;
+
+ if (ACPI_FAILURE(AMW0_set_capabilities())) {
+ pr_err("Unable to detect available AMW0 devices\n");
+ return -ENODEV;
+ }
+ }
+
+ if (wmi_has_guid(AMW0_GUID1))
+ AMW0_find_mailled();
+
+ if (!interface) {
+ pr_err("No or unsupported WMI interface, unable to load\n");
+ return -ENODEV;
+ }
+
+ set_quirks();
+
+ if (dmi_check_system(video_vendor_dmi_table))
+ acpi_video_dmi_promote_vendor();
+ if (acpi_video_backlight_support()) {
+ interface->capability &= ~ACER_CAP_BRIGHTNESS;
+ pr_info("Brightness must be controlled by acpi video driver\n");
+ } else {
+ pr_info("Disabling ACPI video driver\n");
+ acpi_video_unregister();
+ }
+
+ if (wmi_has_guid(WMID_GUID3)) {
+ if (ec_raw_mode) {
+ if (ACPI_FAILURE(acer_wmi_enable_ec_raw())) {
+ pr_err("Cannot enable EC raw mode\n");
+ return -ENODEV;
+ }
+ } else if (ACPI_FAILURE(acer_wmi_enable_lm())) {
+ pr_err("Cannot enable Launch Manager mode\n");
+ return -ENODEV;
+ }
+ } else if (ec_raw_mode) {
+ pr_info("No WMID EC raw mode enable method\n");
+ }
+
+ if (wmi_has_guid(ACERWMID_EVENT_GUID)) {
+ err = acer_wmi_input_setup();
+ if (err)
+ return err;
+ }
+
+ acer_wmi_accel_setup();
+
+ err = platform_driver_register(&acer_platform_driver);
+ if (err) {
+ pr_err("Unable to register platform driver\n");
+ goto error_platform_register;
+ }
+
+ acer_platform_device = platform_device_alloc("acer-wmi", -1);
+ if (!acer_platform_device) {
+ err = -ENOMEM;
+ goto error_device_alloc;
+ }
+
+ err = platform_device_add(acer_platform_device);
+ if (err)
+ goto error_device_add;
+
+ err = create_sysfs();
+ if (err)
+ goto error_create_sys;
+
+ if (wmi_has_guid(WMID_GUID2)) {
+ interface->debug.wmid_devices = get_wmid_devices();
+ err = create_debugfs();
+ if (err)
+ goto error_create_debugfs;
+ }
+
+ /* Override any initial settings with values from the commandline */
+ acer_commandline_init();
+
+ return 0;
+
+error_create_debugfs:
+ remove_sysfs(acer_platform_device);
+error_create_sys:
+ platform_device_del(acer_platform_device);
+error_device_add:
+ platform_device_put(acer_platform_device);
+error_device_alloc:
+ platform_driver_unregister(&acer_platform_driver);
+error_platform_register:
+ if (wmi_has_guid(ACERWMID_EVENT_GUID))
+ acer_wmi_input_destroy();
+ if (has_cap(ACER_CAP_ACCEL))
+ acer_wmi_accel_destroy();
+
+ return err;
+}
+
+static void __exit acer_wmi_exit(void)
+{
+ if (wmi_has_guid(ACERWMID_EVENT_GUID))
+ acer_wmi_input_destroy();
+
+ if (has_cap(ACER_CAP_ACCEL))
+ acer_wmi_accel_destroy();
+
+ remove_sysfs(acer_platform_device);
+ remove_debugfs();
+ platform_device_unregister(acer_platform_device);
+ platform_driver_unregister(&acer_platform_driver);
+
+ pr_info("Acer Laptop WMI Extras unloaded\n");
+ return;
+}
+
+module_init(acer_wmi_init);
+module_exit(acer_wmi_exit);
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
new file mode 100644
index 00000000..f94467c0
--- /dev/null
+++ b/drivers/platform/x86/acerhdf.c
@@ -0,0 +1,739 @@
+/*
+ * acerhdf - A driver which monitors the temperature
+ * of the aspire one netbook, turns on/off the fan
+ * as soon as the upper/lower threshold is reached.
+ *
+ * (C) 2009 - Peter Feuerer peter (a) piie.net
+ * http://piie.net
+ * 2009 Borislav Petkov bp (a) alien8.de
+ *
+ * Inspired by and many thanks to:
+ * o acerfand - Rachel Greenham
+ * o acer_ec.pl - Michael Kurz michi.kurz (at) googlemail.com
+ * - Petr Tomasek tomasek (#) etf,cuni,cz
+ * - Carlos Corbacho cathectic (at) gmail.com
+ * o lkml - Matthew Garrett
+ * - Borislav Petkov
+ * - Andreas Mohr
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) "acerhdf: " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+
+/*
+ * The driver is started with "kernel mode off" by default. That means, the BIOS
+ * is still in control of the fan. In this mode the driver allows to read the
+ * temperature of the cpu and a userspace tool may take over control of the fan.
+ * If the driver is switched to "kernel mode" (e.g. via module parameter) the
+ * driver is in full control of the fan. If you want the module to be started in
+ * kernel mode by default, define the following:
+ */
+#undef START_IN_KERNEL_MODE
+
+#define DRV_VER "0.5.26"
+
+/*
+ * According to the Atom N270 datasheet,
+ * (http://download.intel.com/design/processor/datashts/320032.pdf) the
+ * CPU's optimal operating limits denoted in junction temperature as
+ * measured by the on-die thermal monitor are within 0 <= Tj <= 90. So,
+ * assume 89°C is critical temperature.
+ */
+#define ACERHDF_TEMP_CRIT 89000
+#define ACERHDF_FAN_OFF 0
+#define ACERHDF_FAN_AUTO 1
+
+/*
+ * No matter what value the user puts into the fanon variable, turn on the fan
+ * at 80 degree Celsius to prevent hardware damage
+ */
+#define ACERHDF_MAX_FANON 80000
+
+/*
+ * Maximum interval between two temperature checks is 15 seconds, as the die
+ * can get hot really fast under heavy load (plus we shouldn't forget about
+ * possible impact of _external_ aggressive sources such as heaters, sun etc.)
+ */
+#define ACERHDF_MAX_INTERVAL 15
+
+#ifdef START_IN_KERNEL_MODE
+static int kernelmode = 1;
+#else
+static int kernelmode;
+#endif
+
+static unsigned int interval = 10;
+static unsigned int fanon = 60000;
+static unsigned int fanoff = 53000;
+static unsigned int verbose;
+static unsigned int fanstate = ACERHDF_FAN_AUTO;
+static char force_bios[16];
+static char force_product[16];
+static unsigned int prev_interval;
+static struct thermal_zone_device *thz_dev;
+static struct thermal_cooling_device *cl_dev;
+static struct platform_device *acerhdf_dev;
+
+module_param(kernelmode, uint, 0);
+MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off");
+module_param(interval, uint, 0600);
+MODULE_PARM_DESC(interval, "Polling interval of temperature check");
+module_param(fanon, uint, 0600);
+MODULE_PARM_DESC(fanon, "Turn the fan on above this temperature");
+module_param(fanoff, uint, 0600);
+MODULE_PARM_DESC(fanoff, "Turn the fan off below this temperature");
+module_param(verbose, uint, 0600);
+MODULE_PARM_DESC(verbose, "Enable verbose dmesg output");
+module_param_string(force_bios, force_bios, 16, 0);
+MODULE_PARM_DESC(force_bios, "Force BIOS version and omit BIOS check");
+module_param_string(force_product, force_product, 16, 0);
+MODULE_PARM_DESC(force_product, "Force BIOS product and omit BIOS check");
+
+/*
+ * cmd_off: to switch the fan completely off and check if the fan is off
+ * cmd_auto: to set the BIOS in control of the fan. The BIOS regulates then
+ * the fan speed depending on the temperature
+ */
+struct fancmd {
+ u8 cmd_off;
+ u8 cmd_auto;
+};
+
+/* BIOS settings */
+struct bios_settings_t {
+ const char *vendor;
+ const char *product;
+ const char *version;
+ unsigned char fanreg;
+ unsigned char tempreg;
+ struct fancmd cmd;
+};
+
+/* Register addresses and values for different BIOS versions */
+static const struct bios_settings_t bios_tbl[] = {
+ /* AOA110 */
+ {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00} },
+ {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00} },
+ {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00} },
+ {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00} },
+ {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00} },
+ {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00} },
+ {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00} },
+ {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00} },
+ {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00} },
+ /* AOA150 */
+ {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x1f, 0x00} },
+ {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00} },
+ /* LT1005u */
+ {"Acer", "LT-10Q", "v0.3310", 0x55, 0x58, {0x20, 0x00} },
+ /* Acer 1410 */
+ {"Acer", "Aspire 1410", "v0.3108", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v0.3115", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v0.3117", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v0.3119", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v1.3204", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v1.3308", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v1.3310", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1410", "v1.3314", 0x55, 0x58, {0x9e, 0x00} },
+ /* Acer 1810xx */
+ {"Acer", "Aspire 1810TZ", "v0.3108", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v0.3108", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v0.3113", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v0.3113", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v0.3115", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v0.3115", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v0.3117", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v0.3117", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v0.3119", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v0.3119", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v0.3120", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v1.3204", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v1.3204", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v1.3303", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v1.3308", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v1.3308", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v1.3310", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v1.3310", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810TZ", "v1.3314", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1810T", "v1.3314", 0x55, 0x58, {0x9e, 0x00} },
+ /* Acer 531 */
+ {"Acer", "AO531h", "v0.3104", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AO531h", "v0.3201", 0x55, 0x58, {0x20, 0x00} },
+ {"Acer", "AO531h", "v0.3304", 0x55, 0x58, {0x20, 0x00} },
+ /* Acer 751 */
+ {"Acer", "AO751h", "V0.3212", 0x55, 0x58, {0x21, 0x00} },
+ /* Acer 1825 */
+ {"Acer", "Aspire 1825PTZ", "V1.3118", 0x55, 0x58, {0x9e, 0x00} },
+ {"Acer", "Aspire 1825PTZ", "V1.3127", 0x55, 0x58, {0x9e, 0x00} },
+ /* Acer TravelMate 7730 */
+ {"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00} },
+ /* Gateway */
+ {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x00} },
+ {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x00} },
+ {"Gateway", "LT31", "v1.3103", 0x55, 0x58, {0x9e, 0x00} },
+ {"Gateway", "LT31", "v1.3201", 0x55, 0x58, {0x9e, 0x00} },
+ {"Gateway", "LT31", "v1.3302", 0x55, 0x58, {0x9e, 0x00} },
+ {"Gateway", "LT31", "v1.3303t", 0x55, 0x58, {0x9e, 0x00} },
+ /* Packard Bell */
+ {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x00} },
+ {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} },
+ {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x00} },
+ {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} },
+ {"Packard Bell", "ENBFT", "V1.3118", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "ENBFT", "V1.3127", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v1.3303", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v0.3120", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v0.3108", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v0.3113", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v0.3115", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v0.3117", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v0.3119", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMU", "v1.3204", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMA", "v1.3201", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMA", "v1.3302", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTMA", "v1.3303t", 0x55, 0x58, {0x9e, 0x00} },
+ {"Packard Bell", "DOTVR46", "v1.3308", 0x55, 0x58, {0x9e, 0x00} },
+ /* pewpew-terminator */
+ {"", "", "", 0, 0, {0, 0} }
+};
+
+static const struct bios_settings_t *bios_cfg __read_mostly;
+
+static int acerhdf_get_temp(int *temp)
+{
+ u8 read_temp;
+
+ if (ec_read(bios_cfg->tempreg, &read_temp))
+ return -EINVAL;
+
+ *temp = read_temp * 1000;
+
+ return 0;
+}
+
+static int acerhdf_get_fanstate(int *state)
+{
+ u8 fan;
+
+ if (ec_read(bios_cfg->fanreg, &fan))
+ return -EINVAL;
+
+ if (fan != bios_cfg->cmd.cmd_off)
+ *state = ACERHDF_FAN_AUTO;
+ else
+ *state = ACERHDF_FAN_OFF;
+
+ return 0;
+}
+
+static void acerhdf_change_fanstate(int state)
+{
+ unsigned char cmd;
+
+ if (verbose)
+ pr_notice("fan %s\n", state == ACERHDF_FAN_OFF ? "OFF" : "ON");
+
+ if ((state != ACERHDF_FAN_OFF) && (state != ACERHDF_FAN_AUTO)) {
+ pr_err("invalid fan state %d requested, setting to auto!\n",
+ state);
+ state = ACERHDF_FAN_AUTO;
+ }
+
+ cmd = (state == ACERHDF_FAN_OFF) ? bios_cfg->cmd.cmd_off
+ : bios_cfg->cmd.cmd_auto;
+ fanstate = state;
+
+ ec_write(bios_cfg->fanreg, cmd);
+}
+
+static void acerhdf_check_param(struct thermal_zone_device *thermal)
+{
+ if (fanon > ACERHDF_MAX_FANON) {
+ pr_err("fanon temperature too high, set to %d\n",
+ ACERHDF_MAX_FANON);
+ fanon = ACERHDF_MAX_FANON;
+ }
+
+ if (kernelmode && prev_interval != interval) {
+ if (interval > ACERHDF_MAX_INTERVAL) {
+ pr_err("interval too high, set to %d\n",
+ ACERHDF_MAX_INTERVAL);
+ interval = ACERHDF_MAX_INTERVAL;
+ }
+ if (verbose)
+ pr_notice("interval changed to: %d\n", interval);
+ thermal->polling_delay = interval*1000;
+ prev_interval = interval;
+ }
+}
+
+/*
+ * This is the thermal zone callback which does the delayed polling of the fan
+ * state. We do check /sysfs-originating settings here in acerhdf_check_param()
+ * as late as the polling interval is since we can't do that in the respective
+ * accessors of the module parameters.
+ */
+static int acerhdf_get_ec_temp(struct thermal_zone_device *thermal,
+ unsigned long *t)
+{
+ int temp, err = 0;
+
+ acerhdf_check_param(thermal);
+
+ err = acerhdf_get_temp(&temp);
+ if (err)
+ return err;
+
+ if (verbose)
+ pr_notice("temp %d\n", temp);
+
+ *t = temp;
+ return 0;
+}
+
+static int acerhdf_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ /* if the cooling device is the one from acerhdf bind it */
+ if (cdev != cl_dev)
+ return 0;
+
+ if (thermal_zone_bind_cooling_device(thermal, 0, cdev,
+ THERMAL_NO_LIMIT, THERMAL_NO_LIMIT)) {
+ pr_err("error binding cooling dev\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int acerhdf_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ if (cdev != cl_dev)
+ return 0;
+
+ if (thermal_zone_unbind_cooling_device(thermal, 0, cdev)) {
+ pr_err("error unbinding cooling dev\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static inline void acerhdf_revert_to_bios_mode(void)
+{
+ acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+ kernelmode = 0;
+ if (thz_dev)
+ thz_dev->polling_delay = 0;
+ pr_notice("kernel mode fan control OFF\n");
+}
+static inline void acerhdf_enable_kernelmode(void)
+{
+ kernelmode = 1;
+
+ thz_dev->polling_delay = interval*1000;
+ thermal_zone_device_update(thz_dev);
+ pr_notice("kernel mode fan control ON\n");
+}
+
+static int acerhdf_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ if (verbose)
+ pr_notice("kernel mode fan control %d\n", kernelmode);
+
+ *mode = (kernelmode) ? THERMAL_DEVICE_ENABLED
+ : THERMAL_DEVICE_DISABLED;
+
+ return 0;
+}
+
+/*
+ * set operation mode;
+ * enabled: the thermal layer of the kernel takes care about
+ * the temperature and the fan.
+ * disabled: the BIOS takes control of the fan.
+ */
+static int acerhdf_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ if (mode == THERMAL_DEVICE_DISABLED && kernelmode)
+ acerhdf_revert_to_bios_mode();
+ else if (mode == THERMAL_DEVICE_ENABLED && !kernelmode)
+ acerhdf_enable_kernelmode();
+
+ return 0;
+}
+
+static int acerhdf_get_trip_type(struct thermal_zone_device *thermal, int trip,
+ enum thermal_trip_type *type)
+{
+ if (trip == 0)
+ *type = THERMAL_TRIP_ACTIVE;
+
+ return 0;
+}
+
+static int acerhdf_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+ unsigned long *temp)
+{
+ if (trip == 0)
+ *temp = fanon;
+
+ return 0;
+}
+
+static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temperature)
+{
+ *temperature = ACERHDF_TEMP_CRIT;
+ return 0;
+}
+
+/* bind callback functions to thermalzone */
+static struct thermal_zone_device_ops acerhdf_dev_ops = {
+ .bind = acerhdf_bind,
+ .unbind = acerhdf_unbind,
+ .get_temp = acerhdf_get_ec_temp,
+ .get_mode = acerhdf_get_mode,
+ .set_mode = acerhdf_set_mode,
+ .get_trip_type = acerhdf_get_trip_type,
+ .get_trip_temp = acerhdf_get_trip_temp,
+ .get_crit_temp = acerhdf_get_crit_temp,
+};
+
+
+/*
+ * cooling device callback functions
+ * get maximal fan cooling state
+ */
+static int acerhdf_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ *state = 1;
+
+ return 0;
+}
+
+static int acerhdf_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int err = 0, tmp;
+
+ err = acerhdf_get_fanstate(&tmp);
+ if (err)
+ return err;
+
+ *state = (tmp == ACERHDF_FAN_AUTO) ? 1 : 0;
+ return 0;
+}
+
+/* change current fan state - is overwritten when running in kernel mode */
+static int acerhdf_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ int cur_temp, cur_state, err = 0;
+
+ if (!kernelmode)
+ return 0;
+
+ err = acerhdf_get_temp(&cur_temp);
+ if (err) {
+ pr_err("error reading temperature, hand off control to BIOS\n");
+ goto err_out;
+ }
+
+ err = acerhdf_get_fanstate(&cur_state);
+ if (err) {
+ pr_err("error reading fan state, hand off control to BIOS\n");
+ goto err_out;
+ }
+
+ if (state == 0) {
+ /* turn fan off only if below fanoff temperature */
+ if ((cur_state == ACERHDF_FAN_AUTO) &&
+ (cur_temp < fanoff))
+ acerhdf_change_fanstate(ACERHDF_FAN_OFF);
+ } else {
+ if (cur_state == ACERHDF_FAN_OFF)
+ acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+ }
+ return 0;
+
+err_out:
+ acerhdf_revert_to_bios_mode();
+ return -EINVAL;
+}
+
+/* bind fan callbacks to fan device */
+static struct thermal_cooling_device_ops acerhdf_cooling_ops = {
+ .get_max_state = acerhdf_get_max_state,
+ .get_cur_state = acerhdf_get_cur_state,
+ .set_cur_state = acerhdf_set_cur_state,
+};
+
+/* suspend / resume functionality */
+static int acerhdf_suspend(struct device *dev)
+{
+ if (kernelmode)
+ acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+
+ if (verbose)
+ pr_notice("going suspend\n");
+
+ return 0;
+}
+
+static int acerhdf_probe(struct platform_device *device)
+{
+ return 0;
+}
+
+static int acerhdf_remove(struct platform_device *device)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops acerhdf_pm_ops = {
+ .suspend = acerhdf_suspend,
+ .freeze = acerhdf_suspend,
+};
+
+static struct platform_driver acerhdf_driver = {
+ .driver = {
+ .name = "acerhdf",
+ .owner = THIS_MODULE,
+ .pm = &acerhdf_pm_ops,
+ },
+ .probe = acerhdf_probe,
+ .remove = acerhdf_remove,
+};
+
+/* checks if str begins with start */
+static int str_starts_with(const char *str, const char *start)
+{
+ unsigned long str_len = 0, start_len = 0;
+
+ str_len = strlen(str);
+ start_len = strlen(start);
+
+ if (str_len >= start_len &&
+ !strncmp(str, start, start_len))
+ return 1;
+
+ return 0;
+}
+
+/* check hardware */
+static int acerhdf_check_hardware(void)
+{
+ char const *vendor, *version, *product;
+ const struct bios_settings_t *bt = NULL;
+
+ /* get BIOS data */
+ vendor = dmi_get_system_info(DMI_SYS_VENDOR);
+ version = dmi_get_system_info(DMI_BIOS_VERSION);
+ product = dmi_get_system_info(DMI_PRODUCT_NAME);
+
+ if (!vendor || !version || !product) {
+ pr_err("error getting hardware information\n");
+ return -EINVAL;
+ }
+
+ pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER);
+
+ if (force_bios[0]) {
+ version = force_bios;
+ pr_info("forcing BIOS version: %s\n", version);
+ kernelmode = 0;
+ }
+
+ if (force_product[0]) {
+ product = force_product;
+ pr_info("forcing BIOS product: %s\n", product);
+ kernelmode = 0;
+ }
+
+ if (verbose)
+ pr_info("BIOS info: %s %s, product: %s\n",
+ vendor, version, product);
+
+ /* search BIOS version and vendor in BIOS settings table */
+ for (bt = bios_tbl; bt->vendor[0]; bt++) {
+ /*
+ * check if actual hardware BIOS vendor, product and version
+ * IDs start with the strings of BIOS table entry
+ */
+ if (str_starts_with(vendor, bt->vendor) &&
+ str_starts_with(product, bt->product) &&
+ str_starts_with(version, bt->version)) {
+ bios_cfg = bt;
+ break;
+ }
+ }
+
+ if (!bios_cfg) {
+ pr_err("unknown (unsupported) BIOS version %s/%s/%s, please report, aborting!\n",
+ vendor, product, version);
+ return -EINVAL;
+ }
+
+ /*
+ * if started with kernel mode off, prevent the kernel from switching
+ * off the fan
+ */
+ if (!kernelmode) {
+ pr_notice("Fan control off, to enable do:\n");
+ pr_notice("echo -n \"enabled\" > /sys/class/thermal/thermal_zone0/mode\n");
+ }
+
+ return 0;
+}
+
+static int acerhdf_register_platform(void)
+{
+ int err = 0;
+
+ err = platform_driver_register(&acerhdf_driver);
+ if (err)
+ return err;
+
+ acerhdf_dev = platform_device_alloc("acerhdf", -1);
+ if (!acerhdf_dev) {
+ err = -ENOMEM;
+ goto err_device_alloc;
+ }
+ err = platform_device_add(acerhdf_dev);
+ if (err)
+ goto err_device_add;
+
+ return 0;
+
+err_device_add:
+ platform_device_put(acerhdf_dev);
+err_device_alloc:
+ platform_driver_unregister(&acerhdf_driver);
+ return err;
+}
+
+static void acerhdf_unregister_platform(void)
+{
+ platform_device_unregister(acerhdf_dev);
+ platform_driver_unregister(&acerhdf_driver);
+}
+
+static int acerhdf_register_thermal(void)
+{
+ cl_dev = thermal_cooling_device_register("acerhdf-fan", NULL,
+ &acerhdf_cooling_ops);
+
+ if (IS_ERR(cl_dev))
+ return -EINVAL;
+
+ thz_dev = thermal_zone_device_register("acerhdf", 1, 0, NULL,
+ &acerhdf_dev_ops, NULL, 0,
+ (kernelmode) ? interval*1000 : 0);
+ if (IS_ERR(thz_dev))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void acerhdf_unregister_thermal(void)
+{
+ if (cl_dev) {
+ thermal_cooling_device_unregister(cl_dev);
+ cl_dev = NULL;
+ }
+
+ if (thz_dev) {
+ thermal_zone_device_unregister(thz_dev);
+ thz_dev = NULL;
+ }
+}
+
+static int __init acerhdf_init(void)
+{
+ int err = 0;
+
+ err = acerhdf_check_hardware();
+ if (err)
+ goto out_err;
+
+ err = acerhdf_register_platform();
+ if (err)
+ goto out_err;
+
+ err = acerhdf_register_thermal();
+ if (err)
+ goto err_unreg;
+
+ return 0;
+
+err_unreg:
+ acerhdf_unregister_thermal();
+ acerhdf_unregister_platform();
+
+out_err:
+ return err;
+}
+
+static void __exit acerhdf_exit(void)
+{
+ acerhdf_change_fanstate(ACERHDF_FAN_AUTO);
+ acerhdf_unregister_thermal();
+ acerhdf_unregister_platform();
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Feuerer");
+MODULE_DESCRIPTION("Aspire One temperature and fan driver");
+MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1410*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1810*:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAspire*1825PTZ:");
+MODULE_ALIAS("dmi:*:*Acer*:pnAO531*:");
+MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:");
+MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:");
+MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnAOA*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOA*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMU*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnENBFT*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTMA*:");
+MODULE_ALIAS("dmi:*:*Packard*Bell*:pnDOTVR46*:");
+
+module_init(acerhdf_init);
+module_exit(acerhdf_exit);
diff --git a/drivers/platform/x86/amilo-rfkill.c b/drivers/platform/x86/amilo-rfkill.c
new file mode 100644
index 00000000..6296f078
--- /dev/null
+++ b/drivers/platform/x86/amilo-rfkill.c
@@ -0,0 +1,176 @@
+/*
+ * Support for rfkill on some Fujitsu-Siemens Amilo laptops.
+ * Copyright 2011 Ben Hutchings.
+ *
+ * Based in part on the fsam7440 driver, which is:
+ * Copyright 2005 Alejandro Vidal Mata & Javier Vidal Mata.
+ * and on the fsaa1655g driver, which is:
+ * Copyright 2006 Martin Večeř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.
+ */
+
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/i8042.h>
+#include <linux/io.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+
+/*
+ * These values were obtained from disassembling and debugging the
+ * PM.exe program installed in the Fujitsu-Siemens AMILO A1655G
+ */
+#define A1655_WIFI_COMMAND 0x10C5
+#define A1655_WIFI_ON 0x25
+#define A1655_WIFI_OFF 0x45
+
+static int amilo_a1655_rfkill_set_block(void *data, bool blocked)
+{
+ u8 param = blocked ? A1655_WIFI_OFF : A1655_WIFI_ON;
+ int rc;
+
+ i8042_lock_chip();
+ rc = i8042_command(&param, A1655_WIFI_COMMAND);
+ i8042_unlock_chip();
+ return rc;
+}
+
+static const struct rfkill_ops amilo_a1655_rfkill_ops = {
+ .set_block = amilo_a1655_rfkill_set_block
+};
+
+/*
+ * These values were obtained from disassembling the PM.exe program
+ * installed in the Fujitsu-Siemens AMILO M 7440
+ */
+#define M7440_PORT1 0x118f
+#define M7440_PORT2 0x118e
+#define M7440_RADIO_ON1 0x12
+#define M7440_RADIO_ON2 0x80
+#define M7440_RADIO_OFF1 0x10
+#define M7440_RADIO_OFF2 0x00
+
+static int amilo_m7440_rfkill_set_block(void *data, bool blocked)
+{
+ u8 val1 = blocked ? M7440_RADIO_OFF1 : M7440_RADIO_ON1;
+ u8 val2 = blocked ? M7440_RADIO_OFF2 : M7440_RADIO_ON2;
+
+ outb(val1, M7440_PORT1);
+ outb(val2, M7440_PORT2);
+
+ /* Check whether the state has changed correctly */
+ if (inb(M7440_PORT1) != val1 || inb(M7440_PORT2) != val2)
+ return -EIO;
+
+ return 0;
+}
+
+static const struct rfkill_ops amilo_m7440_rfkill_ops = {
+ .set_block = amilo_m7440_rfkill_set_block
+};
+
+static const struct dmi_system_id amilo_rfkill_id_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_BOARD_NAME, "AMILO A1655"),
+ },
+ .driver_data = (void *)&amilo_a1655_rfkill_ops
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_BOARD_NAME, "AMILO M7440"),
+ },
+ .driver_data = (void *)&amilo_m7440_rfkill_ops
+ },
+ {}
+};
+
+static struct platform_device *amilo_rfkill_pdev;
+static struct rfkill *amilo_rfkill_dev;
+
+static int amilo_rfkill_probe(struct platform_device *device)
+{
+ int rc;
+ const struct dmi_system_id *system_id =
+ dmi_first_match(amilo_rfkill_id_table);
+
+ if (!system_id)
+ return -ENXIO;
+
+ amilo_rfkill_dev = rfkill_alloc(KBUILD_MODNAME, &device->dev,
+ RFKILL_TYPE_WLAN,
+ system_id->driver_data, NULL);
+ if (!amilo_rfkill_dev)
+ return -ENOMEM;
+
+ rc = rfkill_register(amilo_rfkill_dev);
+ if (rc)
+ goto fail;
+
+ return 0;
+
+fail:
+ rfkill_destroy(amilo_rfkill_dev);
+ return rc;
+}
+
+static int amilo_rfkill_remove(struct platform_device *device)
+{
+ rfkill_unregister(amilo_rfkill_dev);
+ rfkill_destroy(amilo_rfkill_dev);
+ return 0;
+}
+
+static struct platform_driver amilo_rfkill_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = amilo_rfkill_probe,
+ .remove = amilo_rfkill_remove,
+};
+
+static int __init amilo_rfkill_init(void)
+{
+ int rc;
+
+ if (dmi_first_match(amilo_rfkill_id_table) == NULL)
+ return -ENODEV;
+
+ rc = platform_driver_register(&amilo_rfkill_driver);
+ if (rc)
+ return rc;
+
+ amilo_rfkill_pdev = platform_device_register_simple(KBUILD_MODNAME, -1,
+ NULL, 0);
+ if (IS_ERR(amilo_rfkill_pdev)) {
+ rc = PTR_ERR(amilo_rfkill_pdev);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ platform_driver_unregister(&amilo_rfkill_driver);
+ return rc;
+}
+
+static void __exit amilo_rfkill_exit(void)
+{
+ platform_device_unregister(amilo_rfkill_pdev);
+ platform_driver_unregister(&amilo_rfkill_driver);
+}
+
+MODULE_AUTHOR("Ben Hutchings <ben@decadent.org.uk>");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(dmi, amilo_rfkill_id_table);
+
+module_init(amilo_rfkill_init);
+module_exit(amilo_rfkill_exit);
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c
new file mode 100644
index 00000000..f74bfcbb
--- /dev/null
+++ b/drivers/platform/x86/apple-gmux.c
@@ -0,0 +1,633 @@
+/*
+ * Gmux driver for Apple laptops
+ *
+ * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com>
+ * Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/backlight.h>
+#include <linux/acpi.h>
+#include <linux/pnp.h>
+#include <linux/apple_bl.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/vga_switcheroo.h>
+#include <acpi/video.h>
+#include <asm/io.h>
+
+struct apple_gmux_data {
+ unsigned long iostart;
+ unsigned long iolen;
+ bool indexed;
+ struct mutex index_lock;
+
+ struct backlight_device *bdev;
+
+ /* switcheroo data */
+ acpi_handle dhandle;
+ int gpe;
+ enum vga_switcheroo_client_id resume_client_id;
+ enum vga_switcheroo_state power_state;
+ struct completion powerchange_done;
+};
+
+static struct apple_gmux_data *apple_gmux_data;
+
+/*
+ * gmux port offsets. Many of these are not yet used, but may be in the
+ * future, and it's useful to have them documented here anyhow.
+ */
+#define GMUX_PORT_VERSION_MAJOR 0x04
+#define GMUX_PORT_VERSION_MINOR 0x05
+#define GMUX_PORT_VERSION_RELEASE 0x06
+#define GMUX_PORT_SWITCH_DISPLAY 0x10
+#define GMUX_PORT_SWITCH_GET_DISPLAY 0x11
+#define GMUX_PORT_INTERRUPT_ENABLE 0x14
+#define GMUX_PORT_INTERRUPT_STATUS 0x16
+#define GMUX_PORT_SWITCH_DDC 0x28
+#define GMUX_PORT_SWITCH_EXTERNAL 0x40
+#define GMUX_PORT_SWITCH_GET_EXTERNAL 0x41
+#define GMUX_PORT_DISCRETE_POWER 0x50
+#define GMUX_PORT_MAX_BRIGHTNESS 0x70
+#define GMUX_PORT_BRIGHTNESS 0x74
+#define GMUX_PORT_VALUE 0xc2
+#define GMUX_PORT_READ 0xd0
+#define GMUX_PORT_WRITE 0xd4
+
+#define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4)
+
+#define GMUX_INTERRUPT_ENABLE 0xff
+#define GMUX_INTERRUPT_DISABLE 0x00
+
+#define GMUX_INTERRUPT_STATUS_ACTIVE 0
+#define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0)
+#define GMUX_INTERRUPT_STATUS_POWER (1 << 2)
+#define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3)
+
+#define GMUX_BRIGHTNESS_MASK 0x00ffffff
+#define GMUX_MAX_BRIGHTNESS GMUX_BRIGHTNESS_MASK
+
+static u8 gmux_pio_read8(struct apple_gmux_data *gmux_data, int port)
+{
+ return inb(gmux_data->iostart + port);
+}
+
+static void gmux_pio_write8(struct apple_gmux_data *gmux_data, int port,
+ u8 val)
+{
+ outb(val, gmux_data->iostart + port);
+}
+
+static u32 gmux_pio_read32(struct apple_gmux_data *gmux_data, int port)
+{
+ return inl(gmux_data->iostart + port);
+}
+
+static void gmux_pio_write32(struct apple_gmux_data *gmux_data, int port,
+ u32 val)
+{
+ int i;
+ u8 tmpval;
+
+ for (i = 0; i < 4; i++) {
+ tmpval = (val >> (i * 8)) & 0xff;
+ outb(tmpval, gmux_data->iostart + port + i);
+ }
+}
+
+static int gmux_index_wait_ready(struct apple_gmux_data *gmux_data)
+{
+ int i = 200;
+ u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+
+ while (i && (gwr & 0x01)) {
+ inb(gmux_data->iostart + GMUX_PORT_READ);
+ gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+ udelay(100);
+ i--;
+ }
+
+ return !!i;
+}
+
+static int gmux_index_wait_complete(struct apple_gmux_data *gmux_data)
+{
+ int i = 200;
+ u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+
+ while (i && !(gwr & 0x01)) {
+ gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
+ udelay(100);
+ i--;
+ }
+
+ if (gwr & 0x01)
+ inb(gmux_data->iostart + GMUX_PORT_READ);
+
+ return !!i;
+}
+
+static u8 gmux_index_read8(struct apple_gmux_data *gmux_data, int port)
+{
+ u8 val;
+
+ mutex_lock(&gmux_data->index_lock);
+ gmux_index_wait_ready(gmux_data);
+ outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
+ gmux_index_wait_complete(gmux_data);
+ val = inb(gmux_data->iostart + GMUX_PORT_VALUE);
+ mutex_unlock(&gmux_data->index_lock);
+
+ return val;
+}
+
+static void gmux_index_write8(struct apple_gmux_data *gmux_data, int port,
+ u8 val)
+{
+ mutex_lock(&gmux_data->index_lock);
+ outb(val, gmux_data->iostart + GMUX_PORT_VALUE);
+ gmux_index_wait_ready(gmux_data);
+ outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
+ gmux_index_wait_complete(gmux_data);
+ mutex_unlock(&gmux_data->index_lock);
+}
+
+static u32 gmux_index_read32(struct apple_gmux_data *gmux_data, int port)
+{
+ u32 val;
+
+ mutex_lock(&gmux_data->index_lock);
+ gmux_index_wait_ready(gmux_data);
+ outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
+ gmux_index_wait_complete(gmux_data);
+ val = inl(gmux_data->iostart + GMUX_PORT_VALUE);
+ mutex_unlock(&gmux_data->index_lock);
+
+ return val;
+}
+
+static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port,
+ u32 val)
+{
+ int i;
+ u8 tmpval;
+
+ mutex_lock(&gmux_data->index_lock);
+
+ for (i = 0; i < 4; i++) {
+ tmpval = (val >> (i * 8)) & 0xff;
+ outb(tmpval, gmux_data->iostart + GMUX_PORT_VALUE + i);
+ }
+
+ gmux_index_wait_ready(gmux_data);
+ outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
+ gmux_index_wait_complete(gmux_data);
+ mutex_unlock(&gmux_data->index_lock);
+}
+
+static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port)
+{
+ if (gmux_data->indexed)
+ return gmux_index_read8(gmux_data, port);
+ else
+ return gmux_pio_read8(gmux_data, port);
+}
+
+static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val)
+{
+ if (gmux_data->indexed)
+ gmux_index_write8(gmux_data, port, val);
+ else
+ gmux_pio_write8(gmux_data, port, val);
+}
+
+static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port)
+{
+ if (gmux_data->indexed)
+ return gmux_index_read32(gmux_data, port);
+ else
+ return gmux_pio_read32(gmux_data, port);
+}
+
+static void gmux_write32(struct apple_gmux_data *gmux_data, int port,
+ u32 val)
+{
+ if (gmux_data->indexed)
+ gmux_index_write32(gmux_data, port, val);
+ else
+ gmux_pio_write32(gmux_data, port, val);
+}
+
+static bool gmux_is_indexed(struct apple_gmux_data *gmux_data)
+{
+ u16 val;
+
+ outb(0xaa, gmux_data->iostart + 0xcc);
+ outb(0x55, gmux_data->iostart + 0xcd);
+ outb(0x00, gmux_data->iostart + 0xce);
+
+ val = inb(gmux_data->iostart + 0xcc) |
+ (inb(gmux_data->iostart + 0xcd) << 8);
+
+ if (val == 0x55aa)
+ return true;
+
+ return false;
+}
+
+static int gmux_get_brightness(struct backlight_device *bd)
+{
+ struct apple_gmux_data *gmux_data = bl_get_data(bd);
+ return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) &
+ GMUX_BRIGHTNESS_MASK;
+}
+
+static int gmux_update_status(struct backlight_device *bd)
+{
+ struct apple_gmux_data *gmux_data = bl_get_data(bd);
+ u32 brightness = bd->props.brightness;
+
+ if (bd->props.state & BL_CORE_SUSPENDED)
+ return 0;
+
+ gmux_write32(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
+
+ return 0;
+}
+
+static const struct backlight_ops gmux_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .get_brightness = gmux_get_brightness,
+ .update_status = gmux_update_status,
+};
+
+static int gmux_switchto(enum vga_switcheroo_client_id id)
+{
+ if (id == VGA_SWITCHEROO_IGD) {
+ gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1);
+ gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 2);
+ gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 2);
+ } else {
+ gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 2);
+ gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 3);
+ gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3);
+ }
+
+ return 0;
+}
+
+static int gmux_set_discrete_state(struct apple_gmux_data *gmux_data,
+ enum vga_switcheroo_state state)
+{
+ INIT_COMPLETION(gmux_data->powerchange_done);
+
+ if (state == VGA_SWITCHEROO_ON) {
+ gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1);
+ gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 3);
+ pr_debug("Discrete card powered up\n");
+ } else {
+ gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1);
+ gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 0);
+ pr_debug("Discrete card powered down\n");
+ }
+
+ gmux_data->power_state = state;
+
+ if (gmux_data->gpe >= 0 &&
+ !wait_for_completion_interruptible_timeout(&gmux_data->powerchange_done,
+ msecs_to_jiffies(200)))
+ pr_warn("Timeout waiting for gmux switch to complete\n");
+
+ return 0;
+}
+
+static int gmux_set_power_state(enum vga_switcheroo_client_id id,
+ enum vga_switcheroo_state state)
+{
+ if (id == VGA_SWITCHEROO_IGD)
+ return 0;
+
+ return gmux_set_discrete_state(apple_gmux_data, state);
+}
+
+static int gmux_get_client_id(struct pci_dev *pdev)
+{
+ /*
+ * Early Macbook Pros with switchable graphics use nvidia
+ * integrated graphics. Hardcode that the 9400M is integrated.
+ */
+ if (pdev->vendor == PCI_VENDOR_ID_INTEL)
+ return VGA_SWITCHEROO_IGD;
+ else if (pdev->vendor == PCI_VENDOR_ID_NVIDIA &&
+ pdev->device == 0x0863)
+ return VGA_SWITCHEROO_IGD;
+ else
+ return VGA_SWITCHEROO_DIS;
+}
+
+static enum vga_switcheroo_client_id
+gmux_active_client(struct apple_gmux_data *gmux_data)
+{
+ if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DISPLAY) == 2)
+ return VGA_SWITCHEROO_IGD;
+
+ return VGA_SWITCHEROO_DIS;
+}
+
+static struct vga_switcheroo_handler gmux_handler = {
+ .switchto = gmux_switchto,
+ .power_state = gmux_set_power_state,
+ .get_client_id = gmux_get_client_id,
+};
+
+static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data)
+{
+ gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE,
+ GMUX_INTERRUPT_DISABLE);
+}
+
+static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data)
+{
+ gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE,
+ GMUX_INTERRUPT_ENABLE);
+}
+
+static inline u8 gmux_interrupt_get_status(struct apple_gmux_data *gmux_data)
+{
+ return gmux_read8(gmux_data, GMUX_PORT_INTERRUPT_STATUS);
+}
+
+static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data)
+{
+ u8 status;
+
+ /* to clear interrupts write back current status */
+ status = gmux_interrupt_get_status(gmux_data);
+ gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status);
+}
+
+static void gmux_notify_handler(acpi_handle device, u32 value, void *context)
+{
+ u8 status;
+ struct pnp_dev *pnp = (struct pnp_dev *)context;
+ struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+
+ status = gmux_interrupt_get_status(gmux_data);
+ gmux_disable_interrupts(gmux_data);
+ pr_debug("Notify handler called: status %d\n", status);
+
+ gmux_clear_interrupts(gmux_data);
+ gmux_enable_interrupts(gmux_data);
+
+ if (status & GMUX_INTERRUPT_STATUS_POWER)
+ complete(&gmux_data->powerchange_done);
+}
+
+static int gmux_suspend(struct pnp_dev *pnp, pm_message_t state)
+{
+ struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+ gmux_data->resume_client_id = gmux_active_client(gmux_data);
+ gmux_disable_interrupts(gmux_data);
+ return 0;
+}
+
+static int gmux_resume(struct pnp_dev *pnp)
+{
+ struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+ gmux_enable_interrupts(gmux_data);
+ gmux_switchto(gmux_data->resume_client_id);
+ if (gmux_data->power_state == VGA_SWITCHEROO_OFF)
+ gmux_set_discrete_state(gmux_data, gmux_data->power_state);
+ return 0;
+}
+
+static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
+{
+ struct apple_gmux_data *gmux_data;
+ struct resource *res;
+ struct backlight_properties props;
+ struct backlight_device *bdev;
+ u8 ver_major, ver_minor, ver_release;
+ int ret = -ENXIO;
+ acpi_status status;
+ unsigned long long gpe;
+
+ if (apple_gmux_data)
+ return -EBUSY;
+
+ gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL);
+ if (!gmux_data)
+ return -ENOMEM;
+ pnp_set_drvdata(pnp, gmux_data);
+
+ res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
+ if (!res) {
+ pr_err("Failed to find gmux I/O resource\n");
+ goto err_free;
+ }
+
+ gmux_data->iostart = res->start;
+ gmux_data->iolen = res->end - res->start;
+
+ if (gmux_data->iolen < GMUX_MIN_IO_LEN) {
+ pr_err("gmux I/O region too small (%lu < %u)\n",
+ gmux_data->iolen, GMUX_MIN_IO_LEN);
+ goto err_free;
+ }
+
+ if (!request_region(gmux_data->iostart, gmux_data->iolen,
+ "Apple gmux")) {
+ pr_err("gmux I/O already in use\n");
+ goto err_free;
+ }
+
+ /*
+ * Invalid version information may indicate either that the gmux
+ * device isn't present or that it's a new one that uses indexed
+ * io
+ */
+
+ ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR);
+ ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR);
+ ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
+ if (ver_major == 0xff && ver_minor == 0xff && ver_release == 0xff) {
+ if (gmux_is_indexed(gmux_data)) {
+ u32 version;
+ mutex_init(&gmux_data->index_lock);
+ gmux_data->indexed = true;
+ version = gmux_read32(gmux_data,
+ GMUX_PORT_VERSION_MAJOR);
+ ver_major = (version >> 24) & 0xff;
+ ver_minor = (version >> 16) & 0xff;
+ ver_release = (version >> 8) & 0xff;
+ } else {
+ pr_info("gmux device not present\n");
+ ret = -ENODEV;
+ goto err_release;
+ }
+ }
+ pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor,
+ ver_release, (gmux_data->indexed ? "indexed" : "classic"));
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
+
+ /*
+ * Currently it's assumed that the maximum brightness is less than
+ * 2^24 for compatibility with old gmux versions. Cap the max
+ * brightness at this value, but print a warning if the hardware
+ * reports something higher so that it can be fixed.
+ */
+ if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
+ props.max_brightness = GMUX_MAX_BRIGHTNESS;
+
+ bdev = backlight_device_register("gmux_backlight", &pnp->dev,
+ gmux_data, &gmux_bl_ops, &props);
+ if (IS_ERR(bdev)) {
+ ret = PTR_ERR(bdev);
+ goto err_release;
+ }
+
+ gmux_data->bdev = bdev;
+ bdev->props.brightness = gmux_get_brightness(bdev);
+ backlight_update_status(bdev);
+
+ /*
+ * The backlight situation on Macs is complicated. If the gmux is
+ * present it's the best choice, because it always works for
+ * backlight control and supports more levels than other options.
+ * Disable the other backlight choices.
+ */
+ acpi_video_dmi_promote_vendor();
+ acpi_video_unregister();
+ apple_bl_unregister();
+
+ gmux_data->power_state = VGA_SWITCHEROO_ON;
+
+ gmux_data->dhandle = DEVICE_ACPI_HANDLE(&pnp->dev);
+ if (!gmux_data->dhandle) {
+ pr_err("Cannot find acpi handle for pnp device %s\n",
+ dev_name(&pnp->dev));
+ ret = -ENODEV;
+ goto err_notify;
+ }
+
+ status = acpi_evaluate_integer(gmux_data->dhandle, "GMGP", NULL, &gpe);
+ if (ACPI_SUCCESS(status)) {
+ gmux_data->gpe = (int)gpe;
+
+ status = acpi_install_notify_handler(gmux_data->dhandle,
+ ACPI_DEVICE_NOTIFY,
+ &gmux_notify_handler, pnp);
+ if (ACPI_FAILURE(status)) {
+ pr_err("Install notify handler failed: %s\n",
+ acpi_format_exception(status));
+ ret = -ENODEV;
+ goto err_notify;
+ }
+
+ status = acpi_enable_gpe(NULL, gmux_data->gpe);
+ if (ACPI_FAILURE(status)) {
+ pr_err("Cannot enable gpe: %s\n",
+ acpi_format_exception(status));
+ goto err_enable_gpe;
+ }
+ } else {
+ pr_warn("No GPE found for gmux\n");
+ gmux_data->gpe = -1;
+ }
+
+ if (vga_switcheroo_register_handler(&gmux_handler)) {
+ ret = -ENODEV;
+ goto err_register_handler;
+ }
+
+ init_completion(&gmux_data->powerchange_done);
+ apple_gmux_data = gmux_data;
+ gmux_enable_interrupts(gmux_data);
+
+ return 0;
+
+err_register_handler:
+ if (gmux_data->gpe >= 0)
+ acpi_disable_gpe(NULL, gmux_data->gpe);
+err_enable_gpe:
+ if (gmux_data->gpe >= 0)
+ acpi_remove_notify_handler(gmux_data->dhandle,
+ ACPI_DEVICE_NOTIFY,
+ &gmux_notify_handler);
+err_notify:
+ backlight_device_unregister(bdev);
+err_release:
+ release_region(gmux_data->iostart, gmux_data->iolen);
+err_free:
+ kfree(gmux_data);
+ return ret;
+}
+
+static void gmux_remove(struct pnp_dev *pnp)
+{
+ struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
+
+ vga_switcheroo_unregister_handler();
+ gmux_disable_interrupts(gmux_data);
+ if (gmux_data->gpe >= 0) {
+ acpi_disable_gpe(NULL, gmux_data->gpe);
+ acpi_remove_notify_handler(gmux_data->dhandle,
+ ACPI_DEVICE_NOTIFY,
+ &gmux_notify_handler);
+ }
+
+ backlight_device_unregister(gmux_data->bdev);
+
+ release_region(gmux_data->iostart, gmux_data->iolen);
+ apple_gmux_data = NULL;
+ kfree(gmux_data);
+
+ acpi_video_dmi_demote_vendor();
+ acpi_video_register();
+ apple_bl_register();
+}
+
+static const struct pnp_device_id gmux_device_ids[] = {
+ {"APP000B", 0},
+ {"", 0}
+};
+
+static struct pnp_driver gmux_pnp_driver = {
+ .name = "apple-gmux",
+ .probe = gmux_probe,
+ .remove = gmux_remove,
+ .id_table = gmux_device_ids,
+ .suspend = gmux_suspend,
+ .resume = gmux_resume
+};
+
+static int __init apple_gmux_init(void)
+{
+ return pnp_register_driver(&gmux_pnp_driver);
+}
+
+static void __exit apple_gmux_exit(void)
+{
+ pnp_unregister_driver(&gmux_pnp_driver);
+}
+
+module_init(apple_gmux_init);
+module_exit(apple_gmux_exit);
+
+MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>");
+MODULE_DESCRIPTION("Apple Gmux Driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pnp, gmux_device_ids);
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
new file mode 100644
index 00000000..0eea09c1
--- /dev/null
+++ b/drivers/platform/x86/asus-laptop.c
@@ -0,0 +1,2011 @@
+/*
+ * asus-laptop.c - Asus Laptop Support
+ *
+ *
+ * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor
+ * Copyright (C) 2006-2007 Corentin Chary
+ * Copyright (C) 2011 Wind River Systems
+ *
+ * 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
+ *
+ *
+ * The development page for this driver is located at
+ * http://sourceforge.net/projects/acpi4asus/
+ *
+ * Credits:
+ * Pontus Fuchs - Helper functions, cleanup
+ * Johann Wiesner - Small compile fixes
+ * John Belmonte - ACPI code for Toshiba laptop was a good starting point.
+ * Eric Burghard - LED display support for W1N
+ * Josh Green - Light Sens support
+ * Thomas Tuttle - His first patch for led support was very helpful
+ * Sam Lin - GPS support
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/proc_fs.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/input-polldev.h>
+#include <linux/rfkill.h>
+#include <linux/slab.h>
+#include <linux/dmi.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+
+#define ASUS_LAPTOP_VERSION "0.42"
+
+#define ASUS_LAPTOP_NAME "Asus Laptop Support"
+#define ASUS_LAPTOP_CLASS "hotkey"
+#define ASUS_LAPTOP_DEVICE_NAME "Hotkey"
+#define ASUS_LAPTOP_FILE KBUILD_MODNAME
+#define ASUS_LAPTOP_PREFIX "\\_SB.ATKD."
+
+MODULE_AUTHOR("Julien Lerouge, Karol Kozimor, Corentin Chary");
+MODULE_DESCRIPTION(ASUS_LAPTOP_NAME);
+MODULE_LICENSE("GPL");
+
+/*
+ * WAPF defines the behavior of the Fn+Fx wlan key
+ * The significance of values is yet to be found, but
+ * most of the time:
+ * Bit | Bluetooth | WLAN
+ * 0 | Hardware | Hardware
+ * 1 | Hardware | Software
+ * 4 | Software | Software
+ */
+static uint wapf = 1;
+module_param(wapf, uint, 0444);
+MODULE_PARM_DESC(wapf, "WAPF value");
+
+static char *wled_type = "unknown";
+static char *bled_type = "unknown";
+
+module_param(wled_type, charp, 0444);
+MODULE_PARM_DESC(wled_type, "Set the wled type on boot "
+ "(unknown, led or rfkill). "
+ "default is unknown");
+
+module_param(bled_type, charp, 0444);
+MODULE_PARM_DESC(bled_type, "Set the bled type on boot "
+ "(unknown, led or rfkill). "
+ "default is unknown");
+
+static int wlan_status = 1;
+static int bluetooth_status = 1;
+static int wimax_status = -1;
+static int wwan_status = -1;
+static int als_status;
+
+module_param(wlan_status, int, 0444);
+MODULE_PARM_DESC(wlan_status, "Set the wireless status on boot "
+ "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+ "default is -1");
+
+module_param(bluetooth_status, int, 0444);
+MODULE_PARM_DESC(bluetooth_status, "Set the wireless status on boot "
+ "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+ "default is -1");
+
+module_param(wimax_status, int, 0444);
+MODULE_PARM_DESC(wimax_status, "Set the wireless status on boot "
+ "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+ "default is -1");
+
+module_param(wwan_status, int, 0444);
+MODULE_PARM_DESC(wwan_status, "Set the wireless status on boot "
+ "(0 = disabled, 1 = enabled, -1 = don't do anything). "
+ "default is -1");
+
+module_param(als_status, int, 0444);
+MODULE_PARM_DESC(als_status, "Set the ALS status on boot "
+ "(0 = disabled, 1 = enabled). "
+ "default is 0");
+
+/*
+ * Some events we use, same for all Asus
+ */
+#define ATKD_BRNUP_MIN 0x10
+#define ATKD_BRNUP_MAX 0x1f
+#define ATKD_BRNDOWN_MIN 0x20
+#define ATKD_BRNDOWN_MAX 0x2f
+#define ATKD_BRNDOWN 0x20
+#define ATKD_BRNUP 0x2f
+#define ATKD_LCD_ON 0x33
+#define ATKD_LCD_OFF 0x34
+
+/*
+ * Known bits returned by \_SB.ATKD.HWRS
+ */
+#define WL_HWRS 0x80
+#define BT_HWRS 0x100
+
+/*
+ * Flags for hotk status
+ * WL_ON and BT_ON are also used for wireless_status()
+ */
+#define WL_RSTS 0x01 /* internal Wifi */
+#define BT_RSTS 0x02 /* internal Bluetooth */
+#define WM_RSTS 0x08 /* internal wimax */
+#define WW_RSTS 0x20 /* internal wwan */
+
+/* WLED and BLED type */
+#define TYPE_UNKNOWN 0
+#define TYPE_LED 1
+#define TYPE_RFKILL 2
+
+/* LED */
+#define METHOD_MLED "MLED"
+#define METHOD_TLED "TLED"
+#define METHOD_RLED "RLED" /* W1JC */
+#define METHOD_PLED "PLED" /* A7J */
+#define METHOD_GLED "GLED" /* G1, G2 (probably) */
+
+/* LEDD */
+#define METHOD_LEDD "SLCM"
+
+/*
+ * Bluetooth and WLAN
+ * WLED and BLED are not handled like other XLED, because in some dsdt
+ * they also control the WLAN/Bluetooth device.
+ */
+#define METHOD_WLAN "WLED"
+#define METHOD_BLUETOOTH "BLED"
+
+/* WWAN and WIMAX */
+#define METHOD_WWAN "GSMC"
+#define METHOD_WIMAX "WMXC"
+
+#define METHOD_WL_STATUS "RSTS"
+
+/* Brightness */
+#define METHOD_BRIGHTNESS_SET "SPLV"
+#define METHOD_BRIGHTNESS_GET "GPLV"
+
+/* Display */
+#define METHOD_SWITCH_DISPLAY "SDSP"
+
+#define METHOD_ALS_CONTROL "ALSC" /* Z71A Z71V */
+#define METHOD_ALS_LEVEL "ALSL" /* Z71A Z71V */
+
+/* GPS */
+/* R2H use different handle for GPS on/off */
+#define METHOD_GPS_ON "SDON"
+#define METHOD_GPS_OFF "SDOF"
+#define METHOD_GPS_STATUS "GPST"
+
+/* Keyboard light */
+#define METHOD_KBD_LIGHT_SET "SLKB"
+#define METHOD_KBD_LIGHT_GET "GLKB"
+
+/* For Pegatron Lucid tablet */
+#define DEVICE_NAME_PEGA "Lucid"
+
+#define METHOD_PEGA_ENABLE "ENPR"
+#define METHOD_PEGA_DISABLE "DAPR"
+#define PEGA_WLAN 0x00
+#define PEGA_BLUETOOTH 0x01
+#define PEGA_WWAN 0x02
+#define PEGA_ALS 0x04
+#define PEGA_ALS_POWER 0x05
+
+#define METHOD_PEGA_READ "RDLN"
+#define PEGA_READ_ALS_H 0x02
+#define PEGA_READ_ALS_L 0x03
+
+#define PEGA_ACCEL_NAME "pega_accel"
+#define PEGA_ACCEL_DESC "Pegatron Lucid Tablet Accelerometer"
+#define METHOD_XLRX "XLRX"
+#define METHOD_XLRY "XLRY"
+#define METHOD_XLRZ "XLRZ"
+#define PEGA_ACC_CLAMP 512 /* 1G accel is reported as ~256, so clamp to 2G */
+#define PEGA_ACC_RETRIES 3
+
+/*
+ * Define a specific led structure to keep the main structure clean
+ */
+struct asus_led {
+ int wk;
+ struct work_struct work;
+ struct led_classdev led;
+ struct asus_laptop *asus;
+ const char *method;
+};
+
+/*
+ * Same thing for rfkill
+ */
+struct asus_rfkill {
+ /* type of control. Maps to PEGA_* values or *_RSTS */
+ int control_id;
+ struct rfkill *rfkill;
+ struct asus_laptop *asus;
+};
+
+/*
+ * This is the main structure, we can use it to store anything interesting
+ * about the hotk device
+ */
+struct asus_laptop {
+ char *name; /* laptop name */
+
+ struct acpi_table_header *dsdt_info;
+ struct platform_device *platform_device;
+ struct acpi_device *device; /* the device we are in */
+ struct backlight_device *backlight_device;
+
+ struct input_dev *inputdev;
+ struct key_entry *keymap;
+ struct input_polled_dev *pega_accel_poll;
+
+ struct asus_led wled;
+ struct asus_led bled;
+ struct asus_led mled;
+ struct asus_led tled;
+ struct asus_led rled;
+ struct asus_led pled;
+ struct asus_led gled;
+ struct asus_led kled;
+ struct workqueue_struct *led_workqueue;
+
+ int wled_type;
+ int bled_type;
+ int wireless_status;
+ bool have_rsts;
+ bool is_pega_lucid;
+ bool pega_acc_live;
+ int pega_acc_x;
+ int pega_acc_y;
+ int pega_acc_z;
+
+ struct asus_rfkill wlan;
+ struct asus_rfkill bluetooth;
+ struct asus_rfkill wwan;
+ struct asus_rfkill wimax;
+ struct asus_rfkill gps;
+
+ acpi_handle handle; /* the handle of the hotk device */
+ u32 ledd_status; /* status of the LED display */
+ u8 light_level; /* light sensor level */
+ u8 light_switch; /* light sensor switch value */
+ u16 event_count[128]; /* count for each event TODO make this better */
+};
+
+static const struct key_entry asus_keymap[] = {
+ /* Lenovo SL Specific keycodes */
+ {KE_KEY, 0x02, { KEY_SCREENLOCK } },
+ {KE_KEY, 0x05, { KEY_WLAN } },
+ {KE_KEY, 0x08, { KEY_F13 } },
+ {KE_KEY, 0x09, { KEY_PROG2 } }, /* Dock */
+ {KE_KEY, 0x17, { KEY_ZOOM } },
+ {KE_KEY, 0x1f, { KEY_BATTERY } },
+ /* End of Lenovo SL Specific keycodes */
+ {KE_KEY, ATKD_BRNDOWN, { KEY_BRIGHTNESSDOWN } },
+ {KE_KEY, ATKD_BRNUP, { KEY_BRIGHTNESSUP } },
+ {KE_KEY, 0x30, { KEY_VOLUMEUP } },
+ {KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
+ {KE_KEY, 0x32, { KEY_MUTE } },
+ {KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */
+ {KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */
+ {KE_KEY, 0x40, { KEY_PREVIOUSSONG } },
+ {KE_KEY, 0x41, { KEY_NEXTSONG } },
+ {KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */
+ {KE_KEY, 0x45, { KEY_PLAYPAUSE } },
+ {KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */
+ {KE_KEY, 0x50, { KEY_EMAIL } },
+ {KE_KEY, 0x51, { KEY_WWW } },
+ {KE_KEY, 0x55, { KEY_CALC } },
+ {KE_IGNORE, 0x57, }, /* Battery mode */
+ {KE_IGNORE, 0x58, }, /* AC mode */
+ {KE_KEY, 0x5C, { KEY_SCREENLOCK } }, /* Screenlock */
+ {KE_KEY, 0x5D, { KEY_WLAN } }, /* WLAN Toggle */
+ {KE_KEY, 0x5E, { KEY_WLAN } }, /* WLAN Enable */
+ {KE_KEY, 0x5F, { KEY_WLAN } }, /* WLAN Disable */
+ {KE_KEY, 0x60, { KEY_TOUCHPAD_ON } },
+ {KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */
+ {KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */
+ {KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */
+ {KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */
+ {KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */
+ {KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */
+ {KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */
+ {KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, /* Lock Touchpad */
+ {KE_KEY, 0x6C, { KEY_SLEEP } }, /* Suspend */
+ {KE_KEY, 0x6D, { KEY_SLEEP } }, /* Hibernate */
+ {KE_IGNORE, 0x6E, }, /* Low Battery notification */
+ {KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */
+ {KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */
+ {KE_KEY, 0x82, { KEY_CAMERA } },
+ {KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */
+ {KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+ {KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
+ {KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
+ {KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
+ {KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */
+ {KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */
+ {KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */
+ {KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */
+ {KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
+ {KE_KEY, 0x95, { KEY_MEDIA } },
+ {KE_KEY, 0x99, { KEY_PHONE } },
+ {KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
+ {KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
+ {KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
+ {KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */
+ {KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */
+ {KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */
+ {KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */
+ {KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */
+ {KE_KEY, 0xB5, { KEY_CALC } },
+ {KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
+ {KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
+ {KE_END, 0},
+};
+
+
+/*
+ * This function evaluates an ACPI method, given an int as parameter, the
+ * method is searched within the scope of the handle, can be NULL. The output
+ * of the method is written is output, which can also be NULL
+ *
+ * returns 0 if write is successful, -1 else.
+ */
+static int write_acpi_int_ret(acpi_handle handle, const char *method, int val,
+ struct acpi_buffer *output)
+{
+ struct acpi_object_list params; /* list of input parameters (an int) */
+ union acpi_object in_obj; /* the only param we use */
+ acpi_status status;
+
+ if (!handle)
+ return -1;
+
+ params.count = 1;
+ params.pointer = &in_obj;
+ in_obj.type = ACPI_TYPE_INTEGER;
+ in_obj.integer.value = val;
+
+ status = acpi_evaluate_object(handle, (char *)method, &params, output);
+ if (status == AE_OK)
+ return 0;
+ else
+ return -1;
+}
+
+static int write_acpi_int(acpi_handle handle, const char *method, int val)
+{
+ return write_acpi_int_ret(handle, method, val, NULL);
+}
+
+static int acpi_check_handle(acpi_handle handle, const char *method,
+ acpi_handle *ret)
+{
+ acpi_status status;
+
+ if (method == NULL)
+ return -ENODEV;
+
+ if (ret)
+ status = acpi_get_handle(handle, (char *)method,
+ ret);
+ else {
+ acpi_handle dummy;
+
+ status = acpi_get_handle(handle, (char *)method,
+ &dummy);
+ }
+
+ if (status != AE_OK) {
+ if (ret)
+ pr_warn("Error finding %s\n", method);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static bool asus_check_pega_lucid(struct asus_laptop *asus)
+{
+ return !strcmp(asus->name, DEVICE_NAME_PEGA) &&
+ !acpi_check_handle(asus->handle, METHOD_PEGA_ENABLE, NULL) &&
+ !acpi_check_handle(asus->handle, METHOD_PEGA_DISABLE, NULL) &&
+ !acpi_check_handle(asus->handle, METHOD_PEGA_READ, NULL);
+}
+
+static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable)
+{
+ char *method = enable ? METHOD_PEGA_ENABLE : METHOD_PEGA_DISABLE;
+ return write_acpi_int(asus->handle, method, unit);
+}
+
+static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method)
+{
+ int i, delta;
+ unsigned long long val;
+ for (i = 0; i < PEGA_ACC_RETRIES; i++) {
+ acpi_evaluate_integer(asus->handle, method, NULL, &val);
+
+ /* The output is noisy. From reading the ASL
+ * dissassembly, timeout errors are returned with 1's
+ * in the high word, and the lack of locking around
+ * thei hi/lo byte reads means that a transition
+ * between (for example) -1 and 0 could be read as
+ * 0xff00 or 0x00ff. */
+ delta = abs(curr - (short)val);
+ if (delta < 128 && !(val & ~0xffff))
+ break;
+ }
+ return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP);
+}
+
+static void pega_accel_poll(struct input_polled_dev *ipd)
+{
+ struct device *parent = ipd->input->dev.parent;
+ struct asus_laptop *asus = dev_get_drvdata(parent);
+
+ /* In some cases, the very first call to poll causes a
+ * recursive fault under the polldev worker. This is
+ * apparently related to very early userspace access to the
+ * device, and perhaps a firmware bug. Fake the first report. */
+ if (!asus->pega_acc_live) {
+ asus->pega_acc_live = true;
+ input_report_abs(ipd->input, ABS_X, 0);
+ input_report_abs(ipd->input, ABS_Y, 0);
+ input_report_abs(ipd->input, ABS_Z, 0);
+ input_sync(ipd->input);
+ return;
+ }
+
+ asus->pega_acc_x = pega_acc_axis(asus, asus->pega_acc_x, METHOD_XLRX);
+ asus->pega_acc_y = pega_acc_axis(asus, asus->pega_acc_y, METHOD_XLRY);
+ asus->pega_acc_z = pega_acc_axis(asus, asus->pega_acc_z, METHOD_XLRZ);
+
+ /* Note transform, convert to "right/up/out" in the native
+ * landscape orientation (i.e. the vector is the direction of
+ * "real up" in the device's cartiesian coordinates). */
+ input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x);
+ input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y);
+ input_report_abs(ipd->input, ABS_Z, asus->pega_acc_z);
+ input_sync(ipd->input);
+}
+
+static void pega_accel_exit(struct asus_laptop *asus)
+{
+ if (asus->pega_accel_poll) {
+ input_unregister_polled_device(asus->pega_accel_poll);
+ input_free_polled_device(asus->pega_accel_poll);
+ }
+ asus->pega_accel_poll = NULL;
+}
+
+static int pega_accel_init(struct asus_laptop *asus)
+{
+ int err;
+ struct input_polled_dev *ipd;
+
+ if (!asus->is_pega_lucid)
+ return -ENODEV;
+
+ if (acpi_check_handle(asus->handle, METHOD_XLRX, NULL) ||
+ acpi_check_handle(asus->handle, METHOD_XLRY, NULL) ||
+ acpi_check_handle(asus->handle, METHOD_XLRZ, NULL))
+ return -ENODEV;
+
+ ipd = input_allocate_polled_device();
+ if (!ipd)
+ return -ENOMEM;
+
+ ipd->poll = pega_accel_poll;
+ ipd->poll_interval = 125;
+ ipd->poll_interval_min = 50;
+ ipd->poll_interval_max = 2000;
+
+ ipd->input->name = PEGA_ACCEL_DESC;
+ ipd->input->phys = PEGA_ACCEL_NAME "/input0";
+ ipd->input->dev.parent = &asus->platform_device->dev;
+ ipd->input->id.bustype = BUS_HOST;
+
+ set_bit(EV_ABS, ipd->input->evbit);
+ input_set_abs_params(ipd->input, ABS_X,
+ -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
+ input_set_abs_params(ipd->input, ABS_Y,
+ -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
+ input_set_abs_params(ipd->input, ABS_Z,
+ -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0);
+
+ err = input_register_polled_device(ipd);
+ if (err)
+ goto exit;
+
+ asus->pega_accel_poll = ipd;
+ return 0;
+
+exit:
+ input_free_polled_device(ipd);
+ return err;
+}
+
+/* Generic LED function */
+static int asus_led_set(struct asus_laptop *asus, const char *method,
+ int value)
+{
+ if (!strcmp(method, METHOD_MLED))
+ value = !value;
+ else if (!strcmp(method, METHOD_GLED))
+ value = !value + 1;
+ else
+ value = !!value;
+
+ return write_acpi_int(asus->handle, method, value);
+}
+
+/*
+ * LEDs
+ */
+/* /sys/class/led handlers */
+static void asus_led_cdev_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct asus_led *led = container_of(led_cdev, struct asus_led, led);
+ struct asus_laptop *asus = led->asus;
+
+ led->wk = !!value;
+ queue_work(asus->led_workqueue, &led->work);
+}
+
+static void asus_led_cdev_update(struct work_struct *work)
+{
+ struct asus_led *led = container_of(work, struct asus_led, work);
+ struct asus_laptop *asus = led->asus;
+
+ asus_led_set(asus, led->method, led->wk);
+}
+
+static enum led_brightness asus_led_cdev_get(struct led_classdev *led_cdev)
+{
+ return led_cdev->brightness;
+}
+
+/*
+ * Keyboard backlight (also a LED)
+ */
+static int asus_kled_lvl(struct asus_laptop *asus)
+{
+ unsigned long long kblv;
+ struct acpi_object_list params;
+ union acpi_object in_obj;
+ acpi_status rv;
+
+ params.count = 1;
+ params.pointer = &in_obj;
+ in_obj.type = ACPI_TYPE_INTEGER;
+ in_obj.integer.value = 2;
+
+ rv = acpi_evaluate_integer(asus->handle, METHOD_KBD_LIGHT_GET,
+ &params, &kblv);
+ if (ACPI_FAILURE(rv)) {
+ pr_warn("Error reading kled level\n");
+ return -ENODEV;
+ }
+ return kblv;
+}
+
+static int asus_kled_set(struct asus_laptop *asus, int kblv)
+{
+ if (kblv > 0)
+ kblv = (1 << 7) | (kblv & 0x7F);
+ else
+ kblv = 0;
+
+ if (write_acpi_int(asus->handle, METHOD_KBD_LIGHT_SET, kblv)) {
+ pr_warn("Keyboard LED display write failed\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void asus_kled_cdev_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct asus_led *led = container_of(led_cdev, struct asus_led, led);
+ struct asus_laptop *asus = led->asus;
+
+ led->wk = value;
+ queue_work(asus->led_workqueue, &led->work);
+}
+
+static void asus_kled_cdev_update(struct work_struct *work)
+{
+ struct asus_led *led = container_of(work, struct asus_led, work);
+ struct asus_laptop *asus = led->asus;
+
+ asus_kled_set(asus, led->wk);
+}
+
+static enum led_brightness asus_kled_cdev_get(struct led_classdev *led_cdev)
+{
+ struct asus_led *led = container_of(led_cdev, struct asus_led, led);
+ struct asus_laptop *asus = led->asus;
+
+ return asus_kled_lvl(asus);
+}
+
+static void asus_led_exit(struct asus_laptop *asus)
+{
+ if (!IS_ERR_OR_NULL(asus->wled.led.dev))
+ led_classdev_unregister(&asus->wled.led);
+ if (!IS_ERR_OR_NULL(asus->bled.led.dev))
+ led_classdev_unregister(&asus->bled.led);
+ if (!IS_ERR_OR_NULL(asus->mled.led.dev))
+ led_classdev_unregister(&asus->mled.led);
+ if (!IS_ERR_OR_NULL(asus->tled.led.dev))
+ led_classdev_unregister(&asus->tled.led);
+ if (!IS_ERR_OR_NULL(asus->pled.led.dev))
+ led_classdev_unregister(&asus->pled.led);
+ if (!IS_ERR_OR_NULL(asus->rled.led.dev))
+ led_classdev_unregister(&asus->rled.led);
+ if (!IS_ERR_OR_NULL(asus->gled.led.dev))
+ led_classdev_unregister(&asus->gled.led);
+ if (!IS_ERR_OR_NULL(asus->kled.led.dev))
+ led_classdev_unregister(&asus->kled.led);
+ if (asus->led_workqueue) {
+ destroy_workqueue(asus->led_workqueue);
+ asus->led_workqueue = NULL;
+ }
+}
+
+/* Ugly macro, need to fix that later */
+static int asus_led_register(struct asus_laptop *asus,
+ struct asus_led *led,
+ const char *name, const char *method)
+{
+ struct led_classdev *led_cdev = &led->led;
+
+ if (!method || acpi_check_handle(asus->handle, method, NULL))
+ return 0; /* Led not present */
+
+ led->asus = asus;
+ led->method = method;
+
+ INIT_WORK(&led->work, asus_led_cdev_update);
+ led_cdev->name = name;
+ led_cdev->brightness_set = asus_led_cdev_set;
+ led_cdev->brightness_get = asus_led_cdev_get;
+ led_cdev->max_brightness = 1;
+ return led_classdev_register(&asus->platform_device->dev, led_cdev);
+}
+
+static int asus_led_init(struct asus_laptop *asus)
+{
+ int r = 0;
+
+ /*
+ * The Pegatron Lucid has no physical leds, but all methods are
+ * available in the DSDT...
+ */
+ if (asus->is_pega_lucid)
+ return 0;
+
+ /*
+ * Functions that actually update the LED's are called from a
+ * workqueue. By doing this as separate work rather than when the LED
+ * subsystem asks, we avoid messing with the Asus ACPI stuff during a
+ * potentially bad time, such as a timer interrupt.
+ */
+ asus->led_workqueue = create_singlethread_workqueue("led_workqueue");
+ if (!asus->led_workqueue)
+ return -ENOMEM;
+
+ if (asus->wled_type == TYPE_LED)
+ r = asus_led_register(asus, &asus->wled, "asus::wlan",
+ METHOD_WLAN);
+ if (r)
+ goto error;
+ if (asus->bled_type == TYPE_LED)
+ r = asus_led_register(asus, &asus->bled, "asus::bluetooth",
+ METHOD_BLUETOOTH);
+ if (r)
+ goto error;
+ r = asus_led_register(asus, &asus->mled, "asus::mail", METHOD_MLED);
+ if (r)
+ goto error;
+ r = asus_led_register(asus, &asus->tled, "asus::touchpad", METHOD_TLED);
+ if (r)
+ goto error;
+ r = asus_led_register(asus, &asus->rled, "asus::record", METHOD_RLED);
+ if (r)
+ goto error;
+ r = asus_led_register(asus, &asus->pled, "asus::phone", METHOD_PLED);
+ if (r)
+ goto error;
+ r = asus_led_register(asus, &asus->gled, "asus::gaming", METHOD_GLED);
+ if (r)
+ goto error;
+ if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL) &&
+ !acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_GET, NULL)) {
+ struct asus_led *led = &asus->kled;
+ struct led_classdev *cdev = &led->led;
+
+ led->asus = asus;
+
+ INIT_WORK(&led->work, asus_kled_cdev_update);
+ cdev->name = "asus::kbd_backlight";
+ cdev->brightness_set = asus_kled_cdev_set;
+ cdev->brightness_get = asus_kled_cdev_get;
+ cdev->max_brightness = 3;
+ r = led_classdev_register(&asus->platform_device->dev, cdev);
+ }
+error:
+ if (r)
+ asus_led_exit(asus);
+ return r;
+}
+
+/*
+ * Backlight device
+ */
+static int asus_read_brightness(struct backlight_device *bd)
+{
+ struct asus_laptop *asus = bl_get_data(bd);
+ unsigned long long value;
+ acpi_status rv = AE_OK;
+
+ rv = acpi_evaluate_integer(asus->handle, METHOD_BRIGHTNESS_GET,
+ NULL, &value);
+ if (ACPI_FAILURE(rv))
+ pr_warn("Error reading brightness\n");
+
+ return value;
+}
+
+static int asus_set_brightness(struct backlight_device *bd, int value)
+{
+ struct asus_laptop *asus = bl_get_data(bd);
+
+ if (write_acpi_int(asus->handle, METHOD_BRIGHTNESS_SET, value)) {
+ pr_warn("Error changing brightness\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+ int value = bd->props.brightness;
+
+ return asus_set_brightness(bd, value);
+}
+
+static const struct backlight_ops asusbl_ops = {
+ .get_brightness = asus_read_brightness,
+ .update_status = update_bl_status,
+};
+
+static int asus_backlight_notify(struct asus_laptop *asus)
+{
+ struct backlight_device *bd = asus->backlight_device;
+ int old = bd->props.brightness;
+
+ backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
+
+ return old;
+}
+
+static int asus_backlight_init(struct asus_laptop *asus)
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
+
+ if (acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_GET, NULL) ||
+ acpi_check_handle(asus->handle, METHOD_BRIGHTNESS_SET, NULL))
+ return 0;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = 15;
+ props.type = BACKLIGHT_PLATFORM;
+
+ bd = backlight_device_register(ASUS_LAPTOP_FILE,
+ &asus->platform_device->dev, asus,
+ &asusbl_ops, &props);
+ if (IS_ERR(bd)) {
+ pr_err("Could not register asus backlight device\n");
+ asus->backlight_device = NULL;
+ return PTR_ERR(bd);
+ }
+
+ asus->backlight_device = bd;
+ bd->props.brightness = asus_read_brightness(bd);
+ bd->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(bd);
+ return 0;
+}
+
+static void asus_backlight_exit(struct asus_laptop *asus)
+{
+ if (asus->backlight_device)
+ backlight_device_unregister(asus->backlight_device);
+ asus->backlight_device = NULL;
+}
+
+/*
+ * Platform device handlers
+ */
+
+/*
+ * We write our info in page, we begin at offset off and cannot write more
+ * than count bytes. We set eof to 1 if we handle those 2 values. We return the
+ * number of bytes written in page
+ */
+static ssize_t show_infos(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+ int len = 0;
+ unsigned long long temp;
+ char buf[16]; /* enough for all info */
+ acpi_status rv = AE_OK;
+
+ /*
+ * We use the easy way, we don't care of off and count,
+ * so we don't set eof to 1
+ */
+
+ len += sprintf(page, ASUS_LAPTOP_NAME " " ASUS_LAPTOP_VERSION "\n");
+ len += sprintf(page + len, "Model reference : %s\n", asus->name);
+ /*
+ * The SFUN method probably allows the original driver to get the list
+ * of features supported by a given model. For now, 0x0100 or 0x0800
+ * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
+ * The significance of others is yet to be found.
+ */
+ rv = acpi_evaluate_integer(asus->handle, "SFUN", NULL, &temp);
+ if (!ACPI_FAILURE(rv))
+ len += sprintf(page + len, "SFUN value : %#x\n",
+ (uint) temp);
+ /*
+ * The HWRS method return informations about the hardware.
+ * 0x80 bit is for WLAN, 0x100 for Bluetooth.
+ * 0x40 for WWAN, 0x10 for WIMAX.
+ * The significance of others is yet to be found.
+ * We don't currently use this for device detection, and it
+ * takes several seconds to run on some systems.
+ */
+ rv = acpi_evaluate_integer(asus->handle, "HWRS", NULL, &temp);
+ if (!ACPI_FAILURE(rv))
+ len += sprintf(page + len, "HWRS value : %#x\n",
+ (uint) temp);
+ /*
+ * Another value for userspace: the ASYM method returns 0x02 for
+ * battery low and 0x04 for battery critical, its readings tend to be
+ * more accurate than those provided by _BST.
+ * Note: since not all the laptops provide this method, errors are
+ * silently ignored.
+ */
+ rv = acpi_evaluate_integer(asus->handle, "ASYM", NULL, &temp);
+ if (!ACPI_FAILURE(rv))
+ len += sprintf(page + len, "ASYM value : %#x\n",
+ (uint) temp);
+ if (asus->dsdt_info) {
+ snprintf(buf, 16, "%d", asus->dsdt_info->length);
+ len += sprintf(page + len, "DSDT length : %s\n", buf);
+ snprintf(buf, 16, "%d", asus->dsdt_info->checksum);
+ len += sprintf(page + len, "DSDT checksum : %s\n", buf);
+ snprintf(buf, 16, "%d", asus->dsdt_info->revision);
+ len += sprintf(page + len, "DSDT revision : %s\n", buf);
+ snprintf(buf, 7, "%s", asus->dsdt_info->oem_id);
+ len += sprintf(page + len, "OEM id : %s\n", buf);
+ snprintf(buf, 9, "%s", asus->dsdt_info->oem_table_id);
+ len += sprintf(page + len, "OEM table id : %s\n", buf);
+ snprintf(buf, 16, "%x", asus->dsdt_info->oem_revision);
+ len += sprintf(page + len, "OEM revision : 0x%s\n", buf);
+ snprintf(buf, 5, "%s", asus->dsdt_info->asl_compiler_id);
+ len += sprintf(page + len, "ASL comp vendor id : %s\n", buf);
+ snprintf(buf, 16, "%x", asus->dsdt_info->asl_compiler_revision);
+ len += sprintf(page + len, "ASL comp revision : 0x%s\n", buf);
+ }
+
+ return len;
+}
+
+static int parse_arg(const char *buf, unsigned long count, int *val)
+{
+ if (!count)
+ return 0;
+ if (count > 31)
+ return -EINVAL;
+ if (sscanf(buf, "%i", val) != 1)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t sysfs_acpi_set(struct asus_laptop *asus,
+ const char *buf, size_t count,
+ const char *method)
+{
+ int rv, value;
+ int out = 0;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv > 0)
+ out = value ? 1 : 0;
+
+ if (write_acpi_int(asus->handle, method, value))
+ return -ENODEV;
+ return rv;
+}
+
+/*
+ * LEDD display
+ */
+static ssize_t show_ledd(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "0x%08x\n", asus->ledd_status);
+}
+
+static ssize_t store_ledd(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+ int rv, value;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv > 0) {
+ if (write_acpi_int(asus->handle, METHOD_LEDD, value)) {
+ pr_warn("LED display write failed\n");
+ return -ENODEV;
+ }
+ asus->ledd_status = (u32) value;
+ }
+ return rv;
+}
+
+/*
+ * Wireless
+ */
+static int asus_wireless_status(struct asus_laptop *asus, int mask)
+{
+ unsigned long long status;
+ acpi_status rv = AE_OK;
+
+ if (!asus->have_rsts)
+ return (asus->wireless_status & mask) ? 1 : 0;
+
+ rv = acpi_evaluate_integer(asus->handle, METHOD_WL_STATUS,
+ NULL, &status);
+ if (ACPI_FAILURE(rv)) {
+ pr_warn("Error reading Wireless status\n");
+ return -EINVAL;
+ }
+ return !!(status & mask);
+}
+
+/*
+ * WLAN
+ */
+static int asus_wlan_set(struct asus_laptop *asus, int status)
+{
+ if (write_acpi_int(asus->handle, METHOD_WLAN, !!status)) {
+ pr_warn("Error setting wlan status to %d\n", status);
+ return -EIO;
+ }
+ return 0;
+}
+
+static ssize_t show_wlan(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", asus_wireless_status(asus, WL_RSTS));
+}
+
+static ssize_t store_wlan(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sysfs_acpi_set(asus, buf, count, METHOD_WLAN);
+}
+
+/*e
+ * Bluetooth
+ */
+static int asus_bluetooth_set(struct asus_laptop *asus, int status)
+{
+ if (write_acpi_int(asus->handle, METHOD_BLUETOOTH, !!status)) {
+ pr_warn("Error setting bluetooth status to %d\n", status);
+ return -EIO;
+ }
+ return 0;
+}
+
+static ssize_t show_bluetooth(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", asus_wireless_status(asus, BT_RSTS));
+}
+
+static ssize_t store_bluetooth(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sysfs_acpi_set(asus, buf, count, METHOD_BLUETOOTH);
+}
+
+/*
+ * Wimax
+ */
+static int asus_wimax_set(struct asus_laptop *asus, int status)
+{
+ if (write_acpi_int(asus->handle, METHOD_WIMAX, !!status)) {
+ pr_warn("Error setting wimax status to %d\n", status);
+ return -EIO;
+ }
+ return 0;
+}
+
+static ssize_t show_wimax(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", asus_wireless_status(asus, WM_RSTS));
+}
+
+static ssize_t store_wimax(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sysfs_acpi_set(asus, buf, count, METHOD_WIMAX);
+}
+
+/*
+ * Wwan
+ */
+static int asus_wwan_set(struct asus_laptop *asus, int status)
+{
+ if (write_acpi_int(asus->handle, METHOD_WWAN, !!status)) {
+ pr_warn("Error setting wwan status to %d\n", status);
+ return -EIO;
+ }
+ return 0;
+}
+
+static ssize_t show_wwan(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", asus_wireless_status(asus, WW_RSTS));
+}
+
+static ssize_t store_wwan(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sysfs_acpi_set(asus, buf, count, METHOD_WWAN);
+}
+
+/*
+ * Display
+ */
+static void asus_set_display(struct asus_laptop *asus, int value)
+{
+ /* no sanity check needed for now */
+ if (write_acpi_int(asus->handle, METHOD_SWITCH_DISPLAY, value))
+ pr_warn("Error setting display\n");
+ return;
+}
+
+/*
+ * Experimental support for display switching. As of now: 1 should activate
+ * the LCD output, 2 should do for CRT, 4 for TV-Out and 8 for DVI.
+ * Any combination (bitwise) of these will suffice. I never actually tested 4
+ * displays hooked up simultaneously, so be warned. See the acpi4asus README
+ * for more info.
+ */
+static ssize_t store_disp(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+ int rv, value;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv > 0)
+ asus_set_display(asus, value);
+ return rv;
+}
+
+/*
+ * Light Sens
+ */
+static void asus_als_switch(struct asus_laptop *asus, int value)
+{
+ int ret;
+
+ if (asus->is_pega_lucid) {
+ ret = asus_pega_lucid_set(asus, PEGA_ALS, value);
+ if (!ret)
+ ret = asus_pega_lucid_set(asus, PEGA_ALS_POWER, value);
+ } else {
+ ret = write_acpi_int(asus->handle, METHOD_ALS_CONTROL, value);
+ }
+ if (ret)
+ pr_warning("Error setting light sensor switch\n");
+
+ asus->light_switch = value;
+}
+
+static ssize_t show_lssw(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", asus->light_switch);
+}
+
+static ssize_t store_lssw(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+ int rv, value;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv > 0)
+ asus_als_switch(asus, value ? 1 : 0);
+
+ return rv;
+}
+
+static void asus_als_level(struct asus_laptop *asus, int value)
+{
+ if (write_acpi_int(asus->handle, METHOD_ALS_LEVEL, value))
+ pr_warn("Error setting light sensor level\n");
+ asus->light_level = value;
+}
+
+static ssize_t show_lslvl(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", asus->light_level);
+}
+
+static ssize_t store_lslvl(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+ int rv, value;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv > 0) {
+ value = (0 < value) ? ((15 < value) ? 15 : value) : 0;
+ /* 0 <= value <= 15 */
+ asus_als_level(asus, value);
+ }
+
+ return rv;
+}
+
+static int pega_int_read(struct asus_laptop *asus, int arg, int *result)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ int err = write_acpi_int_ret(asus->handle, METHOD_PEGA_READ, arg,
+ &buffer);
+ if (!err) {
+ union acpi_object *obj = buffer.pointer;
+ if (obj && obj->type == ACPI_TYPE_INTEGER)
+ *result = obj->integer.value;
+ else
+ err = -EIO;
+ }
+ return err;
+}
+
+static ssize_t show_lsvalue(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+ int err, hi, lo;
+
+ err = pega_int_read(asus, PEGA_READ_ALS_H, &hi);
+ if (!err)
+ err = pega_int_read(asus, PEGA_READ_ALS_L, &lo);
+ if (!err)
+ return sprintf(buf, "%d\n", 10 * hi + lo);
+ return err;
+}
+
+/*
+ * GPS
+ */
+static int asus_gps_status(struct asus_laptop *asus)
+{
+ unsigned long long status;
+ acpi_status rv = AE_OK;
+
+ rv = acpi_evaluate_integer(asus->handle, METHOD_GPS_STATUS,
+ NULL, &status);
+ if (ACPI_FAILURE(rv)) {
+ pr_warn("Error reading GPS status\n");
+ return -ENODEV;
+ }
+ return !!status;
+}
+
+static int asus_gps_switch(struct asus_laptop *asus, int status)
+{
+ const char *meth = status ? METHOD_GPS_ON : METHOD_GPS_OFF;
+
+ if (write_acpi_int(asus->handle, meth, 0x02))
+ return -ENODEV;
+ return 0;
+}
+
+static ssize_t show_gps(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", asus_gps_status(asus));
+}
+
+static ssize_t store_gps(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_laptop *asus = dev_get_drvdata(dev);
+ int rv, value;
+ int ret;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv <= 0)
+ return -EINVAL;
+ ret = asus_gps_switch(asus, !!value);
+ if (ret)
+ return ret;
+ rfkill_set_sw_state(asus->gps.rfkill, !value);
+ return rv;
+}
+
+/*
+ * rfkill
+ */
+static int asus_gps_rfkill_set(void *data, bool blocked)
+{
+ struct asus_laptop *asus = data;
+
+ return asus_gps_switch(asus, !blocked);
+}
+
+static const struct rfkill_ops asus_gps_rfkill_ops = {
+ .set_block = asus_gps_rfkill_set,
+};
+
+static int asus_rfkill_set(void *data, bool blocked)
+{
+ struct asus_rfkill *rfk = data;
+ struct asus_laptop *asus = rfk->asus;
+
+ if (rfk->control_id == WL_RSTS)
+ return asus_wlan_set(asus, !blocked);
+ else if (rfk->control_id == BT_RSTS)
+ return asus_bluetooth_set(asus, !blocked);
+ else if (rfk->control_id == WM_RSTS)
+ return asus_wimax_set(asus, !blocked);
+ else if (rfk->control_id == WW_RSTS)
+ return asus_wwan_set(asus, !blocked);
+
+ return -EINVAL;
+}
+
+static const struct rfkill_ops asus_rfkill_ops = {
+ .set_block = asus_rfkill_set,
+};
+
+static void asus_rfkill_terminate(struct asus_rfkill *rfk)
+{
+ if (!rfk->rfkill)
+ return ;
+
+ rfkill_unregister(rfk->rfkill);
+ rfkill_destroy(rfk->rfkill);
+ rfk->rfkill = NULL;
+}
+
+static void asus_rfkill_exit(struct asus_laptop *asus)
+{
+ asus_rfkill_terminate(&asus->wwan);
+ asus_rfkill_terminate(&asus->bluetooth);
+ asus_rfkill_terminate(&asus->wlan);
+ asus_rfkill_terminate(&asus->gps);
+}
+
+static int asus_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk,
+ const char *name, int control_id, int type,
+ const struct rfkill_ops *ops)
+{
+ int result;
+
+ rfk->control_id = control_id;
+ rfk->asus = asus;
+ rfk->rfkill = rfkill_alloc(name, &asus->platform_device->dev,
+ type, ops, rfk);
+ if (!rfk->rfkill)
+ return -EINVAL;
+
+ result = rfkill_register(rfk->rfkill);
+ if (result) {
+ rfkill_destroy(rfk->rfkill);
+ rfk->rfkill = NULL;
+ }
+
+ return result;
+}
+
+static int asus_rfkill_init(struct asus_laptop *asus)
+{
+ int result = 0;
+
+ if (asus->is_pega_lucid)
+ return -ENODEV;
+
+ if (!acpi_check_handle(asus->handle, METHOD_GPS_ON, NULL) &&
+ !acpi_check_handle(asus->handle, METHOD_GPS_OFF, NULL) &&
+ !acpi_check_handle(asus->handle, METHOD_GPS_STATUS, NULL))
+ result = asus_rfkill_setup(asus, &asus->gps, "asus-gps",
+ -1, RFKILL_TYPE_GPS,
+ &asus_gps_rfkill_ops);
+ if (result)
+ goto exit;
+
+
+ if (!acpi_check_handle(asus->handle, METHOD_WLAN, NULL) &&
+ asus->wled_type == TYPE_RFKILL)
+ result = asus_rfkill_setup(asus, &asus->wlan, "asus-wlan",
+ WL_RSTS, RFKILL_TYPE_WLAN,
+ &asus_rfkill_ops);
+ if (result)
+ goto exit;
+
+ if (!acpi_check_handle(asus->handle, METHOD_BLUETOOTH, NULL) &&
+ asus->bled_type == TYPE_RFKILL)
+ result = asus_rfkill_setup(asus, &asus->bluetooth,
+ "asus-bluetooth", BT_RSTS,
+ RFKILL_TYPE_BLUETOOTH,
+ &asus_rfkill_ops);
+ if (result)
+ goto exit;
+
+ if (!acpi_check_handle(asus->handle, METHOD_WWAN, NULL))
+ result = asus_rfkill_setup(asus, &asus->wwan, "asus-wwan",
+ WW_RSTS, RFKILL_TYPE_WWAN,
+ &asus_rfkill_ops);
+ if (result)
+ goto exit;
+
+ if (!acpi_check_handle(asus->handle, METHOD_WIMAX, NULL))
+ result = asus_rfkill_setup(asus, &asus->wimax, "asus-wimax",
+ WM_RSTS, RFKILL_TYPE_WIMAX,
+ &asus_rfkill_ops);
+ if (result)
+ goto exit;
+
+exit:
+ if (result)
+ asus_rfkill_exit(asus);
+
+ return result;
+}
+
+static int pega_rfkill_set(void *data, bool blocked)
+{
+ struct asus_rfkill *rfk = data;
+
+ int ret = asus_pega_lucid_set(rfk->asus, rfk->control_id, !blocked);
+ return ret;
+}
+
+static const struct rfkill_ops pega_rfkill_ops = {
+ .set_block = pega_rfkill_set,
+};
+
+static int pega_rfkill_setup(struct asus_laptop *asus, struct asus_rfkill *rfk,
+ const char *name, int controlid, int rfkill_type)
+{
+ return asus_rfkill_setup(asus, rfk, name, controlid, rfkill_type,
+ &pega_rfkill_ops);
+}
+
+static int pega_rfkill_init(struct asus_laptop *asus)
+{
+ int ret = 0;
+
+ if(!asus->is_pega_lucid)
+ return -ENODEV;
+
+ ret = pega_rfkill_setup(asus, &asus->wlan, "pega-wlan",
+ PEGA_WLAN, RFKILL_TYPE_WLAN);
+ if(ret)
+ goto exit;
+
+ ret = pega_rfkill_setup(asus, &asus->bluetooth, "pega-bt",
+ PEGA_BLUETOOTH, RFKILL_TYPE_BLUETOOTH);
+ if(ret)
+ goto exit;
+
+ ret = pega_rfkill_setup(asus, &asus->wwan, "pega-wwan",
+ PEGA_WWAN, RFKILL_TYPE_WWAN);
+
+exit:
+ if (ret)
+ asus_rfkill_exit(asus);
+
+ return ret;
+}
+
+/*
+ * Input device (i.e. hotkeys)
+ */
+static void asus_input_notify(struct asus_laptop *asus, int event)
+{
+ if (!asus->inputdev)
+ return ;
+ if (!sparse_keymap_report_event(asus->inputdev, event, 1, true))
+ pr_info("Unknown key %x pressed\n", event);
+}
+
+static int asus_input_init(struct asus_laptop *asus)
+{
+ struct input_dev *input;
+ int error;
+
+ input = input_allocate_device();
+ if (!input) {
+ pr_warn("Unable to allocate input device\n");
+ return -ENOMEM;
+ }
+ input->name = "Asus Laptop extra buttons";
+ input->phys = ASUS_LAPTOP_FILE "/input0";
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = &asus->platform_device->dev;
+
+ error = sparse_keymap_setup(input, asus_keymap, NULL);
+ if (error) {
+ pr_err("Unable to setup input device keymap\n");
+ goto err_free_dev;
+ }
+ error = input_register_device(input);
+ if (error) {
+ pr_warn("Unable to register input device\n");
+ goto err_free_keymap;
+ }
+
+ asus->inputdev = input;
+ return 0;
+
+err_free_keymap:
+ sparse_keymap_free(input);
+err_free_dev:
+ input_free_device(input);
+ return error;
+}
+
+static void asus_input_exit(struct asus_laptop *asus)
+{
+ if (asus->inputdev) {
+ sparse_keymap_free(asus->inputdev);
+ input_unregister_device(asus->inputdev);
+ }
+ asus->inputdev = NULL;
+}
+
+/*
+ * ACPI driver
+ */
+static void asus_acpi_notify(struct acpi_device *device, u32 event)
+{
+ struct asus_laptop *asus = acpi_driver_data(device);
+ u16 count;
+
+ /* TODO Find a better way to handle events count. */
+ count = asus->event_count[event % 128]++;
+ acpi_bus_generate_proc_event(asus->device, event, count);
+ acpi_bus_generate_netlink_event(asus->device->pnp.device_class,
+ dev_name(&asus->device->dev), event,
+ count);
+
+ if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX)
+ event = ATKD_BRNUP;
+ else if (event >= ATKD_BRNDOWN_MIN &&
+ event <= ATKD_BRNDOWN_MAX)
+ event = ATKD_BRNDOWN;
+
+ /* Brightness events are special */
+ if (event == ATKD_BRNDOWN || event == ATKD_BRNUP) {
+ if (asus->backlight_device != NULL) {
+ /* Update the backlight device. */
+ asus_backlight_notify(asus);
+ return ;
+ }
+ }
+
+ /* Accelerometer "coarse orientation change" event */
+ if (asus->pega_accel_poll && event == 0xEA) {
+ kobject_uevent(&asus->pega_accel_poll->input->dev.kobj,
+ KOBJ_CHANGE);
+ return ;
+ }
+
+ asus_input_notify(asus, event);
+}
+
+static DEVICE_ATTR(infos, S_IRUGO, show_infos, NULL);
+static DEVICE_ATTR(wlan, S_IRUGO | S_IWUSR, show_wlan, store_wlan);
+static DEVICE_ATTR(bluetooth, S_IRUGO | S_IWUSR,
+ show_bluetooth, store_bluetooth);
+static DEVICE_ATTR(wimax, S_IRUGO | S_IWUSR, show_wimax, store_wimax);
+static DEVICE_ATTR(wwan, S_IRUGO | S_IWUSR, show_wwan, store_wwan);
+static DEVICE_ATTR(display, S_IWUSR, NULL, store_disp);
+static DEVICE_ATTR(ledd, S_IRUGO | S_IWUSR, show_ledd, store_ledd);
+static DEVICE_ATTR(ls_value, S_IRUGO, show_lsvalue, NULL);
+static DEVICE_ATTR(ls_level, S_IRUGO | S_IWUSR, show_lslvl, store_lslvl);
+static DEVICE_ATTR(ls_switch, S_IRUGO | S_IWUSR, show_lssw, store_lssw);
+static DEVICE_ATTR(gps, S_IRUGO | S_IWUSR, show_gps, store_gps);
+
+static struct attribute *asus_attributes[] = {
+ &dev_attr_infos.attr,
+ &dev_attr_wlan.attr,
+ &dev_attr_bluetooth.attr,
+ &dev_attr_wimax.attr,
+ &dev_attr_wwan.attr,
+ &dev_attr_display.attr,
+ &dev_attr_ledd.attr,
+ &dev_attr_ls_value.attr,
+ &dev_attr_ls_level.attr,
+ &dev_attr_ls_switch.attr,
+ &dev_attr_gps.attr,
+ NULL
+};
+
+static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct platform_device *pdev = to_platform_device(dev);
+ struct asus_laptop *asus = platform_get_drvdata(pdev);
+ acpi_handle handle = asus->handle;
+ bool supported;
+
+ if (asus->is_pega_lucid) {
+ /* no ls_level interface on the Lucid */
+ if (attr == &dev_attr_ls_switch.attr)
+ supported = true;
+ else if (attr == &dev_attr_ls_level.attr)
+ supported = false;
+ else
+ goto normal;
+
+ return supported;
+ }
+
+normal:
+ if (attr == &dev_attr_wlan.attr) {
+ supported = !acpi_check_handle(handle, METHOD_WLAN, NULL);
+
+ } else if (attr == &dev_attr_bluetooth.attr) {
+ supported = !acpi_check_handle(handle, METHOD_BLUETOOTH, NULL);
+
+ } else if (attr == &dev_attr_display.attr) {
+ supported = !acpi_check_handle(handle, METHOD_SWITCH_DISPLAY, NULL);
+
+ } else if (attr == &dev_attr_wimax.attr) {
+ supported =
+ !acpi_check_handle(asus->handle, METHOD_WIMAX, NULL);
+
+ } else if (attr == &dev_attr_wwan.attr) {
+ supported = !acpi_check_handle(asus->handle, METHOD_WWAN, NULL);
+
+ } else if (attr == &dev_attr_ledd.attr) {
+ supported = !acpi_check_handle(handle, METHOD_LEDD, NULL);
+
+ } else if (attr == &dev_attr_ls_switch.attr ||
+ attr == &dev_attr_ls_level.attr) {
+ supported = !acpi_check_handle(handle, METHOD_ALS_CONTROL, NULL) &&
+ !acpi_check_handle(handle, METHOD_ALS_LEVEL, NULL);
+ } else if (attr == &dev_attr_ls_value.attr) {
+ supported = asus->is_pega_lucid;
+ } else if (attr == &dev_attr_gps.attr) {
+ supported = !acpi_check_handle(handle, METHOD_GPS_ON, NULL) &&
+ !acpi_check_handle(handle, METHOD_GPS_OFF, NULL) &&
+ !acpi_check_handle(handle, METHOD_GPS_STATUS, NULL);
+ } else {
+ supported = true;
+ }
+
+ return supported ? attr->mode : 0;
+}
+
+
+static const struct attribute_group asus_attr_group = {
+ .is_visible = asus_sysfs_is_visible,
+ .attrs = asus_attributes,
+};
+
+static int asus_platform_init(struct asus_laptop *asus)
+{
+ int result;
+
+ asus->platform_device = platform_device_alloc(ASUS_LAPTOP_FILE, -1);
+ if (!asus->platform_device)
+ return -ENOMEM;
+ platform_set_drvdata(asus->platform_device, asus);
+
+ result = platform_device_add(asus->platform_device);
+ if (result)
+ goto fail_platform_device;
+
+ result = sysfs_create_group(&asus->platform_device->dev.kobj,
+ &asus_attr_group);
+ if (result)
+ goto fail_sysfs;
+
+ return 0;
+
+fail_sysfs:
+ platform_device_del(asus->platform_device);
+fail_platform_device:
+ platform_device_put(asus->platform_device);
+ return result;
+}
+
+static void asus_platform_exit(struct asus_laptop *asus)
+{
+ sysfs_remove_group(&asus->platform_device->dev.kobj, &asus_attr_group);
+ platform_device_unregister(asus->platform_device);
+}
+
+static struct platform_driver platform_driver = {
+ .driver = {
+ .name = ASUS_LAPTOP_FILE,
+ .owner = THIS_MODULE,
+ },
+};
+
+/*
+ * This function is used to initialize the context with right values. In this
+ * method, we can make all the detection we want, and modify the asus_laptop
+ * struct
+ */
+static int asus_laptop_get_info(struct asus_laptop *asus)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *model = NULL;
+ unsigned long long bsts_result;
+ char *string = NULL;
+ acpi_status status;
+
+ /*
+ * Get DSDT headers early enough to allow for differentiating between
+ * models, but late enough to allow acpi_bus_register_driver() to fail
+ * before doing anything ACPI-specific. Should we encounter a machine,
+ * which needs special handling (i.e. its hotkey device has a different
+ * HID), this bit will be moved.
+ */
+ status = acpi_get_table(ACPI_SIG_DSDT, 1, &asus->dsdt_info);
+ if (ACPI_FAILURE(status))
+ pr_warn("Couldn't get the DSDT table header\n");
+
+ /* We have to write 0 on init this far for all ASUS models */
+ if (write_acpi_int_ret(asus->handle, "INIT", 0, &buffer)) {
+ pr_err("Hotkey initialization failed\n");
+ return -ENODEV;
+ }
+
+ /* This needs to be called for some laptops to init properly */
+ status =
+ acpi_evaluate_integer(asus->handle, "BSTS", NULL, &bsts_result);
+ if (ACPI_FAILURE(status))
+ pr_warn("Error calling BSTS\n");
+ else if (bsts_result)
+ pr_notice("BSTS called, 0x%02x returned\n",
+ (uint) bsts_result);
+
+ /* This too ... */
+ if (write_acpi_int(asus->handle, "CWAP", wapf))
+ pr_err("Error calling CWAP(%d)\n", wapf);
+ /*
+ * Try to match the object returned by INIT to the specific model.
+ * Handle every possible object (or the lack of thereof) the DSDT
+ * writers might throw at us. When in trouble, we pass NULL to
+ * asus_model_match() and try something completely different.
+ */
+ if (buffer.pointer) {
+ model = buffer.pointer;
+ switch (model->type) {
+ case ACPI_TYPE_STRING:
+ string = model->string.pointer;
+ break;
+ case ACPI_TYPE_BUFFER:
+ string = model->buffer.pointer;
+ break;
+ default:
+ string = "";
+ break;
+ }
+ }
+ asus->name = kstrdup(string, GFP_KERNEL);
+ if (!asus->name) {
+ kfree(buffer.pointer);
+ return -ENOMEM;
+ }
+
+ if (string)
+ pr_notice(" %s model detected\n", string);
+
+ if (!acpi_check_handle(asus->handle, METHOD_WL_STATUS, NULL))
+ asus->have_rsts = true;
+
+ kfree(model);
+
+ return AE_OK;
+}
+
+static int asus_acpi_init(struct asus_laptop *asus)
+{
+ int result = 0;
+
+ result = acpi_bus_get_status(asus->device);
+ if (result)
+ return result;
+ if (!asus->device->status.present) {
+ pr_err("Hotkey device not present, aborting\n");
+ return -ENODEV;
+ }
+
+ result = asus_laptop_get_info(asus);
+ if (result)
+ return result;
+
+ if (!strcmp(bled_type, "led"))
+ asus->bled_type = TYPE_LED;
+ else if (!strcmp(bled_type, "rfkill"))
+ asus->bled_type = TYPE_RFKILL;
+
+ if (!strcmp(wled_type, "led"))
+ asus->wled_type = TYPE_LED;
+ else if (!strcmp(wled_type, "rfkill"))
+ asus->wled_type = TYPE_RFKILL;
+
+ if (bluetooth_status >= 0)
+ asus_bluetooth_set(asus, !!bluetooth_status);
+
+ if (wlan_status >= 0)
+ asus_wlan_set(asus, !!wlan_status);
+
+ if (wimax_status >= 0)
+ asus_wimax_set(asus, !!wimax_status);
+
+ if (wwan_status >= 0)
+ asus_wwan_set(asus, !!wwan_status);
+
+ /* Keyboard Backlight is on by default */
+ if (!acpi_check_handle(asus->handle, METHOD_KBD_LIGHT_SET, NULL))
+ asus_kled_set(asus, 1);
+
+ /* LED display is off by default */
+ asus->ledd_status = 0xFFF;
+
+ /* Set initial values of light sensor and level */
+ asus->light_switch = !!als_status;
+ asus->light_level = 5; /* level 5 for sensor sensitivity */
+
+ if (asus->is_pega_lucid) {
+ asus_als_switch(asus, asus->light_switch);
+ } else if (!acpi_check_handle(asus->handle, METHOD_ALS_CONTROL, NULL) &&
+ !acpi_check_handle(asus->handle, METHOD_ALS_LEVEL, NULL)) {
+ asus_als_switch(asus, asus->light_switch);
+ asus_als_level(asus, asus->light_level);
+ }
+
+ return result;
+}
+
+static void asus_dmi_check(void)
+{
+ const char *model;
+
+ model = dmi_get_system_info(DMI_PRODUCT_NAME);
+ if (!model)
+ return;
+
+ /* On L1400B WLED control the sound card, don't mess with it ... */
+ if (strncmp(model, "L1400B", 6) == 0) {
+ wlan_status = -1;
+ }
+}
+
+static bool asus_device_present;
+
+static int asus_acpi_add(struct acpi_device *device)
+{
+ struct asus_laptop *asus;
+ int result;
+
+ pr_notice("Asus Laptop Support version %s\n",
+ ASUS_LAPTOP_VERSION);
+ asus = kzalloc(sizeof(struct asus_laptop), GFP_KERNEL);
+ if (!asus)
+ return -ENOMEM;
+ asus->handle = device->handle;
+ strcpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
+ strcpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
+ device->driver_data = asus;
+ asus->device = device;
+
+ asus_dmi_check();
+
+ result = asus_acpi_init(asus);
+ if (result)
+ goto fail_platform;
+
+ /*
+ * Need platform type detection first, then the platform
+ * device. It is used as a parent for the sub-devices below.
+ */
+ asus->is_pega_lucid = asus_check_pega_lucid(asus);
+ result = asus_platform_init(asus);
+ if (result)
+ goto fail_platform;
+
+ if (!acpi_video_backlight_support()) {
+ result = asus_backlight_init(asus);
+ if (result)
+ goto fail_backlight;
+ } else
+ pr_info("Backlight controlled by ACPI video driver\n");
+
+ result = asus_input_init(asus);
+ if (result)
+ goto fail_input;
+
+ result = asus_led_init(asus);
+ if (result)
+ goto fail_led;
+
+ result = asus_rfkill_init(asus);
+ if (result && result != -ENODEV)
+ goto fail_rfkill;
+
+ result = pega_accel_init(asus);
+ if (result && result != -ENODEV)
+ goto fail_pega_accel;
+
+ result = pega_rfkill_init(asus);
+ if (result && result != -ENODEV)
+ goto fail_pega_rfkill;
+
+ asus_device_present = true;
+ return 0;
+
+fail_pega_rfkill:
+ pega_accel_exit(asus);
+fail_pega_accel:
+ asus_rfkill_exit(asus);
+fail_rfkill:
+ asus_led_exit(asus);
+fail_led:
+ asus_input_exit(asus);
+fail_input:
+ asus_backlight_exit(asus);
+fail_backlight:
+ asus_platform_exit(asus);
+fail_platform:
+ kfree(asus->name);
+ kfree(asus);
+
+ return result;
+}
+
+static int asus_acpi_remove(struct acpi_device *device)
+{
+ struct asus_laptop *asus = acpi_driver_data(device);
+
+ asus_backlight_exit(asus);
+ asus_rfkill_exit(asus);
+ asus_led_exit(asus);
+ asus_input_exit(asus);
+ pega_accel_exit(asus);
+ asus_platform_exit(asus);
+
+ kfree(asus->name);
+ kfree(asus);
+ return 0;
+}
+
+static const struct acpi_device_id asus_device_ids[] = {
+ {"ATK0100", 0},
+ {"ATK0101", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, asus_device_ids);
+
+static struct acpi_driver asus_acpi_driver = {
+ .name = ASUS_LAPTOP_NAME,
+ .class = ASUS_LAPTOP_CLASS,
+ .owner = THIS_MODULE,
+ .ids = asus_device_ids,
+ .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+ .ops = {
+ .add = asus_acpi_add,
+ .remove = asus_acpi_remove,
+ .notify = asus_acpi_notify,
+ },
+};
+
+static int __init asus_laptop_init(void)
+{
+ int result;
+
+ result = platform_driver_register(&platform_driver);
+ if (result < 0)
+ return result;
+
+ result = acpi_bus_register_driver(&asus_acpi_driver);
+ if (result < 0)
+ goto fail_acpi_driver;
+ if (!asus_device_present) {
+ result = -ENODEV;
+ goto fail_no_device;
+ }
+ return 0;
+
+fail_no_device:
+ acpi_bus_unregister_driver(&asus_acpi_driver);
+fail_acpi_driver:
+ platform_driver_unregister(&platform_driver);
+ return result;
+}
+
+static void __exit asus_laptop_exit(void)
+{
+ acpi_bus_unregister_driver(&asus_acpi_driver);
+ platform_driver_unregister(&platform_driver);
+}
+
+module_init(asus_laptop_init);
+module_exit(asus_laptop_exit);
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
new file mode 100644
index 00000000..8fcb41e1
--- /dev/null
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -0,0 +1,284 @@
+/*
+ * Asus Notebooks WMI hotkey driver
+ *
+ * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/fb.h>
+#include <linux/dmi.h>
+
+#include "asus-wmi.h"
+
+#define ASUS_NB_WMI_FILE "asus-nb-wmi"
+
+MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>");
+MODULE_DESCRIPTION("Asus Notebooks WMI Hotkey Driver");
+MODULE_LICENSE("GPL");
+
+#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C"
+
+MODULE_ALIAS("wmi:"ASUS_NB_WMI_EVENT_GUID);
+
+/*
+ * WAPF defines the behavior of the Fn+Fx wlan key
+ * The significance of values is yet to be found, but
+ * most of the time:
+ * Bit | Bluetooth | WLAN
+ * 0 | Hardware | Hardware
+ * 1 | Hardware | Software
+ * 4 | Software | Software
+ */
+static int wapf = -1;
+module_param(wapf, uint, 0444);
+MODULE_PARM_DESC(wapf, "WAPF value");
+
+static struct quirk_entry *quirks;
+
+static struct quirk_entry quirk_asus_unknown = {
+ .wapf = 0,
+};
+
+/*
+ * For those machines that need software to control bt/wifi status
+ * and can't adjust brightness through ACPI interface
+ * and have duplicate events(ACPI and WMI) for display toggle
+ */
+static struct quirk_entry quirk_asus_x55u = {
+ .wapf = 4,
+ .wmi_backlight_power = true,
+ .no_display_toggle = true,
+};
+
+static struct quirk_entry quirk_asus_x401u = {
+ .wapf = 4,
+};
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+ quirks = dmi->driver_data;
+ return 1;
+}
+
+static struct dmi_system_id asus_quirks[] = {
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X401U",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X401U"),
+ },
+ .driver_data = &quirk_asus_x55u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X401A",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X401A"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X401A1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X401A1"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X501U",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X501U"),
+ },
+ .driver_data = &quirk_asus_x55u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X501A",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X501A"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X501A1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X501A1"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X55A",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X55A"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X55C",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X55C"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X55U",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X55U"),
+ },
+ .driver_data = &quirk_asus_x55u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X55VD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X55VD"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK COMPUTER INC. X75A",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X75A"),
+ },
+ .driver_data = &quirk_asus_x401u,
+ },
+ {},
+};
+
+static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver)
+{
+ quirks = &quirk_asus_unknown;
+ dmi_check_system(asus_quirks);
+
+ driver->quirks = quirks;
+ driver->panel_power = FB_BLANK_UNBLANK;
+
+ /* overwrite the wapf setting if the wapf paramater is specified */
+ if (wapf != -1)
+ quirks->wapf = wapf;
+ else
+ wapf = quirks->wapf;
+}
+
+static const struct key_entry asus_nb_wmi_keymap[] = {
+ { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 0x30, { KEY_VOLUMEUP } },
+ { KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 0x32, { KEY_MUTE } },
+ { KE_KEY, 0x33, { KEY_DISPLAYTOGGLE } }, /* LCD on */
+ { KE_KEY, 0x34, { KEY_DISPLAY_OFF } }, /* LCD off */
+ { KE_KEY, 0x40, { KEY_PREVIOUSSONG } },
+ { KE_KEY, 0x41, { KEY_NEXTSONG } },
+ { KE_KEY, 0x43, { KEY_STOPCD } }, /* Stop/Eject */
+ { KE_KEY, 0x45, { KEY_PLAYPAUSE } },
+ { KE_KEY, 0x4c, { KEY_MEDIA } }, /* WMP Key */
+ { KE_KEY, 0x50, { KEY_EMAIL } },
+ { KE_KEY, 0x51, { KEY_WWW } },
+ { KE_KEY, 0x55, { KEY_CALC } },
+ { KE_IGNORE, 0x57, }, /* Battery mode */
+ { KE_IGNORE, 0x58, }, /* AC mode */
+ { KE_KEY, 0x5C, { KEY_F15 } }, /* Power Gear key */
+ { KE_KEY, 0x5D, { KEY_WLAN } }, /* Wireless console Toggle */
+ { KE_KEY, 0x5E, { KEY_WLAN } }, /* Wireless console Enable */
+ { KE_KEY, 0x5F, { KEY_WLAN } }, /* Wireless console Disable */
+ { KE_KEY, 0x60, { KEY_TOUCHPAD_ON } },
+ { KE_KEY, 0x61, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD only */
+ { KE_KEY, 0x62, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT only */
+ { KE_KEY, 0x63, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT */
+ { KE_KEY, 0x64, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV */
+ { KE_KEY, 0x65, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV */
+ { KE_KEY, 0x66, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV */
+ { KE_KEY, 0x67, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV */
+ { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } },
+ { KE_IGNORE, 0x6E, }, /* Low Battery notification */
+ { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */
+ { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */
+ { KE_KEY, 0x82, { KEY_CAMERA } },
+ { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */
+ { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
+ { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
+ { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
+ { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
+ { KE_KEY, 0x8F, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + DVI */
+ { KE_KEY, 0x90, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + DVI */
+ { KE_KEY, 0x91, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + DVI */
+ { KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */
+ { KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
+ { KE_KEY, 0x95, { KEY_MEDIA } },
+ { KE_KEY, 0x99, { KEY_PHONE } },
+ { KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
+ { KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
+ { KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
+ { KE_KEY, 0xA3, { KEY_SWITCHVIDEOMODE } }, /* SDSP TV + HDMI */
+ { KE_KEY, 0xA4, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + HDMI */
+ { KE_KEY, 0xA5, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + TV + HDMI */
+ { KE_KEY, 0xA6, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + HDMI */
+ { KE_KEY, 0xA7, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + HDMI */
+ { KE_KEY, 0xB5, { KEY_CALC } },
+ { KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
+ { KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
+ { KE_END, 0},
+};
+
+static struct asus_wmi_driver asus_nb_wmi_driver = {
+ .name = ASUS_NB_WMI_FILE,
+ .owner = THIS_MODULE,
+ .event_guid = ASUS_NB_WMI_EVENT_GUID,
+ .keymap = asus_nb_wmi_keymap,
+ .input_name = "Asus WMI hotkeys",
+ .input_phys = ASUS_NB_WMI_FILE "/input0",
+ .detect_quirks = asus_nb_wmi_quirks,
+};
+
+
+static int __init asus_nb_wmi_init(void)
+{
+ return asus_wmi_register_driver(&asus_nb_wmi_driver);
+}
+
+static void __exit asus_nb_wmi_exit(void)
+{
+ asus_wmi_unregister_driver(&asus_nb_wmi_driver);
+}
+
+module_init(asus_nb_wmi_init);
+module_exit(asus_nb_wmi_exit);
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
new file mode 100644
index 00000000..c11b2426
--- /dev/null
+++ b/drivers/platform/x86/asus-wmi.c
@@ -0,0 +1,2002 @@
+/*
+ * Asus PC WMI hotkey driver
+ *
+ * Copyright(C) 2010 Intel Corporation.
+ * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/rfkill.h>
+#include <linux/pci.h>
+#include <linux/pci_hotplug.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/video.h>
+
+#include "asus-wmi.h"
+
+MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, "
+ "Yong Wang <yong.y.wang@intel.com>");
+MODULE_DESCRIPTION("Asus Generic WMI Driver");
+MODULE_LICENSE("GPL");
+
+#define to_platform_driver(drv) \
+ (container_of((drv), struct platform_driver, driver))
+
+#define to_asus_wmi_driver(pdrv) \
+ (container_of((pdrv), struct asus_wmi_driver, platform_driver))
+
+#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66"
+
+#define NOTIFY_BRNUP_MIN 0x11
+#define NOTIFY_BRNUP_MAX 0x1f
+#define NOTIFY_BRNDOWN_MIN 0x20
+#define NOTIFY_BRNDOWN_MAX 0x2e
+#define NOTIFY_KBD_BRTUP 0xc4
+#define NOTIFY_KBD_BRTDWN 0xc5
+
+/* WMI Methods */
+#define ASUS_WMI_METHODID_SPEC 0x43455053 /* BIOS SPECification */
+#define ASUS_WMI_METHODID_SFBD 0x44424653 /* Set First Boot Device */
+#define ASUS_WMI_METHODID_GLCD 0x44434C47 /* Get LCD status */
+#define ASUS_WMI_METHODID_GPID 0x44495047 /* Get Panel ID?? (Resol) */
+#define ASUS_WMI_METHODID_QMOD 0x444F4D51 /* Quiet MODe */
+#define ASUS_WMI_METHODID_SPLV 0x4C425053 /* Set Panel Light Value */
+#define ASUS_WMI_METHODID_SFUN 0x4E554653 /* FUNCtionalities */
+#define ASUS_WMI_METHODID_SDSP 0x50534453 /* Set DiSPlay output */
+#define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */
+#define ASUS_WMI_METHODID_DEVP 0x50564544 /* DEVice Policy */
+#define ASUS_WMI_METHODID_OSVR 0x5256534F /* OS VeRsion */
+#define ASUS_WMI_METHODID_DSTS 0x53544344 /* Device STatuS */
+#define ASUS_WMI_METHODID_DSTS2 0x53545344 /* Device STatuS #2*/
+#define ASUS_WMI_METHODID_BSTS 0x53545342 /* Bios STatuS ? */
+#define ASUS_WMI_METHODID_DEVS 0x53564544 /* DEVice Set */
+#define ASUS_WMI_METHODID_CFVS 0x53564643 /* CPU Frequency Volt Set */
+#define ASUS_WMI_METHODID_KBFT 0x5446424B /* KeyBoard FilTer */
+#define ASUS_WMI_METHODID_INIT 0x54494E49 /* INITialize */
+#define ASUS_WMI_METHODID_HKEY 0x59454B48 /* Hot KEY ?? */
+
+#define ASUS_WMI_UNSUPPORTED_METHOD 0xFFFFFFFE
+
+/* Wireless */
+#define ASUS_WMI_DEVID_HW_SWITCH 0x00010001
+#define ASUS_WMI_DEVID_WIRELESS_LED 0x00010002
+#define ASUS_WMI_DEVID_CWAP 0x00010003
+#define ASUS_WMI_DEVID_WLAN 0x00010011
+#define ASUS_WMI_DEVID_WLAN_LED 0x00010012
+#define ASUS_WMI_DEVID_BLUETOOTH 0x00010013
+#define ASUS_WMI_DEVID_GPS 0x00010015
+#define ASUS_WMI_DEVID_WIMAX 0x00010017
+#define ASUS_WMI_DEVID_WWAN3G 0x00010019
+#define ASUS_WMI_DEVID_UWB 0x00010021
+
+/* Leds */
+/* 0x000200XX and 0x000400XX */
+#define ASUS_WMI_DEVID_LED1 0x00020011
+#define ASUS_WMI_DEVID_LED2 0x00020012
+#define ASUS_WMI_DEVID_LED3 0x00020013
+#define ASUS_WMI_DEVID_LED4 0x00020014
+#define ASUS_WMI_DEVID_LED5 0x00020015
+#define ASUS_WMI_DEVID_LED6 0x00020016
+
+/* Backlight and Brightness */
+#define ASUS_WMI_DEVID_BACKLIGHT 0x00050011
+#define ASUS_WMI_DEVID_BRIGHTNESS 0x00050012
+#define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021
+#define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* ?? */
+
+/* Misc */
+#define ASUS_WMI_DEVID_CAMERA 0x00060013
+
+/* Storage */
+#define ASUS_WMI_DEVID_CARDREADER 0x00080013
+
+/* Input */
+#define ASUS_WMI_DEVID_TOUCHPAD 0x00100011
+#define ASUS_WMI_DEVID_TOUCHPAD_LED 0x00100012
+
+/* Fan, Thermal */
+#define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011
+#define ASUS_WMI_DEVID_FAN_CTRL 0x00110012
+
+/* Power */
+#define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012
+
+/* Deep S3 / Resume on LID open */
+#define ASUS_WMI_DEVID_LID_RESUME 0x00120031
+
+/* DSTS masks */
+#define ASUS_WMI_DSTS_STATUS_BIT 0x00000001
+#define ASUS_WMI_DSTS_UNKNOWN_BIT 0x00000002
+#define ASUS_WMI_DSTS_PRESENCE_BIT 0x00010000
+#define ASUS_WMI_DSTS_USER_BIT 0x00020000
+#define ASUS_WMI_DSTS_BIOS_BIT 0x00040000
+#define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF
+#define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00
+
+struct bios_args {
+ u32 arg0;
+ u32 arg1;
+} __packed;
+
+/*
+ * <platform>/ - debugfs root directory
+ * dev_id - current dev_id
+ * ctrl_param - current ctrl_param
+ * method_id - current method_id
+ * devs - call DEVS(dev_id, ctrl_param) and print result
+ * dsts - call DSTS(dev_id) and print result
+ * call - call method_id(dev_id, ctrl_param) and print result
+ */
+struct asus_wmi_debug {
+ struct dentry *root;
+ u32 method_id;
+ u32 dev_id;
+ u32 ctrl_param;
+};
+
+struct asus_rfkill {
+ struct asus_wmi *asus;
+ struct rfkill *rfkill;
+ u32 dev_id;
+};
+
+struct asus_wmi {
+ int dsts_id;
+ int spec;
+ int sfun;
+
+ struct input_dev *inputdev;
+ struct backlight_device *backlight_device;
+ struct device *hwmon_device;
+ struct platform_device *platform_device;
+
+ struct led_classdev wlan_led;
+ int wlan_led_wk;
+ struct led_classdev tpd_led;
+ int tpd_led_wk;
+ struct led_classdev kbd_led;
+ int kbd_led_wk;
+ struct workqueue_struct *led_workqueue;
+ struct work_struct tpd_led_work;
+ struct work_struct kbd_led_work;
+ struct work_struct wlan_led_work;
+
+ struct asus_rfkill wlan;
+ struct asus_rfkill bluetooth;
+ struct asus_rfkill wimax;
+ struct asus_rfkill wwan3g;
+ struct asus_rfkill gps;
+ struct asus_rfkill uwb;
+
+ struct hotplug_slot *hotplug_slot;
+ struct mutex hotplug_lock;
+ struct mutex wmi_lock;
+ struct workqueue_struct *hotplug_workqueue;
+ struct work_struct hotplug_work;
+
+ struct asus_wmi_debug debug;
+
+ struct asus_wmi_driver *driver;
+};
+
+static int asus_wmi_input_init(struct asus_wmi *asus)
+{
+ int err;
+
+ asus->inputdev = input_allocate_device();
+ if (!asus->inputdev)
+ return -ENOMEM;
+
+ asus->inputdev->name = asus->driver->input_name;
+ asus->inputdev->phys = asus->driver->input_phys;
+ asus->inputdev->id.bustype = BUS_HOST;
+ asus->inputdev->dev.parent = &asus->platform_device->dev;
+ set_bit(EV_REP, asus->inputdev->evbit);
+
+ err = sparse_keymap_setup(asus->inputdev, asus->driver->keymap, NULL);
+ if (err)
+ goto err_free_dev;
+
+ err = input_register_device(asus->inputdev);
+ if (err)
+ goto err_free_keymap;
+
+ return 0;
+
+err_free_keymap:
+ sparse_keymap_free(asus->inputdev);
+err_free_dev:
+ input_free_device(asus->inputdev);
+ return err;
+}
+
+static void asus_wmi_input_exit(struct asus_wmi *asus)
+{
+ if (asus->inputdev) {
+ sparse_keymap_free(asus->inputdev);
+ input_unregister_device(asus->inputdev);
+ }
+
+ asus->inputdev = NULL;
+}
+
+static int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
+ u32 *retval)
+{
+ struct bios_args args = {
+ .arg0 = arg0,
+ .arg1 = arg1,
+ };
+ struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+ union acpi_object *obj;
+ u32 tmp;
+
+ status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 1, method_id,
+ &input, &output);
+
+ if (ACPI_FAILURE(status))
+ goto exit;
+
+ obj = (union acpi_object *)output.pointer;
+ if (obj && obj->type == ACPI_TYPE_INTEGER)
+ tmp = (u32) obj->integer.value;
+ else
+ tmp = 0;
+
+ if (retval)
+ *retval = tmp;
+
+ kfree(obj);
+
+exit:
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval)
+{
+ return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval);
+}
+
+static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
+ u32 *retval)
+{
+ return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id,
+ ctrl_param, retval);
+}
+
+/* Helper for special devices with magic return codes */
+static int asus_wmi_get_devstate_bits(struct asus_wmi *asus,
+ u32 dev_id, u32 mask)
+{
+ u32 retval = 0;
+ int err;
+
+ err = asus_wmi_get_devstate(asus, dev_id, &retval);
+
+ if (err < 0)
+ return err;
+
+ if (!(retval & ASUS_WMI_DSTS_PRESENCE_BIT))
+ return -ENODEV;
+
+ if (mask == ASUS_WMI_DSTS_STATUS_BIT) {
+ if (retval & ASUS_WMI_DSTS_UNKNOWN_BIT)
+ return -ENODEV;
+ }
+
+ return retval & mask;
+}
+
+static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id)
+{
+ return asus_wmi_get_devstate_bits(asus, dev_id,
+ ASUS_WMI_DSTS_STATUS_BIT);
+}
+
+/*
+ * LEDs
+ */
+/*
+ * These functions actually update the LED's, and are called from a
+ * workqueue. By doing this as separate work rather than when the LED
+ * subsystem asks, we avoid messing with the Asus ACPI stuff during a
+ * potentially bad time, such as a timer interrupt.
+ */
+static void tpd_led_update(struct work_struct *work)
+{
+ int ctrl_param;
+ struct asus_wmi *asus;
+
+ asus = container_of(work, struct asus_wmi, tpd_led_work);
+
+ ctrl_param = asus->tpd_led_wk;
+ asus_wmi_set_devstate(ASUS_WMI_DEVID_TOUCHPAD_LED, ctrl_param, NULL);
+}
+
+static void tpd_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct asus_wmi *asus;
+
+ asus = container_of(led_cdev, struct asus_wmi, tpd_led);
+
+ asus->tpd_led_wk = !!value;
+ queue_work(asus->led_workqueue, &asus->tpd_led_work);
+}
+
+static int read_tpd_led_state(struct asus_wmi *asus)
+{
+ return asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_TOUCHPAD_LED);
+}
+
+static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
+{
+ struct asus_wmi *asus;
+
+ asus = container_of(led_cdev, struct asus_wmi, tpd_led);
+
+ return read_tpd_led_state(asus);
+}
+
+static void kbd_led_update(struct work_struct *work)
+{
+ int ctrl_param = 0;
+ struct asus_wmi *asus;
+
+ asus = container_of(work, struct asus_wmi, kbd_led_work);
+
+ /*
+ * bits 0-2: level
+ * bit 7: light on/off
+ */
+ if (asus->kbd_led_wk > 0)
+ ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
+
+ asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
+}
+
+static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
+{
+ int retval;
+
+ /*
+ * bits 0-2: level
+ * bit 7: light on/off
+ * bit 8-10: environment (0: dark, 1: normal, 2: light)
+ * bit 17: status unknown
+ */
+ retval = asus_wmi_get_devstate_bits(asus, ASUS_WMI_DEVID_KBD_BACKLIGHT,
+ 0xFFFF);
+
+ /* Unknown status is considered as off */
+ if (retval == 0x8000)
+ retval = 0;
+
+ if (retval >= 0) {
+ if (level)
+ *level = retval & 0x7F;
+ if (env)
+ *env = (retval >> 8) & 0x7F;
+ retval = 0;
+ }
+
+ return retval;
+}
+
+static void kbd_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct asus_wmi *asus;
+
+ asus = container_of(led_cdev, struct asus_wmi, kbd_led);
+
+ if (value > asus->kbd_led.max_brightness)
+ value = asus->kbd_led.max_brightness;
+ else if (value < 0)
+ value = 0;
+
+ asus->kbd_led_wk = value;
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
+}
+
+static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
+{
+ struct asus_wmi *asus;
+ int retval, value;
+
+ asus = container_of(led_cdev, struct asus_wmi, kbd_led);
+
+ retval = kbd_led_read(asus, &value, NULL);
+
+ if (retval < 0)
+ return retval;
+
+ return value;
+}
+
+static int wlan_led_unknown_state(struct asus_wmi *asus)
+{
+ u32 result;
+
+ asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+ return result & ASUS_WMI_DSTS_UNKNOWN_BIT;
+}
+
+static int wlan_led_presence(struct asus_wmi *asus)
+{
+ u32 result;
+
+ asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+ return result & ASUS_WMI_DSTS_PRESENCE_BIT;
+}
+
+static void wlan_led_update(struct work_struct *work)
+{
+ int ctrl_param;
+ struct asus_wmi *asus;
+
+ asus = container_of(work, struct asus_wmi, wlan_led_work);
+
+ ctrl_param = asus->wlan_led_wk;
+ asus_wmi_set_devstate(ASUS_WMI_DEVID_WIRELESS_LED, ctrl_param, NULL);
+}
+
+static void wlan_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct asus_wmi *asus;
+
+ asus = container_of(led_cdev, struct asus_wmi, wlan_led);
+
+ asus->wlan_led_wk = !!value;
+ queue_work(asus->led_workqueue, &asus->wlan_led_work);
+}
+
+static enum led_brightness wlan_led_get(struct led_classdev *led_cdev)
+{
+ struct asus_wmi *asus;
+ u32 result;
+
+ asus = container_of(led_cdev, struct asus_wmi, wlan_led);
+ asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result);
+
+ return result & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
+}
+
+static void asus_wmi_led_exit(struct asus_wmi *asus)
+{
+ if (!IS_ERR_OR_NULL(asus->kbd_led.dev))
+ led_classdev_unregister(&asus->kbd_led);
+ if (!IS_ERR_OR_NULL(asus->tpd_led.dev))
+ led_classdev_unregister(&asus->tpd_led);
+ if (!IS_ERR_OR_NULL(asus->wlan_led.dev))
+ led_classdev_unregister(&asus->wlan_led);
+ if (asus->led_workqueue)
+ destroy_workqueue(asus->led_workqueue);
+}
+
+static int asus_wmi_led_init(struct asus_wmi *asus)
+{
+ int rv = 0;
+
+ asus->led_workqueue = create_singlethread_workqueue("led_workqueue");
+ if (!asus->led_workqueue)
+ return -ENOMEM;
+
+ if (read_tpd_led_state(asus) >= 0) {
+ INIT_WORK(&asus->tpd_led_work, tpd_led_update);
+
+ asus->tpd_led.name = "asus::touchpad";
+ asus->tpd_led.brightness_set = tpd_led_set;
+ asus->tpd_led.brightness_get = tpd_led_get;
+ asus->tpd_led.max_brightness = 1;
+
+ rv = led_classdev_register(&asus->platform_device->dev,
+ &asus->tpd_led);
+ if (rv)
+ goto error;
+ }
+
+ if (kbd_led_read(asus, NULL, NULL) >= 0) {
+ INIT_WORK(&asus->kbd_led_work, kbd_led_update);
+
+ asus->kbd_led.name = "asus::kbd_backlight";
+ asus->kbd_led.brightness_set = kbd_led_set;
+ asus->kbd_led.brightness_get = kbd_led_get;
+ asus->kbd_led.max_brightness = 3;
+
+ rv = led_classdev_register(&asus->platform_device->dev,
+ &asus->kbd_led);
+ if (rv)
+ goto error;
+ }
+
+ if (wlan_led_presence(asus)) {
+ INIT_WORK(&asus->wlan_led_work, wlan_led_update);
+
+ asus->wlan_led.name = "asus::wlan";
+ asus->wlan_led.brightness_set = wlan_led_set;
+ if (!wlan_led_unknown_state(asus))
+ asus->wlan_led.brightness_get = wlan_led_get;
+ asus->wlan_led.flags = LED_CORE_SUSPENDRESUME;
+ asus->wlan_led.max_brightness = 1;
+ asus->wlan_led.default_trigger = "asus-wlan";
+
+ rv = led_classdev_register(&asus->platform_device->dev,
+ &asus->wlan_led);
+ }
+
+error:
+ if (rv)
+ asus_wmi_led_exit(asus);
+
+ return rv;
+}
+
+
+/*
+ * PCI hotplug (for wlan rfkill)
+ */
+static bool asus_wlan_rfkill_blocked(struct asus_wmi *asus)
+{
+ int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);
+
+ if (result < 0)
+ return false;
+ return !result;
+}
+
+static void asus_rfkill_hotplug(struct asus_wmi *asus)
+{
+ struct pci_dev *dev;
+ struct pci_bus *bus;
+ bool blocked;
+ bool absent;
+ u32 l;
+
+ mutex_lock(&asus->wmi_lock);
+ blocked = asus_wlan_rfkill_blocked(asus);
+ mutex_unlock(&asus->wmi_lock);
+
+ mutex_lock(&asus->hotplug_lock);
+
+ if (asus->wlan.rfkill)
+ rfkill_set_sw_state(asus->wlan.rfkill, blocked);
+
+ if (asus->hotplug_slot) {
+ bus = pci_find_bus(0, 1);
+ if (!bus) {
+ pr_warn("Unable to find PCI bus 1?\n");
+ goto out_unlock;
+ }
+
+ if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) {
+ pr_err("Unable to read PCI config space?\n");
+ goto out_unlock;
+ }
+ absent = (l == 0xffffffff);
+
+ if (blocked != absent) {
+ pr_warn("BIOS says wireless lan is %s, "
+ "but the pci device is %s\n",
+ blocked ? "blocked" : "unblocked",
+ absent ? "absent" : "present");
+ pr_warn("skipped wireless hotplug as probably "
+ "inappropriate for this model\n");
+ goto out_unlock;
+ }
+
+ if (!blocked) {
+ dev = pci_get_slot(bus, 0);
+ if (dev) {
+ /* Device already present */
+ pci_dev_put(dev);
+ goto out_unlock;
+ }
+ dev = pci_scan_single_device(bus, 0);
+ if (dev) {
+ pci_bus_assign_resources(bus);
+ if (pci_bus_add_device(dev))
+ pr_err("Unable to hotplug wifi\n");
+ }
+ } else {
+ dev = pci_get_slot(bus, 0);
+ if (dev) {
+ pci_stop_and_remove_bus_device(dev);
+ pci_dev_put(dev);
+ }
+ }
+ }
+
+out_unlock:
+ mutex_unlock(&asus->hotplug_lock);
+}
+
+static void asus_rfkill_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct asus_wmi *asus = data;
+
+ if (event != ACPI_NOTIFY_BUS_CHECK)
+ return;
+
+ /*
+ * We can't call directly asus_rfkill_hotplug because most
+ * of the time WMBC is still being executed and not reetrant.
+ * There is currently no way to tell ACPICA that we want this
+ * method to be serialized, we schedule a asus_rfkill_hotplug
+ * call later, in a safer context.
+ */
+ queue_work(asus->hotplug_workqueue, &asus->hotplug_work);
+}
+
+static int asus_register_rfkill_notifier(struct asus_wmi *asus, char *node)
+{
+ acpi_status status;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_install_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ asus_rfkill_notify, asus);
+ if (ACPI_FAILURE(status))
+ pr_warn("Failed to register notify on %s\n", node);
+ } else
+ return -ENODEV;
+
+ return 0;
+}
+
+static void asus_unregister_rfkill_notifier(struct asus_wmi *asus, char *node)
+{
+ acpi_status status = AE_OK;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_remove_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ asus_rfkill_notify);
+ if (ACPI_FAILURE(status))
+ pr_err("Error removing rfkill notify handler %s\n",
+ node);
+ }
+}
+
+static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot,
+ u8 *value)
+{
+ struct asus_wmi *asus = hotplug_slot->private;
+ int result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);
+
+ if (result < 0)
+ return result;
+
+ *value = !!result;
+ return 0;
+}
+
+static void asus_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
+{
+ kfree(hotplug_slot->info);
+ kfree(hotplug_slot);
+}
+
+static struct hotplug_slot_ops asus_hotplug_slot_ops = {
+ .owner = THIS_MODULE,
+ .get_adapter_status = asus_get_adapter_status,
+ .get_power_status = asus_get_adapter_status,
+};
+
+static void asus_hotplug_work(struct work_struct *work)
+{
+ struct asus_wmi *asus;
+
+ asus = container_of(work, struct asus_wmi, hotplug_work);
+ asus_rfkill_hotplug(asus);
+}
+
+static int asus_setup_pci_hotplug(struct asus_wmi *asus)
+{
+ int ret = -ENOMEM;
+ struct pci_bus *bus = pci_find_bus(0, 1);
+
+ if (!bus) {
+ pr_err("Unable to find wifi PCI bus\n");
+ return -ENODEV;
+ }
+
+ asus->hotplug_workqueue =
+ create_singlethread_workqueue("hotplug_workqueue");
+ if (!asus->hotplug_workqueue)
+ goto error_workqueue;
+
+ INIT_WORK(&asus->hotplug_work, asus_hotplug_work);
+
+ asus->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
+ if (!asus->hotplug_slot)
+ goto error_slot;
+
+ asus->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
+ GFP_KERNEL);
+ if (!asus->hotplug_slot->info)
+ goto error_info;
+
+ asus->hotplug_slot->private = asus;
+ asus->hotplug_slot->release = &asus_cleanup_pci_hotplug;
+ asus->hotplug_slot->ops = &asus_hotplug_slot_ops;
+ asus_get_adapter_status(asus->hotplug_slot,
+ &asus->hotplug_slot->info->adapter_status);
+
+ ret = pci_hp_register(asus->hotplug_slot, bus, 0, "asus-wifi");
+ if (ret) {
+ pr_err("Unable to register hotplug slot - %d\n", ret);
+ goto error_register;
+ }
+
+ return 0;
+
+error_register:
+ kfree(asus->hotplug_slot->info);
+error_info:
+ kfree(asus->hotplug_slot);
+ asus->hotplug_slot = NULL;
+error_slot:
+ destroy_workqueue(asus->hotplug_workqueue);
+error_workqueue:
+ return ret;
+}
+
+/*
+ * Rfkill devices
+ */
+static int asus_rfkill_set(void *data, bool blocked)
+{
+ struct asus_rfkill *priv = data;
+ u32 ctrl_param = !blocked;
+ u32 dev_id = priv->dev_id;
+
+ /*
+ * If the user bit is set, BIOS can't set and record the wlan status,
+ * it will report the value read from id ASUS_WMI_DEVID_WLAN_LED
+ * while we query the wlan status through WMI(ASUS_WMI_DEVID_WLAN).
+ * So, we have to record wlan status in id ASUS_WMI_DEVID_WLAN_LED
+ * while setting the wlan status through WMI.
+ * This is also the behavior that windows app will do.
+ */
+ if ((dev_id == ASUS_WMI_DEVID_WLAN) &&
+ priv->asus->driver->wlan_ctrl_by_user)
+ dev_id = ASUS_WMI_DEVID_WLAN_LED;
+
+ return asus_wmi_set_devstate(dev_id, ctrl_param, NULL);
+}
+
+static void asus_rfkill_query(struct rfkill *rfkill, void *data)
+{
+ struct asus_rfkill *priv = data;
+ int result;
+
+ result = asus_wmi_get_devstate_simple(priv->asus, priv->dev_id);
+
+ if (result < 0)
+ return;
+
+ rfkill_set_sw_state(priv->rfkill, !result);
+}
+
+static int asus_rfkill_wlan_set(void *data, bool blocked)
+{
+ struct asus_rfkill *priv = data;
+ struct asus_wmi *asus = priv->asus;
+ int ret;
+
+ /*
+ * This handler is enabled only if hotplug is enabled.
+ * In this case, the asus_wmi_set_devstate() will
+ * trigger a wmi notification and we need to wait
+ * this call to finish before being able to call
+ * any wmi method
+ */
+ mutex_lock(&asus->wmi_lock);
+ ret = asus_rfkill_set(data, blocked);
+ mutex_unlock(&asus->wmi_lock);
+ return ret;
+}
+
+static const struct rfkill_ops asus_rfkill_wlan_ops = {
+ .set_block = asus_rfkill_wlan_set,
+ .query = asus_rfkill_query,
+};
+
+static const struct rfkill_ops asus_rfkill_ops = {
+ .set_block = asus_rfkill_set,
+ .query = asus_rfkill_query,
+};
+
+static int asus_new_rfkill(struct asus_wmi *asus,
+ struct asus_rfkill *arfkill,
+ const char *name, enum rfkill_type type, int dev_id)
+{
+ int result = asus_wmi_get_devstate_simple(asus, dev_id);
+ struct rfkill **rfkill = &arfkill->rfkill;
+
+ if (result < 0)
+ return result;
+
+ arfkill->dev_id = dev_id;
+ arfkill->asus = asus;
+
+ if (dev_id == ASUS_WMI_DEVID_WLAN &&
+ asus->driver->quirks->hotplug_wireless)
+ *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
+ &asus_rfkill_wlan_ops, arfkill);
+ else
+ *rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
+ &asus_rfkill_ops, arfkill);
+
+ if (!*rfkill)
+ return -EINVAL;
+
+ if (dev_id == ASUS_WMI_DEVID_WLAN)
+ rfkill_set_led_trigger_name(*rfkill, "asus-wlan");
+
+ rfkill_init_sw_state(*rfkill, !result);
+ result = rfkill_register(*rfkill);
+ if (result) {
+ rfkill_destroy(*rfkill);
+ *rfkill = NULL;
+ return result;
+ }
+ return 0;
+}
+
+static void asus_wmi_rfkill_exit(struct asus_wmi *asus)
+{
+ asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P5");
+ asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P6");
+ asus_unregister_rfkill_notifier(asus, "\\_SB.PCI0.P0P7");
+ if (asus->wlan.rfkill) {
+ rfkill_unregister(asus->wlan.rfkill);
+ rfkill_destroy(asus->wlan.rfkill);
+ asus->wlan.rfkill = NULL;
+ }
+ /*
+ * Refresh pci hotplug in case the rfkill state was changed after
+ * asus_unregister_rfkill_notifier()
+ */
+ asus_rfkill_hotplug(asus);
+ if (asus->hotplug_slot)
+ pci_hp_deregister(asus->hotplug_slot);
+ if (asus->hotplug_workqueue)
+ destroy_workqueue(asus->hotplug_workqueue);
+
+ if (asus->bluetooth.rfkill) {
+ rfkill_unregister(asus->bluetooth.rfkill);
+ rfkill_destroy(asus->bluetooth.rfkill);
+ asus->bluetooth.rfkill = NULL;
+ }
+ if (asus->wimax.rfkill) {
+ rfkill_unregister(asus->wimax.rfkill);
+ rfkill_destroy(asus->wimax.rfkill);
+ asus->wimax.rfkill = NULL;
+ }
+ if (asus->wwan3g.rfkill) {
+ rfkill_unregister(asus->wwan3g.rfkill);
+ rfkill_destroy(asus->wwan3g.rfkill);
+ asus->wwan3g.rfkill = NULL;
+ }
+ if (asus->gps.rfkill) {
+ rfkill_unregister(asus->gps.rfkill);
+ rfkill_destroy(asus->gps.rfkill);
+ asus->gps.rfkill = NULL;
+ }
+ if (asus->uwb.rfkill) {
+ rfkill_unregister(asus->uwb.rfkill);
+ rfkill_destroy(asus->uwb.rfkill);
+ asus->uwb.rfkill = NULL;
+ }
+}
+
+static int asus_wmi_rfkill_init(struct asus_wmi *asus)
+{
+ int result = 0;
+
+ mutex_init(&asus->hotplug_lock);
+ mutex_init(&asus->wmi_lock);
+
+ result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan",
+ RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = asus_new_rfkill(asus, &asus->bluetooth,
+ "asus-bluetooth", RFKILL_TYPE_BLUETOOTH,
+ ASUS_WMI_DEVID_BLUETOOTH);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = asus_new_rfkill(asus, &asus->wimax, "asus-wimax",
+ RFKILL_TYPE_WIMAX, ASUS_WMI_DEVID_WIMAX);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = asus_new_rfkill(asus, &asus->wwan3g, "asus-wwan3g",
+ RFKILL_TYPE_WWAN, ASUS_WMI_DEVID_WWAN3G);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = asus_new_rfkill(asus, &asus->gps, "asus-gps",
+ RFKILL_TYPE_GPS, ASUS_WMI_DEVID_GPS);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = asus_new_rfkill(asus, &asus->uwb, "asus-uwb",
+ RFKILL_TYPE_UWB, ASUS_WMI_DEVID_UWB);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ if (!asus->driver->quirks->hotplug_wireless)
+ goto exit;
+
+ result = asus_setup_pci_hotplug(asus);
+ /*
+ * If we get -EBUSY then something else is handling the PCI hotplug -
+ * don't fail in this case
+ */
+ if (result == -EBUSY)
+ result = 0;
+
+ asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P5");
+ asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P6");
+ asus_register_rfkill_notifier(asus, "\\_SB.PCI0.P0P7");
+ /*
+ * Refresh pci hotplug in case the rfkill state was changed during
+ * setup.
+ */
+ asus_rfkill_hotplug(asus);
+
+exit:
+ if (result && result != -ENODEV)
+ asus_wmi_rfkill_exit(asus);
+
+ if (result == -ENODEV)
+ result = 0;
+
+ return result;
+}
+
+/*
+ * Hwmon device
+ */
+static ssize_t asus_hwmon_pwm1(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ u32 value;
+ int err;
+
+ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value);
+
+ if (err < 0)
+ return err;
+
+ value &= 0xFF;
+
+ if (value == 1) /* Low Speed */
+ value = 85;
+ else if (value == 2)
+ value = 170;
+ else if (value == 3)
+ value = 255;
+ else if (value != 0) {
+ pr_err("Unknown fan speed %#x", value);
+ value = -1;
+ }
+
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t asus_hwmon_temp1(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ u32 value;
+ int err;
+
+ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_THERMAL_CTRL, &value);
+
+ if (err < 0)
+ return err;
+
+ value = KELVIN_TO_CELSIUS((value & 0xFFFF)) * 1000;
+
+ return sprintf(buf, "%d\n", value);
+}
+
+static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO, asus_hwmon_pwm1, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL, 0);
+
+static ssize_t
+show_name(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "asus\n");
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+static struct attribute *hwmon_attributes[] = {
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_name.dev_attr.attr,
+ NULL
+};
+
+static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct platform_device *pdev = to_platform_device(dev->parent);
+ struct asus_wmi *asus = platform_get_drvdata(pdev);
+ bool ok = true;
+ int dev_id = -1;
+ u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
+
+ if (attr == &sensor_dev_attr_pwm1.dev_attr.attr)
+ dev_id = ASUS_WMI_DEVID_FAN_CTRL;
+ else if (attr == &sensor_dev_attr_temp1_input.dev_attr.attr)
+ dev_id = ASUS_WMI_DEVID_THERMAL_CTRL;
+
+ if (dev_id != -1) {
+ int err = asus_wmi_get_devstate(asus, dev_id, &value);
+
+ if (err < 0)
+ return 0; /* can't return negative here */
+ }
+
+ if (dev_id == ASUS_WMI_DEVID_FAN_CTRL) {
+ /*
+ * We need to find a better way, probably using sfun,
+ * bits or spec ...
+ * Currently we disable it if:
+ * - ASUS_WMI_UNSUPPORTED_METHOD is returned
+ * - reverved bits are non-zero
+ * - sfun and presence bit are not set
+ */
+ if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000
+ || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT)))
+ ok = false;
+ } else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) {
+ /* If value is zero, something is clearly wrong */
+ if (value == 0)
+ ok = false;
+ }
+
+ return ok ? attr->mode : 0;
+}
+
+static struct attribute_group hwmon_attribute_group = {
+ .is_visible = asus_hwmon_sysfs_is_visible,
+ .attrs = hwmon_attributes
+};
+
+static void asus_wmi_hwmon_exit(struct asus_wmi *asus)
+{
+ struct device *hwmon;
+
+ hwmon = asus->hwmon_device;
+ if (!hwmon)
+ return;
+ sysfs_remove_group(&hwmon->kobj, &hwmon_attribute_group);
+ hwmon_device_unregister(hwmon);
+ asus->hwmon_device = NULL;
+}
+
+static int asus_wmi_hwmon_init(struct asus_wmi *asus)
+{
+ struct device *hwmon;
+ int result;
+
+ hwmon = hwmon_device_register(&asus->platform_device->dev);
+ if (IS_ERR(hwmon)) {
+ pr_err("Could not register asus hwmon device\n");
+ return PTR_ERR(hwmon);
+ }
+ dev_set_drvdata(hwmon, asus);
+ asus->hwmon_device = hwmon;
+ result = sysfs_create_group(&hwmon->kobj, &hwmon_attribute_group);
+ if (result)
+ asus_wmi_hwmon_exit(asus);
+ return result;
+}
+
+/*
+ * Backlight
+ */
+static int read_backlight_power(struct asus_wmi *asus)
+{
+ int ret;
+ if (asus->driver->quirks->store_backlight_power)
+ ret = !asus->driver->panel_power;
+ else
+ ret = asus_wmi_get_devstate_simple(asus,
+ ASUS_WMI_DEVID_BACKLIGHT);
+
+ if (ret < 0)
+ return ret;
+
+ return ret ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+}
+
+static int read_brightness_max(struct asus_wmi *asus)
+{
+ u32 retval;
+ int err;
+
+ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval);
+
+ if (err < 0)
+ return err;
+
+ retval = retval & ASUS_WMI_DSTS_MAX_BRIGTH_MASK;
+ retval >>= 8;
+
+ if (!retval)
+ return -ENODEV;
+
+ return retval;
+}
+
+static int read_brightness(struct backlight_device *bd)
+{
+ struct asus_wmi *asus = bl_get_data(bd);
+ u32 retval;
+ int err;
+
+ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval);
+
+ if (err < 0)
+ return err;
+
+ return retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
+}
+
+static u32 get_scalar_command(struct backlight_device *bd)
+{
+ struct asus_wmi *asus = bl_get_data(bd);
+ u32 ctrl_param = 0;
+
+ if ((asus->driver->brightness < bd->props.brightness) ||
+ bd->props.brightness == bd->props.max_brightness)
+ ctrl_param = 0x00008001;
+ else if ((asus->driver->brightness > bd->props.brightness) ||
+ bd->props.brightness == 0)
+ ctrl_param = 0x00008000;
+
+ asus->driver->brightness = bd->props.brightness;
+
+ return ctrl_param;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+ struct asus_wmi *asus = bl_get_data(bd);
+ u32 ctrl_param;
+ int power, err = 0;
+
+ power = read_backlight_power(asus);
+ if (power != -ENODEV && bd->props.power != power) {
+ ctrl_param = !!(bd->props.power == FB_BLANK_UNBLANK);
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT,
+ ctrl_param, NULL);
+ if (asus->driver->quirks->store_backlight_power)
+ asus->driver->panel_power = bd->props.power;
+
+ /* When using scalar brightness, updating the brightness
+ * will mess with the backlight power */
+ if (asus->driver->quirks->scalar_panel_brightness)
+ return err;
+ }
+
+ if (asus->driver->quirks->scalar_panel_brightness)
+ ctrl_param = get_scalar_command(bd);
+ else
+ ctrl_param = bd->props.brightness;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BRIGHTNESS,
+ ctrl_param, NULL);
+
+ return err;
+}
+
+static const struct backlight_ops asus_wmi_bl_ops = {
+ .get_brightness = read_brightness,
+ .update_status = update_bl_status,
+};
+
+static int asus_wmi_backlight_notify(struct asus_wmi *asus, int code)
+{
+ struct backlight_device *bd = asus->backlight_device;
+ int old = bd->props.brightness;
+ int new = old;
+
+ if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
+ new = code - NOTIFY_BRNUP_MIN + 1;
+ else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX)
+ new = code - NOTIFY_BRNDOWN_MIN;
+
+ bd->props.brightness = new;
+ backlight_update_status(bd);
+ backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
+
+ return old;
+}
+
+static int asus_wmi_backlight_init(struct asus_wmi *asus)
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
+ int max;
+ int power;
+
+ max = read_brightness_max(asus);
+
+ if (max == -ENODEV)
+ max = 0;
+ else if (max < 0)
+ return max;
+
+ power = read_backlight_power(asus);
+
+ if (power == -ENODEV)
+ power = FB_BLANK_UNBLANK;
+ else if (power < 0)
+ return power;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = max;
+ bd = backlight_device_register(asus->driver->name,
+ &asus->platform_device->dev, asus,
+ &asus_wmi_bl_ops, &props);
+ if (IS_ERR(bd)) {
+ pr_err("Could not register backlight device\n");
+ return PTR_ERR(bd);
+ }
+
+ asus->backlight_device = bd;
+
+ if (asus->driver->quirks->store_backlight_power)
+ asus->driver->panel_power = power;
+
+ bd->props.brightness = read_brightness(bd);
+ bd->props.power = power;
+ backlight_update_status(bd);
+
+ asus->driver->brightness = bd->props.brightness;
+
+ return 0;
+}
+
+static void asus_wmi_backlight_exit(struct asus_wmi *asus)
+{
+ if (asus->backlight_device)
+ backlight_device_unregister(asus->backlight_device);
+
+ asus->backlight_device = NULL;
+}
+
+static int is_display_toggle(int code)
+{
+ /* display toggle keys */
+ if ((code >= 0x61 && code <= 0x67) ||
+ (code >= 0x8c && code <= 0x93) ||
+ (code >= 0xa0 && code <= 0xa7) ||
+ (code >= 0xd0 && code <= 0xd5))
+ return 1;
+
+ return 0;
+}
+
+static void asus_wmi_notify(u32 value, void *context)
+{
+ struct asus_wmi *asus = context;
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+ int code;
+ int orig_code;
+ unsigned int key_value = 1;
+ bool autorelease = 1;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_err("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+
+ if (!obj || obj->type != ACPI_TYPE_INTEGER)
+ goto exit;
+
+ code = obj->integer.value;
+ orig_code = code;
+
+ if (asus->driver->key_filter) {
+ asus->driver->key_filter(asus->driver, &code, &key_value,
+ &autorelease);
+ if (code == ASUS_WMI_KEY_IGNORE)
+ goto exit;
+ }
+
+ if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
+ code = ASUS_WMI_BRN_UP;
+ else if (code >= NOTIFY_BRNDOWN_MIN &&
+ code <= NOTIFY_BRNDOWN_MAX)
+ code = ASUS_WMI_BRN_DOWN;
+
+ if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) {
+ if (!acpi_video_backlight_support()) {
+ asus_wmi_backlight_notify(asus, orig_code);
+ goto exit;
+ }
+ }
+
+ if (is_display_toggle(code) &&
+ asus->driver->quirks->no_display_toggle)
+ goto exit;
+
+ if (!sparse_keymap_report_event(asus->inputdev, code,
+ key_value, autorelease))
+ pr_info("Unknown key %x pressed\n", code);
+
+exit:
+ kfree(obj);
+}
+
+/*
+ * Sys helpers
+ */
+static int parse_arg(const char *buf, unsigned long count, int *val)
+{
+ if (!count)
+ return 0;
+ if (sscanf(buf, "%i", val) != 1)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t store_sys_wmi(struct asus_wmi *asus, int devid,
+ const char *buf, size_t count)
+{
+ u32 retval;
+ int rv, err, value;
+
+ value = asus_wmi_get_devstate_simple(asus, devid);
+ if (value == -ENODEV) /* Check device presence */
+ return value;
+
+ rv = parse_arg(buf, count, &value);
+ err = asus_wmi_set_devstate(devid, value, &retval);
+
+ if (err < 0)
+ return err;
+
+ return rv;
+}
+
+static ssize_t show_sys_wmi(struct asus_wmi *asus, int devid, char *buf)
+{
+ int value = asus_wmi_get_devstate_simple(asus, devid);
+
+ if (value < 0)
+ return value;
+
+ return sprintf(buf, "%d\n", value);
+}
+
+#define ASUS_WMI_CREATE_DEVICE_ATTR(_name, _mode, _cm) \
+ static ssize_t show_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ struct asus_wmi *asus = dev_get_drvdata(dev); \
+ \
+ return show_sys_wmi(asus, _cm, buf); \
+ } \
+ static ssize_t store_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ struct asus_wmi *asus = dev_get_drvdata(dev); \
+ \
+ return store_sys_wmi(asus, _cm, buf, count); \
+ } \
+ static struct device_attribute dev_attr_##_name = { \
+ .attr = { \
+ .name = __stringify(_name), \
+ .mode = _mode }, \
+ .show = show_##_name, \
+ .store = store_##_name, \
+ }
+
+ASUS_WMI_CREATE_DEVICE_ATTR(touchpad, 0644, ASUS_WMI_DEVID_TOUCHPAD);
+ASUS_WMI_CREATE_DEVICE_ATTR(camera, 0644, ASUS_WMI_DEVID_CAMERA);
+ASUS_WMI_CREATE_DEVICE_ATTR(cardr, 0644, ASUS_WMI_DEVID_CARDREADER);
+ASUS_WMI_CREATE_DEVICE_ATTR(lid_resume, 0644, ASUS_WMI_DEVID_LID_RESUME);
+
+static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int value, rv;
+
+ if (!count || sscanf(buf, "%i", &value) != 1)
+ return -EINVAL;
+ if (value < 0 || value > 2)
+ return -EINVAL;
+
+ rv = asus_wmi_evaluate_method(ASUS_WMI_METHODID_CFVS, value, 0, NULL);
+ if (rv < 0)
+ return rv;
+
+ return count;
+}
+
+static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv);
+
+static struct attribute *platform_attributes[] = {
+ &dev_attr_cpufv.attr,
+ &dev_attr_camera.attr,
+ &dev_attr_cardr.attr,
+ &dev_attr_touchpad.attr,
+ &dev_attr_lid_resume.attr,
+ NULL
+};
+
+static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct platform_device *pdev = to_platform_device(dev);
+ struct asus_wmi *asus = platform_get_drvdata(pdev);
+ bool ok = true;
+ int devid = -1;
+
+ if (attr == &dev_attr_camera.attr)
+ devid = ASUS_WMI_DEVID_CAMERA;
+ else if (attr == &dev_attr_cardr.attr)
+ devid = ASUS_WMI_DEVID_CARDREADER;
+ else if (attr == &dev_attr_touchpad.attr)
+ devid = ASUS_WMI_DEVID_TOUCHPAD;
+ else if (attr == &dev_attr_lid_resume.attr)
+ devid = ASUS_WMI_DEVID_LID_RESUME;
+
+ if (devid != -1)
+ ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
+
+ return ok ? attr->mode : 0;
+}
+
+static struct attribute_group platform_attribute_group = {
+ .is_visible = asus_sysfs_is_visible,
+ .attrs = platform_attributes
+};
+
+static void asus_wmi_sysfs_exit(struct platform_device *device)
+{
+ sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
+}
+
+static int asus_wmi_sysfs_init(struct platform_device *device)
+{
+ return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
+}
+
+/*
+ * Platform device
+ */
+static int asus_wmi_platform_init(struct asus_wmi *asus)
+{
+ int rv;
+
+ /* INIT enable hotkeys on some models */
+ if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_INIT, 0, 0, &rv))
+ pr_info("Initialization: %#x", rv);
+
+ /* We don't know yet what to do with this version... */
+ if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SPEC, 0, 0x9, &rv)) {
+ pr_info("BIOS WMI version: %d.%d", rv >> 16, rv & 0xFF);
+ asus->spec = rv;
+ }
+
+ /*
+ * The SFUN method probably allows the original driver to get the list
+ * of features supported by a given model. For now, 0x0100 or 0x0800
+ * bit signifies that the laptop is equipped with a Wi-Fi MiniPCI card.
+ * The significance of others is yet to be found.
+ */
+ if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_SFUN, 0, 0, &rv)) {
+ pr_info("SFUN value: %#x", rv);
+ asus->sfun = rv;
+ }
+
+ /*
+ * Eee PC and Notebooks seems to have different method_id for DSTS,
+ * but it may also be related to the BIOS's SPEC.
+ * Note, on most Eeepc, there is no way to check if a method exist
+ * or note, while on notebooks, they returns 0xFFFFFFFE on failure,
+ * but once again, SPEC may probably be used for that kind of things.
+ */
+ if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL))
+ asus->dsts_id = ASUS_WMI_METHODID_DSTS;
+ else
+ asus->dsts_id = ASUS_WMI_METHODID_DSTS2;
+
+ /* CWAP allow to define the behavior of the Fn+F2 key,
+ * this method doesn't seems to be present on Eee PCs */
+ if (asus->driver->quirks->wapf >= 0)
+ asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP,
+ asus->driver->quirks->wapf, NULL);
+
+ return asus_wmi_sysfs_init(asus->platform_device);
+}
+
+static void asus_wmi_platform_exit(struct asus_wmi *asus)
+{
+ asus_wmi_sysfs_exit(asus->platform_device);
+}
+
+/*
+ * debugfs
+ */
+struct asus_wmi_debugfs_node {
+ struct asus_wmi *asus;
+ char *name;
+ int (*show) (struct seq_file *m, void *data);
+};
+
+static int show_dsts(struct seq_file *m, void *data)
+{
+ struct asus_wmi *asus = m->private;
+ int err;
+ u32 retval = -1;
+
+ err = asus_wmi_get_devstate(asus, asus->debug.dev_id, &retval);
+
+ if (err < 0)
+ return err;
+
+ seq_printf(m, "DSTS(%#x) = %#x\n", asus->debug.dev_id, retval);
+
+ return 0;
+}
+
+static int show_devs(struct seq_file *m, void *data)
+{
+ struct asus_wmi *asus = m->private;
+ int err;
+ u32 retval = -1;
+
+ err = asus_wmi_set_devstate(asus->debug.dev_id, asus->debug.ctrl_param,
+ &retval);
+
+ if (err < 0)
+ return err;
+
+ seq_printf(m, "DEVS(%#x, %#x) = %#x\n", asus->debug.dev_id,
+ asus->debug.ctrl_param, retval);
+
+ return 0;
+}
+
+static int show_call(struct seq_file *m, void *data)
+{
+ struct asus_wmi *asus = m->private;
+ struct bios_args args = {
+ .arg0 = asus->debug.dev_id,
+ .arg1 = asus->debug.ctrl_param,
+ };
+ struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID,
+ 1, asus->debug.method_id,
+ &input, &output);
+
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ obj = (union acpi_object *)output.pointer;
+ if (obj && obj->type == ACPI_TYPE_INTEGER)
+ seq_printf(m, "%#x(%#x, %#x) = %#x\n", asus->debug.method_id,
+ asus->debug.dev_id, asus->debug.ctrl_param,
+ (u32) obj->integer.value);
+ else
+ seq_printf(m, "%#x(%#x, %#x) = t:%d\n", asus->debug.method_id,
+ asus->debug.dev_id, asus->debug.ctrl_param,
+ obj ? obj->type : -1);
+
+ kfree(obj);
+
+ return 0;
+}
+
+static struct asus_wmi_debugfs_node asus_wmi_debug_files[] = {
+ {NULL, "devs", show_devs},
+ {NULL, "dsts", show_dsts},
+ {NULL, "call", show_call},
+};
+
+static int asus_wmi_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct asus_wmi_debugfs_node *node = inode->i_private;
+
+ return single_open(file, node->show, node->asus);
+}
+
+static const struct file_operations asus_wmi_debugfs_io_ops = {
+ .owner = THIS_MODULE,
+ .open = asus_wmi_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void asus_wmi_debugfs_exit(struct asus_wmi *asus)
+{
+ debugfs_remove_recursive(asus->debug.root);
+}
+
+static int asus_wmi_debugfs_init(struct asus_wmi *asus)
+{
+ struct dentry *dent;
+ int i;
+
+ asus->debug.root = debugfs_create_dir(asus->driver->name, NULL);
+ if (!asus->debug.root) {
+ pr_err("failed to create debugfs directory");
+ goto error_debugfs;
+ }
+
+ dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR,
+ asus->debug.root, &asus->debug.method_id);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR,
+ asus->debug.root, &asus->debug.dev_id);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR,
+ asus->debug.root, &asus->debug.ctrl_param);
+ if (!dent)
+ goto error_debugfs;
+
+ for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) {
+ struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i];
+
+ node->asus = asus;
+ dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+ asus->debug.root, node,
+ &asus_wmi_debugfs_io_ops);
+ if (!dent) {
+ pr_err("failed to create debug file: %s\n", node->name);
+ goto error_debugfs;
+ }
+ }
+
+ return 0;
+
+error_debugfs:
+ asus_wmi_debugfs_exit(asus);
+ return -ENOMEM;
+}
+
+/*
+ * WMI Driver
+ */
+static int asus_wmi_add(struct platform_device *pdev)
+{
+ struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
+ struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv);
+ struct asus_wmi *asus;
+ acpi_status status;
+ int err;
+ u32 result;
+
+ asus = kzalloc(sizeof(struct asus_wmi), GFP_KERNEL);
+ if (!asus)
+ return -ENOMEM;
+
+ asus->driver = wdrv;
+ asus->platform_device = pdev;
+ wdrv->platform_device = pdev;
+ platform_set_drvdata(asus->platform_device, asus);
+
+ if (wdrv->detect_quirks)
+ wdrv->detect_quirks(asus->driver);
+
+ err = asus_wmi_platform_init(asus);
+ if (err)
+ goto fail_platform;
+
+ err = asus_wmi_input_init(asus);
+ if (err)
+ goto fail_input;
+
+ err = asus_wmi_hwmon_init(asus);
+ if (err)
+ goto fail_hwmon;
+
+ err = asus_wmi_led_init(asus);
+ if (err)
+ goto fail_leds;
+
+ err = asus_wmi_rfkill_init(asus);
+ if (err)
+ goto fail_rfkill;
+
+ if (asus->driver->quirks->wmi_backlight_power)
+ acpi_video_dmi_promote_vendor();
+ if (!acpi_video_backlight_support()) {
+ pr_info("Disabling ACPI video driver\n");
+ acpi_video_unregister();
+ err = asus_wmi_backlight_init(asus);
+ if (err && err != -ENODEV)
+ goto fail_backlight;
+ } else
+ pr_info("Backlight controlled by ACPI video driver\n");
+
+ status = wmi_install_notify_handler(asus->driver->event_guid,
+ asus_wmi_notify, asus);
+ if (ACPI_FAILURE(status)) {
+ pr_err("Unable to register notify handler - %d\n", status);
+ err = -ENODEV;
+ goto fail_wmi_handler;
+ }
+
+ err = asus_wmi_debugfs_init(asus);
+ if (err)
+ goto fail_debugfs;
+
+ asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WLAN, &result);
+ if (result & (ASUS_WMI_DSTS_PRESENCE_BIT | ASUS_WMI_DSTS_USER_BIT))
+ asus->driver->wlan_ctrl_by_user = 1;
+
+ return 0;
+
+fail_debugfs:
+ wmi_remove_notify_handler(asus->driver->event_guid);
+fail_wmi_handler:
+ asus_wmi_backlight_exit(asus);
+fail_backlight:
+ asus_wmi_rfkill_exit(asus);
+fail_rfkill:
+ asus_wmi_led_exit(asus);
+fail_leds:
+ asus_wmi_hwmon_exit(asus);
+fail_hwmon:
+ asus_wmi_input_exit(asus);
+fail_input:
+ asus_wmi_platform_exit(asus);
+fail_platform:
+ kfree(asus);
+ return err;
+}
+
+static int asus_wmi_remove(struct platform_device *device)
+{
+ struct asus_wmi *asus;
+
+ asus = platform_get_drvdata(device);
+ wmi_remove_notify_handler(asus->driver->event_guid);
+ asus_wmi_backlight_exit(asus);
+ asus_wmi_input_exit(asus);
+ asus_wmi_hwmon_exit(asus);
+ asus_wmi_led_exit(asus);
+ asus_wmi_rfkill_exit(asus);
+ asus_wmi_debugfs_exit(asus);
+ asus_wmi_platform_exit(asus);
+
+ kfree(asus);
+ return 0;
+}
+
+/*
+ * Platform driver - hibernate/resume callbacks
+ */
+static int asus_hotk_thaw(struct device *device)
+{
+ struct asus_wmi *asus = dev_get_drvdata(device);
+
+ if (asus->wlan.rfkill) {
+ bool wlan;
+
+ /*
+ * Work around bios bug - acpi _PTS turns off the wireless led
+ * during suspend. Normally it restores it on resume, but
+ * we should kick it ourselves in case hibernation is aborted.
+ */
+ wlan = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WLAN);
+ asus_wmi_set_devstate(ASUS_WMI_DEVID_WLAN, wlan, NULL);
+ }
+
+ return 0;
+}
+
+static int asus_hotk_restore(struct device *device)
+{
+ struct asus_wmi *asus = dev_get_drvdata(device);
+ int bl;
+
+ /* Refresh both wlan rfkill state and pci hotplug */
+ if (asus->wlan.rfkill)
+ asus_rfkill_hotplug(asus);
+
+ if (asus->bluetooth.rfkill) {
+ bl = !asus_wmi_get_devstate_simple(asus,
+ ASUS_WMI_DEVID_BLUETOOTH);
+ rfkill_set_sw_state(asus->bluetooth.rfkill, bl);
+ }
+ if (asus->wimax.rfkill) {
+ bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WIMAX);
+ rfkill_set_sw_state(asus->wimax.rfkill, bl);
+ }
+ if (asus->wwan3g.rfkill) {
+ bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_WWAN3G);
+ rfkill_set_sw_state(asus->wwan3g.rfkill, bl);
+ }
+ if (asus->gps.rfkill) {
+ bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPS);
+ rfkill_set_sw_state(asus->gps.rfkill, bl);
+ }
+ if (asus->uwb.rfkill) {
+ bl = !asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_UWB);
+ rfkill_set_sw_state(asus->uwb.rfkill, bl);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops asus_pm_ops = {
+ .thaw = asus_hotk_thaw,
+ .restore = asus_hotk_restore,
+};
+
+static int asus_wmi_probe(struct platform_device *pdev)
+{
+ struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
+ struct asus_wmi_driver *wdrv = to_asus_wmi_driver(pdrv);
+ int ret;
+
+ if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) {
+ pr_warn("Management GUID not found\n");
+ return -ENODEV;
+ }
+
+ if (wdrv->event_guid && !wmi_has_guid(wdrv->event_guid)) {
+ pr_warn("Event GUID not found\n");
+ return -ENODEV;
+ }
+
+ if (wdrv->probe) {
+ ret = wdrv->probe(pdev);
+ if (ret)
+ return ret;
+ }
+
+ return asus_wmi_add(pdev);
+}
+
+static bool used;
+
+int __init_or_module asus_wmi_register_driver(struct asus_wmi_driver *driver)
+{
+ struct platform_driver *platform_driver;
+ struct platform_device *platform_device;
+
+ if (used)
+ return -EBUSY;
+
+ platform_driver = &driver->platform_driver;
+ platform_driver->remove = asus_wmi_remove;
+ platform_driver->driver.owner = driver->owner;
+ platform_driver->driver.name = driver->name;
+ platform_driver->driver.pm = &asus_pm_ops;
+
+ platform_device = platform_create_bundle(platform_driver,
+ asus_wmi_probe,
+ NULL, 0, NULL, 0);
+ if (IS_ERR(platform_device))
+ return PTR_ERR(platform_device);
+
+ used = true;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_wmi_register_driver);
+
+void asus_wmi_unregister_driver(struct asus_wmi_driver *driver)
+{
+ platform_device_unregister(driver->platform_device);
+ platform_driver_unregister(&driver->platform_driver);
+ used = false;
+}
+EXPORT_SYMBOL_GPL(asus_wmi_unregister_driver);
+
+static int __init asus_wmi_init(void)
+{
+ if (!wmi_has_guid(ASUS_WMI_MGMT_GUID)) {
+ pr_info("Asus Management GUID not found");
+ return -ENODEV;
+ }
+
+ pr_info("ASUS WMI generic driver loaded");
+ return 0;
+}
+
+static void __exit asus_wmi_exit(void)
+{
+ pr_info("ASUS WMI generic driver unloaded");
+}
+
+module_init(asus_wmi_init);
+module_exit(asus_wmi_exit);
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
new file mode 100644
index 00000000..4da4c8ba
--- /dev/null
+++ b/drivers/platform/x86/asus-wmi.h
@@ -0,0 +1,84 @@
+/*
+ * Asus PC WMI hotkey driver
+ *
+ * Copyright(C) 2010 Intel Corporation.
+ * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ * 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
+ */
+
+#ifndef _ASUS_WMI_H_
+#define _ASUS_WMI_H_
+
+#include <linux/platform_device.h>
+
+#define ASUS_WMI_KEY_IGNORE (-1)
+#define ASUS_WMI_BRN_DOWN 0x20
+#define ASUS_WMI_BRN_UP 0x2f
+
+struct module;
+struct key_entry;
+struct asus_wmi;
+
+struct quirk_entry {
+ bool hotplug_wireless;
+ bool scalar_panel_brightness;
+ bool store_backlight_power;
+ bool wmi_backlight_power;
+ int wapf;
+ /*
+ * For machines with AMD graphic chips, it will send out WMI event
+ * and ACPI interrupt at the same time while hitting the hotkey.
+ * To simplify the problem, we just have to ignore the WMI event,
+ * and let the ACPI interrupt to send out the key event.
+ */
+ int no_display_toggle;
+};
+
+struct asus_wmi_driver {
+ int brightness;
+ int panel_power;
+ int wlan_ctrl_by_user;
+
+ const char *name;
+ struct module *owner;
+
+ const char *event_guid;
+
+ const struct key_entry *keymap;
+ const char *input_name;
+ const char *input_phys;
+ struct quirk_entry *quirks;
+ /* Returns new code, value, and autorelease values in arguments.
+ * Return ASUS_WMI_KEY_IGNORE in code if event should be ignored. */
+ void (*key_filter) (struct asus_wmi_driver *driver, int *code,
+ unsigned int *value, bool *autorelease);
+
+ int (*probe) (struct platform_device *device);
+ void (*detect_quirks) (struct asus_wmi_driver *driver);
+
+ struct platform_driver platform_driver;
+ struct platform_device *platform_device;
+};
+
+int asus_wmi_register_driver(struct asus_wmi_driver *driver);
+void asus_wmi_unregister_driver(struct asus_wmi_driver *driver);
+
+#endif /* !_ASUS_WMI_H_ */
diff --git a/drivers/platform/x86/chromeos_laptop.c b/drivers/platform/x86/chromeos_laptop.c
new file mode 100644
index 00000000..3e5b4497
--- /dev/null
+++ b/drivers/platform/x86/chromeos_laptop.c
@@ -0,0 +1,408 @@
+/*
+ * chromeos_laptop.c - Driver to instantiate Chromebook i2c/smbus devices.
+ *
+ * Author : Benson Leung <bleung@chromium.org>
+ *
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * 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/dmi.h>
+#include <linux/i2c.h>
+#include <linux/i2c/atmel_mxt_ts.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#define ATMEL_TP_I2C_ADDR 0x4b
+#define ATMEL_TP_I2C_BL_ADDR 0x25
+#define ATMEL_TS_I2C_ADDR 0x4a
+#define ATMEL_TS_I2C_BL_ADDR 0x26
+#define CYAPA_TP_I2C_ADDR 0x67
+#define ISL_ALS_I2C_ADDR 0x44
+#define TAOS_ALS_I2C_ADDR 0x29
+
+static struct i2c_client *als;
+static struct i2c_client *tp;
+static struct i2c_client *ts;
+
+const char *i2c_adapter_names[] = {
+ "SMBus I801 adapter",
+ "i915 gmbus vga",
+ "i915 gmbus panel",
+};
+
+/* Keep this enum consistent with i2c_adapter_names */
+enum i2c_adapter_type {
+ I2C_ADAPTER_SMBUS = 0,
+ I2C_ADAPTER_VGADDC,
+ I2C_ADAPTER_PANEL,
+};
+
+static struct i2c_board_info __initdata cyapa_device = {
+ I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR),
+ .flags = I2C_CLIENT_WAKE,
+};
+
+static struct i2c_board_info __initdata isl_als_device = {
+ I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR),
+};
+
+static struct i2c_board_info __initdata tsl2583_als_device = {
+ I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR),
+};
+
+static struct i2c_board_info __initdata tsl2563_als_device = {
+ I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR),
+};
+
+static struct mxt_platform_data atmel_224s_tp_platform_data = {
+ .x_line = 18,
+ .y_line = 12,
+ .x_size = 102*20,
+ .y_size = 68*20,
+ .blen = 0x80, /* Gain setting is in upper 4 bits */
+ .threshold = 0x32,
+ .voltage = 0, /* 3.3V */
+ .orient = MXT_VERTICAL_FLIP,
+ .irqflags = IRQF_TRIGGER_FALLING,
+ .is_tp = true,
+ .key_map = { KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ BTN_LEFT },
+ .config = NULL,
+ .config_length = 0,
+};
+
+static struct i2c_board_info __initdata atmel_224s_tp_device = {
+ I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR),
+ .platform_data = &atmel_224s_tp_platform_data,
+ .flags = I2C_CLIENT_WAKE,
+};
+
+static struct mxt_platform_data atmel_1664s_platform_data = {
+ .x_line = 32,
+ .y_line = 50,
+ .x_size = 1700,
+ .y_size = 2560,
+ .blen = 0x89, /* Gain setting is in upper 4 bits */
+ .threshold = 0x28,
+ .voltage = 0, /* 3.3V */
+ .orient = MXT_ROTATED_90_COUNTER,
+ .irqflags = IRQF_TRIGGER_FALLING,
+ .is_tp = false,
+ .config = NULL,
+ .config_length = 0,
+};
+
+static struct i2c_board_info __initdata atmel_1664s_device = {
+ I2C_BOARD_INFO("atmel_mxt_ts", ATMEL_TS_I2C_ADDR),
+ .platform_data = &atmel_1664s_platform_data,
+ .flags = I2C_CLIENT_WAKE,
+};
+
+static struct i2c_client __init *__add_probed_i2c_device(
+ const char *name,
+ int bus,
+ struct i2c_board_info *info,
+ const unsigned short *addrs)
+{
+ const struct dmi_device *dmi_dev;
+ const struct dmi_dev_onboard *dev_data;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+
+ if (bus < 0)
+ return NULL;
+ /*
+ * If a name is specified, look for irq platform information stashed
+ * in DMI_DEV_TYPE_DEV_ONBOARD by the Chrome OS custom system firmware.
+ */
+ if (name) {
+ dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, name, NULL);
+ if (!dmi_dev) {
+ pr_err("%s failed to dmi find device %s.\n",
+ __func__,
+ name);
+ return NULL;
+ }
+ dev_data = (struct dmi_dev_onboard *)dmi_dev->device_data;
+ if (!dev_data) {
+ pr_err("%s failed to get data from dmi for %s.\n",
+ __func__, name);
+ return NULL;
+ }
+ info->irq = dev_data->instance;
+ }
+
+ adapter = i2c_get_adapter(bus);
+ if (!adapter) {
+ pr_err("%s failed to get i2c adapter %d.\n", __func__, bus);
+ return NULL;
+ }
+
+ /* add the i2c device */
+ client = i2c_new_probed_device(adapter, info, addrs, NULL);
+ if (!client)
+ pr_err("%s failed to register device %d-%02x\n",
+ __func__, bus, info->addr);
+ else
+ pr_debug("%s added i2c device %d-%02x\n",
+ __func__, bus, info->addr);
+
+ i2c_put_adapter(adapter);
+ return client;
+}
+
+static int __init __find_i2c_adap(struct device *dev, void *data)
+{
+ const char *name = data;
+ static const char *prefix = "i2c-";
+ struct i2c_adapter *adapter;
+ if (strncmp(dev_name(dev), prefix, strlen(prefix)) != 0)
+ return 0;
+ adapter = to_i2c_adapter(dev);
+ return (strncmp(adapter->name, name, strlen(name)) == 0);
+}
+
+static int __init find_i2c_adapter_num(enum i2c_adapter_type type)
+{
+ struct device *dev = NULL;
+ struct i2c_adapter *adapter;
+ const char *name = i2c_adapter_names[type];
+ /* find the adapter by name */
+ dev = bus_find_device(&i2c_bus_type, NULL, (void *)name,
+ __find_i2c_adap);
+ if (!dev) {
+ pr_err("%s: i2c adapter %s not found on system.\n", __func__,
+ name);
+ return -ENODEV;
+ }
+ adapter = to_i2c_adapter(dev);
+ return adapter->nr;
+}
+
+/*
+ * Takes a list of addresses in addrs as such :
+ * { addr1, ... , addrn, I2C_CLIENT_END };
+ * add_probed_i2c_device will use i2c_new_probed_device
+ * and probe for devices at all of the addresses listed.
+ * Returns NULL if no devices found.
+ * See Documentation/i2c/instantiating-devices for more information.
+ */
+static __init struct i2c_client *add_probed_i2c_device(
+ const char *name,
+ enum i2c_adapter_type type,
+ struct i2c_board_info *info,
+ const unsigned short *addrs)
+{
+ return __add_probed_i2c_device(name,
+ find_i2c_adapter_num(type),
+ info,
+ addrs);
+}
+
+/*
+ * Probes for a device at a single address, the one provided by
+ * info->addr.
+ * Returns NULL if no device found.
+ */
+static __init struct i2c_client *add_i2c_device(const char *name,
+ enum i2c_adapter_type type,
+ struct i2c_board_info *info)
+{
+ const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END };
+ return __add_probed_i2c_device(name,
+ find_i2c_adapter_num(type),
+ info,
+ addr_list);
+}
+
+
+static struct i2c_client __init *add_smbus_device(const char *name,
+ struct i2c_board_info *info)
+{
+ return add_i2c_device(name, I2C_ADAPTER_SMBUS, info);
+}
+
+static int __init setup_cyapa_smbus_tp(const struct dmi_system_id *id)
+{
+ /* add cyapa touchpad on smbus */
+ tp = add_smbus_device("trackpad", &cyapa_device);
+ return 0;
+}
+
+static int __init setup_atmel_224s_tp(const struct dmi_system_id *id)
+{
+ const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR,
+ ATMEL_TP_I2C_ADDR,
+ I2C_CLIENT_END };
+
+ /* add atmel mxt touchpad on VGA DDC GMBus */
+ tp = add_probed_i2c_device("trackpad", I2C_ADAPTER_VGADDC,
+ &atmel_224s_tp_device, addr_list);
+ return 0;
+}
+
+static int __init setup_atmel_1664s_ts(const struct dmi_system_id *id)
+{
+ const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR,
+ ATMEL_TS_I2C_ADDR,
+ I2C_CLIENT_END };
+
+ /* add atmel mxt touch device on PANEL GMBus */
+ ts = add_probed_i2c_device("touchscreen", I2C_ADAPTER_PANEL,
+ &atmel_1664s_device, addr_list);
+ return 0;
+}
+
+
+static int __init setup_isl29018_als(const struct dmi_system_id *id)
+{
+ /* add isl29018 light sensor */
+ als = add_smbus_device("lightsensor", &isl_als_device);
+ return 0;
+}
+
+static int __init setup_isl29023_als(const struct dmi_system_id *id)
+{
+ /* add isl29023 light sensor on Panel GMBus */
+ als = add_i2c_device("lightsensor", I2C_ADAPTER_PANEL,
+ &isl_als_device);
+ return 0;
+}
+
+static int __init setup_tsl2583_als(const struct dmi_system_id *id)
+{
+ /* add tsl2583 light sensor on smbus */
+ als = add_smbus_device(NULL, &tsl2583_als_device);
+ return 0;
+}
+
+static int __init setup_tsl2563_als(const struct dmi_system_id *id)
+{
+ /* add tsl2563 light sensor on smbus */
+ als = add_smbus_device(NULL, &tsl2563_als_device);
+ return 0;
+}
+
+static struct dmi_system_id __initdata chromeos_laptop_dmi_table[] = {
+ {
+ .ident = "Samsung Series 5 550 - Touchpad",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"),
+ },
+ .callback = setup_cyapa_smbus_tp,
+ },
+ {
+ .ident = "Chromebook Pixel - Touchscreen",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+ },
+ .callback = setup_atmel_1664s_ts,
+ },
+ {
+ .ident = "Chromebook Pixel - Touchpad",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+ },
+ .callback = setup_atmel_224s_tp,
+ },
+ {
+ .ident = "Samsung Series 5 550 - Light Sensor",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"),
+ },
+ .callback = setup_isl29018_als,
+ },
+ {
+ .ident = "Chromebook Pixel - Light Sensor",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+ },
+ .callback = setup_isl29023_als,
+ },
+ {
+ .ident = "Acer C7 Chromebook - Touchpad",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Parrot"),
+ },
+ .callback = setup_cyapa_smbus_tp,
+ },
+ {
+ .ident = "HP Pavilion 14 Chromebook - Touchpad",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly"),
+ },
+ .callback = setup_cyapa_smbus_tp,
+ },
+ {
+ .ident = "Samsung Series 5 - Light Sensor",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alex"),
+ },
+ .callback = setup_tsl2583_als,
+ },
+ {
+ .ident = "Cr-48 - Light Sensor",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "Mario"),
+ },
+ .callback = setup_tsl2563_als,
+ },
+ {
+ .ident = "Acer AC700 - Light Sensor",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"),
+ },
+ .callback = setup_tsl2563_als,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table);
+
+static int __init chromeos_laptop_init(void)
+{
+ if (!dmi_check_system(chromeos_laptop_dmi_table)) {
+ pr_debug("%s unsupported system.\n", __func__);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void __exit chromeos_laptop_exit(void)
+{
+ if (als)
+ i2c_unregister_device(als);
+ if (tp)
+ i2c_unregister_device(tp);
+ if (ts)
+ i2c_unregister_device(ts);
+}
+
+module_init(chromeos_laptop_init);
+module_exit(chromeos_laptop_exit);
+
+MODULE_DESCRIPTION("Chrome OS Laptop driver");
+MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c
new file mode 100644
index 00000000..36e5e6c1
--- /dev/null
+++ b/drivers/platform/x86/classmate-laptop.c
@@ -0,0 +1,1172 @@
+/*
+ * Copyright (C) 2009 Thadeu Lima de Souza Cascardo <cascardo@holoscopio.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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/backlight.h>
+#include <linux/input.h>
+#include <linux/rfkill.h>
+
+MODULE_LICENSE("GPL");
+
+
+struct cmpc_accel {
+ int sensitivity;
+ int g_select;
+ int inputdev_state;
+};
+
+#define CMPC_ACCEL_DEV_STATE_CLOSED 0
+#define CMPC_ACCEL_DEV_STATE_OPEN 1
+
+#define CMPC_ACCEL_SENSITIVITY_DEFAULT 5
+#define CMPC_ACCEL_G_SELECT_DEFAULT 0
+
+#define CMPC_ACCEL_HID "ACCE0000"
+#define CMPC_ACCEL_HID_V4 "ACCE0001"
+#define CMPC_TABLET_HID "TBLT0000"
+#define CMPC_IPML_HID "IPML200"
+#define CMPC_KEYS_HID "FNBT0000"
+
+/*
+ * Generic input device code.
+ */
+
+typedef void (*input_device_init)(struct input_dev *dev);
+
+static int cmpc_add_acpi_notify_device(struct acpi_device *acpi, char *name,
+ input_device_init idev_init)
+{
+ struct input_dev *inputdev;
+ int error;
+
+ inputdev = input_allocate_device();
+ if (!inputdev)
+ return -ENOMEM;
+ inputdev->name = name;
+ inputdev->dev.parent = &acpi->dev;
+ idev_init(inputdev);
+ error = input_register_device(inputdev);
+ if (error) {
+ input_free_device(inputdev);
+ return error;
+ }
+ dev_set_drvdata(&acpi->dev, inputdev);
+ return 0;
+}
+
+static int cmpc_remove_acpi_notify_device(struct acpi_device *acpi)
+{
+ struct input_dev *inputdev = dev_get_drvdata(&acpi->dev);
+ input_unregister_device(inputdev);
+ return 0;
+}
+
+/*
+ * Accelerometer code for Classmate V4
+ */
+static acpi_status cmpc_start_accel_v4(acpi_handle handle)
+{
+ union acpi_object param[4];
+ struct acpi_object_list input;
+ acpi_status status;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x3;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = 0;
+ param[2].type = ACPI_TYPE_INTEGER;
+ param[2].integer.value = 0;
+ param[3].type = ACPI_TYPE_INTEGER;
+ param[3].integer.value = 0;
+ input.count = 4;
+ input.pointer = param;
+ status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+ return status;
+}
+
+static acpi_status cmpc_stop_accel_v4(acpi_handle handle)
+{
+ union acpi_object param[4];
+ struct acpi_object_list input;
+ acpi_status status;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x4;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = 0;
+ param[2].type = ACPI_TYPE_INTEGER;
+ param[2].integer.value = 0;
+ param[3].type = ACPI_TYPE_INTEGER;
+ param[3].integer.value = 0;
+ input.count = 4;
+ input.pointer = param;
+ status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+ return status;
+}
+
+static acpi_status cmpc_accel_set_sensitivity_v4(acpi_handle handle, int val)
+{
+ union acpi_object param[4];
+ struct acpi_object_list input;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x02;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = val;
+ param[2].type = ACPI_TYPE_INTEGER;
+ param[2].integer.value = 0;
+ param[3].type = ACPI_TYPE_INTEGER;
+ param[3].integer.value = 0;
+ input.count = 4;
+ input.pointer = param;
+ return acpi_evaluate_object(handle, "ACMD", &input, NULL);
+}
+
+static acpi_status cmpc_accel_set_g_select_v4(acpi_handle handle, int val)
+{
+ union acpi_object param[4];
+ struct acpi_object_list input;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x05;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = val;
+ param[2].type = ACPI_TYPE_INTEGER;
+ param[2].integer.value = 0;
+ param[3].type = ACPI_TYPE_INTEGER;
+ param[3].integer.value = 0;
+ input.count = 4;
+ input.pointer = param;
+ return acpi_evaluate_object(handle, "ACMD", &input, NULL);
+}
+
+static acpi_status cmpc_get_accel_v4(acpi_handle handle,
+ int16_t *x,
+ int16_t *y,
+ int16_t *z)
+{
+ union acpi_object param[4];
+ struct acpi_object_list input;
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ int16_t *locs;
+ acpi_status status;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x01;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = 0;
+ param[2].type = ACPI_TYPE_INTEGER;
+ param[2].integer.value = 0;
+ param[3].type = ACPI_TYPE_INTEGER;
+ param[3].integer.value = 0;
+ input.count = 4;
+ input.pointer = param;
+ status = acpi_evaluate_object(handle, "ACMD", &input, &output);
+ if (ACPI_SUCCESS(status)) {
+ union acpi_object *obj;
+ obj = output.pointer;
+ locs = (int16_t *) obj->buffer.pointer;
+ *x = locs[0];
+ *y = locs[1];
+ *z = locs[2];
+ kfree(output.pointer);
+ }
+ return status;
+}
+
+static void cmpc_accel_handler_v4(struct acpi_device *dev, u32 event)
+{
+ if (event == 0x81) {
+ int16_t x, y, z;
+ acpi_status status;
+
+ status = cmpc_get_accel_v4(dev->handle, &x, &y, &z);
+ if (ACPI_SUCCESS(status)) {
+ struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
+
+ input_report_abs(inputdev, ABS_X, x);
+ input_report_abs(inputdev, ABS_Y, y);
+ input_report_abs(inputdev, ABS_Z, z);
+ input_sync(inputdev);
+ }
+ }
+}
+
+static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ acpi = to_acpi_device(dev);
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ return sprintf(buf, "%d\n", accel->sensitivity);
+}
+
+static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+ unsigned long sensitivity;
+ int r;
+
+ acpi = to_acpi_device(dev);
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ r = kstrtoul(buf, 0, &sensitivity);
+ if (r)
+ return r;
+
+ /* sensitivity must be between 1 and 127 */
+ if (sensitivity < 1 || sensitivity > 127)
+ return -EINVAL;
+
+ accel->sensitivity = sensitivity;
+ cmpc_accel_set_sensitivity_v4(acpi->handle, sensitivity);
+
+ return strnlen(buf, count);
+}
+
+static struct device_attribute cmpc_accel_sensitivity_attr_v4 = {
+ .attr = { .name = "sensitivity", .mode = 0660 },
+ .show = cmpc_accel_sensitivity_show_v4,
+ .store = cmpc_accel_sensitivity_store_v4
+};
+
+static ssize_t cmpc_accel_g_select_show_v4(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ acpi = to_acpi_device(dev);
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ return sprintf(buf, "%d\n", accel->g_select);
+}
+
+static ssize_t cmpc_accel_g_select_store_v4(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+ unsigned long g_select;
+ int r;
+
+ acpi = to_acpi_device(dev);
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ r = kstrtoul(buf, 0, &g_select);
+ if (r)
+ return r;
+
+ /* 0 means 1.5g, 1 means 6g, everything else is wrong */
+ if (g_select != 0 && g_select != 1)
+ return -EINVAL;
+
+ accel->g_select = g_select;
+ cmpc_accel_set_g_select_v4(acpi->handle, g_select);
+
+ return strnlen(buf, count);
+}
+
+static struct device_attribute cmpc_accel_g_select_attr_v4 = {
+ .attr = { .name = "g_select", .mode = 0660 },
+ .show = cmpc_accel_g_select_show_v4,
+ .store = cmpc_accel_g_select_store_v4
+};
+
+static int cmpc_accel_open_v4(struct input_dev *input)
+{
+ struct acpi_device *acpi;
+ struct cmpc_accel *accel;
+
+ acpi = to_acpi_device(input->dev.parent);
+ accel = dev_get_drvdata(&input->dev);
+
+ cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity);
+ cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select);
+
+ if (ACPI_SUCCESS(cmpc_start_accel_v4(acpi->handle))) {
+ accel->inputdev_state = CMPC_ACCEL_DEV_STATE_OPEN;
+ return 0;
+ }
+ return -EIO;
+}
+
+static void cmpc_accel_close_v4(struct input_dev *input)
+{
+ struct acpi_device *acpi;
+ struct cmpc_accel *accel;
+
+ acpi = to_acpi_device(input->dev.parent);
+ accel = dev_get_drvdata(&input->dev);
+
+ cmpc_stop_accel_v4(acpi->handle);
+ accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED;
+}
+
+static void cmpc_accel_idev_init_v4(struct input_dev *inputdev)
+{
+ set_bit(EV_ABS, inputdev->evbit);
+ input_set_abs_params(inputdev, ABS_X, -255, 255, 16, 0);
+ input_set_abs_params(inputdev, ABS_Y, -255, 255, 16, 0);
+ input_set_abs_params(inputdev, ABS_Z, -255, 255, 16, 0);
+ inputdev->open = cmpc_accel_open_v4;
+ inputdev->close = cmpc_accel_close_v4;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cmpc_accel_suspend_v4(struct device *dev)
+{
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ inputdev = dev_get_drvdata(dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN)
+ return cmpc_stop_accel_v4(to_acpi_device(dev)->handle);
+
+ return 0;
+}
+
+static int cmpc_accel_resume_v4(struct device *dev)
+{
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ inputdev = dev_get_drvdata(dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ if (accel->inputdev_state == CMPC_ACCEL_DEV_STATE_OPEN) {
+ cmpc_accel_set_sensitivity_v4(to_acpi_device(dev)->handle,
+ accel->sensitivity);
+ cmpc_accel_set_g_select_v4(to_acpi_device(dev)->handle,
+ accel->g_select);
+
+ if (ACPI_FAILURE(cmpc_start_accel_v4(to_acpi_device(dev)->handle)))
+ return -EIO;
+ }
+
+ return 0;
+}
+#endif
+
+static int cmpc_accel_add_v4(struct acpi_device *acpi)
+{
+ int error;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ accel = kmalloc(sizeof(*accel), GFP_KERNEL);
+ if (!accel)
+ return -ENOMEM;
+
+ accel->inputdev_state = CMPC_ACCEL_DEV_STATE_CLOSED;
+
+ accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT;
+ cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity);
+
+ error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
+ if (error)
+ goto failed_sensitivity;
+
+ accel->g_select = CMPC_ACCEL_G_SELECT_DEFAULT;
+ cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select);
+
+ error = device_create_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
+ if (error)
+ goto failed_g_select;
+
+ error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel_v4",
+ cmpc_accel_idev_init_v4);
+ if (error)
+ goto failed_input;
+
+ inputdev = dev_get_drvdata(&acpi->dev);
+ dev_set_drvdata(&inputdev->dev, accel);
+
+ return 0;
+
+failed_input:
+ device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
+failed_g_select:
+ device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
+failed_sensitivity:
+ kfree(accel);
+ return error;
+}
+
+static int cmpc_accel_remove_v4(struct acpi_device *acpi)
+{
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4);
+ device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4);
+ return cmpc_remove_acpi_notify_device(acpi);
+}
+
+static SIMPLE_DEV_PM_OPS(cmpc_accel_pm, cmpc_accel_suspend_v4,
+ cmpc_accel_resume_v4);
+
+static const struct acpi_device_id cmpc_accel_device_ids_v4[] = {
+ {CMPC_ACCEL_HID_V4, 0},
+ {"", 0}
+};
+
+static struct acpi_driver cmpc_accel_acpi_driver_v4 = {
+ .owner = THIS_MODULE,
+ .name = "cmpc_accel_v4",
+ .class = "cmpc_accel_v4",
+ .ids = cmpc_accel_device_ids_v4,
+ .ops = {
+ .add = cmpc_accel_add_v4,
+ .remove = cmpc_accel_remove_v4,
+ .notify = cmpc_accel_handler_v4,
+ },
+ .drv.pm = &cmpc_accel_pm,
+};
+
+
+/*
+ * Accelerometer code for Classmate versions prior to V4
+ */
+static acpi_status cmpc_start_accel(acpi_handle handle)
+{
+ union acpi_object param[2];
+ struct acpi_object_list input;
+ acpi_status status;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x3;
+ param[1].type = ACPI_TYPE_INTEGER;
+ input.count = 2;
+ input.pointer = param;
+ status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+ return status;
+}
+
+static acpi_status cmpc_stop_accel(acpi_handle handle)
+{
+ union acpi_object param[2];
+ struct acpi_object_list input;
+ acpi_status status;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x4;
+ param[1].type = ACPI_TYPE_INTEGER;
+ input.count = 2;
+ input.pointer = param;
+ status = acpi_evaluate_object(handle, "ACMD", &input, NULL);
+ return status;
+}
+
+static acpi_status cmpc_accel_set_sensitivity(acpi_handle handle, int val)
+{
+ union acpi_object param[2];
+ struct acpi_object_list input;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x02;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = val;
+ input.count = 2;
+ input.pointer = param;
+ return acpi_evaluate_object(handle, "ACMD", &input, NULL);
+}
+
+static acpi_status cmpc_get_accel(acpi_handle handle,
+ unsigned char *x,
+ unsigned char *y,
+ unsigned char *z)
+{
+ union acpi_object param[2];
+ struct acpi_object_list input;
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, 0 };
+ unsigned char *locs;
+ acpi_status status;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0x01;
+ param[1].type = ACPI_TYPE_INTEGER;
+ input.count = 2;
+ input.pointer = param;
+ status = acpi_evaluate_object(handle, "ACMD", &input, &output);
+ if (ACPI_SUCCESS(status)) {
+ union acpi_object *obj;
+ obj = output.pointer;
+ locs = obj->buffer.pointer;
+ *x = locs[0];
+ *y = locs[1];
+ *z = locs[2];
+ kfree(output.pointer);
+ }
+ return status;
+}
+
+static void cmpc_accel_handler(struct acpi_device *dev, u32 event)
+{
+ if (event == 0x81) {
+ unsigned char x, y, z;
+ acpi_status status;
+
+ status = cmpc_get_accel(dev->handle, &x, &y, &z);
+ if (ACPI_SUCCESS(status)) {
+ struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
+
+ input_report_abs(inputdev, ABS_X, x);
+ input_report_abs(inputdev, ABS_Y, y);
+ input_report_abs(inputdev, ABS_Z, z);
+ input_sync(inputdev);
+ }
+ }
+}
+
+static ssize_t cmpc_accel_sensitivity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ acpi = to_acpi_device(dev);
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ return sprintf(buf, "%d\n", accel->sensitivity);
+}
+
+static ssize_t cmpc_accel_sensitivity_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+ unsigned long sensitivity;
+ int r;
+
+ acpi = to_acpi_device(dev);
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ r = strict_strtoul(buf, 0, &sensitivity);
+ if (r)
+ return r;
+
+ accel->sensitivity = sensitivity;
+ cmpc_accel_set_sensitivity(acpi->handle, sensitivity);
+
+ return strnlen(buf, count);
+}
+
+static struct device_attribute cmpc_accel_sensitivity_attr = {
+ .attr = { .name = "sensitivity", .mode = 0660 },
+ .show = cmpc_accel_sensitivity_show,
+ .store = cmpc_accel_sensitivity_store
+};
+
+static int cmpc_accel_open(struct input_dev *input)
+{
+ struct acpi_device *acpi;
+
+ acpi = to_acpi_device(input->dev.parent);
+ if (ACPI_SUCCESS(cmpc_start_accel(acpi->handle)))
+ return 0;
+ return -EIO;
+}
+
+static void cmpc_accel_close(struct input_dev *input)
+{
+ struct acpi_device *acpi;
+
+ acpi = to_acpi_device(input->dev.parent);
+ cmpc_stop_accel(acpi->handle);
+}
+
+static void cmpc_accel_idev_init(struct input_dev *inputdev)
+{
+ set_bit(EV_ABS, inputdev->evbit);
+ input_set_abs_params(inputdev, ABS_X, 0, 255, 8, 0);
+ input_set_abs_params(inputdev, ABS_Y, 0, 255, 8, 0);
+ input_set_abs_params(inputdev, ABS_Z, 0, 255, 8, 0);
+ inputdev->open = cmpc_accel_open;
+ inputdev->close = cmpc_accel_close;
+}
+
+static int cmpc_accel_add(struct acpi_device *acpi)
+{
+ int error;
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ accel = kmalloc(sizeof(*accel), GFP_KERNEL);
+ if (!accel)
+ return -ENOMEM;
+
+ accel->sensitivity = CMPC_ACCEL_SENSITIVITY_DEFAULT;
+ cmpc_accel_set_sensitivity(acpi->handle, accel->sensitivity);
+
+ error = device_create_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
+ if (error)
+ goto failed_file;
+
+ error = cmpc_add_acpi_notify_device(acpi, "cmpc_accel",
+ cmpc_accel_idev_init);
+ if (error)
+ goto failed_input;
+
+ inputdev = dev_get_drvdata(&acpi->dev);
+ dev_set_drvdata(&inputdev->dev, accel);
+
+ return 0;
+
+failed_input:
+ device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
+failed_file:
+ kfree(accel);
+ return error;
+}
+
+static int cmpc_accel_remove(struct acpi_device *acpi)
+{
+ struct input_dev *inputdev;
+ struct cmpc_accel *accel;
+
+ inputdev = dev_get_drvdata(&acpi->dev);
+ accel = dev_get_drvdata(&inputdev->dev);
+
+ device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr);
+ return cmpc_remove_acpi_notify_device(acpi);
+}
+
+static const struct acpi_device_id cmpc_accel_device_ids[] = {
+ {CMPC_ACCEL_HID, 0},
+ {"", 0}
+};
+
+static struct acpi_driver cmpc_accel_acpi_driver = {
+ .owner = THIS_MODULE,
+ .name = "cmpc_accel",
+ .class = "cmpc_accel",
+ .ids = cmpc_accel_device_ids,
+ .ops = {
+ .add = cmpc_accel_add,
+ .remove = cmpc_accel_remove,
+ .notify = cmpc_accel_handler,
+ }
+};
+
+
+/*
+ * Tablet mode code.
+ */
+static acpi_status cmpc_get_tablet(acpi_handle handle,
+ unsigned long long *value)
+{
+ union acpi_object param;
+ struct acpi_object_list input;
+ unsigned long long output;
+ acpi_status status;
+
+ param.type = ACPI_TYPE_INTEGER;
+ param.integer.value = 0x01;
+ input.count = 1;
+ input.pointer = &param;
+ status = acpi_evaluate_integer(handle, "TCMD", &input, &output);
+ if (ACPI_SUCCESS(status))
+ *value = output;
+ return status;
+}
+
+static void cmpc_tablet_handler(struct acpi_device *dev, u32 event)
+{
+ unsigned long long val = 0;
+ struct input_dev *inputdev = dev_get_drvdata(&dev->dev);
+
+ if (event == 0x81) {
+ if (ACPI_SUCCESS(cmpc_get_tablet(dev->handle, &val))) {
+ input_report_switch(inputdev, SW_TABLET_MODE, !val);
+ input_sync(inputdev);
+ }
+ }
+}
+
+static void cmpc_tablet_idev_init(struct input_dev *inputdev)
+{
+ unsigned long long val = 0;
+ struct acpi_device *acpi;
+
+ set_bit(EV_SW, inputdev->evbit);
+ set_bit(SW_TABLET_MODE, inputdev->swbit);
+
+ acpi = to_acpi_device(inputdev->dev.parent);
+ if (ACPI_SUCCESS(cmpc_get_tablet(acpi->handle, &val))) {
+ input_report_switch(inputdev, SW_TABLET_MODE, !val);
+ input_sync(inputdev);
+ }
+}
+
+static int cmpc_tablet_add(struct acpi_device *acpi)
+{
+ return cmpc_add_acpi_notify_device(acpi, "cmpc_tablet",
+ cmpc_tablet_idev_init);
+}
+
+static int cmpc_tablet_remove(struct acpi_device *acpi)
+{
+ return cmpc_remove_acpi_notify_device(acpi);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cmpc_tablet_resume(struct device *dev)
+{
+ struct input_dev *inputdev = dev_get_drvdata(dev);
+
+ unsigned long long val = 0;
+ if (ACPI_SUCCESS(cmpc_get_tablet(to_acpi_device(dev)->handle, &val))) {
+ input_report_switch(inputdev, SW_TABLET_MODE, !val);
+ input_sync(inputdev);
+ }
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(cmpc_tablet_pm, NULL, cmpc_tablet_resume);
+
+static const struct acpi_device_id cmpc_tablet_device_ids[] = {
+ {CMPC_TABLET_HID, 0},
+ {"", 0}
+};
+
+static struct acpi_driver cmpc_tablet_acpi_driver = {
+ .owner = THIS_MODULE,
+ .name = "cmpc_tablet",
+ .class = "cmpc_tablet",
+ .ids = cmpc_tablet_device_ids,
+ .ops = {
+ .add = cmpc_tablet_add,
+ .remove = cmpc_tablet_remove,
+ .notify = cmpc_tablet_handler,
+ },
+ .drv.pm = &cmpc_tablet_pm,
+};
+
+
+/*
+ * Backlight code.
+ */
+
+static acpi_status cmpc_get_brightness(acpi_handle handle,
+ unsigned long long *value)
+{
+ union acpi_object param;
+ struct acpi_object_list input;
+ unsigned long long output;
+ acpi_status status;
+
+ param.type = ACPI_TYPE_INTEGER;
+ param.integer.value = 0xC0;
+ input.count = 1;
+ input.pointer = &param;
+ status = acpi_evaluate_integer(handle, "GRDI", &input, &output);
+ if (ACPI_SUCCESS(status))
+ *value = output;
+ return status;
+}
+
+static acpi_status cmpc_set_brightness(acpi_handle handle,
+ unsigned long long value)
+{
+ union acpi_object param[2];
+ struct acpi_object_list input;
+ acpi_status status;
+ unsigned long long output;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0xC0;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = value;
+ input.count = 2;
+ input.pointer = param;
+ status = acpi_evaluate_integer(handle, "GWRI", &input, &output);
+ return status;
+}
+
+static int cmpc_bl_get_brightness(struct backlight_device *bd)
+{
+ acpi_status status;
+ acpi_handle handle;
+ unsigned long long brightness;
+
+ handle = bl_get_data(bd);
+ status = cmpc_get_brightness(handle, &brightness);
+ if (ACPI_SUCCESS(status))
+ return brightness;
+ else
+ return -1;
+}
+
+static int cmpc_bl_update_status(struct backlight_device *bd)
+{
+ acpi_status status;
+ acpi_handle handle;
+
+ handle = bl_get_data(bd);
+ status = cmpc_set_brightness(handle, bd->props.brightness);
+ if (ACPI_SUCCESS(status))
+ return 0;
+ else
+ return -1;
+}
+
+static const struct backlight_ops cmpc_bl_ops = {
+ .get_brightness = cmpc_bl_get_brightness,
+ .update_status = cmpc_bl_update_status
+};
+
+/*
+ * RFKILL code.
+ */
+
+static acpi_status cmpc_get_rfkill_wlan(acpi_handle handle,
+ unsigned long long *value)
+{
+ union acpi_object param;
+ struct acpi_object_list input;
+ unsigned long long output;
+ acpi_status status;
+
+ param.type = ACPI_TYPE_INTEGER;
+ param.integer.value = 0xC1;
+ input.count = 1;
+ input.pointer = &param;
+ status = acpi_evaluate_integer(handle, "GRDI", &input, &output);
+ if (ACPI_SUCCESS(status))
+ *value = output;
+ return status;
+}
+
+static acpi_status cmpc_set_rfkill_wlan(acpi_handle handle,
+ unsigned long long value)
+{
+ union acpi_object param[2];
+ struct acpi_object_list input;
+ acpi_status status;
+ unsigned long long output;
+
+ param[0].type = ACPI_TYPE_INTEGER;
+ param[0].integer.value = 0xC1;
+ param[1].type = ACPI_TYPE_INTEGER;
+ param[1].integer.value = value;
+ input.count = 2;
+ input.pointer = param;
+ status = acpi_evaluate_integer(handle, "GWRI", &input, &output);
+ return status;
+}
+
+static void cmpc_rfkill_query(struct rfkill *rfkill, void *data)
+{
+ acpi_status status;
+ acpi_handle handle;
+ unsigned long long state;
+ bool blocked;
+
+ handle = data;
+ status = cmpc_get_rfkill_wlan(handle, &state);
+ if (ACPI_SUCCESS(status)) {
+ blocked = state & 1 ? false : true;
+ rfkill_set_sw_state(rfkill, blocked);
+ }
+}
+
+static int cmpc_rfkill_block(void *data, bool blocked)
+{
+ acpi_status status;
+ acpi_handle handle;
+ unsigned long long state;
+ bool is_blocked;
+
+ handle = data;
+ status = cmpc_get_rfkill_wlan(handle, &state);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+ /* Check if we really need to call cmpc_set_rfkill_wlan */
+ is_blocked = state & 1 ? false : true;
+ if (is_blocked != blocked) {
+ state = blocked ? 0 : 1;
+ status = cmpc_set_rfkill_wlan(handle, state);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static const struct rfkill_ops cmpc_rfkill_ops = {
+ .query = cmpc_rfkill_query,
+ .set_block = cmpc_rfkill_block,
+};
+
+/*
+ * Common backlight and rfkill code.
+ */
+
+struct ipml200_dev {
+ struct backlight_device *bd;
+ struct rfkill *rf;
+};
+
+static int cmpc_ipml_add(struct acpi_device *acpi)
+{
+ int retval;
+ struct ipml200_dev *ipml;
+ struct backlight_properties props;
+
+ ipml = kmalloc(sizeof(*ipml), GFP_KERNEL);
+ if (ipml == NULL)
+ return -ENOMEM;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = 7;
+ ipml->bd = backlight_device_register("cmpc_bl", &acpi->dev,
+ acpi->handle, &cmpc_bl_ops,
+ &props);
+ if (IS_ERR(ipml->bd)) {
+ retval = PTR_ERR(ipml->bd);
+ goto out_bd;
+ }
+
+ ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN,
+ &cmpc_rfkill_ops, acpi->handle);
+ /*
+ * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV).
+ * This is OK, however, since all other uses of the device will not
+ * derefence it.
+ */
+ if (ipml->rf) {
+ retval = rfkill_register(ipml->rf);
+ if (retval) {
+ rfkill_destroy(ipml->rf);
+ ipml->rf = NULL;
+ }
+ }
+
+ dev_set_drvdata(&acpi->dev, ipml);
+ return 0;
+
+out_bd:
+ kfree(ipml);
+ return retval;
+}
+
+static int cmpc_ipml_remove(struct acpi_device *acpi)
+{
+ struct ipml200_dev *ipml;
+
+ ipml = dev_get_drvdata(&acpi->dev);
+
+ backlight_device_unregister(ipml->bd);
+
+ if (ipml->rf) {
+ rfkill_unregister(ipml->rf);
+ rfkill_destroy(ipml->rf);
+ }
+
+ kfree(ipml);
+
+ return 0;
+}
+
+static const struct acpi_device_id cmpc_ipml_device_ids[] = {
+ {CMPC_IPML_HID, 0},
+ {"", 0}
+};
+
+static struct acpi_driver cmpc_ipml_acpi_driver = {
+ .owner = THIS_MODULE,
+ .name = "cmpc",
+ .class = "cmpc",
+ .ids = cmpc_ipml_device_ids,
+ .ops = {
+ .add = cmpc_ipml_add,
+ .remove = cmpc_ipml_remove
+ }
+};
+
+
+/*
+ * Extra keys code.
+ */
+static int cmpc_keys_codes[] = {
+ KEY_UNKNOWN,
+ KEY_WLAN,
+ KEY_SWITCHVIDEOMODE,
+ KEY_BRIGHTNESSDOWN,
+ KEY_BRIGHTNESSUP,
+ KEY_VENDOR,
+ KEY_UNKNOWN,
+ KEY_CAMERA,
+ KEY_BACK,
+ KEY_FORWARD,
+ KEY_MAX
+};
+
+static void cmpc_keys_handler(struct acpi_device *dev, u32 event)
+{
+ struct input_dev *inputdev;
+ int code = KEY_MAX;
+
+ if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes))
+ code = cmpc_keys_codes[event & 0x0F];
+ inputdev = dev_get_drvdata(&dev->dev);
+ input_report_key(inputdev, code, !(event & 0x10));
+ input_sync(inputdev);
+}
+
+static void cmpc_keys_idev_init(struct input_dev *inputdev)
+{
+ int i;
+
+ set_bit(EV_KEY, inputdev->evbit);
+ for (i = 0; cmpc_keys_codes[i] != KEY_MAX; i++)
+ set_bit(cmpc_keys_codes[i], inputdev->keybit);
+}
+
+static int cmpc_keys_add(struct acpi_device *acpi)
+{
+ return cmpc_add_acpi_notify_device(acpi, "cmpc_keys",
+ cmpc_keys_idev_init);
+}
+
+static int cmpc_keys_remove(struct acpi_device *acpi)
+{
+ return cmpc_remove_acpi_notify_device(acpi);
+}
+
+static const struct acpi_device_id cmpc_keys_device_ids[] = {
+ {CMPC_KEYS_HID, 0},
+ {"", 0}
+};
+
+static struct acpi_driver cmpc_keys_acpi_driver = {
+ .owner = THIS_MODULE,
+ .name = "cmpc_keys",
+ .class = "cmpc_keys",
+ .ids = cmpc_keys_device_ids,
+ .ops = {
+ .add = cmpc_keys_add,
+ .remove = cmpc_keys_remove,
+ .notify = cmpc_keys_handler,
+ }
+};
+
+
+/*
+ * General init/exit code.
+ */
+
+static int cmpc_init(void)
+{
+ int r;
+
+ r = acpi_bus_register_driver(&cmpc_keys_acpi_driver);
+ if (r)
+ goto failed_keys;
+
+ r = acpi_bus_register_driver(&cmpc_ipml_acpi_driver);
+ if (r)
+ goto failed_bl;
+
+ r = acpi_bus_register_driver(&cmpc_tablet_acpi_driver);
+ if (r)
+ goto failed_tablet;
+
+ r = acpi_bus_register_driver(&cmpc_accel_acpi_driver);
+ if (r)
+ goto failed_accel;
+
+ r = acpi_bus_register_driver(&cmpc_accel_acpi_driver_v4);
+ if (r)
+ goto failed_accel_v4;
+
+ return r;
+
+failed_accel_v4:
+ acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
+
+failed_accel:
+ acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
+
+failed_tablet:
+ acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
+
+failed_bl:
+ acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
+
+failed_keys:
+ return r;
+}
+
+static void cmpc_exit(void)
+{
+ acpi_bus_unregister_driver(&cmpc_accel_acpi_driver_v4);
+ acpi_bus_unregister_driver(&cmpc_accel_acpi_driver);
+ acpi_bus_unregister_driver(&cmpc_tablet_acpi_driver);
+ acpi_bus_unregister_driver(&cmpc_ipml_acpi_driver);
+ acpi_bus_unregister_driver(&cmpc_keys_acpi_driver);
+}
+
+module_init(cmpc_init);
+module_exit(cmpc_exit);
+
+static const struct acpi_device_id cmpc_device_ids[] = {
+ {CMPC_ACCEL_HID, 0},
+ {CMPC_ACCEL_HID_V4, 0},
+ {CMPC_TABLET_HID, 0},
+ {CMPC_IPML_HID, 0},
+ {CMPC_KEYS_HID, 0},
+ {"", 0}
+};
+
+MODULE_DEVICE_TABLE(acpi, cmpc_device_ids);
diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c
new file mode 100644
index 00000000..475cc524
--- /dev/null
+++ b/drivers/platform/x86/compal-laptop.c
@@ -0,0 +1,1100 @@
+/*-*-linux-c-*-*/
+
+/*
+ Copyright (C) 2008 Cezary Jackiewicz <cezary.jackiewicz (at) gmail.com>
+
+ based on MSI driver
+
+ Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
+
+ 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.
+ */
+
+/*
+ * compal-laptop.c - Compal laptop support.
+ *
+ * This driver exports a few files in /sys/devices/platform/compal-laptop/:
+ * wake_up_XXX Whether or not we listen to such wake up events (rw)
+ *
+ * In addition to these platform device attributes the driver
+ * registers itself in the Linux backlight control, power_supply, rfkill
+ * and hwmon subsystem and is available to userspace under:
+ *
+ * /sys/class/backlight/compal-laptop/
+ * /sys/class/power_supply/compal-laptop/
+ * /sys/class/rfkill/rfkillX/
+ * /sys/class/hwmon/hwmonX/
+ *
+ * Notes on the power_supply battery interface:
+ * - the "minimum" design voltage is *the* design voltage
+ * - the ambient temperature is the average battery temperature
+ * and the value is an educated guess (see commented code below)
+ *
+ *
+ * This driver might work on other laptops produced by Compal. If you
+ * want to try it you can pass force=1 as argument to the module which
+ * will force it to load even when the DMI data doesn't identify the
+ * laptop as compatible.
+ *
+ * Lots of data available at:
+ * http://service1.marasst.com/Compal/JHL90_91/Service%20Manual/
+ * JHL90%20service%20manual-Final-0725.pdf
+ *
+ *
+ *
+ * Support for the Compal JHL90 added by Roald Frederickx
+ * (roald.frederickx@gmail.com):
+ * Driver got large revision. Added functionalities: backlight
+ * power, wake_on_XXX, a hwmon and power_supply interface.
+ *
+ * In case this gets merged into the kernel source: I want to dedicate this
+ * to Kasper Meerts, the awesome guy who showed me Linux and C!
+ */
+
+/* NOTE: currently the wake_on_XXX, hwmon and power_supply interfaces are
+ * only enabled on a JHL90 board until it is verified that they work on the
+ * other boards too. See the extra_features variable. */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/power_supply.h>
+#include <linux/fb.h>
+
+
+/* ======= */
+/* Defines */
+/* ======= */
+#define DRIVER_NAME "compal-laptop"
+#define DRIVER_VERSION "0.2.7"
+
+#define BACKLIGHT_LEVEL_ADDR 0xB9
+#define BACKLIGHT_LEVEL_MAX 7
+#define BACKLIGHT_STATE_ADDR 0x59
+#define BACKLIGHT_STATE_ON_DATA 0xE1
+#define BACKLIGHT_STATE_OFF_DATA 0xE2
+
+#define WAKE_UP_ADDR 0xA4
+#define WAKE_UP_PME (1 << 0)
+#define WAKE_UP_MODEM (1 << 1)
+#define WAKE_UP_LAN (1 << 2)
+#define WAKE_UP_WLAN (1 << 4)
+#define WAKE_UP_KEY (1 << 6)
+#define WAKE_UP_MOUSE (1 << 7)
+
+#define WIRELESS_ADDR 0xBB
+#define WIRELESS_WLAN (1 << 0)
+#define WIRELESS_BT (1 << 1)
+#define WIRELESS_WLAN_EXISTS (1 << 2)
+#define WIRELESS_BT_EXISTS (1 << 3)
+#define WIRELESS_KILLSWITCH (1 << 4)
+
+#define PWM_ADDRESS 0x46
+#define PWM_DISABLE_ADDR 0x59
+#define PWM_DISABLE_DATA 0xA5
+#define PWM_ENABLE_ADDR 0x59
+#define PWM_ENABLE_DATA 0xA8
+
+#define FAN_ADDRESS 0x46
+#define FAN_DATA 0x81
+#define FAN_FULL_ON_CMD 0x59 /* Doesn't seem to work. Just */
+#define FAN_FULL_ON_ENABLE 0x76 /* force the pwm signal to its */
+#define FAN_FULL_ON_DISABLE 0x77 /* maximum value instead */
+
+#define TEMP_CPU 0xB0
+#define TEMP_CPU_LOCAL 0xB1
+#define TEMP_CPU_DTS 0xB5
+#define TEMP_NORTHBRIDGE 0xB6
+#define TEMP_VGA 0xB4
+#define TEMP_SKIN 0xB2
+
+#define BAT_MANUFACTURER_NAME_ADDR 0x10
+#define BAT_MANUFACTURER_NAME_LEN 9
+#define BAT_MODEL_NAME_ADDR 0x19
+#define BAT_MODEL_NAME_LEN 6
+#define BAT_SERIAL_NUMBER_ADDR 0xC4
+#define BAT_SERIAL_NUMBER_LEN 5
+#define BAT_CHARGE_NOW 0xC2
+#define BAT_CHARGE_DESIGN 0xCA
+#define BAT_VOLTAGE_NOW 0xC6
+#define BAT_VOLTAGE_DESIGN 0xC8
+#define BAT_CURRENT_NOW 0xD0
+#define BAT_CURRENT_AVG 0xD2
+#define BAT_POWER 0xD4
+#define BAT_CAPACITY 0xCE
+#define BAT_TEMP 0xD6
+#define BAT_TEMP_AVG 0xD7
+#define BAT_STATUS0 0xC1
+#define BAT_STATUS1 0xF0
+#define BAT_STATUS2 0xF1
+#define BAT_STOP_CHARGE1 0xF2
+#define BAT_STOP_CHARGE2 0xF3
+
+#define BAT_S0_DISCHARGE (1 << 0)
+#define BAT_S0_DISCHRG_CRITICAL (1 << 2)
+#define BAT_S0_LOW (1 << 3)
+#define BAT_S0_CHARGING (1 << 1)
+#define BAT_S0_AC (1 << 7)
+#define BAT_S1_EXISTS (1 << 0)
+#define BAT_S1_FULL (1 << 1)
+#define BAT_S1_EMPTY (1 << 2)
+#define BAT_S1_LiION_OR_NiMH (1 << 7)
+#define BAT_S2_LOW_LOW (1 << 0)
+#define BAT_STOP_CHRG1_BAD_CELL (1 << 1)
+#define BAT_STOP_CHRG1_COMM_FAIL (1 << 2)
+#define BAT_STOP_CHRG1_OVERVOLTAGE (1 << 6)
+#define BAT_STOP_CHRG1_OVERTEMPERATURE (1 << 7)
+
+
+/* ======= */
+/* Structs */
+/* ======= */
+struct compal_data{
+ /* Fan control */
+ struct device *hwmon_dev;
+ int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by moterboard */
+ unsigned char curr_pwm;
+
+ /* Power supply */
+ struct power_supply psy;
+ struct power_supply_info psy_info;
+ char bat_model_name[BAT_MODEL_NAME_LEN + 1];
+ char bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN + 1];
+ char bat_serial_number[BAT_SERIAL_NUMBER_LEN + 1];
+};
+
+
+/* =============== */
+/* General globals */
+/* =============== */
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+/* Support for the wake_on_XXX, hwmon and power_supply interface. Currently
+ * only gets enabled on a JHL90 board. Might work with the others too */
+static bool extra_features;
+
+/* Nasty stuff. For some reason the fan control is very un-linear. I've
+ * come up with these values by looping through the possible inputs and
+ * watching the output of address 0x4F (do an ec_transaction writing 0x33
+ * into 0x4F and read a few bytes from the output, like so:
+ * u8 writeData = 0x33;
+ * ec_transaction(0x4F, &writeData, 1, buffer, 32);
+ * That address is labeled "fan1 table information" in the service manual.
+ * It should be clear which value in 'buffer' changes). This seems to be
+ * related to fan speed. It isn't a proper 'realtime' fan speed value
+ * though, because physically stopping or speeding up the fan doesn't
+ * change it. It might be the average voltage or current of the pwm output.
+ * Nevertheless, it is more fine-grained than the actual RPM reading */
+static const unsigned char pwm_lookup_table[256] = {
+ 0, 0, 0, 1, 1, 1, 2, 253, 254, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6,
+ 7, 7, 7, 8, 86, 86, 9, 9, 9, 10, 10, 10, 11, 92, 92, 12, 12, 95,
+ 13, 66, 66, 14, 14, 98, 15, 15, 15, 16, 16, 67, 17, 17, 72, 18, 70,
+ 75, 19, 90, 90, 73, 73, 73, 21, 21, 91, 91, 91, 96, 23, 94, 94, 94,
+ 94, 94, 94, 94, 94, 94, 94, 141, 141, 238, 223, 192, 139, 139, 139,
+ 139, 139, 142, 142, 142, 142, 142, 78, 78, 78, 78, 78, 76, 76, 76,
+ 76, 76, 79, 79, 79, 79, 79, 79, 79, 20, 20, 20, 20, 20, 22, 22, 22,
+ 22, 22, 24, 24, 24, 24, 24, 24, 219, 219, 219, 219, 219, 219, 219,
+ 219, 27, 27, 188, 188, 28, 28, 28, 29, 186, 186, 186, 186, 186,
+ 186, 186, 186, 186, 186, 31, 31, 31, 31, 31, 32, 32, 32, 41, 33,
+ 33, 33, 33, 33, 252, 252, 34, 34, 34, 43, 35, 35, 35, 36, 36, 38,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 37, 37, 37, 46, 46,
+ 47, 47, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 48, 48,
+ 48, 48, 48, 40, 40, 40, 49, 42, 42, 42, 42, 42, 42, 42, 42, 44,
+ 189, 189, 189, 189, 54, 54, 45, 45, 45, 45, 45, 45, 45, 45, 251,
+ 191, 199, 199, 199, 199, 199, 215, 215, 215, 215, 187, 187, 187,
+ 187, 187, 193, 50
+};
+
+
+
+
+/* ========================= */
+/* Hardware access functions */
+/* ========================= */
+/* General access */
+static u8 ec_read_u8(u8 addr)
+{
+ u8 value;
+ ec_read(addr, &value);
+ return value;
+}
+
+static s8 ec_read_s8(u8 addr)
+{
+ return (s8)ec_read_u8(addr);
+}
+
+static u16 ec_read_u16(u8 addr)
+{
+ int hi, lo;
+ lo = ec_read_u8(addr);
+ hi = ec_read_u8(addr + 1);
+ return (hi << 8) + lo;
+}
+
+static s16 ec_read_s16(u8 addr)
+{
+ return (s16) ec_read_u16(addr);
+}
+
+static void ec_read_sequence(u8 addr, u8 *buf, int len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ ec_read(addr + i, buf + i);
+}
+
+
+/* Backlight access */
+static int set_backlight_level(int level)
+{
+ if (level < 0 || level > BACKLIGHT_LEVEL_MAX)
+ return -EINVAL;
+
+ ec_write(BACKLIGHT_LEVEL_ADDR, level);
+
+ return 0;
+}
+
+static int get_backlight_level(void)
+{
+ return (int) ec_read_u8(BACKLIGHT_LEVEL_ADDR);
+}
+
+static void set_backlight_state(bool on)
+{
+ u8 data = on ? BACKLIGHT_STATE_ON_DATA : BACKLIGHT_STATE_OFF_DATA;
+ ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0);
+}
+
+
+/* Fan control access */
+static void pwm_enable_control(void)
+{
+ unsigned char writeData = PWM_ENABLE_DATA;
+ ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0);
+}
+
+static void pwm_disable_control(void)
+{
+ unsigned char writeData = PWM_DISABLE_DATA;
+ ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0);
+}
+
+static void set_pwm(int pwm)
+{
+ ec_transaction(PWM_ADDRESS, &pwm_lookup_table[pwm], 1, NULL, 0);
+}
+
+static int get_fan_rpm(void)
+{
+ u8 value, data = FAN_DATA;
+ ec_transaction(FAN_ADDRESS, &data, 1, &value, 1);
+ return 100 * (int)value;
+}
+
+
+
+
+/* =================== */
+/* Interface functions */
+/* =================== */
+
+/* Backlight interface */
+static int bl_get_brightness(struct backlight_device *b)
+{
+ return get_backlight_level();
+}
+
+static int bl_update_status(struct backlight_device *b)
+{
+ int ret = set_backlight_level(b->props.brightness);
+ if (ret)
+ return ret;
+
+ set_backlight_state((b->props.power == FB_BLANK_UNBLANK)
+ && !(b->props.state & BL_CORE_SUSPENDED)
+ && !(b->props.state & BL_CORE_FBBLANK));
+ return 0;
+}
+
+static const struct backlight_ops compalbl_ops = {
+ .get_brightness = bl_get_brightness,
+ .update_status = bl_update_status,
+};
+
+
+/* Wireless interface */
+static int compal_rfkill_set(void *data, bool blocked)
+{
+ unsigned long radio = (unsigned long) data;
+ u8 result = ec_read_u8(WIRELESS_ADDR);
+ u8 value;
+
+ if (!blocked)
+ value = (u8) (result | radio);
+ else
+ value = (u8) (result & ~radio);
+ ec_write(WIRELESS_ADDR, value);
+
+ return 0;
+}
+
+static void compal_rfkill_poll(struct rfkill *rfkill, void *data)
+{
+ u8 result = ec_read_u8(WIRELESS_ADDR);
+ bool hw_blocked = !(result & WIRELESS_KILLSWITCH);
+ rfkill_set_hw_state(rfkill, hw_blocked);
+}
+
+static const struct rfkill_ops compal_rfkill_ops = {
+ .poll = compal_rfkill_poll,
+ .set_block = compal_rfkill_set,
+};
+
+
+/* Wake_up interface */
+#define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK) \
+static ssize_t NAME##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \
+} \
+static ssize_t NAME##_store(struct device *dev, \
+ struct device_attribute *attr, const char *buf, size_t count) \
+{ \
+ int state; \
+ u8 old_val = ec_read_u8(ADDR); \
+ if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1)) \
+ return -EINVAL; \
+ ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK)); \
+ return count; \
+}
+
+SIMPLE_MASKED_STORE_SHOW(wake_up_pme, WAKE_UP_ADDR, WAKE_UP_PME)
+SIMPLE_MASKED_STORE_SHOW(wake_up_modem, WAKE_UP_ADDR, WAKE_UP_MODEM)
+SIMPLE_MASKED_STORE_SHOW(wake_up_lan, WAKE_UP_ADDR, WAKE_UP_LAN)
+SIMPLE_MASKED_STORE_SHOW(wake_up_wlan, WAKE_UP_ADDR, WAKE_UP_WLAN)
+SIMPLE_MASKED_STORE_SHOW(wake_up_key, WAKE_UP_ADDR, WAKE_UP_KEY)
+SIMPLE_MASKED_STORE_SHOW(wake_up_mouse, WAKE_UP_ADDR, WAKE_UP_MOUSE)
+
+
+/* General hwmon interface */
+static ssize_t hwmon_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s\n", DRIVER_NAME);
+}
+
+
+/* Fan control interface */
+static ssize_t pwm_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct compal_data *data = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", data->pwm_enable);
+}
+
+static ssize_t pwm_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct compal_data *data = dev_get_drvdata(dev);
+ long val;
+ int err;
+ err = strict_strtol(buf, 10, &val);
+ if (err)
+ return err;
+ if (val < 0)
+ return -EINVAL;
+
+ data->pwm_enable = val;
+
+ switch (val) {
+ case 0: /* Full speed */
+ pwm_enable_control();
+ set_pwm(255);
+ break;
+ case 1: /* As set by pwm1 */
+ pwm_enable_control();
+ set_pwm(data->curr_pwm);
+ break;
+ default: /* Control by motherboard */
+ pwm_disable_control();
+ break;
+ }
+
+ return count;
+}
+
+static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct compal_data *data = dev_get_drvdata(dev);
+ return sprintf(buf, "%hhu\n", data->curr_pwm);
+}
+
+static ssize_t pwm_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct compal_data *data = dev_get_drvdata(dev);
+ long val;
+ int err;
+ err = strict_strtol(buf, 10, &val);
+ if (err)
+ return err;
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ data->curr_pwm = val;
+
+ if (data->pwm_enable != 1)
+ return count;
+ set_pwm(val);
+
+ return count;
+}
+
+static ssize_t fan_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", get_fan_rpm());
+}
+
+
+/* Temperature interface */
+#define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL) \
+static ssize_t temp_##POSTFIX(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \
+} \
+static ssize_t label_##POSTFIX(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return sprintf(buf, "%s\n", LABEL); \
+}
+
+/* Labels as in service guide */
+TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu, TEMP_CPU, "CPU_TEMP");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_local, TEMP_CPU_LOCAL, "CPU_TEMP_LOCAL");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_DTS, TEMP_CPU_DTS, "CPU_DTS");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(northbridge,TEMP_NORTHBRIDGE,"NorthBridge");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(vga, TEMP_VGA, "VGA_TEMP");
+TEMPERATURE_SHOW_TEMP_AND_LABEL(SKIN, TEMP_SKIN, "SKIN_TEMP90");
+
+
+/* Power supply interface */
+static int bat_status(void)
+{
+ u8 status0 = ec_read_u8(BAT_STATUS0);
+ u8 status1 = ec_read_u8(BAT_STATUS1);
+
+ if (status0 & BAT_S0_CHARGING)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ if (status0 & BAT_S0_DISCHARGE)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ if (status1 & BAT_S1_FULL)
+ return POWER_SUPPLY_STATUS_FULL;
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static int bat_health(void)
+{
+ u8 status = ec_read_u8(BAT_STOP_CHARGE1);
+
+ if (status & BAT_STOP_CHRG1_OVERTEMPERATURE)
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+ if (status & BAT_STOP_CHRG1_OVERVOLTAGE)
+ return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ if (status & BAT_STOP_CHRG1_BAD_CELL)
+ return POWER_SUPPLY_HEALTH_DEAD;
+ if (status & BAT_STOP_CHRG1_COMM_FAIL)
+ return POWER_SUPPLY_HEALTH_UNKNOWN;
+ return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int bat_is_present(void)
+{
+ u8 status = ec_read_u8(BAT_STATUS2);
+ return ((status & BAT_S1_EXISTS) != 0);
+}
+
+static int bat_technology(void)
+{
+ u8 status = ec_read_u8(BAT_STATUS1);
+
+ if (status & BAT_S1_LiION_OR_NiMH)
+ return POWER_SUPPLY_TECHNOLOGY_LION;
+ return POWER_SUPPLY_TECHNOLOGY_NiMH;
+}
+
+static int bat_capacity_level(void)
+{
+ u8 status0 = ec_read_u8(BAT_STATUS0);
+ u8 status1 = ec_read_u8(BAT_STATUS1);
+ u8 status2 = ec_read_u8(BAT_STATUS2);
+
+ if (status0 & BAT_S0_DISCHRG_CRITICAL
+ || status1 & BAT_S1_EMPTY
+ || status2 & BAT_S2_LOW_LOW)
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ if (status0 & BAT_S0_LOW)
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ if (status1 & BAT_S1_FULL)
+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+}
+
+static int bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct compal_data *data;
+ data = container_of(psy, struct compal_data, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bat_status();
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = bat_health();
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = bat_is_present();
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = bat_technology();
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: /* THE design voltage... */
+ val->intval = ec_read_u16(BAT_VOLTAGE_DESIGN) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = ec_read_u16(BAT_VOLTAGE_NOW) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = ec_read_s16(BAT_CURRENT_NOW) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = ec_read_s16(BAT_CURRENT_AVG) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ val->intval = ec_read_u8(BAT_POWER) * 1000000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = ec_read_u16(BAT_CHARGE_DESIGN) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = ec_read_u16(BAT_CHARGE_NOW) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = ec_read_u8(BAT_CAPACITY);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = bat_capacity_level();
+ break;
+ /* It smees that BAT_TEMP_AVG is a (2's complement?) value showing
+ * the number of degrees, whereas BAT_TEMP is somewhat more
+ * complicated. It looks like this is a negative nember with a
+ * 100/256 divider and an offset of 222. Both were determined
+ * experimentally by comparing BAT_TEMP and BAT_TEMP_AVG. */
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = ((222 - (int)ec_read_u8(BAT_TEMP)) * 1000) >> 8;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT: /* Ambient, Avg, ... same thing */
+ val->intval = ec_read_s8(BAT_TEMP_AVG) * 10;
+ break;
+ /* Neither the model name nor manufacturer name work for me. */
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = data->bat_model_name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = data->bat_manufacturer_name;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = data->bat_serial_number;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+
+
+
+/* ============== */
+/* Driver Globals */
+/* ============== */
+static DEVICE_ATTR(wake_up_pme,
+ 0644, wake_up_pme_show, wake_up_pme_store);
+static DEVICE_ATTR(wake_up_modem,
+ 0644, wake_up_modem_show, wake_up_modem_store);
+static DEVICE_ATTR(wake_up_lan,
+ 0644, wake_up_lan_show, wake_up_lan_store);
+static DEVICE_ATTR(wake_up_wlan,
+ 0644, wake_up_wlan_show, wake_up_wlan_store);
+static DEVICE_ATTR(wake_up_key,
+ 0644, wake_up_key_show, wake_up_key_store);
+static DEVICE_ATTR(wake_up_mouse,
+ 0644, wake_up_mouse_show, wake_up_mouse_store);
+
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, hwmon_name_show, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, fan_show, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp_cpu, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, temp_cpu_local, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, temp_cpu_DTS, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, temp_northbridge, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, temp_vga, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, temp_SKIN, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, label_cpu, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, label_cpu_local, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, label_cpu_DTS, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, label_northbridge, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, label_vga, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp6_label, S_IRUGO, label_SKIN, NULL, 1);
+static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, pwm_show, pwm_store, 1);
+static SENSOR_DEVICE_ATTR(pwm1_enable,
+ S_IRUGO | S_IWUSR, pwm_enable_show, pwm_enable_store, 0);
+
+static struct attribute *compal_attributes[] = {
+ &dev_attr_wake_up_pme.attr,
+ &dev_attr_wake_up_modem.attr,
+ &dev_attr_wake_up_lan.attr,
+ &dev_attr_wake_up_wlan.attr,
+ &dev_attr_wake_up_key.attr,
+ &dev_attr_wake_up_mouse.attr,
+ /* Maybe put the sensor-stuff in a separate hwmon-driver? That way,
+ * the hwmon sysfs won't be cluttered with the above files. */
+ &sensor_dev_attr_name.dev_attr.attr,
+ &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ &sensor_dev_attr_temp3_input.dev_attr.attr,
+ &sensor_dev_attr_temp4_input.dev_attr.attr,
+ &sensor_dev_attr_temp5_input.dev_attr.attr,
+ &sensor_dev_attr_temp6_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ &sensor_dev_attr_temp2_label.dev_attr.attr,
+ &sensor_dev_attr_temp3_label.dev_attr.attr,
+ &sensor_dev_attr_temp4_label.dev_attr.attr,
+ &sensor_dev_attr_temp5_label.dev_attr.attr,
+ &sensor_dev_attr_temp6_label.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group compal_attribute_group = {
+ .attrs = compal_attributes
+};
+
+static int compal_probe(struct platform_device *);
+static int compal_remove(struct platform_device *);
+static struct platform_driver compal_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = compal_probe,
+ .remove = compal_remove,
+};
+
+static enum power_supply_property compal_bat_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_POWER_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_AMBIENT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static struct backlight_device *compalbl_device;
+
+static struct platform_device *compal_device;
+
+static struct rfkill *wifi_rfkill;
+static struct rfkill *bt_rfkill;
+
+
+
+
+
+/* =================================== */
+/* Initialization & clean-up functions */
+/* =================================== */
+
+static int dmi_check_cb(const struct dmi_system_id *id)
+{
+ pr_info("Identified laptop model '%s'\n", id->ident);
+ extra_features = false;
+ return 1;
+}
+
+static int dmi_check_cb_extra(const struct dmi_system_id *id)
+{
+ pr_info("Identified laptop model '%s', enabling extra features\n",
+ id->ident);
+ extra_features = true;
+ return 1;
+}
+
+static struct dmi_system_id __initdata compal_dmi_table[] = {
+ {
+ .ident = "FL90/IFL90",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
+ DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "FL90/IFL90",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "IFL90"),
+ DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "FL91/IFL91",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "IFL91"),
+ DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "FL92/JFL92",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "JFL92"),
+ DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "FT00/IFT00",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "IFT00"),
+ DMI_MATCH(DMI_BOARD_VERSION, "IFT00"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "Dell Mini 9",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "Dell Mini 10",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "Dell Mini 10v",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "Dell Mini 1012",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "Dell Inspiron 11z",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "Dell Mini 12",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"),
+ },
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "JHL90",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "JHL90"),
+ DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
+ },
+ .callback = dmi_check_cb_extra
+ },
+ {
+ .ident = "KHLB2",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "KHLB2"),
+ DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"),
+ },
+ .callback = dmi_check_cb_extra
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, compal_dmi_table);
+
+static void initialize_power_supply_data(struct compal_data *data)
+{
+ data->psy.name = DRIVER_NAME;
+ data->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ data->psy.properties = compal_bat_properties;
+ data->psy.num_properties = ARRAY_SIZE(compal_bat_properties);
+ data->psy.get_property = bat_get_property;
+
+ ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR,
+ data->bat_manufacturer_name,
+ BAT_MANUFACTURER_NAME_LEN);
+ data->bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN] = 0;
+
+ ec_read_sequence(BAT_MODEL_NAME_ADDR,
+ data->bat_model_name,
+ BAT_MODEL_NAME_LEN);
+ data->bat_model_name[BAT_MODEL_NAME_LEN] = 0;
+
+ scnprintf(data->bat_serial_number, BAT_SERIAL_NUMBER_LEN + 1, "%d",
+ ec_read_u16(BAT_SERIAL_NUMBER_ADDR));
+}
+
+static void initialize_fan_control_data(struct compal_data *data)
+{
+ data->pwm_enable = 2; /* Keep motherboard in control for now */
+ data->curr_pwm = 255; /* Try not to cause a CPU_on_fire exception
+ if we take over... */
+}
+
+static int setup_rfkill(void)
+{
+ int ret;
+
+ wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev,
+ RFKILL_TYPE_WLAN, &compal_rfkill_ops,
+ (void *) WIRELESS_WLAN);
+ if (!wifi_rfkill)
+ return -ENOMEM;
+
+ ret = rfkill_register(wifi_rfkill);
+ if (ret)
+ goto err_wifi;
+
+ bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev,
+ RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops,
+ (void *) WIRELESS_BT);
+ if (!bt_rfkill) {
+ ret = -ENOMEM;
+ goto err_allocate_bt;
+ }
+ ret = rfkill_register(bt_rfkill);
+ if (ret)
+ goto err_register_bt;
+
+ return 0;
+
+err_register_bt:
+ rfkill_destroy(bt_rfkill);
+
+err_allocate_bt:
+ rfkill_unregister(wifi_rfkill);
+
+err_wifi:
+ rfkill_destroy(wifi_rfkill);
+
+ return ret;
+}
+
+static int __init compal_init(void)
+{
+ int ret;
+
+ if (acpi_disabled) {
+ pr_err("ACPI needs to be enabled for this driver to work!\n");
+ return -ENODEV;
+ }
+
+ if (!force && !dmi_check_system(compal_dmi_table)) {
+ pr_err("Motherboard not recognized (You could try the module's force-parameter)\n");
+ return -ENODEV;
+ }
+
+ if (!acpi_video_backlight_support()) {
+ struct backlight_properties props;
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = BACKLIGHT_LEVEL_MAX;
+ compalbl_device = backlight_device_register(DRIVER_NAME,
+ NULL, NULL,
+ &compalbl_ops,
+ &props);
+ if (IS_ERR(compalbl_device))
+ return PTR_ERR(compalbl_device);
+ }
+
+ ret = platform_driver_register(&compal_driver);
+ if (ret)
+ goto err_backlight;
+
+ compal_device = platform_device_alloc(DRIVER_NAME, -1);
+ if (!compal_device) {
+ ret = -ENOMEM;
+ goto err_platform_driver;
+ }
+
+ ret = platform_device_add(compal_device); /* This calls compal_probe */
+ if (ret)
+ goto err_platform_device;
+
+ ret = setup_rfkill();
+ if (ret)
+ goto err_rfkill;
+
+ pr_info("Driver " DRIVER_VERSION " successfully loaded\n");
+ return 0;
+
+err_rfkill:
+ platform_device_del(compal_device);
+
+err_platform_device:
+ platform_device_put(compal_device);
+
+err_platform_driver:
+ platform_driver_unregister(&compal_driver);
+
+err_backlight:
+ backlight_device_unregister(compalbl_device);
+
+ return ret;
+}
+
+static int compal_probe(struct platform_device *pdev)
+{
+ int err;
+ struct compal_data *data;
+
+ if (!extra_features)
+ return 0;
+
+ /* Fan control */
+ data = kzalloc(sizeof(struct compal_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ initialize_fan_control_data(data);
+
+ err = sysfs_create_group(&pdev->dev.kobj, &compal_attribute_group);
+ if (err) {
+ kfree(data);
+ return err;
+ }
+
+ data->hwmon_dev = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ sysfs_remove_group(&pdev->dev.kobj,
+ &compal_attribute_group);
+ kfree(data);
+ return err;
+ }
+
+ /* Power supply */
+ initialize_power_supply_data(data);
+ power_supply_register(&compal_device->dev, &data->psy);
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static void __exit compal_cleanup(void)
+{
+ platform_device_unregister(compal_device);
+ platform_driver_unregister(&compal_driver);
+ backlight_device_unregister(compalbl_device);
+ rfkill_unregister(wifi_rfkill);
+ rfkill_unregister(bt_rfkill);
+ rfkill_destroy(wifi_rfkill);
+ rfkill_destroy(bt_rfkill);
+
+ pr_info("Driver unloaded\n");
+}
+
+static int compal_remove(struct platform_device *pdev)
+{
+ struct compal_data *data;
+
+ if (!extra_features)
+ return 0;
+
+ pr_info("Unloading: resetting fan control to motherboard\n");
+ pwm_disable_control();
+
+ data = platform_get_drvdata(pdev);
+ hwmon_device_unregister(data->hwmon_dev);
+ power_supply_unregister(&data->psy);
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(data);
+
+ sysfs_remove_group(&pdev->dev.kobj, &compal_attribute_group);
+
+ return 0;
+}
+
+
+module_init(compal_init);
+module_exit(compal_cleanup);
+
+MODULE_AUTHOR("Cezary Jackiewicz");
+MODULE_AUTHOR("Roald Frederickx (roald.frederickx@gmail.com)");
+MODULE_DESCRIPTION("Compal Laptop Support");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c
new file mode 100644
index 00000000..11341195
--- /dev/null
+++ b/drivers/platform/x86/dell-laptop.c
@@ -0,0 +1,636 @@
+/*
+ * Driver for Dell laptop extras
+ *
+ * Copyright (c) Red Hat <mjg@redhat.com>
+ *
+ * Based on documentation in the libsmbios package, Copyright (C) 2005 Dell
+ * Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/power_supply.h>
+#include <linux/acpi.h>
+#include <linux/mm.h>
+#include <linux/i8042.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include "../../firmware/dcdbas.h"
+
+#define BRIGHTNESS_TOKEN 0x7d
+
+/* This structure will be modified by the firmware when we enter
+ * system management mode, hence the volatiles */
+
+struct calling_interface_buffer {
+ u16 class;
+ u16 select;
+ volatile u32 input[4];
+ volatile u32 output[4];
+} __packed;
+
+struct calling_interface_token {
+ u16 tokenID;
+ u16 location;
+ union {
+ u16 value;
+ u16 stringlength;
+ };
+};
+
+struct calling_interface_structure {
+ struct dmi_header header;
+ u16 cmdIOAddress;
+ u8 cmdIOCode;
+ u32 supportedCmds;
+ struct calling_interface_token tokens[];
+} __packed;
+
+struct quirk_entry {
+ u8 touchpad_led;
+};
+
+static struct quirk_entry *quirks;
+
+static struct quirk_entry quirk_dell_vostro_v130 = {
+ .touchpad_led = 1,
+};
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+ quirks = dmi->driver_data;
+ return 1;
+}
+
+static int da_command_address;
+static int da_command_code;
+static int da_num_tokens;
+static struct calling_interface_token *da_tokens;
+
+static struct platform_driver platform_driver = {
+ .driver = {
+ .name = "dell-laptop",
+ .owner = THIS_MODULE,
+ }
+};
+
+static struct platform_device *platform_device;
+static struct backlight_device *dell_backlight_device;
+
+static const struct dmi_system_id dell_device_table[] __initconst = {
+ {
+ .ident = "Dell laptop",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/
+ },
+ },
+ {
+ .ident = "Dell Computer Corporation",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
+ },
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, dell_device_table);
+
+static struct dmi_system_id dell_quirks[] = {
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro V130",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V130"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro V131",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro 3350",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3350"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro 3555",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron N311z",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron M5110",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro 3360",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro 3460",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3460"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro 3560",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3560"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Vostro 3450",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron 5420",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron 5520",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron 5720",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron 7420",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron 7520",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7520"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "Dell Inspiron 7720",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
+ },
+ .driver_data = &quirk_dell_vostro_v130,
+ },
+ { }
+};
+
+static struct calling_interface_buffer *buffer;
+static struct page *bufferpage;
+static DEFINE_MUTEX(buffer_mutex);
+
+static int hwswitch_state;
+
+static void get_buffer(void)
+{
+ mutex_lock(&buffer_mutex);
+ memset(buffer, 0, sizeof(struct calling_interface_buffer));
+}
+
+static void release_buffer(void)
+{
+ mutex_unlock(&buffer_mutex);
+}
+
+static void __init parse_da_table(const struct dmi_header *dm)
+{
+ /* Final token is a terminator, so we don't want to copy it */
+ int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1;
+ struct calling_interface_token *new_da_tokens;
+ struct calling_interface_structure *table =
+ container_of(dm, struct calling_interface_structure, header);
+
+ /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least
+ 6 bytes of entry */
+
+ if (dm->length < 17)
+ return;
+
+ da_command_address = table->cmdIOAddress;
+ da_command_code = table->cmdIOCode;
+
+ new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
+ sizeof(struct calling_interface_token),
+ GFP_KERNEL);
+
+ if (!new_da_tokens)
+ return;
+ da_tokens = new_da_tokens;
+
+ memcpy(da_tokens+da_num_tokens, table->tokens,
+ sizeof(struct calling_interface_token) * tokens);
+
+ da_num_tokens += tokens;
+}
+
+static void __init find_tokens(const struct dmi_header *dm, void *dummy)
+{
+ switch (dm->type) {
+ case 0xd4: /* Indexed IO */
+ case 0xd5: /* Protected Area Type 1 */
+ case 0xd6: /* Protected Area Type 2 */
+ break;
+ case 0xda: /* Calling interface */
+ parse_da_table(dm);
+ break;
+ }
+}
+
+static int find_token_location(int tokenid)
+{
+ int i;
+ for (i = 0; i < da_num_tokens; i++) {
+ if (da_tokens[i].tokenID == tokenid)
+ return da_tokens[i].location;
+ }
+
+ return -1;
+}
+
+static struct calling_interface_buffer *
+dell_send_request(struct calling_interface_buffer *buffer, int class,
+ int select)
+{
+ struct smi_cmd command;
+
+ command.magic = SMI_CMD_MAGIC;
+ command.command_address = da_command_address;
+ command.command_code = da_command_code;
+ command.ebx = virt_to_phys(buffer);
+ command.ecx = 0x42534931;
+
+ buffer->class = class;
+ buffer->select = select;
+
+ dcdbas_smi_request(&command);
+
+ return buffer;
+}
+
+static struct dentry *dell_laptop_dir;
+
+static int dell_debugfs_show(struct seq_file *s, void *data)
+{
+ int status;
+
+ get_buffer();
+ dell_send_request(buffer, 17, 11);
+ status = buffer->output[1];
+ release_buffer();
+
+ seq_printf(s, "status:\t0x%X\n", status);
+ seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n",
+ status & BIT(0));
+ seq_printf(s, "Bit 1 : Wifi locator supported: %lu\n",
+ (status & BIT(1)) >> 1);
+ seq_printf(s, "Bit 2 : Wifi is supported: %lu\n",
+ (status & BIT(2)) >> 2);
+ seq_printf(s, "Bit 3 : Bluetooth is supported: %lu\n",
+ (status & BIT(3)) >> 3);
+ seq_printf(s, "Bit 4 : WWAN is supported: %lu\n",
+ (status & BIT(4)) >> 4);
+ seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n",
+ (status & BIT(5)) >> 5);
+ seq_printf(s, "Bit 8 : Wifi is installed: %lu\n",
+ (status & BIT(8)) >> 8);
+ seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n",
+ (status & BIT(9)) >> 9);
+ seq_printf(s, "Bit 10: WWAN is installed: %lu\n",
+ (status & BIT(10)) >> 10);
+ seq_printf(s, "Bit 16: Hardware switch is on: %lu\n",
+ (status & BIT(16)) >> 16);
+ seq_printf(s, "Bit 17: Wifi is blocked: %lu\n",
+ (status & BIT(17)) >> 17);
+ seq_printf(s, "Bit 18: Bluetooth is blocked: %lu\n",
+ (status & BIT(18)) >> 18);
+ seq_printf(s, "Bit 19: WWAN is blocked: %lu\n",
+ (status & BIT(19)) >> 19);
+
+ seq_printf(s, "\nhwswitch_state:\t0x%X\n", hwswitch_state);
+ seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n",
+ hwswitch_state & BIT(0));
+ seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n",
+ (hwswitch_state & BIT(1)) >> 1);
+ seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n",
+ (hwswitch_state & BIT(2)) >> 2);
+ seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n",
+ (hwswitch_state & BIT(7)) >> 7);
+ seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n",
+ (hwswitch_state & BIT(8)) >> 8);
+ seq_printf(s, "Bit 15: Wifi locator setting locked: %lu\n",
+ (hwswitch_state & BIT(15)) >> 15);
+
+ return 0;
+}
+
+static int dell_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dell_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations dell_debugfs_fops = {
+ .owner = THIS_MODULE,
+ .open = dell_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int dell_send_intensity(struct backlight_device *bd)
+{
+ int ret = 0;
+
+ get_buffer();
+ buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
+ buffer->input[1] = bd->props.brightness;
+
+ if (buffer->input[0] == -1) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (power_supply_is_system_supplied() > 0)
+ dell_send_request(buffer, 1, 2);
+ else
+ dell_send_request(buffer, 1, 1);
+
+out:
+ release_buffer();
+ return 0;
+}
+
+static int dell_get_intensity(struct backlight_device *bd)
+{
+ int ret = 0;
+
+ get_buffer();
+ buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
+
+ if (buffer->input[0] == -1) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (power_supply_is_system_supplied() > 0)
+ dell_send_request(buffer, 0, 2);
+ else
+ dell_send_request(buffer, 0, 1);
+
+ ret = buffer->output[1];
+
+out:
+ release_buffer();
+ return ret;
+}
+
+static const struct backlight_ops dell_ops = {
+ .get_brightness = dell_get_intensity,
+ .update_status = dell_send_intensity,
+};
+
+static void touchpad_led_on(void)
+{
+ int command = 0x97;
+ char data = 1;
+ i8042_command(&data, command | 1 << 12);
+}
+
+static void touchpad_led_off(void)
+{
+ int command = 0x97;
+ char data = 2;
+ i8042_command(&data, command | 1 << 12);
+}
+
+static void touchpad_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ if (value > 0)
+ touchpad_led_on();
+ else
+ touchpad_led_off();
+}
+
+static struct led_classdev touchpad_led = {
+ .name = "dell-laptop::touchpad",
+ .brightness_set = touchpad_led_set,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static int touchpad_led_init(struct device *dev)
+{
+ return led_classdev_register(dev, &touchpad_led);
+}
+
+static void touchpad_led_exit(void)
+{
+ led_classdev_unregister(&touchpad_led);
+}
+
+static int __init dell_init(void)
+{
+ int max_intensity = 0;
+ int ret;
+
+ if (!dmi_check_system(dell_device_table))
+ return -ENODEV;
+
+ quirks = NULL;
+ /* find if this machine support other functions */
+ dmi_check_system(dell_quirks);
+
+ dmi_walk(find_tokens, NULL);
+
+ if (!da_tokens) {
+ pr_info("Unable to find dmi tokens\n");
+ return -ENODEV;
+ }
+
+ ret = platform_driver_register(&platform_driver);
+ if (ret)
+ goto fail_platform_driver;
+ platform_device = platform_device_alloc("dell-laptop", -1);
+ if (!platform_device) {
+ ret = -ENOMEM;
+ goto fail_platform_device1;
+ }
+ ret = platform_device_add(platform_device);
+ if (ret)
+ goto fail_platform_device2;
+
+ /*
+ * Allocate buffer below 4GB for SMI data--only 32-bit physical addr
+ * is passed to SMI handler.
+ */
+ bufferpage = alloc_page(GFP_KERNEL | GFP_DMA32);
+
+ if (!bufferpage)
+ goto fail_buffer;
+ buffer = page_address(bufferpage);
+
+ if (quirks && quirks->touchpad_led)
+ touchpad_led_init(&platform_device->dev);
+
+ dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL);
+
+#ifdef CONFIG_ACPI
+ /* In the event of an ACPI backlight being available, don't
+ * register the platform controller.
+ */
+ if (acpi_video_backlight_support())
+ return 0;
+#endif
+
+ get_buffer();
+ buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
+ if (buffer->input[0] != -1) {
+ dell_send_request(buffer, 0, 2);
+ max_intensity = buffer->output[3];
+ }
+ release_buffer();
+
+ if (max_intensity) {
+ struct backlight_properties props;
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = max_intensity;
+ dell_backlight_device = backlight_device_register("dell_backlight",
+ &platform_device->dev,
+ NULL,
+ &dell_ops,
+ &props);
+
+ if (IS_ERR(dell_backlight_device)) {
+ ret = PTR_ERR(dell_backlight_device);
+ dell_backlight_device = NULL;
+ goto fail_backlight;
+ }
+
+ dell_backlight_device->props.brightness =
+ dell_get_intensity(dell_backlight_device);
+ backlight_update_status(dell_backlight_device);
+ }
+
+ return 0;
+
+fail_backlight:
+ free_page((unsigned long)bufferpage);
+fail_buffer:
+ platform_device_del(platform_device);
+fail_platform_device2:
+ platform_device_put(platform_device);
+fail_platform_device1:
+ platform_driver_unregister(&platform_driver);
+fail_platform_driver:
+ kfree(da_tokens);
+ return ret;
+}
+
+static void __exit dell_exit(void)
+{
+ debugfs_remove_recursive(dell_laptop_dir);
+ if (quirks && quirks->touchpad_led)
+ touchpad_led_exit();
+ backlight_device_unregister(dell_backlight_device);
+ if (platform_device) {
+ platform_device_unregister(platform_device);
+ platform_driver_unregister(&platform_driver);
+ }
+ kfree(da_tokens);
+ free_page((unsigned long)buffer);
+}
+
+module_init(dell_init);
+module_exit(dell_exit);
+
+MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
+MODULE_DESCRIPTION("Dell laptop driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c
new file mode 100644
index 00000000..bcf8cc6b
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi-aio.c
@@ -0,0 +1,215 @@
+/*
+ * WMI hotkeys support for Dell All-In-One series
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/acpi.h>
+#include <linux/string.h>
+
+MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series");
+MODULE_LICENSE("GPL");
+
+#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4"
+#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8"
+
+struct dell_wmi_event {
+ u16 length;
+ /* 0x000: A hot key pressed or an event occurred
+ * 0x00F: A sequence of hot keys are pressed */
+ u16 type;
+ u16 event[];
+};
+
+static const char *dell_wmi_aio_guids[] = {
+ EVENT_GUID1,
+ EVENT_GUID2,
+ NULL
+};
+
+MODULE_ALIAS("wmi:"EVENT_GUID1);
+MODULE_ALIAS("wmi:"EVENT_GUID2);
+
+static const struct key_entry dell_wmi_aio_keymap[] = {
+ { KE_KEY, 0xc0, { KEY_VOLUMEUP } },
+ { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 0xe030, { KEY_VOLUMEUP } },
+ { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 0xe020, { KEY_MUTE } },
+ { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } },
+ { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
+ { KE_END, 0 }
+};
+
+static struct input_dev *dell_wmi_aio_input_dev;
+
+/*
+ * The new WMI event data format will follow the dell_wmi_event structure
+ * So, we will check if the buffer matches the format
+ */
+static bool dell_wmi_aio_event_check(u8 *buffer, int length)
+{
+ struct dell_wmi_event *event = (struct dell_wmi_event *)buffer;
+
+ if (event == NULL || length < 6)
+ return false;
+
+ if ((event->type == 0 || event->type == 0xf) &&
+ event->length >= 2)
+ return true;
+
+ return false;
+}
+
+static void dell_wmi_aio_notify(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ struct dell_wmi_event *event;
+ acpi_status status;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_info("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+ if (obj) {
+ unsigned int scancode = 0;
+
+ switch (obj->type) {
+ case ACPI_TYPE_INTEGER:
+ /* Most All-In-One correctly return integer scancode */
+ scancode = obj->integer.value;
+ sparse_keymap_report_event(dell_wmi_aio_input_dev,
+ scancode, 1, true);
+ break;
+ case ACPI_TYPE_BUFFER:
+ if (dell_wmi_aio_event_check(obj->buffer.pointer,
+ obj->buffer.length)) {
+ event = (struct dell_wmi_event *)
+ obj->buffer.pointer;
+ scancode = event->event[0];
+ } else {
+ /* Broken machines return the scancode in a
+ buffer */
+ if (obj->buffer.pointer &&
+ obj->buffer.length > 0)
+ scancode = obj->buffer.pointer[0];
+ }
+ if (scancode)
+ sparse_keymap_report_event(
+ dell_wmi_aio_input_dev,
+ scancode, 1, true);
+ break;
+ }
+ }
+ kfree(obj);
+}
+
+static int __init dell_wmi_aio_input_setup(void)
+{
+ int err;
+
+ dell_wmi_aio_input_dev = input_allocate_device();
+
+ if (!dell_wmi_aio_input_dev)
+ return -ENOMEM;
+
+ dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys";
+ dell_wmi_aio_input_dev->phys = "wmi/input0";
+ dell_wmi_aio_input_dev->id.bustype = BUS_HOST;
+
+ err = sparse_keymap_setup(dell_wmi_aio_input_dev,
+ dell_wmi_aio_keymap, NULL);
+ if (err) {
+ pr_err("Unable to setup input device keymap\n");
+ goto err_free_dev;
+ }
+ err = input_register_device(dell_wmi_aio_input_dev);
+ if (err) {
+ pr_info("Unable to register input device\n");
+ goto err_free_keymap;
+ }
+ return 0;
+
+err_free_keymap:
+ sparse_keymap_free(dell_wmi_aio_input_dev);
+err_free_dev:
+ input_free_device(dell_wmi_aio_input_dev);
+ return err;
+}
+
+static const char *dell_wmi_aio_find(void)
+{
+ int i;
+
+ for (i = 0; dell_wmi_aio_guids[i] != NULL; i++)
+ if (wmi_has_guid(dell_wmi_aio_guids[i]))
+ return dell_wmi_aio_guids[i];
+
+ return NULL;
+}
+
+static int __init dell_wmi_aio_init(void)
+{
+ int err;
+ const char *guid;
+
+ guid = dell_wmi_aio_find();
+ if (!guid) {
+ pr_warn("No known WMI GUID found\n");
+ return -ENXIO;
+ }
+
+ err = dell_wmi_aio_input_setup();
+ if (err)
+ return err;
+
+ err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL);
+ if (err) {
+ pr_err("Unable to register notify handler - %d\n", err);
+ sparse_keymap_free(dell_wmi_aio_input_dev);
+ input_unregister_device(dell_wmi_aio_input_dev);
+ return err;
+ }
+
+ return 0;
+}
+
+static void __exit dell_wmi_aio_exit(void)
+{
+ const char *guid;
+
+ guid = dell_wmi_aio_find();
+ wmi_remove_notify_handler(guid);
+ sparse_keymap_free(dell_wmi_aio_input_dev);
+ input_unregister_device(dell_wmi_aio_input_dev);
+}
+
+module_init(dell_wmi_aio_init);
+module_exit(dell_wmi_aio_exit);
diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c
new file mode 100644
index 00000000..b264d8fe
--- /dev/null
+++ b/drivers/platform/x86/dell-wmi.c
@@ -0,0 +1,323 @@
+/*
+ * Dell WMI hotkeys
+ *
+ * Copyright (C) 2008 Red Hat <mjg@redhat.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/acpi.h>
+#include <linux/string.h>
+#include <linux/dmi.h>
+
+MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
+MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492"
+
+static int acpi_video;
+
+MODULE_ALIAS("wmi:"DELL_EVENT_GUID);
+
+/*
+ * Certain keys are flagged as KE_IGNORE. All of these are either
+ * notifications (rather than requests for change) or are also sent
+ * via the keyboard controller so should not be sent again.
+ */
+
+static const struct key_entry dell_wmi_legacy_keymap[] __initconst = {
+ { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } },
+
+ { KE_KEY, 0xe045, { KEY_PROG1 } },
+ { KE_KEY, 0xe009, { KEY_EJECTCD } },
+
+ /* These also contain the brightness level at offset 6 */
+ { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } },
+
+ /* Battery health status button */
+ { KE_KEY, 0xe007, { KEY_BATTERY } },
+
+ /* This is actually for all radios. Although physically a
+ * switch, the notification does not provide an indication of
+ * state and so it should be reported as a key */
+ { KE_KEY, 0xe008, { KEY_WLAN } },
+
+ /* The next device is at offset 6, the active devices are at
+ offset 8 and the attached devices at offset 10 */
+ { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } },
+
+ { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } },
+
+ /* BIOS error detected */
+ { KE_IGNORE, 0xe00d, { KEY_RESERVED } },
+
+ /* Wifi Catcher */
+ { KE_KEY, 0xe011, {KEY_PROG2 } },
+
+ /* Ambient light sensor toggle */
+ { KE_IGNORE, 0xe013, { KEY_RESERVED } },
+
+ { KE_IGNORE, 0xe020, { KEY_MUTE } },
+
+ /* Shortcut and audio panel keys */
+ { KE_IGNORE, 0xe025, { KEY_RESERVED } },
+ { KE_IGNORE, 0xe026, { KEY_RESERVED } },
+
+ { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } },
+ { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } },
+ { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } },
+ { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } },
+ { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } },
+ { KE_IGNORE, 0xe045, { KEY_NUMLOCK } },
+ { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } },
+ { KE_IGNORE, 0xe0f7, { KEY_MUTE } },
+ { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } },
+ { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } },
+ { KE_END, 0 }
+};
+
+static bool dell_new_hk_type;
+
+struct dell_bios_keymap_entry {
+ u16 scancode;
+ u16 keycode;
+};
+
+struct dell_bios_hotkey_table {
+ struct dmi_header header;
+ struct dell_bios_keymap_entry keymap[];
+
+};
+
+static const struct dell_bios_hotkey_table *dell_bios_hotkey_table;
+
+static const u16 bios_to_linux_keycode[256] __initconst = {
+
+ KEY_MEDIA, KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG,
+ KEY_STOPCD, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+ KEY_WWW, KEY_UNKNOWN, KEY_VOLUMEDOWN, KEY_MUTE,
+ KEY_VOLUMEUP, KEY_UNKNOWN, KEY_BATTERY, KEY_EJECTCD,
+ KEY_UNKNOWN, KEY_SLEEP, KEY_PROG1, KEY_BRIGHTNESSDOWN,
+ KEY_BRIGHTNESSUP, KEY_UNKNOWN, KEY_KBDILLUMTOGGLE,
+ KEY_UNKNOWN, KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN,
+ KEY_SWITCHVIDEOMODE, KEY_UNKNOWN, KEY_UNKNOWN, KEY_PROG2,
+ KEY_UNKNOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ KEY_PROG3
+};
+
+static struct input_dev *dell_wmi_input_dev;
+
+static void dell_wmi_notify(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_info("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+
+ if (obj && obj->type == ACPI_TYPE_BUFFER) {
+ const struct key_entry *key;
+ int reported_key;
+ u16 *buffer_entry = (u16 *)obj->buffer.pointer;
+ int buffer_size = obj->buffer.length/2;
+
+ if (buffer_size >= 2 && dell_new_hk_type && buffer_entry[1] != 0x10) {
+ pr_info("Received unknown WMI event (0x%x)\n",
+ buffer_entry[1]);
+ kfree(obj);
+ return;
+ }
+
+ if (buffer_size >= 3 && (dell_new_hk_type || buffer_entry[1] == 0x0))
+ reported_key = (int)buffer_entry[2];
+ else if (buffer_size >= 2)
+ reported_key = (int)buffer_entry[1] & 0xffff;
+ else {
+ pr_info("Received unknown WMI event\n");
+ kfree(obj);
+ return;
+ }
+
+ key = sparse_keymap_entry_from_scancode(dell_wmi_input_dev,
+ reported_key);
+ if (!key) {
+ pr_info("Unknown key %x pressed\n", reported_key);
+ } else if ((key->keycode == KEY_BRIGHTNESSUP ||
+ key->keycode == KEY_BRIGHTNESSDOWN) && acpi_video) {
+ /* Don't report brightness notifications that will also
+ * come via ACPI */
+ ;
+ } else {
+ sparse_keymap_report_entry(dell_wmi_input_dev, key,
+ 1, true);
+ }
+ }
+ kfree(obj);
+}
+
+static const struct key_entry * __init dell_wmi_prepare_new_keymap(void)
+{
+ int hotkey_num = (dell_bios_hotkey_table->header.length - 4) /
+ sizeof(struct dell_bios_keymap_entry);
+ struct key_entry *keymap;
+ int i;
+
+ keymap = kcalloc(hotkey_num + 1, sizeof(struct key_entry), GFP_KERNEL);
+ if (!keymap)
+ return NULL;
+
+ for (i = 0; i < hotkey_num; i++) {
+ const struct dell_bios_keymap_entry *bios_entry =
+ &dell_bios_hotkey_table->keymap[i];
+ keymap[i].type = KE_KEY;
+ keymap[i].code = bios_entry->scancode;
+ keymap[i].keycode = bios_entry->keycode < 256 ?
+ bios_to_linux_keycode[bios_entry->keycode] :
+ KEY_RESERVED;
+ }
+
+ keymap[hotkey_num].type = KE_END;
+
+ return keymap;
+}
+
+static int __init dell_wmi_input_setup(void)
+{
+ int err;
+
+ dell_wmi_input_dev = input_allocate_device();
+ if (!dell_wmi_input_dev)
+ return -ENOMEM;
+
+ dell_wmi_input_dev->name = "Dell WMI hotkeys";
+ dell_wmi_input_dev->phys = "wmi/input0";
+ dell_wmi_input_dev->id.bustype = BUS_HOST;
+
+ if (dell_new_hk_type) {
+ const struct key_entry *keymap = dell_wmi_prepare_new_keymap();
+ if (!keymap) {
+ err = -ENOMEM;
+ goto err_free_dev;
+ }
+
+ err = sparse_keymap_setup(dell_wmi_input_dev, keymap, NULL);
+
+ /*
+ * Sparse keymap library makes a copy of keymap so we
+ * don't need the original one that was allocated.
+ */
+ kfree(keymap);
+ } else {
+ err = sparse_keymap_setup(dell_wmi_input_dev,
+ dell_wmi_legacy_keymap, NULL);
+ }
+ if (err)
+ goto err_free_dev;
+
+ err = input_register_device(dell_wmi_input_dev);
+ if (err)
+ goto err_free_keymap;
+
+ return 0;
+
+ err_free_keymap:
+ sparse_keymap_free(dell_wmi_input_dev);
+ err_free_dev:
+ input_free_device(dell_wmi_input_dev);
+ return err;
+}
+
+static void dell_wmi_input_destroy(void)
+{
+ sparse_keymap_free(dell_wmi_input_dev);
+ input_unregister_device(dell_wmi_input_dev);
+}
+
+static void __init find_hk_type(const struct dmi_header *dm, void *dummy)
+{
+ if (dm->type == 0xb2 && dm->length > 6) {
+ dell_new_hk_type = true;
+ dell_bios_hotkey_table =
+ container_of(dm, struct dell_bios_hotkey_table, header);
+ }
+}
+
+static int __init dell_wmi_init(void)
+{
+ int err;
+ acpi_status status;
+
+ if (!wmi_has_guid(DELL_EVENT_GUID)) {
+ pr_warn("No known WMI GUID found\n");
+ return -ENODEV;
+ }
+
+ dmi_walk(find_hk_type, NULL);
+ acpi_video = acpi_video_backlight_support();
+
+ err = dell_wmi_input_setup();
+ if (err)
+ return err;
+
+ status = wmi_install_notify_handler(DELL_EVENT_GUID,
+ dell_wmi_notify, NULL);
+ if (ACPI_FAILURE(status)) {
+ dell_wmi_input_destroy();
+ pr_err("Unable to register notify handler - %d\n", status);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+module_init(dell_wmi_init);
+
+static void __exit dell_wmi_exit(void)
+{
+ wmi_remove_notify_handler(DELL_EVENT_GUID);
+ dell_wmi_input_destroy();
+}
+module_exit(dell_wmi_exit);
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
new file mode 100644
index 00000000..5d26e70b
--- /dev/null
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -0,0 +1,1573 @@
+/*
+ * eeepc-laptop.c - Asus Eee PC extras
+ *
+ * Based on asus_acpi.c as patched for the Eee PC by Asus:
+ * ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar
+ * Based on eee.c from eeepc-linux
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/slab.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/rfkill.h>
+#include <linux/pci.h>
+#include <linux/pci_hotplug.h>
+#include <linux/leds.h>
+#include <linux/dmi.h>
+
+#define EEEPC_LAPTOP_VERSION "0.1"
+#define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver"
+#define EEEPC_LAPTOP_FILE "eeepc"
+
+#define EEEPC_ACPI_CLASS "hotkey"
+#define EEEPC_ACPI_DEVICE_NAME "Hotkey"
+#define EEEPC_ACPI_HID "ASUS010"
+
+MODULE_AUTHOR("Corentin Chary, Eric Cooper");
+MODULE_DESCRIPTION(EEEPC_LAPTOP_NAME);
+MODULE_LICENSE("GPL");
+
+static bool hotplug_disabled;
+
+module_param(hotplug_disabled, bool, 0444);
+MODULE_PARM_DESC(hotplug_disabled,
+ "Disable hotplug for wireless device. "
+ "If your laptop need that, please report to "
+ "acpi4asus-user@lists.sourceforge.net.");
+
+/*
+ * Definitions for Asus EeePC
+ */
+#define NOTIFY_BRN_MIN 0x20
+#define NOTIFY_BRN_MAX 0x2f
+
+enum {
+ DISABLE_ASL_WLAN = 0x0001,
+ DISABLE_ASL_BLUETOOTH = 0x0002,
+ DISABLE_ASL_IRDA = 0x0004,
+ DISABLE_ASL_CAMERA = 0x0008,
+ DISABLE_ASL_TV = 0x0010,
+ DISABLE_ASL_GPS = 0x0020,
+ DISABLE_ASL_DISPLAYSWITCH = 0x0040,
+ DISABLE_ASL_MODEM = 0x0080,
+ DISABLE_ASL_CARDREADER = 0x0100,
+ DISABLE_ASL_3G = 0x0200,
+ DISABLE_ASL_WIMAX = 0x0400,
+ DISABLE_ASL_HWCF = 0x0800
+};
+
+enum {
+ CM_ASL_WLAN = 0,
+ CM_ASL_BLUETOOTH,
+ CM_ASL_IRDA,
+ CM_ASL_1394,
+ CM_ASL_CAMERA,
+ CM_ASL_TV,
+ CM_ASL_GPS,
+ CM_ASL_DVDROM,
+ CM_ASL_DISPLAYSWITCH,
+ CM_ASL_PANELBRIGHT,
+ CM_ASL_BIOSFLASH,
+ CM_ASL_ACPIFLASH,
+ CM_ASL_CPUFV,
+ CM_ASL_CPUTEMPERATURE,
+ CM_ASL_FANCPU,
+ CM_ASL_FANCHASSIS,
+ CM_ASL_USBPORT1,
+ CM_ASL_USBPORT2,
+ CM_ASL_USBPORT3,
+ CM_ASL_MODEM,
+ CM_ASL_CARDREADER,
+ CM_ASL_3G,
+ CM_ASL_WIMAX,
+ CM_ASL_HWCF,
+ CM_ASL_LID,
+ CM_ASL_TYPE,
+ CM_ASL_PANELPOWER, /*P901*/
+ CM_ASL_TPD
+};
+
+static const char *cm_getv[] = {
+ "WLDG", "BTHG", NULL, NULL,
+ "CAMG", NULL, NULL, NULL,
+ NULL, "PBLG", NULL, NULL,
+ "CFVG", NULL, NULL, NULL,
+ "USBG", NULL, NULL, "MODG",
+ "CRDG", "M3GG", "WIMG", "HWCF",
+ "LIDG", "TYPE", "PBPG", "TPDG"
+};
+
+static const char *cm_setv[] = {
+ "WLDS", "BTHS", NULL, NULL,
+ "CAMS", NULL, NULL, NULL,
+ "SDSP", "PBLS", "HDPS", NULL,
+ "CFVS", NULL, NULL, NULL,
+ "USBG", NULL, NULL, "MODS",
+ "CRDS", "M3GS", "WIMS", NULL,
+ NULL, NULL, "PBPS", "TPDS"
+};
+
+static const struct key_entry eeepc_keymap[] = {
+ { KE_KEY, 0x10, { KEY_WLAN } },
+ { KE_KEY, 0x11, { KEY_WLAN } },
+ { KE_KEY, 0x12, { KEY_PROG1 } },
+ { KE_KEY, 0x13, { KEY_MUTE } },
+ { KE_KEY, 0x14, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 0x15, { KEY_VOLUMEUP } },
+ { KE_KEY, 0x16, { KEY_DISPLAY_OFF } },
+ { KE_KEY, 0x1a, { KEY_COFFEE } },
+ { KE_KEY, 0x1b, { KEY_ZOOM } },
+ { KE_KEY, 0x1c, { KEY_PROG2 } },
+ { KE_KEY, 0x1d, { KEY_PROG3 } },
+ { KE_KEY, NOTIFY_BRN_MIN, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, NOTIFY_BRN_MAX, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 0x30, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0x31, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0x32, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0x37, { KEY_F13 } }, /* Disable Touchpad */
+ { KE_KEY, 0x38, { KEY_F14 } },
+ { KE_END, 0 },
+};
+
+/*
+ * This is the main structure, we can use it to store useful information
+ */
+struct eeepc_laptop {
+ acpi_handle handle; /* the handle of the acpi device */
+ u32 cm_supported; /* the control methods supported
+ by this BIOS */
+ bool cpufv_disabled;
+ bool hotplug_disabled;
+ u16 event_count[128]; /* count for each event */
+
+ struct platform_device *platform_device;
+ struct acpi_device *device; /* the device we are in */
+ struct device *hwmon_device;
+ struct backlight_device *backlight_device;
+
+ struct input_dev *inputdev;
+
+ struct rfkill *wlan_rfkill;
+ struct rfkill *bluetooth_rfkill;
+ struct rfkill *wwan3g_rfkill;
+ struct rfkill *wimax_rfkill;
+
+ struct hotplug_slot *hotplug_slot;
+ struct mutex hotplug_lock;
+
+ struct led_classdev tpd_led;
+ int tpd_led_wk;
+ struct workqueue_struct *led_workqueue;
+ struct work_struct tpd_led_work;
+};
+
+/*
+ * ACPI Helpers
+ */
+static int write_acpi_int(acpi_handle handle, const char *method, int val)
+{
+ struct acpi_object_list params;
+ union acpi_object in_obj;
+ acpi_status status;
+
+ params.count = 1;
+ params.pointer = &in_obj;
+ in_obj.type = ACPI_TYPE_INTEGER;
+ in_obj.integer.value = val;
+
+ status = acpi_evaluate_object(handle, (char *)method, &params, NULL);
+ return (status == AE_OK ? 0 : -1);
+}
+
+static int read_acpi_int(acpi_handle handle, const char *method, int *val)
+{
+ acpi_status status;
+ unsigned long long result;
+
+ status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
+ if (ACPI_FAILURE(status)) {
+ *val = -1;
+ return -1;
+ } else {
+ *val = result;
+ return 0;
+ }
+}
+
+static int set_acpi(struct eeepc_laptop *eeepc, int cm, int value)
+{
+ const char *method = cm_setv[cm];
+
+ if (method == NULL)
+ return -ENODEV;
+ if ((eeepc->cm_supported & (0x1 << cm)) == 0)
+ return -ENODEV;
+
+ if (write_acpi_int(eeepc->handle, method, value))
+ pr_warn("Error writing %s\n", method);
+ return 0;
+}
+
+static int get_acpi(struct eeepc_laptop *eeepc, int cm)
+{
+ const char *method = cm_getv[cm];
+ int value;
+
+ if (method == NULL)
+ return -ENODEV;
+ if ((eeepc->cm_supported & (0x1 << cm)) == 0)
+ return -ENODEV;
+
+ if (read_acpi_int(eeepc->handle, method, &value))
+ pr_warn("Error reading %s\n", method);
+ return value;
+}
+
+static int acpi_setter_handle(struct eeepc_laptop *eeepc, int cm,
+ acpi_handle *handle)
+{
+ const char *method = cm_setv[cm];
+ acpi_status status;
+
+ if (method == NULL)
+ return -ENODEV;
+ if ((eeepc->cm_supported & (0x1 << cm)) == 0)
+ return -ENODEV;
+
+ status = acpi_get_handle(eeepc->handle, (char *)method,
+ handle);
+ if (status != AE_OK) {
+ pr_warn("Error finding %s\n", method);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+
+/*
+ * Sys helpers
+ */
+static int parse_arg(const char *buf, unsigned long count, int *val)
+{
+ if (!count)
+ return 0;
+ if (sscanf(buf, "%i", val) != 1)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t store_sys_acpi(struct device *dev, int cm,
+ const char *buf, size_t count)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+ int rv, value;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv > 0)
+ value = set_acpi(eeepc, cm, value);
+ if (value < 0)
+ return -EIO;
+ return rv;
+}
+
+static ssize_t show_sys_acpi(struct device *dev, int cm, char *buf)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+ int value = get_acpi(eeepc, cm);
+
+ if (value < 0)
+ return -EIO;
+ return sprintf(buf, "%d\n", value);
+}
+
+#define EEEPC_CREATE_DEVICE_ATTR(_name, _mode, _cm) \
+ static ssize_t show_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ return show_sys_acpi(dev, _cm, buf); \
+ } \
+ static ssize_t store_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ return store_sys_acpi(dev, _cm, buf, count); \
+ } \
+ static struct device_attribute dev_attr_##_name = { \
+ .attr = { \
+ .name = __stringify(_name), \
+ .mode = _mode }, \
+ .show = show_##_name, \
+ .store = store_##_name, \
+ }
+
+EEEPC_CREATE_DEVICE_ATTR(camera, 0644, CM_ASL_CAMERA);
+EEEPC_CREATE_DEVICE_ATTR(cardr, 0644, CM_ASL_CARDREADER);
+EEEPC_CREATE_DEVICE_ATTR(disp, 0200, CM_ASL_DISPLAYSWITCH);
+
+struct eeepc_cpufv {
+ int num;
+ int cur;
+};
+
+static int get_cpufv(struct eeepc_laptop *eeepc, struct eeepc_cpufv *c)
+{
+ c->cur = get_acpi(eeepc, CM_ASL_CPUFV);
+ c->num = (c->cur >> 8) & 0xff;
+ c->cur &= 0xff;
+ if (c->cur < 0 || c->num <= 0 || c->num > 12)
+ return -ENODEV;
+ return 0;
+}
+
+static ssize_t show_available_cpufv(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+ struct eeepc_cpufv c;
+ int i;
+ ssize_t len = 0;
+
+ if (get_cpufv(eeepc, &c))
+ return -ENODEV;
+ for (i = 0; i < c.num; i++)
+ len += sprintf(buf + len, "%d ", i);
+ len += sprintf(buf + len, "\n");
+ return len;
+}
+
+static ssize_t show_cpufv(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+ struct eeepc_cpufv c;
+
+ if (get_cpufv(eeepc, &c))
+ return -ENODEV;
+ return sprintf(buf, "%#x\n", (c.num << 8) | c.cur);
+}
+
+static ssize_t store_cpufv(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+ struct eeepc_cpufv c;
+ int rv, value;
+
+ if (eeepc->cpufv_disabled)
+ return -EPERM;
+ if (get_cpufv(eeepc, &c))
+ return -ENODEV;
+ rv = parse_arg(buf, count, &value);
+ if (rv < 0)
+ return rv;
+ if (!rv || value < 0 || value >= c.num)
+ return -EINVAL;
+ set_acpi(eeepc, CM_ASL_CPUFV, value);
+ return rv;
+}
+
+static ssize_t show_cpufv_disabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", eeepc->cpufv_disabled);
+}
+
+static ssize_t store_cpufv_disabled(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(dev);
+ int rv, value;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv < 0)
+ return rv;
+
+ switch (value) {
+ case 0:
+ if (eeepc->cpufv_disabled)
+ pr_warn("cpufv enabled (not officially supported "
+ "on this model)\n");
+ eeepc->cpufv_disabled = false;
+ return rv;
+ case 1:
+ return -EPERM;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+static struct device_attribute dev_attr_cpufv = {
+ .attr = {
+ .name = "cpufv",
+ .mode = 0644 },
+ .show = show_cpufv,
+ .store = store_cpufv
+};
+
+static struct device_attribute dev_attr_available_cpufv = {
+ .attr = {
+ .name = "available_cpufv",
+ .mode = 0444 },
+ .show = show_available_cpufv
+};
+
+static struct device_attribute dev_attr_cpufv_disabled = {
+ .attr = {
+ .name = "cpufv_disabled",
+ .mode = 0644 },
+ .show = show_cpufv_disabled,
+ .store = store_cpufv_disabled
+};
+
+
+static struct attribute *platform_attributes[] = {
+ &dev_attr_camera.attr,
+ &dev_attr_cardr.attr,
+ &dev_attr_disp.attr,
+ &dev_attr_cpufv.attr,
+ &dev_attr_available_cpufv.attr,
+ &dev_attr_cpufv_disabled.attr,
+ NULL
+};
+
+static struct attribute_group platform_attribute_group = {
+ .attrs = platform_attributes
+};
+
+static int eeepc_platform_init(struct eeepc_laptop *eeepc)
+{
+ int result;
+
+ eeepc->platform_device = platform_device_alloc(EEEPC_LAPTOP_FILE, -1);
+ if (!eeepc->platform_device)
+ return -ENOMEM;
+ platform_set_drvdata(eeepc->platform_device, eeepc);
+
+ result = platform_device_add(eeepc->platform_device);
+ if (result)
+ goto fail_platform_device;
+
+ result = sysfs_create_group(&eeepc->platform_device->dev.kobj,
+ &platform_attribute_group);
+ if (result)
+ goto fail_sysfs;
+ return 0;
+
+fail_sysfs:
+ platform_device_del(eeepc->platform_device);
+fail_platform_device:
+ platform_device_put(eeepc->platform_device);
+ return result;
+}
+
+static void eeepc_platform_exit(struct eeepc_laptop *eeepc)
+{
+ sysfs_remove_group(&eeepc->platform_device->dev.kobj,
+ &platform_attribute_group);
+ platform_device_unregister(eeepc->platform_device);
+}
+
+/*
+ * LEDs
+ */
+/*
+ * These functions actually update the LED's, and are called from a
+ * workqueue. By doing this as separate work rather than when the LED
+ * subsystem asks, we avoid messing with the Asus ACPI stuff during a
+ * potentially bad time, such as a timer interrupt.
+ */
+static void tpd_led_update(struct work_struct *work)
+ {
+ struct eeepc_laptop *eeepc;
+
+ eeepc = container_of(work, struct eeepc_laptop, tpd_led_work);
+
+ set_acpi(eeepc, CM_ASL_TPD, eeepc->tpd_led_wk);
+}
+
+static void tpd_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct eeepc_laptop *eeepc;
+
+ eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
+
+ eeepc->tpd_led_wk = (value > 0) ? 1 : 0;
+ queue_work(eeepc->led_workqueue, &eeepc->tpd_led_work);
+}
+
+static enum led_brightness tpd_led_get(struct led_classdev *led_cdev)
+{
+ struct eeepc_laptop *eeepc;
+
+ eeepc = container_of(led_cdev, struct eeepc_laptop, tpd_led);
+
+ return get_acpi(eeepc, CM_ASL_TPD);
+}
+
+static int eeepc_led_init(struct eeepc_laptop *eeepc)
+{
+ int rv;
+
+ if (get_acpi(eeepc, CM_ASL_TPD) == -ENODEV)
+ return 0;
+
+ eeepc->led_workqueue = create_singlethread_workqueue("led_workqueue");
+ if (!eeepc->led_workqueue)
+ return -ENOMEM;
+ INIT_WORK(&eeepc->tpd_led_work, tpd_led_update);
+
+ eeepc->tpd_led.name = "eeepc::touchpad";
+ eeepc->tpd_led.brightness_set = tpd_led_set;
+ if (get_acpi(eeepc, CM_ASL_TPD) >= 0) /* if method is available */
+ eeepc->tpd_led.brightness_get = tpd_led_get;
+ eeepc->tpd_led.max_brightness = 1;
+
+ rv = led_classdev_register(&eeepc->platform_device->dev,
+ &eeepc->tpd_led);
+ if (rv) {
+ destroy_workqueue(eeepc->led_workqueue);
+ return rv;
+ }
+
+ return 0;
+}
+
+static void eeepc_led_exit(struct eeepc_laptop *eeepc)
+{
+ if (!IS_ERR_OR_NULL(eeepc->tpd_led.dev))
+ led_classdev_unregister(&eeepc->tpd_led);
+ if (eeepc->led_workqueue)
+ destroy_workqueue(eeepc->led_workqueue);
+}
+
+
+/*
+ * PCI hotplug (for wlan rfkill)
+ */
+static bool eeepc_wlan_rfkill_blocked(struct eeepc_laptop *eeepc)
+{
+ if (get_acpi(eeepc, CM_ASL_WLAN) == 1)
+ return false;
+ return true;
+}
+
+static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle)
+{
+ struct pci_dev *port;
+ struct pci_dev *dev;
+ struct pci_bus *bus;
+ bool blocked = eeepc_wlan_rfkill_blocked(eeepc);
+ bool absent;
+ u32 l;
+
+ if (eeepc->wlan_rfkill)
+ rfkill_set_sw_state(eeepc->wlan_rfkill, blocked);
+
+ mutex_lock(&eeepc->hotplug_lock);
+
+ if (eeepc->hotplug_slot) {
+ port = acpi_get_pci_dev(handle);
+ if (!port) {
+ pr_warning("Unable to find port\n");
+ goto out_unlock;
+ }
+
+ bus = port->subordinate;
+
+ if (!bus) {
+ pr_warn("Unable to find PCI bus 1?\n");
+ goto out_put_dev;
+ }
+
+ if (pci_bus_read_config_dword(bus, 0, PCI_VENDOR_ID, &l)) {
+ pr_err("Unable to read PCI config space?\n");
+ goto out_put_dev;
+ }
+
+ absent = (l == 0xffffffff);
+
+ if (blocked != absent) {
+ pr_warn("BIOS says wireless lan is %s, "
+ "but the pci device is %s\n",
+ blocked ? "blocked" : "unblocked",
+ absent ? "absent" : "present");
+ pr_warn("skipped wireless hotplug as probably "
+ "inappropriate for this model\n");
+ goto out_put_dev;
+ }
+
+ if (!blocked) {
+ dev = pci_get_slot(bus, 0);
+ if (dev) {
+ /* Device already present */
+ pci_dev_put(dev);
+ goto out_put_dev;
+ }
+ dev = pci_scan_single_device(bus, 0);
+ if (dev) {
+ pci_bus_assign_resources(bus);
+ if (pci_bus_add_device(dev))
+ pr_err("Unable to hotplug wifi\n");
+ }
+ } else {
+ dev = pci_get_slot(bus, 0);
+ if (dev) {
+ pci_stop_and_remove_bus_device(dev);
+ pci_dev_put(dev);
+ }
+ }
+out_put_dev:
+ pci_dev_put(port);
+ }
+
+out_unlock:
+ mutex_unlock(&eeepc->hotplug_lock);
+}
+
+static void eeepc_rfkill_hotplug_update(struct eeepc_laptop *eeepc, char *node)
+{
+ acpi_status status = AE_OK;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status))
+ eeepc_rfkill_hotplug(eeepc, handle);
+}
+
+static void eeepc_rfkill_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct eeepc_laptop *eeepc = data;
+
+ if (event != ACPI_NOTIFY_BUS_CHECK)
+ return;
+
+ eeepc_rfkill_hotplug(eeepc, handle);
+}
+
+static int eeepc_register_rfkill_notifier(struct eeepc_laptop *eeepc,
+ char *node)
+{
+ acpi_status status;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_install_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ eeepc_rfkill_notify,
+ eeepc);
+ if (ACPI_FAILURE(status))
+ pr_warn("Failed to register notify on %s\n", node);
+
+ /*
+ * Refresh pci hotplug in case the rfkill state was
+ * changed during setup.
+ */
+ eeepc_rfkill_hotplug(eeepc, handle);
+ } else
+ return -ENODEV;
+
+ return 0;
+}
+
+static void eeepc_unregister_rfkill_notifier(struct eeepc_laptop *eeepc,
+ char *node)
+{
+ acpi_status status = AE_OK;
+ acpi_handle handle;
+
+ status = acpi_get_handle(NULL, node, &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ status = acpi_remove_notify_handler(handle,
+ ACPI_SYSTEM_NOTIFY,
+ eeepc_rfkill_notify);
+ if (ACPI_FAILURE(status))
+ pr_err("Error removing rfkill notify handler %s\n",
+ node);
+ /*
+ * Refresh pci hotplug in case the rfkill
+ * state was changed after
+ * eeepc_unregister_rfkill_notifier()
+ */
+ eeepc_rfkill_hotplug(eeepc, handle);
+ }
+}
+
+static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
+ u8 *value)
+{
+ struct eeepc_laptop *eeepc = hotplug_slot->private;
+ int val = get_acpi(eeepc, CM_ASL_WLAN);
+
+ if (val == 1 || val == 0)
+ *value = val;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
+{
+ kfree(hotplug_slot->info);
+ kfree(hotplug_slot);
+}
+
+static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
+ .owner = THIS_MODULE,
+ .get_adapter_status = eeepc_get_adapter_status,
+ .get_power_status = eeepc_get_adapter_status,
+};
+
+static int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc)
+{
+ int ret = -ENOMEM;
+ struct pci_bus *bus = pci_find_bus(0, 1);
+
+ if (!bus) {
+ pr_err("Unable to find wifi PCI bus\n");
+ return -ENODEV;
+ }
+
+ eeepc->hotplug_slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
+ if (!eeepc->hotplug_slot)
+ goto error_slot;
+
+ eeepc->hotplug_slot->info = kzalloc(sizeof(struct hotplug_slot_info),
+ GFP_KERNEL);
+ if (!eeepc->hotplug_slot->info)
+ goto error_info;
+
+ eeepc->hotplug_slot->private = eeepc;
+ eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
+ eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
+ eeepc_get_adapter_status(eeepc->hotplug_slot,
+ &eeepc->hotplug_slot->info->adapter_status);
+
+ ret = pci_hp_register(eeepc->hotplug_slot, bus, 0, "eeepc-wifi");
+ if (ret) {
+ pr_err("Unable to register hotplug slot - %d\n", ret);
+ goto error_register;
+ }
+
+ return 0;
+
+error_register:
+ kfree(eeepc->hotplug_slot->info);
+error_info:
+ kfree(eeepc->hotplug_slot);
+ eeepc->hotplug_slot = NULL;
+error_slot:
+ return ret;
+}
+
+/*
+ * Rfkill devices
+ */
+static int eeepc_rfkill_set(void *data, bool blocked)
+{
+ acpi_handle handle = data;
+
+ return write_acpi_int(handle, NULL, !blocked);
+}
+
+static const struct rfkill_ops eeepc_rfkill_ops = {
+ .set_block = eeepc_rfkill_set,
+};
+
+static int eeepc_new_rfkill(struct eeepc_laptop *eeepc,
+ struct rfkill **rfkill,
+ const char *name,
+ enum rfkill_type type, int cm)
+{
+ acpi_handle handle;
+ int result;
+
+ result = acpi_setter_handle(eeepc, cm, &handle);
+ if (result < 0)
+ return result;
+
+ *rfkill = rfkill_alloc(name, &eeepc->platform_device->dev, type,
+ &eeepc_rfkill_ops, handle);
+
+ if (!*rfkill)
+ return -EINVAL;
+
+ rfkill_init_sw_state(*rfkill, get_acpi(eeepc, cm) != 1);
+ result = rfkill_register(*rfkill);
+ if (result) {
+ rfkill_destroy(*rfkill);
+ *rfkill = NULL;
+ return result;
+ }
+ return 0;
+}
+
+static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc)
+{
+ eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
+ eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
+ eeepc_unregister_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
+ if (eeepc->wlan_rfkill) {
+ rfkill_unregister(eeepc->wlan_rfkill);
+ rfkill_destroy(eeepc->wlan_rfkill);
+ eeepc->wlan_rfkill = NULL;
+ }
+
+ if (eeepc->hotplug_slot)
+ pci_hp_deregister(eeepc->hotplug_slot);
+
+ if (eeepc->bluetooth_rfkill) {
+ rfkill_unregister(eeepc->bluetooth_rfkill);
+ rfkill_destroy(eeepc->bluetooth_rfkill);
+ eeepc->bluetooth_rfkill = NULL;
+ }
+ if (eeepc->wwan3g_rfkill) {
+ rfkill_unregister(eeepc->wwan3g_rfkill);
+ rfkill_destroy(eeepc->wwan3g_rfkill);
+ eeepc->wwan3g_rfkill = NULL;
+ }
+ if (eeepc->wimax_rfkill) {
+ rfkill_unregister(eeepc->wimax_rfkill);
+ rfkill_destroy(eeepc->wimax_rfkill);
+ eeepc->wimax_rfkill = NULL;
+ }
+}
+
+static int eeepc_rfkill_init(struct eeepc_laptop *eeepc)
+{
+ int result = 0;
+
+ mutex_init(&eeepc->hotplug_lock);
+
+ result = eeepc_new_rfkill(eeepc, &eeepc->wlan_rfkill,
+ "eeepc-wlan", RFKILL_TYPE_WLAN,
+ CM_ASL_WLAN);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = eeepc_new_rfkill(eeepc, &eeepc->bluetooth_rfkill,
+ "eeepc-bluetooth", RFKILL_TYPE_BLUETOOTH,
+ CM_ASL_BLUETOOTH);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = eeepc_new_rfkill(eeepc, &eeepc->wwan3g_rfkill,
+ "eeepc-wwan3g", RFKILL_TYPE_WWAN,
+ CM_ASL_3G);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ result = eeepc_new_rfkill(eeepc, &eeepc->wimax_rfkill,
+ "eeepc-wimax", RFKILL_TYPE_WIMAX,
+ CM_ASL_WIMAX);
+
+ if (result && result != -ENODEV)
+ goto exit;
+
+ if (eeepc->hotplug_disabled)
+ return 0;
+
+ result = eeepc_setup_pci_hotplug(eeepc);
+ /*
+ * If we get -EBUSY then something else is handling the PCI hotplug -
+ * don't fail in this case
+ */
+ if (result == -EBUSY)
+ result = 0;
+
+ eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P5");
+ eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P6");
+ eeepc_register_rfkill_notifier(eeepc, "\\_SB.PCI0.P0P7");
+
+exit:
+ if (result && result != -ENODEV)
+ eeepc_rfkill_exit(eeepc);
+ return result;
+}
+
+/*
+ * Platform driver - hibernate/resume callbacks
+ */
+static int eeepc_hotk_thaw(struct device *device)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(device);
+
+ if (eeepc->wlan_rfkill) {
+ bool wlan;
+
+ /*
+ * Work around bios bug - acpi _PTS turns off the wireless led
+ * during suspend. Normally it restores it on resume, but
+ * we should kick it ourselves in case hibernation is aborted.
+ */
+ wlan = get_acpi(eeepc, CM_ASL_WLAN);
+ set_acpi(eeepc, CM_ASL_WLAN, wlan);
+ }
+
+ return 0;
+}
+
+static int eeepc_hotk_restore(struct device *device)
+{
+ struct eeepc_laptop *eeepc = dev_get_drvdata(device);
+
+ /* Refresh both wlan rfkill state and pci hotplug */
+ if (eeepc->wlan_rfkill) {
+ eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P5");
+ eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P6");
+ eeepc_rfkill_hotplug_update(eeepc, "\\_SB.PCI0.P0P7");
+ }
+
+ if (eeepc->bluetooth_rfkill)
+ rfkill_set_sw_state(eeepc->bluetooth_rfkill,
+ get_acpi(eeepc, CM_ASL_BLUETOOTH) != 1);
+ if (eeepc->wwan3g_rfkill)
+ rfkill_set_sw_state(eeepc->wwan3g_rfkill,
+ get_acpi(eeepc, CM_ASL_3G) != 1);
+ if (eeepc->wimax_rfkill)
+ rfkill_set_sw_state(eeepc->wimax_rfkill,
+ get_acpi(eeepc, CM_ASL_WIMAX) != 1);
+
+ return 0;
+}
+
+static const struct dev_pm_ops eeepc_pm_ops = {
+ .thaw = eeepc_hotk_thaw,
+ .restore = eeepc_hotk_restore,
+};
+
+static struct platform_driver platform_driver = {
+ .driver = {
+ .name = EEEPC_LAPTOP_FILE,
+ .owner = THIS_MODULE,
+ .pm = &eeepc_pm_ops,
+ }
+};
+
+/*
+ * Hwmon device
+ */
+
+#define EEEPC_EC_SC00 0x61
+#define EEEPC_EC_FAN_PWM (EEEPC_EC_SC00 + 2) /* Fan PWM duty cycle (%) */
+#define EEEPC_EC_FAN_HRPM (EEEPC_EC_SC00 + 5) /* High byte, fan speed (RPM) */
+#define EEEPC_EC_FAN_LRPM (EEEPC_EC_SC00 + 6) /* Low byte, fan speed (RPM) */
+
+#define EEEPC_EC_SFB0 0xD0
+#define EEEPC_EC_FAN_CTRL (EEEPC_EC_SFB0 + 3) /* Byte containing SF25 */
+
+static int eeepc_get_fan_pwm(void)
+{
+ u8 value = 0;
+
+ ec_read(EEEPC_EC_FAN_PWM, &value);
+ return value * 255 / 100;
+}
+
+static void eeepc_set_fan_pwm(int value)
+{
+ value = clamp_val(value, 0, 255);
+ value = value * 100 / 255;
+ ec_write(EEEPC_EC_FAN_PWM, value);
+}
+
+static int eeepc_get_fan_rpm(void)
+{
+ u8 high = 0;
+ u8 low = 0;
+
+ ec_read(EEEPC_EC_FAN_HRPM, &high);
+ ec_read(EEEPC_EC_FAN_LRPM, &low);
+ return high << 8 | low;
+}
+
+static int eeepc_get_fan_ctrl(void)
+{
+ u8 value = 0;
+
+ ec_read(EEEPC_EC_FAN_CTRL, &value);
+ if (value & 0x02)
+ return 1; /* manual */
+ else
+ return 2; /* automatic */
+}
+
+static void eeepc_set_fan_ctrl(int manual)
+{
+ u8 value = 0;
+
+ ec_read(EEEPC_EC_FAN_CTRL, &value);
+ if (manual == 1)
+ value |= 0x02;
+ else
+ value &= ~0x02;
+ ec_write(EEEPC_EC_FAN_CTRL, value);
+}
+
+static ssize_t store_sys_hwmon(void (*set)(int), const char *buf, size_t count)
+{
+ int rv, value;
+
+ rv = parse_arg(buf, count, &value);
+ if (rv > 0)
+ set(value);
+ return rv;
+}
+
+static ssize_t show_sys_hwmon(int (*get)(void), char *buf)
+{
+ return sprintf(buf, "%d\n", get());
+}
+
+#define EEEPC_CREATE_SENSOR_ATTR(_name, _mode, _set, _get) \
+ static ssize_t show_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+ { \
+ return show_sys_hwmon(_set, buf); \
+ } \
+ static ssize_t store_##_name(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ return store_sys_hwmon(_get, buf, count); \
+ } \
+ static SENSOR_DEVICE_ATTR(_name, _mode, show_##_name, store_##_name, 0);
+
+EEEPC_CREATE_SENSOR_ATTR(fan1_input, S_IRUGO, eeepc_get_fan_rpm, NULL);
+EEEPC_CREATE_SENSOR_ATTR(pwm1, S_IRUGO | S_IWUSR,
+ eeepc_get_fan_pwm, eeepc_set_fan_pwm);
+EEEPC_CREATE_SENSOR_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
+ eeepc_get_fan_ctrl, eeepc_set_fan_ctrl);
+
+static ssize_t
+show_name(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "eeepc\n");
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+static struct attribute *hwmon_attributes[] = {
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+ &sensor_dev_attr_name.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group hwmon_attribute_group = {
+ .attrs = hwmon_attributes
+};
+
+static void eeepc_hwmon_exit(struct eeepc_laptop *eeepc)
+{
+ struct device *hwmon;
+
+ hwmon = eeepc->hwmon_device;
+ if (!hwmon)
+ return;
+ sysfs_remove_group(&hwmon->kobj,
+ &hwmon_attribute_group);
+ hwmon_device_unregister(hwmon);
+ eeepc->hwmon_device = NULL;
+}
+
+static int eeepc_hwmon_init(struct eeepc_laptop *eeepc)
+{
+ struct device *hwmon;
+ int result;
+
+ hwmon = hwmon_device_register(&eeepc->platform_device->dev);
+ if (IS_ERR(hwmon)) {
+ pr_err("Could not register eeepc hwmon device\n");
+ eeepc->hwmon_device = NULL;
+ return PTR_ERR(hwmon);
+ }
+ eeepc->hwmon_device = hwmon;
+ result = sysfs_create_group(&hwmon->kobj,
+ &hwmon_attribute_group);
+ if (result)
+ eeepc_hwmon_exit(eeepc);
+ return result;
+}
+
+/*
+ * Backlight device
+ */
+static int read_brightness(struct backlight_device *bd)
+{
+ struct eeepc_laptop *eeepc = bl_get_data(bd);
+
+ return get_acpi(eeepc, CM_ASL_PANELBRIGHT);
+}
+
+static int set_brightness(struct backlight_device *bd, int value)
+{
+ struct eeepc_laptop *eeepc = bl_get_data(bd);
+
+ return set_acpi(eeepc, CM_ASL_PANELBRIGHT, value);
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+ return set_brightness(bd, bd->props.brightness);
+}
+
+static const struct backlight_ops eeepcbl_ops = {
+ .get_brightness = read_brightness,
+ .update_status = update_bl_status,
+};
+
+static int eeepc_backlight_notify(struct eeepc_laptop *eeepc)
+{
+ struct backlight_device *bd = eeepc->backlight_device;
+ int old = bd->props.brightness;
+
+ backlight_force_update(bd, BACKLIGHT_UPDATE_HOTKEY);
+
+ return old;
+}
+
+static int eeepc_backlight_init(struct eeepc_laptop *eeepc)
+{
+ struct backlight_properties props;
+ struct backlight_device *bd;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = 15;
+ bd = backlight_device_register(EEEPC_LAPTOP_FILE,
+ &eeepc->platform_device->dev, eeepc,
+ &eeepcbl_ops, &props);
+ if (IS_ERR(bd)) {
+ pr_err("Could not register eeepc backlight device\n");
+ eeepc->backlight_device = NULL;
+ return PTR_ERR(bd);
+ }
+ eeepc->backlight_device = bd;
+ bd->props.brightness = read_brightness(bd);
+ bd->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(bd);
+ return 0;
+}
+
+static void eeepc_backlight_exit(struct eeepc_laptop *eeepc)
+{
+ if (eeepc->backlight_device)
+ backlight_device_unregister(eeepc->backlight_device);
+ eeepc->backlight_device = NULL;
+}
+
+
+/*
+ * Input device (i.e. hotkeys)
+ */
+static int eeepc_input_init(struct eeepc_laptop *eeepc)
+{
+ struct input_dev *input;
+ int error;
+
+ input = input_allocate_device();
+ if (!input) {
+ pr_info("Unable to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input->name = "Asus EeePC extra buttons";
+ input->phys = EEEPC_LAPTOP_FILE "/input0";
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = &eeepc->platform_device->dev;
+
+ error = sparse_keymap_setup(input, eeepc_keymap, NULL);
+ if (error) {
+ pr_err("Unable to setup input device keymap\n");
+ goto err_free_dev;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ pr_err("Unable to register input device\n");
+ goto err_free_keymap;
+ }
+
+ eeepc->inputdev = input;
+ return 0;
+
+err_free_keymap:
+ sparse_keymap_free(input);
+err_free_dev:
+ input_free_device(input);
+ return error;
+}
+
+static void eeepc_input_exit(struct eeepc_laptop *eeepc)
+{
+ if (eeepc->inputdev) {
+ sparse_keymap_free(eeepc->inputdev);
+ input_unregister_device(eeepc->inputdev);
+ }
+ eeepc->inputdev = NULL;
+}
+
+/*
+ * ACPI driver
+ */
+static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event)
+{
+ if (!eeepc->inputdev)
+ return ;
+ if (!sparse_keymap_report_event(eeepc->inputdev, event, 1, true))
+ pr_info("Unknown key %x pressed\n", event);
+}
+
+static void eeepc_acpi_notify(struct acpi_device *device, u32 event)
+{
+ struct eeepc_laptop *eeepc = acpi_driver_data(device);
+ u16 count;
+
+ if (event > ACPI_MAX_SYS_NOTIFY)
+ return;
+ count = eeepc->event_count[event % 128]++;
+ acpi_bus_generate_proc_event(device, event, count);
+ acpi_bus_generate_netlink_event(device->pnp.device_class,
+ dev_name(&device->dev), event,
+ count);
+
+ /* Brightness events are special */
+ if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) {
+
+ /* Ignore them completely if the acpi video driver is used */
+ if (eeepc->backlight_device != NULL) {
+ int old_brightness, new_brightness;
+
+ /* Update the backlight device. */
+ old_brightness = eeepc_backlight_notify(eeepc);
+
+ /* Convert event to keypress (obsolescent hack) */
+ new_brightness = event - NOTIFY_BRN_MIN;
+
+ if (new_brightness < old_brightness) {
+ event = NOTIFY_BRN_MIN; /* brightness down */
+ } else if (new_brightness > old_brightness) {
+ event = NOTIFY_BRN_MAX; /* brightness up */
+ } else {
+ /*
+ * no change in brightness - already at min/max,
+ * event will be desired value (or else ignored)
+ */
+ }
+ eeepc_input_notify(eeepc, event);
+ }
+ } else {
+ /* Everything else is a bona-fide keypress event */
+ eeepc_input_notify(eeepc, event);
+ }
+}
+
+static void eeepc_dmi_check(struct eeepc_laptop *eeepc)
+{
+ const char *model;
+
+ model = dmi_get_system_info(DMI_PRODUCT_NAME);
+ if (!model)
+ return;
+
+ /*
+ * Blacklist for setting cpufv (cpu speed).
+ *
+ * EeePC 4G ("701") implements CFVS, but it is not supported
+ * by the pre-installed OS, and the original option to change it
+ * in the BIOS setup screen was removed in later versions.
+ *
+ * Judging by the lack of "Super Hybrid Engine" on Asus product pages,
+ * this applies to all "701" models (4G/4G Surf/2G Surf).
+ *
+ * So Asus made a deliberate decision not to support it on this model.
+ * We have several reports that using it can cause the system to hang
+ *
+ * The hang has also been reported on a "702" (Model name "8G"?).
+ *
+ * We avoid dmi_check_system() / dmi_match(), because they use
+ * substring matching. We don't want to affect the "701SD"
+ * and "701SDX" models, because they do support S.H.E.
+ */
+ if (strcmp(model, "701") == 0 || strcmp(model, "702") == 0) {
+ eeepc->cpufv_disabled = true;
+ pr_info("model %s does not officially support setting cpu "
+ "speed\n", model);
+ pr_info("cpufv disabled to avoid instability\n");
+ }
+
+ /*
+ * Blacklist for wlan hotplug
+ *
+ * Eeepc 1005HA doesn't work like others models and don't need the
+ * hotplug code. In fact, current hotplug code seems to unplug another
+ * device...
+ */
+ if (strcmp(model, "1005HA") == 0 || strcmp(model, "1201N") == 0 ||
+ strcmp(model, "1005PE") == 0) {
+ eeepc->hotplug_disabled = true;
+ pr_info("wlan hotplug disabled\n");
+ }
+}
+
+static void cmsg_quirk(struct eeepc_laptop *eeepc, int cm, const char *name)
+{
+ int dummy;
+
+ /* Some BIOSes do not report cm although it is available.
+ Check if cm_getv[cm] works and, if yes, assume cm should be set. */
+ if (!(eeepc->cm_supported & (1 << cm))
+ && !read_acpi_int(eeepc->handle, cm_getv[cm], &dummy)) {
+ pr_info("%s (%x) not reported by BIOS,"
+ " enabling anyway\n", name, 1 << cm);
+ eeepc->cm_supported |= 1 << cm;
+ }
+}
+
+static void cmsg_quirks(struct eeepc_laptop *eeepc)
+{
+ cmsg_quirk(eeepc, CM_ASL_LID, "LID");
+ cmsg_quirk(eeepc, CM_ASL_TYPE, "TYPE");
+ cmsg_quirk(eeepc, CM_ASL_PANELPOWER, "PANELPOWER");
+ cmsg_quirk(eeepc, CM_ASL_TPD, "TPD");
+}
+
+static int eeepc_acpi_init(struct eeepc_laptop *eeepc)
+{
+ unsigned int init_flags;
+ int result;
+
+ result = acpi_bus_get_status(eeepc->device);
+ if (result)
+ return result;
+ if (!eeepc->device->status.present) {
+ pr_err("Hotkey device not present, aborting\n");
+ return -ENODEV;
+ }
+
+ init_flags = DISABLE_ASL_WLAN | DISABLE_ASL_DISPLAYSWITCH;
+ pr_notice("Hotkey init flags 0x%x\n", init_flags);
+
+ if (write_acpi_int(eeepc->handle, "INIT", init_flags)) {
+ pr_err("Hotkey initialization failed\n");
+ return -ENODEV;
+ }
+
+ /* get control methods supported */
+ if (read_acpi_int(eeepc->handle, "CMSG", &eeepc->cm_supported)) {
+ pr_err("Get control methods supported failed\n");
+ return -ENODEV;
+ }
+ cmsg_quirks(eeepc);
+ pr_info("Get control methods supported: 0x%x\n", eeepc->cm_supported);
+
+ return 0;
+}
+
+static void eeepc_enable_camera(struct eeepc_laptop *eeepc)
+{
+ /*
+ * If the following call to set_acpi() fails, it's because there's no
+ * camera so we can ignore the error.
+ */
+ if (get_acpi(eeepc, CM_ASL_CAMERA) == 0)
+ set_acpi(eeepc, CM_ASL_CAMERA, 1);
+}
+
+static bool eeepc_device_present;
+
+static int eeepc_acpi_add(struct acpi_device *device)
+{
+ struct eeepc_laptop *eeepc;
+ int result;
+
+ pr_notice(EEEPC_LAPTOP_NAME "\n");
+ eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL);
+ if (!eeepc)
+ return -ENOMEM;
+ eeepc->handle = device->handle;
+ strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
+ strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
+ device->driver_data = eeepc;
+ eeepc->device = device;
+
+ eeepc->hotplug_disabled = hotplug_disabled;
+
+ eeepc_dmi_check(eeepc);
+
+ result = eeepc_acpi_init(eeepc);
+ if (result)
+ goto fail_platform;
+ eeepc_enable_camera(eeepc);
+
+ /*
+ * Register the platform device first. It is used as a parent for the
+ * sub-devices below.
+ *
+ * Note that if there are multiple instances of this ACPI device it
+ * will bail out, because the platform device is registered with a
+ * fixed name. Of course it doesn't make sense to have more than one,
+ * and machine-specific scripts find the fixed name convenient. But
+ * It's also good for us to exclude multiple instances because both
+ * our hwmon and our wlan rfkill subdevice use global ACPI objects
+ * (the EC and the wlan PCI slot respectively).
+ */
+ result = eeepc_platform_init(eeepc);
+ if (result)
+ goto fail_platform;
+
+ if (!acpi_video_backlight_support()) {
+ result = eeepc_backlight_init(eeepc);
+ if (result)
+ goto fail_backlight;
+ } else
+ pr_info("Backlight controlled by ACPI video driver\n");
+
+ result = eeepc_input_init(eeepc);
+ if (result)
+ goto fail_input;
+
+ result = eeepc_hwmon_init(eeepc);
+ if (result)
+ goto fail_hwmon;
+
+ result = eeepc_led_init(eeepc);
+ if (result)
+ goto fail_led;
+
+ result = eeepc_rfkill_init(eeepc);
+ if (result)
+ goto fail_rfkill;
+
+ eeepc_device_present = true;
+ return 0;
+
+fail_rfkill:
+ eeepc_led_exit(eeepc);
+fail_led:
+ eeepc_hwmon_exit(eeepc);
+fail_hwmon:
+ eeepc_input_exit(eeepc);
+fail_input:
+ eeepc_backlight_exit(eeepc);
+fail_backlight:
+ eeepc_platform_exit(eeepc);
+fail_platform:
+ kfree(eeepc);
+
+ return result;
+}
+
+static int eeepc_acpi_remove(struct acpi_device *device)
+{
+ struct eeepc_laptop *eeepc = acpi_driver_data(device);
+
+ eeepc_backlight_exit(eeepc);
+ eeepc_rfkill_exit(eeepc);
+ eeepc_input_exit(eeepc);
+ eeepc_hwmon_exit(eeepc);
+ eeepc_led_exit(eeepc);
+ eeepc_platform_exit(eeepc);
+
+ kfree(eeepc);
+ return 0;
+}
+
+
+static const struct acpi_device_id eeepc_device_ids[] = {
+ {EEEPC_ACPI_HID, 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
+
+static struct acpi_driver eeepc_acpi_driver = {
+ .name = EEEPC_LAPTOP_NAME,
+ .class = EEEPC_ACPI_CLASS,
+ .owner = THIS_MODULE,
+ .ids = eeepc_device_ids,
+ .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+ .ops = {
+ .add = eeepc_acpi_add,
+ .remove = eeepc_acpi_remove,
+ .notify = eeepc_acpi_notify,
+ },
+};
+
+
+static int __init eeepc_laptop_init(void)
+{
+ int result;
+
+ result = platform_driver_register(&platform_driver);
+ if (result < 0)
+ return result;
+
+ result = acpi_bus_register_driver(&eeepc_acpi_driver);
+ if (result < 0)
+ goto fail_acpi_driver;
+
+ if (!eeepc_device_present) {
+ result = -ENODEV;
+ goto fail_no_device;
+ }
+
+ return 0;
+
+fail_no_device:
+ acpi_bus_unregister_driver(&eeepc_acpi_driver);
+fail_acpi_driver:
+ platform_driver_unregister(&platform_driver);
+ return result;
+}
+
+static void __exit eeepc_laptop_exit(void)
+{
+ acpi_bus_unregister_driver(&eeepc_acpi_driver);
+ platform_driver_unregister(&platform_driver);
+}
+
+module_init(eeepc_laptop_init);
+module_exit(eeepc_laptop_exit);
diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c
new file mode 100644
index 00000000..af67e6e5
--- /dev/null
+++ b/drivers/platform/x86/eeepc-wmi.c
@@ -0,0 +1,278 @@
+/*
+ * Eee PC WMI hotkey driver
+ *
+ * Copyright(C) 2010 Intel Corporation.
+ * Copyright(C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/dmi.h>
+#include <linux/fb.h>
+#include <acpi/acpi_bus.h>
+
+#include "asus-wmi.h"
+
+#define EEEPC_WMI_FILE "eeepc-wmi"
+
+MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>");
+MODULE_DESCRIPTION("Eee PC WMI Hotkey Driver");
+MODULE_LICENSE("GPL");
+
+#define EEEPC_ACPI_HID "ASUS010" /* old _HID used in eeepc-laptop */
+
+#define EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000"
+
+MODULE_ALIAS("wmi:"EEEPC_WMI_EVENT_GUID);
+
+static bool hotplug_wireless;
+
+module_param(hotplug_wireless, bool, 0444);
+MODULE_PARM_DESC(hotplug_wireless,
+ "Enable hotplug for wireless device. "
+ "If your laptop needs that, please report to "
+ "acpi4asus-user@lists.sourceforge.net.");
+
+/* Values for T101MT "Home" key */
+#define HOME_PRESS 0xe4
+#define HOME_HOLD 0xea
+#define HOME_RELEASE 0xe5
+
+static const struct key_entry eeepc_wmi_keymap[] = {
+ { KE_KEY, ASUS_WMI_BRN_DOWN, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, ASUS_WMI_BRN_UP, { KEY_BRIGHTNESSUP } },
+ /* Sleep already handled via generic ACPI code */
+ { KE_KEY, 0x30, { KEY_VOLUMEUP } },
+ { KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 0x32, { KEY_MUTE } },
+ { KE_KEY, 0x5c, { KEY_F15 } }, /* Power Gear key */
+ { KE_KEY, 0x5d, { KEY_WLAN } },
+ { KE_KEY, 0x6b, { KEY_TOUCHPAD_TOGGLE } }, /* Toggle Touchpad */
+ { KE_KEY, 0x82, { KEY_CAMERA } },
+ { KE_KEY, 0x83, { KEY_CAMERA_ZOOMIN } },
+ { KE_KEY, 0x88, { KEY_WLAN } },
+ { KE_KEY, 0xbd, { KEY_CAMERA } },
+ { KE_KEY, 0xcc, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0xe0, { KEY_PROG1 } }, /* Task Manager */
+ { KE_KEY, 0xe1, { KEY_F14 } }, /* Change Resolution */
+ { KE_KEY, HOME_PRESS, { KEY_CONFIG } }, /* Home/Express gate key */
+ { KE_KEY, 0xe8, { KEY_SCREENLOCK } },
+ { KE_KEY, 0xe9, { KEY_DISPLAYTOGGLE } },
+ { KE_KEY, 0xeb, { KEY_CAMERA_ZOOMOUT } },
+ { KE_KEY, 0xec, { KEY_CAMERA_UP } },
+ { KE_KEY, 0xed, { KEY_CAMERA_DOWN } },
+ { KE_KEY, 0xee, { KEY_CAMERA_LEFT } },
+ { KE_KEY, 0xef, { KEY_CAMERA_RIGHT } },
+ { KE_KEY, 0xf3, { KEY_MENU } },
+ { KE_KEY, 0xf5, { KEY_HOMEPAGE } },
+ { KE_KEY, 0xf6, { KEY_ESC } },
+ { KE_END, 0},
+};
+
+static struct quirk_entry quirk_asus_unknown = {
+};
+
+static struct quirk_entry quirk_asus_1000h = {
+ .hotplug_wireless = true,
+};
+
+static struct quirk_entry quirk_asus_et2012_type1 = {
+ .store_backlight_power = true,
+};
+
+static struct quirk_entry quirk_asus_et2012_type3 = {
+ .scalar_panel_brightness = true,
+ .store_backlight_power = true,
+};
+
+static struct quirk_entry quirk_asus_x101ch = {
+ /* We need this when ACPI function doesn't do this well */
+ .wmi_backlight_power = true,
+};
+
+static struct quirk_entry *quirks;
+
+static void et2012_quirks(void)
+{
+ const struct dmi_device *dev = NULL;
+ char oemstring[30];
+
+ while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
+ if (sscanf(dev->name, "AEMS%24c", oemstring) == 1) {
+ if (oemstring[18] == '1')
+ quirks = &quirk_asus_et2012_type1;
+ else if (oemstring[18] == '3')
+ quirks = &quirk_asus_et2012_type3;
+ break;
+ }
+ }
+}
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+ char *model;
+
+ quirks = dmi->driver_data;
+
+ model = (char *)dmi->matches[1].substr;
+ if (unlikely(strncmp(model, "ET2012", 6) == 0))
+ et2012_quirks();
+
+ return 1;
+}
+
+static struct dmi_system_id asus_quirks[] = {
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK Computer INC. 1000H",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "1000H"),
+ },
+ .driver_data = &quirk_asus_1000h,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK Computer INC. ET2012E/I",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ET2012"),
+ },
+ .driver_data = &quirk_asus_unknown,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK Computer INC. X101CH",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X101CH"),
+ },
+ .driver_data = &quirk_asus_x101ch,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUSTeK Computer INC. 1015CX",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "1015CX"),
+ },
+ .driver_data = &quirk_asus_x101ch,
+ },
+ {},
+};
+
+static void eeepc_wmi_key_filter(struct asus_wmi_driver *asus_wmi, int *code,
+ unsigned int *value, bool *autorelease)
+{
+ switch (*code) {
+ case HOME_PRESS:
+ *value = 1;
+ *autorelease = 0;
+ break;
+ case HOME_HOLD:
+ *code = ASUS_WMI_KEY_IGNORE;
+ break;
+ case HOME_RELEASE:
+ *code = HOME_PRESS;
+ *value = 0;
+ *autorelease = 0;
+ break;
+ }
+}
+
+static acpi_status eeepc_wmi_parse_device(acpi_handle handle, u32 level,
+ void *context, void **retval)
+{
+ pr_warn("Found legacy ATKD device (%s)\n", EEEPC_ACPI_HID);
+ *(bool *)context = true;
+ return AE_CTRL_TERMINATE;
+}
+
+static int eeepc_wmi_check_atkd(void)
+{
+ acpi_status status;
+ bool found = false;
+
+ status = acpi_get_devices(EEEPC_ACPI_HID, eeepc_wmi_parse_device,
+ &found, NULL);
+
+ if (ACPI_FAILURE(status) || !found)
+ return 0;
+ return -1;
+}
+
+static int eeepc_wmi_probe(struct platform_device *pdev)
+{
+ if (eeepc_wmi_check_atkd()) {
+ pr_warn("WMI device present, but legacy ATKD device is also "
+ "present and enabled\n");
+ pr_warn("You probably booted with acpi_osi=\"Linux\" or "
+ "acpi_osi=\"!Windows 2009\"\n");
+ pr_warn("Can't load eeepc-wmi, use default acpi_osi "
+ "(preferred) or eeepc-laptop\n");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static void eeepc_wmi_quirks(struct asus_wmi_driver *driver)
+{
+ quirks = &quirk_asus_unknown;
+ quirks->hotplug_wireless = hotplug_wireless;
+
+ dmi_check_system(asus_quirks);
+
+ driver->quirks = quirks;
+ driver->quirks->wapf = -1;
+ driver->panel_power = FB_BLANK_UNBLANK;
+}
+
+static struct asus_wmi_driver asus_wmi_driver = {
+ .name = EEEPC_WMI_FILE,
+ .owner = THIS_MODULE,
+ .event_guid = EEEPC_WMI_EVENT_GUID,
+ .keymap = eeepc_wmi_keymap,
+ .input_name = "Eee PC WMI hotkeys",
+ .input_phys = EEEPC_WMI_FILE "/input0",
+ .key_filter = eeepc_wmi_key_filter,
+ .probe = eeepc_wmi_probe,
+ .detect_quirks = eeepc_wmi_quirks,
+};
+
+
+static int __init eeepc_wmi_init(void)
+{
+ return asus_wmi_register_driver(&asus_wmi_driver);
+}
+
+static void __exit eeepc_wmi_exit(void)
+{
+ asus_wmi_unregister_driver(&asus_wmi_driver);
+}
+
+module_init(eeepc_wmi_init);
+module_exit(eeepc_wmi_exit);
diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
new file mode 100644
index 00000000..1c9386e7
--- /dev/null
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -0,0 +1,1250 @@
+/*-*-linux-c-*-*/
+
+/*
+ Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net>
+ Copyright (C) 2008 Peter Gruber <nokos@gmx.net>
+ Copyright (C) 2008 Tony Vroon <tony@linx.net>
+ Based on earlier work:
+ Copyright (C) 2003 Shane Spencer <shane@bogomip.com>
+ Adrian Yee <brewt-fujitsu@brewt.org>
+
+ Templated from msi-laptop.c and thinkpad_acpi.c which is copyright
+ by its respective authors.
+
+ 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.
+ */
+
+/*
+ * fujitsu-laptop.c - Fujitsu laptop support, providing access to additional
+ * features made available on a range of Fujitsu laptops including the
+ * P2xxx/P5xxx/S6xxx/S7xxx series.
+ *
+ * This driver exports a few files in /sys/devices/platform/fujitsu-laptop/;
+ * others may be added at a later date.
+ *
+ * lcd_level - Screen brightness: contains a single integer in the
+ * range 0..7. (rw)
+ *
+ * In addition to these platform device attributes the driver
+ * registers itself in the Linux backlight control subsystem and is
+ * available to userspace under /sys/class/backlight/fujitsu-laptop/.
+ *
+ * Hotkeys present on certain Fujitsu laptops (eg: the S6xxx series) are
+ * also supported by this driver.
+ *
+ * This driver has been tested on a Fujitsu Lifebook S6410, S7020 and
+ * P8010. It should work on most P-series and S-series Lifebooks, but
+ * YMMV.
+ *
+ * The module parameter use_alt_lcd_levels switches between different ACPI
+ * brightness controls which are used by different Fujitsu laptops. In most
+ * cases the correct method is automatically detected. "use_alt_lcd_levels=1"
+ * is applicable for a Fujitsu Lifebook S6410 if autodetection fails.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/backlight.h>
+#include <linux/input.h>
+#include <linux/kfifo.h>
+#include <linux/video_output.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+#include <linux/leds.h>
+#endif
+
+#define FUJITSU_DRIVER_VERSION "0.6.0"
+
+#define FUJITSU_LCD_N_LEVELS 8
+
+#define ACPI_FUJITSU_CLASS "fujitsu"
+#define ACPI_FUJITSU_HID "FUJ02B1"
+#define ACPI_FUJITSU_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver"
+#define ACPI_FUJITSU_DEVICE_NAME "Fujitsu FUJ02B1"
+#define ACPI_FUJITSU_HOTKEY_HID "FUJ02E3"
+#define ACPI_FUJITSU_HOTKEY_DRIVER_NAME "Fujitsu laptop FUJ02E3 ACPI hotkeys driver"
+#define ACPI_FUJITSU_HOTKEY_DEVICE_NAME "Fujitsu FUJ02E3"
+
+#define ACPI_FUJITSU_NOTIFY_CODE1 0x80
+
+#define ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS 0x86
+#define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS 0x87
+
+/* FUNC interface - command values */
+#define FUNC_RFKILL 0x1000
+#define FUNC_LEDS 0x1001
+#define FUNC_BUTTONS 0x1002
+#define FUNC_BACKLIGHT 0x1004
+
+/* FUNC interface - responses */
+#define UNSUPPORTED_CMD 0x80000000
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+/* FUNC interface - LED control */
+#define FUNC_LED_OFF 0x1
+#define FUNC_LED_ON 0x30001
+#define KEYBOARD_LAMPS 0x100
+#define LOGOLAMP_POWERON 0x2000
+#define LOGOLAMP_ALWAYS 0x4000
+#endif
+
+/* Hotkey details */
+#define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */
+#define KEY2_CODE 0x411
+#define KEY3_CODE 0x412
+#define KEY4_CODE 0x413
+
+#define MAX_HOTKEY_RINGBUFFER_SIZE 100
+#define RINGBUFFERSIZE 40
+
+/* Debugging */
+#define FUJLAPTOP_LOG ACPI_FUJITSU_HID ": "
+#define FUJLAPTOP_ERR KERN_ERR FUJLAPTOP_LOG
+#define FUJLAPTOP_NOTICE KERN_NOTICE FUJLAPTOP_LOG
+#define FUJLAPTOP_INFO KERN_INFO FUJLAPTOP_LOG
+#define FUJLAPTOP_DEBUG KERN_DEBUG FUJLAPTOP_LOG
+
+#define FUJLAPTOP_DBG_ALL 0xffff
+#define FUJLAPTOP_DBG_ERROR 0x0001
+#define FUJLAPTOP_DBG_WARN 0x0002
+#define FUJLAPTOP_DBG_INFO 0x0004
+#define FUJLAPTOP_DBG_TRACE 0x0008
+
+#define dbg_printk(a_dbg_level, format, arg...) \
+ do { if (dbg_level & a_dbg_level) \
+ printk(FUJLAPTOP_DEBUG "%s: " format, __func__ , ## arg); \
+ } while (0)
+#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+#define vdbg_printk(a_dbg_level, format, arg...) \
+ dbg_printk(a_dbg_level, format, ## arg)
+#else
+#define vdbg_printk(a_dbg_level, format, arg...)
+#endif
+
+/* Device controlling the backlight and associated keys */
+struct fujitsu_t {
+ acpi_handle acpi_handle;
+ struct acpi_device *dev;
+ struct input_dev *input;
+ char phys[32];
+ struct backlight_device *bl_device;
+ struct platform_device *pf_device;
+ int keycode1, keycode2, keycode3, keycode4;
+
+ unsigned int max_brightness;
+ unsigned int brightness_changed;
+ unsigned int brightness_level;
+};
+
+static struct fujitsu_t *fujitsu;
+static int use_alt_lcd_levels = -1;
+static int disable_brightness_adjust = -1;
+
+/* Device used to access other hotkeys on the laptop */
+struct fujitsu_hotkey_t {
+ acpi_handle acpi_handle;
+ struct acpi_device *dev;
+ struct input_dev *input;
+ char phys[32];
+ struct platform_device *pf_device;
+ struct kfifo fifo;
+ spinlock_t fifo_lock;
+ int rfkill_supported;
+ int rfkill_state;
+ int logolamp_registered;
+ int kblamps_registered;
+};
+
+static struct fujitsu_hotkey_t *fujitsu_hotkey;
+
+static void acpi_fujitsu_hotkey_notify(struct acpi_device *device, u32 event);
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+static enum led_brightness logolamp_get(struct led_classdev *cdev);
+static void logolamp_set(struct led_classdev *cdev,
+ enum led_brightness brightness);
+
+static struct led_classdev logolamp_led = {
+ .name = "fujitsu::logolamp",
+ .brightness_get = logolamp_get,
+ .brightness_set = logolamp_set
+};
+
+static enum led_brightness kblamps_get(struct led_classdev *cdev);
+static void kblamps_set(struct led_classdev *cdev,
+ enum led_brightness brightness);
+
+static struct led_classdev kblamps_led = {
+ .name = "fujitsu::kblamps",
+ .brightness_get = kblamps_get,
+ .brightness_set = kblamps_set
+};
+#endif
+
+#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+static u32 dbg_level = 0x03;
+#endif
+
+static void acpi_fujitsu_notify(struct acpi_device *device, u32 event);
+
+/* Fujitsu ACPI interface function */
+
+static int call_fext_func(int cmd, int arg0, int arg1, int arg2)
+{
+ acpi_status status = AE_OK;
+ union acpi_object params[4] = {
+ { .type = ACPI_TYPE_INTEGER },
+ { .type = ACPI_TYPE_INTEGER },
+ { .type = ACPI_TYPE_INTEGER },
+ { .type = ACPI_TYPE_INTEGER }
+ };
+ struct acpi_object_list arg_list = { 4, &params[0] };
+ struct acpi_buffer output;
+ union acpi_object out_obj;
+ acpi_handle handle = NULL;
+
+ status = acpi_get_handle(fujitsu_hotkey->acpi_handle, "FUNC", &handle);
+ if (ACPI_FAILURE(status)) {
+ vdbg_printk(FUJLAPTOP_DBG_ERROR,
+ "FUNC interface is not present\n");
+ return -ENODEV;
+ }
+
+ params[0].integer.value = cmd;
+ params[1].integer.value = arg0;
+ params[2].integer.value = arg1;
+ params[3].integer.value = arg2;
+
+ output.length = sizeof(out_obj);
+ output.pointer = &out_obj;
+
+ status = acpi_evaluate_object(handle, NULL, &arg_list, &output);
+ if (ACPI_FAILURE(status)) {
+ vdbg_printk(FUJLAPTOP_DBG_WARN,
+ "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) call failed\n",
+ cmd, arg0, arg1, arg2);
+ return -ENODEV;
+ }
+
+ if (out_obj.type != ACPI_TYPE_INTEGER) {
+ vdbg_printk(FUJLAPTOP_DBG_WARN,
+ "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) did not "
+ "return an integer\n",
+ cmd, arg0, arg1, arg2);
+ return -ENODEV;
+ }
+
+ vdbg_printk(FUJLAPTOP_DBG_TRACE,
+ "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
+ cmd, arg0, arg1, arg2, (int)out_obj.integer.value);
+ return out_obj.integer.value;
+}
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+/* LED class callbacks */
+
+static void logolamp_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ if (brightness >= LED_FULL) {
+ call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_ON);
+ call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, FUNC_LED_ON);
+ } else if (brightness >= LED_HALF) {
+ call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_ON);
+ call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, FUNC_LED_OFF);
+ } else {
+ call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_OFF);
+ }
+}
+
+static void kblamps_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ if (brightness >= LED_FULL)
+ call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_ON);
+ else
+ call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF);
+}
+
+static enum led_brightness logolamp_get(struct led_classdev *cdev)
+{
+ enum led_brightness brightness = LED_OFF;
+ int poweron, always;
+
+ poweron = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
+ if (poweron == FUNC_LED_ON) {
+ brightness = LED_HALF;
+ always = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
+ if (always == FUNC_LED_ON)
+ brightness = LED_FULL;
+ }
+ return brightness;
+}
+
+static enum led_brightness kblamps_get(struct led_classdev *cdev)
+{
+ enum led_brightness brightness = LED_OFF;
+
+ if (call_fext_func(FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
+ brightness = LED_FULL;
+
+ return brightness;
+}
+#endif
+
+/* Hardware access for LCD brightness control */
+
+static int set_lcd_level(int level)
+{
+ acpi_status status = AE_OK;
+ union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+ struct acpi_object_list arg_list = { 1, &arg0 };
+ acpi_handle handle = NULL;
+
+ vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBLL [%d]\n",
+ level);
+
+ if (level < 0 || level >= fujitsu->max_brightness)
+ return -EINVAL;
+
+ status = acpi_get_handle(fujitsu->acpi_handle, "SBLL", &handle);
+ if (ACPI_FAILURE(status)) {
+ vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBLL not present\n");
+ return -ENODEV;
+ }
+
+ arg0.integer.value = level;
+
+ status = acpi_evaluate_object(handle, NULL, &arg_list, NULL);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ return 0;
+}
+
+static int set_lcd_level_alt(int level)
+{
+ acpi_status status = AE_OK;
+ union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+ struct acpi_object_list arg_list = { 1, &arg0 };
+ acpi_handle handle = NULL;
+
+ vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBL2 [%d]\n",
+ level);
+
+ if (level < 0 || level >= fujitsu->max_brightness)
+ return -EINVAL;
+
+ status = acpi_get_handle(fujitsu->acpi_handle, "SBL2", &handle);
+ if (ACPI_FAILURE(status)) {
+ vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBL2 not present\n");
+ return -ENODEV;
+ }
+
+ arg0.integer.value = level;
+
+ status = acpi_evaluate_object(handle, NULL, &arg_list, NULL);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ return 0;
+}
+
+static int get_lcd_level(void)
+{
+ unsigned long long state = 0;
+ acpi_status status = AE_OK;
+
+ vdbg_printk(FUJLAPTOP_DBG_TRACE, "get lcd level via GBLL\n");
+
+ status =
+ acpi_evaluate_integer(fujitsu->acpi_handle, "GBLL", NULL, &state);
+ if (ACPI_FAILURE(status))
+ return 0;
+
+ fujitsu->brightness_level = state & 0x0fffffff;
+
+ if (state & 0x80000000)
+ fujitsu->brightness_changed = 1;
+ else
+ fujitsu->brightness_changed = 0;
+
+ return fujitsu->brightness_level;
+}
+
+static int get_max_brightness(void)
+{
+ unsigned long long state = 0;
+ acpi_status status = AE_OK;
+
+ vdbg_printk(FUJLAPTOP_DBG_TRACE, "get max lcd level via RBLL\n");
+
+ status =
+ acpi_evaluate_integer(fujitsu->acpi_handle, "RBLL", NULL, &state);
+ if (ACPI_FAILURE(status))
+ return -1;
+
+ fujitsu->max_brightness = state;
+
+ return fujitsu->max_brightness;
+}
+
+/* Backlight device stuff */
+
+static int bl_get_brightness(struct backlight_device *b)
+{
+ return get_lcd_level();
+}
+
+static int bl_update_status(struct backlight_device *b)
+{
+ int ret;
+ if (b->props.power == 4)
+ ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3);
+ else
+ ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0);
+ if (ret != 0)
+ vdbg_printk(FUJLAPTOP_DBG_ERROR,
+ "Unable to adjust backlight power, error code %i\n",
+ ret);
+
+ if (use_alt_lcd_levels)
+ ret = set_lcd_level_alt(b->props.brightness);
+ else
+ ret = set_lcd_level(b->props.brightness);
+ if (ret != 0)
+ vdbg_printk(FUJLAPTOP_DBG_ERROR,
+ "Unable to adjust LCD brightness, error code %i\n",
+ ret);
+ return ret;
+}
+
+static const struct backlight_ops fujitsubl_ops = {
+ .get_brightness = bl_get_brightness,
+ .update_status = bl_update_status,
+};
+
+/* Platform LCD brightness device */
+
+static ssize_t
+show_max_brightness(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret;
+
+ ret = get_max_brightness();
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", ret);
+}
+
+static ssize_t
+show_brightness_changed(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret;
+
+ ret = fujitsu->brightness_changed;
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", ret);
+}
+
+static ssize_t show_lcd_level(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret;
+
+ ret = get_lcd_level();
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", ret);
+}
+
+static ssize_t store_lcd_level(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+
+ int level, ret;
+
+ if (sscanf(buf, "%i", &level) != 1
+ || (level < 0 || level >= fujitsu->max_brightness))
+ return -EINVAL;
+
+ if (use_alt_lcd_levels)
+ ret = set_lcd_level_alt(level);
+ else
+ ret = set_lcd_level(level);
+ if (ret < 0)
+ return ret;
+
+ ret = get_lcd_level();
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t
+ignore_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return count;
+}
+
+static ssize_t
+show_lid_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (!(fujitsu_hotkey->rfkill_supported & 0x100))
+ return sprintf(buf, "unknown\n");
+ if (fujitsu_hotkey->rfkill_state & 0x100)
+ return sprintf(buf, "open\n");
+ else
+ return sprintf(buf, "closed\n");
+}
+
+static ssize_t
+show_dock_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (!(fujitsu_hotkey->rfkill_supported & 0x200))
+ return sprintf(buf, "unknown\n");
+ if (fujitsu_hotkey->rfkill_state & 0x200)
+ return sprintf(buf, "docked\n");
+ else
+ return sprintf(buf, "undocked\n");
+}
+
+static ssize_t
+show_radios_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (!(fujitsu_hotkey->rfkill_supported & 0x20))
+ return sprintf(buf, "unknown\n");
+ if (fujitsu_hotkey->rfkill_state & 0x20)
+ return sprintf(buf, "on\n");
+ else
+ return sprintf(buf, "killed\n");
+}
+
+static DEVICE_ATTR(max_brightness, 0444, show_max_brightness, ignore_store);
+static DEVICE_ATTR(brightness_changed, 0444, show_brightness_changed,
+ ignore_store);
+static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
+static DEVICE_ATTR(lid, 0444, show_lid_state, ignore_store);
+static DEVICE_ATTR(dock, 0444, show_dock_state, ignore_store);
+static DEVICE_ATTR(radios, 0444, show_radios_state, ignore_store);
+
+static struct attribute *fujitsupf_attributes[] = {
+ &dev_attr_brightness_changed.attr,
+ &dev_attr_max_brightness.attr,
+ &dev_attr_lcd_level.attr,
+ &dev_attr_lid.attr,
+ &dev_attr_dock.attr,
+ &dev_attr_radios.attr,
+ NULL
+};
+
+static struct attribute_group fujitsupf_attribute_group = {
+ .attrs = fujitsupf_attributes
+};
+
+static struct platform_driver fujitsupf_driver = {
+ .driver = {
+ .name = "fujitsu-laptop",
+ .owner = THIS_MODULE,
+ }
+};
+
+static void dmi_check_cb_common(const struct dmi_system_id *id)
+{
+ acpi_handle handle;
+ pr_info("Identified laptop model '%s'\n", id->ident);
+ if (use_alt_lcd_levels == -1) {
+ if (ACPI_SUCCESS(acpi_get_handle(NULL,
+ "\\_SB.PCI0.LPCB.FJEX.SBL2", &handle)))
+ use_alt_lcd_levels = 1;
+ else
+ use_alt_lcd_levels = 0;
+ vdbg_printk(FUJLAPTOP_DBG_TRACE, "auto-detected usealt as "
+ "%i\n", use_alt_lcd_levels);
+ }
+}
+
+static int dmi_check_cb_s6410(const struct dmi_system_id *id)
+{
+ dmi_check_cb_common(id);
+ fujitsu->keycode1 = KEY_SCREENLOCK; /* "Lock" */
+ fujitsu->keycode2 = KEY_HELP; /* "Mobility Center" */
+ return 1;
+}
+
+static int dmi_check_cb_s6420(const struct dmi_system_id *id)
+{
+ dmi_check_cb_common(id);
+ fujitsu->keycode1 = KEY_SCREENLOCK; /* "Lock" */
+ fujitsu->keycode2 = KEY_HELP; /* "Mobility Center" */
+ return 1;
+}
+
+static int dmi_check_cb_p8010(const struct dmi_system_id *id)
+{
+ dmi_check_cb_common(id);
+ fujitsu->keycode1 = KEY_HELP; /* "Support" */
+ fujitsu->keycode3 = KEY_SWITCHVIDEOMODE; /* "Presentation" */
+ fujitsu->keycode4 = KEY_WWW; /* "Internet" */
+ return 1;
+}
+
+static struct dmi_system_id fujitsu_dmi_table[] = {
+ {
+ .ident = "Fujitsu Siemens S6410",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"),
+ },
+ .callback = dmi_check_cb_s6410},
+ {
+ .ident = "Fujitsu Siemens S6420",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"),
+ },
+ .callback = dmi_check_cb_s6420},
+ {
+ .ident = "Fujitsu LifeBook P8010",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"),
+ },
+ .callback = dmi_check_cb_p8010},
+ {}
+};
+
+/* ACPI device for LCD brightness control */
+
+static int acpi_fujitsu_add(struct acpi_device *device)
+{
+ acpi_handle handle;
+ int result = 0;
+ int state = 0;
+ struct input_dev *input;
+ int error;
+
+ if (!device)
+ return -EINVAL;
+
+ fujitsu->acpi_handle = device->handle;
+ sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_DEVICE_NAME);
+ sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
+ device->driver_data = fujitsu;
+
+ fujitsu->input = input = input_allocate_device();
+ if (!input) {
+ error = -ENOMEM;
+ goto err_stop;
+ }
+
+ snprintf(fujitsu->phys, sizeof(fujitsu->phys),
+ "%s/video/input0", acpi_device_hid(device));
+
+ input->name = acpi_device_name(device);
+ input->phys = fujitsu->phys;
+ input->id.bustype = BUS_HOST;
+ input->id.product = 0x06;
+ input->dev.parent = &device->dev;
+ input->evbit[0] = BIT(EV_KEY);
+ set_bit(KEY_BRIGHTNESSUP, input->keybit);
+ set_bit(KEY_BRIGHTNESSDOWN, input->keybit);
+ set_bit(KEY_UNKNOWN, input->keybit);
+
+ error = input_register_device(input);
+ if (error)
+ goto err_free_input_dev;
+
+ result = acpi_bus_update_power(fujitsu->acpi_handle, &state);
+ if (result) {
+ pr_err("Error reading power state\n");
+ goto err_unregister_input_dev;
+ }
+
+ pr_info("ACPI: %s [%s] (%s)\n",
+ acpi_device_name(device), acpi_device_bid(device),
+ !device->power.state ? "on" : "off");
+
+ fujitsu->dev = device;
+
+ if (ACPI_SUCCESS
+ (acpi_get_handle(device->handle, METHOD_NAME__INI, &handle))) {
+ vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
+ if (ACPI_FAILURE
+ (acpi_evaluate_object
+ (device->handle, METHOD_NAME__INI, NULL, NULL)))
+ pr_err("_INI Method failed\n");
+ }
+
+ /* do config (detect defaults) */
+ use_alt_lcd_levels = use_alt_lcd_levels == 1 ? 1 : 0;
+ disable_brightness_adjust = disable_brightness_adjust == 1 ? 1 : 0;
+ vdbg_printk(FUJLAPTOP_DBG_INFO,
+ "config: [alt interface: %d], [adjust disable: %d]\n",
+ use_alt_lcd_levels, disable_brightness_adjust);
+
+ if (get_max_brightness() <= 0)
+ fujitsu->max_brightness = FUJITSU_LCD_N_LEVELS;
+ get_lcd_level();
+
+ return result;
+
+err_unregister_input_dev:
+ input_unregister_device(input);
+ input = NULL;
+err_free_input_dev:
+ input_free_device(input);
+err_stop:
+ return result;
+}
+
+static int acpi_fujitsu_remove(struct acpi_device *device)
+{
+ struct fujitsu_t *fujitsu = acpi_driver_data(device);
+ struct input_dev *input = fujitsu->input;
+
+ input_unregister_device(input);
+
+ fujitsu->acpi_handle = NULL;
+
+ return 0;
+}
+
+/* Brightness notify */
+
+static void acpi_fujitsu_notify(struct acpi_device *device, u32 event)
+{
+ struct input_dev *input;
+ int keycode;
+ int oldb, newb;
+
+ input = fujitsu->input;
+
+ switch (event) {
+ case ACPI_FUJITSU_NOTIFY_CODE1:
+ keycode = 0;
+ oldb = fujitsu->brightness_level;
+ get_lcd_level();
+ newb = fujitsu->brightness_level;
+
+ vdbg_printk(FUJLAPTOP_DBG_TRACE,
+ "brightness button event [%i -> %i (%i)]\n",
+ oldb, newb, fujitsu->brightness_changed);
+
+ if (oldb < newb) {
+ if (disable_brightness_adjust != 1) {
+ if (use_alt_lcd_levels)
+ set_lcd_level_alt(newb);
+ else
+ set_lcd_level(newb);
+ }
+ acpi_bus_generate_proc_event(fujitsu->dev,
+ ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, 0);
+ keycode = KEY_BRIGHTNESSUP;
+ } else if (oldb > newb) {
+ if (disable_brightness_adjust != 1) {
+ if (use_alt_lcd_levels)
+ set_lcd_level_alt(newb);
+ else
+ set_lcd_level(newb);
+ }
+ acpi_bus_generate_proc_event(fujitsu->dev,
+ ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, 0);
+ keycode = KEY_BRIGHTNESSDOWN;
+ }
+ break;
+ default:
+ keycode = KEY_UNKNOWN;
+ vdbg_printk(FUJLAPTOP_DBG_WARN,
+ "unsupported event [0x%x]\n", event);
+ break;
+ }
+
+ if (keycode != 0) {
+ input_report_key(input, keycode, 1);
+ input_sync(input);
+ input_report_key(input, keycode, 0);
+ input_sync(input);
+ }
+}
+
+/* ACPI device for hotkey handling */
+
+static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
+{
+ acpi_handle handle;
+ int result = 0;
+ int state = 0;
+ struct input_dev *input;
+ int error;
+ int i;
+
+ if (!device)
+ return -EINVAL;
+
+ fujitsu_hotkey->acpi_handle = device->handle;
+ sprintf(acpi_device_name(device), "%s",
+ ACPI_FUJITSU_HOTKEY_DEVICE_NAME);
+ sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
+ device->driver_data = fujitsu_hotkey;
+
+ /* kfifo */
+ spin_lock_init(&fujitsu_hotkey->fifo_lock);
+ error = kfifo_alloc(&fujitsu_hotkey->fifo, RINGBUFFERSIZE * sizeof(int),
+ GFP_KERNEL);
+ if (error) {
+ pr_err("kfifo_alloc failed\n");
+ goto err_stop;
+ }
+
+ fujitsu_hotkey->input = input = input_allocate_device();
+ if (!input) {
+ error = -ENOMEM;
+ goto err_free_fifo;
+ }
+
+ snprintf(fujitsu_hotkey->phys, sizeof(fujitsu_hotkey->phys),
+ "%s/video/input0", acpi_device_hid(device));
+
+ input->name = acpi_device_name(device);
+ input->phys = fujitsu_hotkey->phys;
+ input->id.bustype = BUS_HOST;
+ input->id.product = 0x06;
+ input->dev.parent = &device->dev;
+
+ set_bit(EV_KEY, input->evbit);
+ set_bit(fujitsu->keycode1, input->keybit);
+ set_bit(fujitsu->keycode2, input->keybit);
+ set_bit(fujitsu->keycode3, input->keybit);
+ set_bit(fujitsu->keycode4, input->keybit);
+ set_bit(KEY_UNKNOWN, input->keybit);
+
+ error = input_register_device(input);
+ if (error)
+ goto err_free_input_dev;
+
+ result = acpi_bus_update_power(fujitsu_hotkey->acpi_handle, &state);
+ if (result) {
+ pr_err("Error reading power state\n");
+ goto err_unregister_input_dev;
+ }
+
+ pr_info("ACPI: %s [%s] (%s)\n",
+ acpi_device_name(device), acpi_device_bid(device),
+ !device->power.state ? "on" : "off");
+
+ fujitsu_hotkey->dev = device;
+
+ if (ACPI_SUCCESS
+ (acpi_get_handle(device->handle, METHOD_NAME__INI, &handle))) {
+ vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
+ if (ACPI_FAILURE
+ (acpi_evaluate_object
+ (device->handle, METHOD_NAME__INI, NULL, NULL)))
+ pr_err("_INI Method failed\n");
+ }
+
+ i = 0;
+ while (call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0
+ && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE)
+ ; /* No action, result is discarded */
+ vdbg_printk(FUJLAPTOP_DBG_INFO, "Discarded %i ringbuffer entries\n", i);
+
+ fujitsu_hotkey->rfkill_supported =
+ call_fext_func(FUNC_RFKILL, 0x0, 0x0, 0x0);
+
+ /* Make sure our bitmask of supported functions is cleared if the
+ RFKILL function block is not implemented, like on the S7020. */
+ if (fujitsu_hotkey->rfkill_supported == UNSUPPORTED_CMD)
+ fujitsu_hotkey->rfkill_supported = 0;
+
+ if (fujitsu_hotkey->rfkill_supported)
+ fujitsu_hotkey->rfkill_state =
+ call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
+
+ /* Suspect this is a keymap of the application panel, print it */
+ pr_info("BTNI: [0x%x]\n", call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0));
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+ if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
+ result = led_classdev_register(&fujitsu->pf_device->dev,
+ &logolamp_led);
+ if (result == 0) {
+ fujitsu_hotkey->logolamp_registered = 1;
+ } else {
+ pr_err("Could not register LED handler for logo lamp, error %i\n",
+ result);
+ }
+ }
+
+ if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
+ (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
+ result = led_classdev_register(&fujitsu->pf_device->dev,
+ &kblamps_led);
+ if (result == 0) {
+ fujitsu_hotkey->kblamps_registered = 1;
+ } else {
+ pr_err("Could not register LED handler for keyboard lamps, error %i\n",
+ result);
+ }
+ }
+#endif
+
+ return result;
+
+err_unregister_input_dev:
+ input_unregister_device(input);
+ input = NULL;
+err_free_input_dev:
+ input_free_device(input);
+err_free_fifo:
+ kfifo_free(&fujitsu_hotkey->fifo);
+err_stop:
+ return result;
+}
+
+static int acpi_fujitsu_hotkey_remove(struct acpi_device *device)
+{
+ struct fujitsu_hotkey_t *fujitsu_hotkey = acpi_driver_data(device);
+ struct input_dev *input = fujitsu_hotkey->input;
+
+#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE)
+ if (fujitsu_hotkey->logolamp_registered)
+ led_classdev_unregister(&logolamp_led);
+
+ if (fujitsu_hotkey->kblamps_registered)
+ led_classdev_unregister(&kblamps_led);
+#endif
+
+ input_unregister_device(input);
+
+ kfifo_free(&fujitsu_hotkey->fifo);
+
+ fujitsu_hotkey->acpi_handle = NULL;
+
+ return 0;
+}
+
+static void acpi_fujitsu_hotkey_notify(struct acpi_device *device, u32 event)
+{
+ struct input_dev *input;
+ int keycode, keycode_r;
+ unsigned int irb = 1;
+ int i, status;
+
+ input = fujitsu_hotkey->input;
+
+ if (fujitsu_hotkey->rfkill_supported)
+ fujitsu_hotkey->rfkill_state =
+ call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
+
+ switch (event) {
+ case ACPI_FUJITSU_NOTIFY_CODE1:
+ i = 0;
+ while ((irb =
+ call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0
+ && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) {
+ switch (irb & 0x4ff) {
+ case KEY1_CODE:
+ keycode = fujitsu->keycode1;
+ break;
+ case KEY2_CODE:
+ keycode = fujitsu->keycode2;
+ break;
+ case KEY3_CODE:
+ keycode = fujitsu->keycode3;
+ break;
+ case KEY4_CODE:
+ keycode = fujitsu->keycode4;
+ break;
+ case 0:
+ keycode = 0;
+ break;
+ default:
+ vdbg_printk(FUJLAPTOP_DBG_WARN,
+ "Unknown GIRB result [%x]\n", irb);
+ keycode = -1;
+ break;
+ }
+ if (keycode > 0) {
+ vdbg_printk(FUJLAPTOP_DBG_TRACE,
+ "Push keycode into ringbuffer [%d]\n",
+ keycode);
+ status = kfifo_in_locked(&fujitsu_hotkey->fifo,
+ (unsigned char *)&keycode,
+ sizeof(keycode),
+ &fujitsu_hotkey->fifo_lock);
+ if (status != sizeof(keycode)) {
+ vdbg_printk(FUJLAPTOP_DBG_WARN,
+ "Could not push keycode [0x%x]\n",
+ keycode);
+ } else {
+ input_report_key(input, keycode, 1);
+ input_sync(input);
+ }
+ } else if (keycode == 0) {
+ while ((status =
+ kfifo_out_locked(
+ &fujitsu_hotkey->fifo,
+ (unsigned char *) &keycode_r,
+ sizeof(keycode_r),
+ &fujitsu_hotkey->fifo_lock))
+ == sizeof(keycode_r)) {
+ input_report_key(input, keycode_r, 0);
+ input_sync(input);
+ vdbg_printk(FUJLAPTOP_DBG_TRACE,
+ "Pop keycode from ringbuffer [%d]\n",
+ keycode_r);
+ }
+ }
+ }
+
+ break;
+ default:
+ keycode = KEY_UNKNOWN;
+ vdbg_printk(FUJLAPTOP_DBG_WARN,
+ "Unsupported event [0x%x]\n", event);
+ input_report_key(input, keycode, 1);
+ input_sync(input);
+ input_report_key(input, keycode, 0);
+ input_sync(input);
+ break;
+ }
+}
+
+/* Initialization */
+
+static const struct acpi_device_id fujitsu_device_ids[] = {
+ {ACPI_FUJITSU_HID, 0},
+ {"", 0},
+};
+
+static struct acpi_driver acpi_fujitsu_driver = {
+ .name = ACPI_FUJITSU_DRIVER_NAME,
+ .class = ACPI_FUJITSU_CLASS,
+ .ids = fujitsu_device_ids,
+ .ops = {
+ .add = acpi_fujitsu_add,
+ .remove = acpi_fujitsu_remove,
+ .notify = acpi_fujitsu_notify,
+ },
+};
+
+static const struct acpi_device_id fujitsu_hotkey_device_ids[] = {
+ {ACPI_FUJITSU_HOTKEY_HID, 0},
+ {"", 0},
+};
+
+static struct acpi_driver acpi_fujitsu_hotkey_driver = {
+ .name = ACPI_FUJITSU_HOTKEY_DRIVER_NAME,
+ .class = ACPI_FUJITSU_CLASS,
+ .ids = fujitsu_hotkey_device_ids,
+ .ops = {
+ .add = acpi_fujitsu_hotkey_add,
+ .remove = acpi_fujitsu_hotkey_remove,
+ .notify = acpi_fujitsu_hotkey_notify,
+ },
+};
+
+static int __init fujitsu_init(void)
+{
+ int ret, result, max_brightness;
+
+ if (acpi_disabled)
+ return -ENODEV;
+
+ fujitsu = kzalloc(sizeof(struct fujitsu_t), GFP_KERNEL);
+ if (!fujitsu)
+ return -ENOMEM;
+ fujitsu->keycode1 = KEY_PROG1;
+ fujitsu->keycode2 = KEY_PROG2;
+ fujitsu->keycode3 = KEY_PROG3;
+ fujitsu->keycode4 = KEY_PROG4;
+ dmi_check_system(fujitsu_dmi_table);
+
+ result = acpi_bus_register_driver(&acpi_fujitsu_driver);
+ if (result < 0) {
+ ret = -ENODEV;
+ goto fail_acpi;
+ }
+
+ /* Register platform stuff */
+
+ fujitsu->pf_device = platform_device_alloc("fujitsu-laptop", -1);
+ if (!fujitsu->pf_device) {
+ ret = -ENOMEM;
+ goto fail_platform_driver;
+ }
+
+ ret = platform_device_add(fujitsu->pf_device);
+ if (ret)
+ goto fail_platform_device1;
+
+ ret =
+ sysfs_create_group(&fujitsu->pf_device->dev.kobj,
+ &fujitsupf_attribute_group);
+ if (ret)
+ goto fail_platform_device2;
+
+ /* Register backlight stuff */
+
+ if (!acpi_video_backlight_support()) {
+ struct backlight_properties props;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ max_brightness = fujitsu->max_brightness;
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = max_brightness - 1;
+ fujitsu->bl_device = backlight_device_register("fujitsu-laptop",
+ NULL, NULL,
+ &fujitsubl_ops,
+ &props);
+ if (IS_ERR(fujitsu->bl_device)) {
+ ret = PTR_ERR(fujitsu->bl_device);
+ fujitsu->bl_device = NULL;
+ goto fail_sysfs_group;
+ }
+ fujitsu->bl_device->props.brightness = fujitsu->brightness_level;
+ }
+
+ ret = platform_driver_register(&fujitsupf_driver);
+ if (ret)
+ goto fail_backlight;
+
+ /* Register hotkey driver */
+
+ fujitsu_hotkey = kzalloc(sizeof(struct fujitsu_hotkey_t), GFP_KERNEL);
+ if (!fujitsu_hotkey) {
+ ret = -ENOMEM;
+ goto fail_hotkey;
+ }
+
+ result = acpi_bus_register_driver(&acpi_fujitsu_hotkey_driver);
+ if (result < 0) {
+ ret = -ENODEV;
+ goto fail_hotkey1;
+ }
+
+ /* Sync backlight power status (needs FUJ02E3 device, hence deferred) */
+
+ if (!acpi_video_backlight_support()) {
+ if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
+ fujitsu->bl_device->props.power = 4;
+ else
+ fujitsu->bl_device->props.power = 0;
+ }
+
+ pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n");
+
+ return 0;
+
+fail_hotkey1:
+ kfree(fujitsu_hotkey);
+fail_hotkey:
+ platform_driver_unregister(&fujitsupf_driver);
+fail_backlight:
+ if (fujitsu->bl_device)
+ backlight_device_unregister(fujitsu->bl_device);
+fail_sysfs_group:
+ sysfs_remove_group(&fujitsu->pf_device->dev.kobj,
+ &fujitsupf_attribute_group);
+fail_platform_device2:
+ platform_device_del(fujitsu->pf_device);
+fail_platform_device1:
+ platform_device_put(fujitsu->pf_device);
+fail_platform_driver:
+ acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+fail_acpi:
+ kfree(fujitsu);
+
+ return ret;
+}
+
+static void __exit fujitsu_cleanup(void)
+{
+ acpi_bus_unregister_driver(&acpi_fujitsu_hotkey_driver);
+
+ kfree(fujitsu_hotkey);
+
+ platform_driver_unregister(&fujitsupf_driver);
+
+ if (fujitsu->bl_device)
+ backlight_device_unregister(fujitsu->bl_device);
+
+ sysfs_remove_group(&fujitsu->pf_device->dev.kobj,
+ &fujitsupf_attribute_group);
+
+ platform_device_unregister(fujitsu->pf_device);
+
+ acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+
+ kfree(fujitsu);
+
+ pr_info("driver unloaded\n");
+}
+
+module_init(fujitsu_init);
+module_exit(fujitsu_cleanup);
+
+module_param(use_alt_lcd_levels, uint, 0644);
+MODULE_PARM_DESC(use_alt_lcd_levels,
+ "Use alternative interface for lcd_levels (needed for Lifebook s6410).");
+module_param(disable_brightness_adjust, uint, 0644);
+MODULE_PARM_DESC(disable_brightness_adjust, "Disable brightness adjustment .");
+#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
+module_param_named(debug, dbg_level, uint, 0644);
+MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
+#endif
+
+MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon");
+MODULE_DESCRIPTION("Fujitsu laptop extras support");
+MODULE_VERSION(FUJITSU_DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*");
+MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1E6:*:cvrS6420:*");
+MODULE_ALIAS("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*");
+
+static struct pnp_device_id pnp_ids[] __used = {
+ {.id = "FUJ02bf"},
+ {.id = "FUJ02B1"},
+ {.id = "FUJ02E3"},
+ {.id = ""}
+};
+
+MODULE_DEVICE_TABLE(pnp, pnp_ids);
diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c
new file mode 100644
index 00000000..570926c1
--- /dev/null
+++ b/drivers/platform/x86/fujitsu-tablet.c
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2006-2012 Robert Gerlach <khnz@gmx.de>
+ * Copyright (C) 2005-2006 Jan Rychter <jan@rychter.com>
+ *
+ * You can redistribute and/or modify this program under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/dmi.h>
+
+#define MODULENAME "fujitsu-tablet"
+
+#define ACPI_FUJITSU_CLASS "fujitsu"
+
+#define INVERT_TABLET_MODE_BIT 0x01
+#define INVERT_DOCK_STATE_BIT 0x02
+#define FORCE_TABLET_MODE_IF_UNDOCK 0x04
+
+#define KEYMAP_LEN 16
+
+static const struct acpi_device_id fujitsu_ids[] = {
+ { .id = "FUJ02BD" },
+ { .id = "FUJ02BF" },
+ { .id = "" }
+};
+
+struct fujitsu_config {
+ unsigned short keymap[KEYMAP_LEN];
+ unsigned int quirks;
+};
+
+static unsigned short keymap_Lifebook_Tseries[KEYMAP_LEN] __initdata = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_SCROLLDOWN,
+ KEY_SCROLLUP,
+ KEY_DIRECTION,
+ KEY_LEFTCTRL,
+ KEY_BRIGHTNESSUP,
+ KEY_BRIGHTNESSDOWN,
+ KEY_BRIGHTNESS_ZERO,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_LEFTALT
+};
+
+static unsigned short keymap_Lifebook_U810[KEYMAP_LEN] __initdata = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_PROG1,
+ KEY_PROG2,
+ KEY_DIRECTION,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_UP,
+ KEY_DOWN,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_LEFTCTRL,
+ KEY_LEFTALT
+};
+
+static unsigned short keymap_Stylistic_Tseries[KEYMAP_LEN] __initdata = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_PRINT,
+ KEY_BACKSPACE,
+ KEY_SPACE,
+ KEY_ENTER,
+ KEY_BRIGHTNESSUP,
+ KEY_BRIGHTNESSDOWN,
+ KEY_DOWN,
+ KEY_UP,
+ KEY_SCROLLUP,
+ KEY_SCROLLDOWN,
+ KEY_LEFTCTRL,
+ KEY_LEFTALT
+};
+
+static unsigned short keymap_Stylistic_ST5xxx[KEYMAP_LEN] __initdata = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_MAIL,
+ KEY_DIRECTION,
+ KEY_ESC,
+ KEY_ENTER,
+ KEY_BRIGHTNESSUP,
+ KEY_BRIGHTNESSDOWN,
+ KEY_DOWN,
+ KEY_UP,
+ KEY_SCROLLUP,
+ KEY_SCROLLDOWN,
+ KEY_LEFTCTRL,
+ KEY_LEFTALT
+};
+
+static struct {
+ struct input_dev *idev;
+ struct fujitsu_config config;
+ unsigned long prev_keymask;
+
+ char phys[21];
+
+ int irq;
+ int io_base;
+ int io_length;
+} fujitsu;
+
+static u8 fujitsu_ack(void)
+{
+ return inb(fujitsu.io_base + 2);
+}
+
+static u8 fujitsu_status(void)
+{
+ return inb(fujitsu.io_base + 6);
+}
+
+static u8 fujitsu_read_register(const u8 addr)
+{
+ outb(addr, fujitsu.io_base);
+ return inb(fujitsu.io_base + 4);
+}
+
+static void fujitsu_send_state(void)
+{
+ int state;
+ int dock, tablet_mode;
+
+ state = fujitsu_read_register(0xdd);
+
+ dock = state & 0x02;
+ if (fujitsu.config.quirks & INVERT_DOCK_STATE_BIT)
+ dock = !dock;
+
+ if ((fujitsu.config.quirks & FORCE_TABLET_MODE_IF_UNDOCK) && (!dock)) {
+ tablet_mode = 1;
+ } else{
+ tablet_mode = state & 0x01;
+ if (fujitsu.config.quirks & INVERT_TABLET_MODE_BIT)
+ tablet_mode = !tablet_mode;
+ }
+
+ input_report_switch(fujitsu.idev, SW_DOCK, dock);
+ input_report_switch(fujitsu.idev, SW_TABLET_MODE, tablet_mode);
+ input_sync(fujitsu.idev);
+}
+
+static void fujitsu_reset(void)
+{
+ int timeout = 50;
+
+ fujitsu_ack();
+
+ while ((fujitsu_status() & 0x02) && (--timeout))
+ msleep(20);
+
+ fujitsu_send_state();
+}
+
+static int input_fujitsu_setup(struct device *parent, const char *name,
+ const char *phys)
+{
+ struct input_dev *idev;
+ int error;
+ int i;
+
+ idev = input_allocate_device();
+ if (!idev)
+ return -ENOMEM;
+
+ idev->dev.parent = parent;
+ idev->phys = phys;
+ idev->name = name;
+ idev->id.bustype = BUS_HOST;
+ idev->id.vendor = 0x1734; /* Fujitsu Siemens Computer GmbH */
+ idev->id.product = 0x0001;
+ idev->id.version = 0x0101;
+
+ idev->keycode = fujitsu.config.keymap;
+ idev->keycodesize = sizeof(fujitsu.config.keymap[0]);
+ idev->keycodemax = ARRAY_SIZE(fujitsu.config.keymap);
+
+ __set_bit(EV_REP, idev->evbit);
+
+ for (i = 0; i < ARRAY_SIZE(fujitsu.config.keymap); i++)
+ if (fujitsu.config.keymap[i])
+ input_set_capability(idev, EV_KEY, fujitsu.config.keymap[i]);
+
+ input_set_capability(idev, EV_MSC, MSC_SCAN);
+
+ input_set_capability(idev, EV_SW, SW_DOCK);
+ input_set_capability(idev, EV_SW, SW_TABLET_MODE);
+
+ error = input_register_device(idev);
+ if (error) {
+ input_free_device(idev);
+ return error;
+ }
+
+ fujitsu.idev = idev;
+ return 0;
+}
+
+static void input_fujitsu_remove(void)
+{
+ input_unregister_device(fujitsu.idev);
+}
+
+static irqreturn_t fujitsu_interrupt(int irq, void *dev_id)
+{
+ unsigned long keymask, changed;
+ unsigned int keycode;
+ int pressed;
+ int i;
+
+ if (unlikely(!(fujitsu_status() & 0x01)))
+ return IRQ_NONE;
+
+ fujitsu_send_state();
+
+ keymask = fujitsu_read_register(0xde);
+ keymask |= fujitsu_read_register(0xdf) << 8;
+ keymask ^= 0xffff;
+
+ changed = keymask ^ fujitsu.prev_keymask;
+ if (changed) {
+ fujitsu.prev_keymask = keymask;
+
+ for_each_set_bit(i, &changed, KEYMAP_LEN) {
+ keycode = fujitsu.config.keymap[i];
+ pressed = keymask & changed & BIT(i);
+
+ if (pressed)
+ input_event(fujitsu.idev, EV_MSC, MSC_SCAN, i);
+
+ input_report_key(fujitsu.idev, keycode, pressed);
+ input_sync(fujitsu.idev);
+ }
+ }
+
+ fujitsu_ack();
+ return IRQ_HANDLED;
+}
+
+static void fujitsu_dmi_common(const struct dmi_system_id *dmi)
+{
+ pr_info("%s\n", dmi->ident);
+ memcpy(fujitsu.config.keymap, dmi->driver_data,
+ sizeof(fujitsu.config.keymap));
+}
+
+static int fujitsu_dmi_lifebook(const struct dmi_system_id *dmi)
+{
+ fujitsu_dmi_common(dmi);
+ fujitsu.config.quirks |= INVERT_TABLET_MODE_BIT;
+ return 1;
+}
+
+static int fujitsu_dmi_stylistic(const struct dmi_system_id *dmi)
+{
+ fujitsu_dmi_common(dmi);
+ fujitsu.config.quirks |= FORCE_TABLET_MODE_IF_UNDOCK;
+ fujitsu.config.quirks |= INVERT_DOCK_STATE_BIT;
+ return 1;
+}
+
+static const struct dmi_system_id dmi_ids[] __initconst = {
+ {
+ .callback = fujitsu_dmi_lifebook,
+ .ident = "Fujitsu Siemens P/T Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK")
+ },
+ .driver_data = keymap_Lifebook_Tseries
+ },
+ {
+ .callback = fujitsu_dmi_lifebook,
+ .ident = "Fujitsu Lifebook T Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T")
+ },
+ .driver_data = keymap_Lifebook_Tseries
+ },
+ {
+ .callback = fujitsu_dmi_stylistic,
+ .ident = "Fujitsu Siemens Stylistic T Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic T")
+ },
+ .driver_data = keymap_Stylistic_Tseries
+ },
+ {
+ .callback = fujitsu_dmi_lifebook,
+ .ident = "Fujitsu LifeBook U810",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook U810")
+ },
+ .driver_data = keymap_Lifebook_U810
+ },
+ {
+ .callback = fujitsu_dmi_stylistic,
+ .ident = "Fujitsu Siemens Stylistic ST5xxx Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "STYLISTIC ST5")
+ },
+ .driver_data = keymap_Stylistic_ST5xxx
+ },
+ {
+ .callback = fujitsu_dmi_stylistic,
+ .ident = "Fujitsu Siemens Stylistic ST5xxx Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic ST5")
+ },
+ .driver_data = keymap_Stylistic_ST5xxx
+ },
+ {
+ .callback = fujitsu_dmi_lifebook,
+ .ident = "Unknown (using defaults)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, ""),
+ DMI_MATCH(DMI_PRODUCT_NAME, "")
+ },
+ .driver_data = keymap_Lifebook_Tseries
+ },
+ { NULL }
+};
+
+static acpi_status fujitsu_walk_resources(struct acpi_resource *res, void *data)
+{
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_IRQ:
+ fujitsu.irq = res->data.irq.interrupts[0];
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_IO:
+ fujitsu.io_base = res->data.io.minimum;
+ fujitsu.io_length = res->data.io.address_length;
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ if (fujitsu.irq && fujitsu.io_base)
+ return AE_OK;
+ else
+ return AE_NOT_FOUND;
+
+ default:
+ return AE_ERROR;
+ }
+}
+
+static int acpi_fujitsu_add(struct acpi_device *adev)
+{
+ acpi_status status;
+ int error;
+
+ if (!adev)
+ return -EINVAL;
+
+ status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
+ fujitsu_walk_resources, NULL);
+ if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base)
+ return -ENODEV;
+
+ sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev));
+ sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS);
+
+ snprintf(fujitsu.phys, sizeof(fujitsu.phys),
+ "%s/input0", acpi_device_hid(adev));
+
+ error = input_fujitsu_setup(&adev->dev,
+ acpi_device_name(adev), fujitsu.phys);
+ if (error)
+ return error;
+
+ if (!request_region(fujitsu.io_base, fujitsu.io_length, MODULENAME)) {
+ input_fujitsu_remove();
+ return -EBUSY;
+ }
+
+ fujitsu_reset();
+
+ error = request_irq(fujitsu.irq, fujitsu_interrupt,
+ IRQF_SHARED, MODULENAME, fujitsu_interrupt);
+ if (error) {
+ release_region(fujitsu.io_base, fujitsu.io_length);
+ input_fujitsu_remove();
+ return error;
+ }
+
+ return 0;
+}
+
+static int acpi_fujitsu_remove(struct acpi_device *adev)
+{
+ free_irq(fujitsu.irq, fujitsu_interrupt);
+ release_region(fujitsu.io_base, fujitsu.io_length);
+ input_fujitsu_remove();
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_fujitsu_resume(struct device *dev)
+{
+ fujitsu_reset();
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(acpi_fujitsu_pm, NULL, acpi_fujitsu_resume);
+
+static struct acpi_driver acpi_fujitsu_driver = {
+ .name = MODULENAME,
+ .class = "hotkey",
+ .ids = fujitsu_ids,
+ .ops = {
+ .add = acpi_fujitsu_add,
+ .remove = acpi_fujitsu_remove,
+ },
+ .drv.pm = &acpi_fujitsu_pm,
+};
+
+static int __init fujitsu_module_init(void)
+{
+ int error;
+
+ dmi_check_system(dmi_ids);
+
+ error = acpi_bus_register_driver(&acpi_fujitsu_driver);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static void __exit fujitsu_module_exit(void)
+{
+ acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+}
+
+module_init(fujitsu_module_init);
+module_exit(fujitsu_module_exit);
+
+MODULE_AUTHOR("Robert Gerlach <khnz@gmx.de>");
+MODULE_DESCRIPTION("Fujitsu tablet pc extras driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("2.5");
+
+MODULE_DEVICE_TABLE(acpi, fujitsu_ids);
diff --git a/drivers/platform/x86/hdaps.c b/drivers/platform/x86/hdaps.c
new file mode 100644
index 00000000..777c7e3d
--- /dev/null
+++ b/drivers/platform/x86/hdaps.c
@@ -0,0 +1,642 @@
+/*
+ * hdaps.c - driver for IBM's Hard Drive Active Protection System
+ *
+ * Copyright (C) 2005 Robert Love <rml@novell.com>
+ * Copyright (C) 2005 Jesper Juhl <jj@chaosbits.net>
+ *
+ * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
+ * starting with the R40, T41, and X40. It provides a basic two-axis
+ * accelerometer and other data, such as the device's temperature.
+ *
+ * This driver is based on the document by Mark A. Smith available at
+ * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
+ * and error.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License v2 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 Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/input-polldev.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/dmi.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+
+#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
+#define HDAPS_NR_PORTS 0x30 /* number of ports: 0x1600 - 0x162f */
+
+#define HDAPS_PORT_STATE 0x1611 /* device state */
+#define HDAPS_PORT_YPOS 0x1612 /* y-axis position */
+#define HDAPS_PORT_XPOS 0x1614 /* x-axis position */
+#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in Celsius */
+#define HDAPS_PORT_YVAR 0x1617 /* y-axis variance (what is this?) */
+#define HDAPS_PORT_XVAR 0x1619 /* x-axis variance (what is this?) */
+#define HDAPS_PORT_TEMP2 0x161b /* device temperature (again?) */
+#define HDAPS_PORT_UNKNOWN 0x161c /* what is this? */
+#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */
+
+#define STATE_FRESH 0x50 /* accelerometer data is fresh */
+
+#define KEYBD_MASK 0x20 /* set if keyboard activity */
+#define MOUSE_MASK 0x40 /* set if mouse activity */
+#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK)) /* keyboard used? */
+#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK)) /* mouse used? */
+
+#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */
+#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */
+
+#define HDAPS_POLL_INTERVAL 50 /* poll for input every 1/20s (50 ms)*/
+#define HDAPS_INPUT_FUZZ 4 /* input event threshold */
+#define HDAPS_INPUT_FLAT 4
+
+#define HDAPS_X_AXIS (1 << 0)
+#define HDAPS_Y_AXIS (1 << 1)
+#define HDAPS_BOTH_AXES (HDAPS_X_AXIS | HDAPS_Y_AXIS)
+
+static struct platform_device *pdev;
+static struct input_polled_dev *hdaps_idev;
+static unsigned int hdaps_invert;
+static u8 km_activity;
+static int rest_x;
+static int rest_y;
+
+static DEFINE_MUTEX(hdaps_mtx);
+
+/*
+ * __get_latch - Get the value from a given port. Callers must hold hdaps_mtx.
+ */
+static inline u8 __get_latch(u16 port)
+{
+ return inb(port) & 0xff;
+}
+
+/*
+ * __check_latch - Check a port latch for a given value. Returns zero if the
+ * port contains the given value. Callers must hold hdaps_mtx.
+ */
+static inline int __check_latch(u16 port, u8 val)
+{
+ if (__get_latch(port) == val)
+ return 0;
+ return -EINVAL;
+}
+
+/*
+ * __wait_latch - Wait up to 100us for a port latch to get a certain value,
+ * returning zero if the value is obtained. Callers must hold hdaps_mtx.
+ */
+static int __wait_latch(u16 port, u8 val)
+{
+ unsigned int i;
+
+ for (i = 0; i < 20; i++) {
+ if (!__check_latch(port, val))
+ return 0;
+ udelay(5);
+ }
+
+ return -EIO;
+}
+
+/*
+ * __device_refresh - request a refresh from the accelerometer. Does not wait
+ * for refresh to complete. Callers must hold hdaps_mtx.
+ */
+static void __device_refresh(void)
+{
+ udelay(200);
+ if (inb(0x1604) != STATE_FRESH) {
+ outb(0x11, 0x1610);
+ outb(0x01, 0x161f);
+ }
+}
+
+/*
+ * __device_refresh_sync - request a synchronous refresh from the
+ * accelerometer. We wait for the refresh to complete. Returns zero if
+ * successful and nonzero on error. Callers must hold hdaps_mtx.
+ */
+static int __device_refresh_sync(void)
+{
+ __device_refresh();
+ return __wait_latch(0x1604, STATE_FRESH);
+}
+
+/*
+ * __device_complete - indicate to the accelerometer that we are done reading
+ * data, and then initiate an async refresh. Callers must hold hdaps_mtx.
+ */
+static inline void __device_complete(void)
+{
+ inb(0x161f);
+ inb(0x1604);
+ __device_refresh();
+}
+
+/*
+ * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
+ * the given pointer. Returns zero on success or a negative error on failure.
+ * Can sleep.
+ */
+static int hdaps_readb_one(unsigned int port, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&hdaps_mtx);
+
+ /* do a sync refresh -- we need to be sure that we read fresh data */
+ ret = __device_refresh_sync();
+ if (ret)
+ goto out;
+
+ *val = inb(port);
+ __device_complete();
+
+out:
+ mutex_unlock(&hdaps_mtx);
+ return ret;
+}
+
+/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
+static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
+ int *x, int *y)
+{
+ /* do a sync refresh -- we need to be sure that we read fresh data */
+ if (__device_refresh_sync())
+ return -EIO;
+
+ *y = inw(port2);
+ *x = inw(port1);
+ km_activity = inb(HDAPS_PORT_KMACT);
+ __device_complete();
+
+ /* hdaps_invert is a bitvector to negate the axes */
+ if (hdaps_invert & HDAPS_X_AXIS)
+ *x = -*x;
+ if (hdaps_invert & HDAPS_Y_AXIS)
+ *y = -*y;
+
+ return 0;
+}
+
+/*
+ * hdaps_read_pair - reads the values from a pair of ports, placing the values
+ * in the given pointers. Returns zero on success. Can sleep.
+ */
+static int hdaps_read_pair(unsigned int port1, unsigned int port2,
+ int *val1, int *val2)
+{
+ int ret;
+
+ mutex_lock(&hdaps_mtx);
+ ret = __hdaps_read_pair(port1, port2, val1, val2);
+ mutex_unlock(&hdaps_mtx);
+
+ return ret;
+}
+
+/*
+ * hdaps_device_init - initialize the accelerometer. Returns zero on success
+ * and negative error code on failure. Can sleep.
+ */
+static int hdaps_device_init(void)
+{
+ int total, ret = -ENXIO;
+
+ mutex_lock(&hdaps_mtx);
+
+ outb(0x13, 0x1610);
+ outb(0x01, 0x161f);
+ if (__wait_latch(0x161f, 0x00))
+ goto out;
+
+ /*
+ * Most ThinkPads return 0x01.
+ *
+ * Others--namely the R50p, T41p, and T42p--return 0x03. These laptops
+ * have "inverted" axises.
+ *
+ * The 0x02 value occurs when the chip has been previously initialized.
+ */
+ if (__check_latch(0x1611, 0x03) &&
+ __check_latch(0x1611, 0x02) &&
+ __check_latch(0x1611, 0x01))
+ goto out;
+
+ printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",
+ __get_latch(0x1611));
+
+ outb(0x17, 0x1610);
+ outb(0x81, 0x1611);
+ outb(0x01, 0x161f);
+ if (__wait_latch(0x161f, 0x00))
+ goto out;
+ if (__wait_latch(0x1611, 0x00))
+ goto out;
+ if (__wait_latch(0x1612, 0x60))
+ goto out;
+ if (__wait_latch(0x1613, 0x00))
+ goto out;
+ outb(0x14, 0x1610);
+ outb(0x01, 0x1611);
+ outb(0x01, 0x161f);
+ if (__wait_latch(0x161f, 0x00))
+ goto out;
+ outb(0x10, 0x1610);
+ outb(0xc8, 0x1611);
+ outb(0x00, 0x1612);
+ outb(0x02, 0x1613);
+ outb(0x01, 0x161f);
+ if (__wait_latch(0x161f, 0x00))
+ goto out;
+ if (__device_refresh_sync())
+ goto out;
+ if (__wait_latch(0x1611, 0x00))
+ goto out;
+
+ /* we have done our dance, now let's wait for the applause */
+ for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
+ int x, y;
+
+ /* a read of the device helps push it into action */
+ __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
+ if (!__wait_latch(0x1611, 0x02)) {
+ ret = 0;
+ break;
+ }
+
+ msleep(INIT_WAIT_MSECS);
+ }
+
+out:
+ mutex_unlock(&hdaps_mtx);
+ return ret;
+}
+
+
+/* Device model stuff */
+
+static int hdaps_probe(struct platform_device *dev)
+{
+ int ret;
+
+ ret = hdaps_device_init();
+ if (ret)
+ return ret;
+
+ pr_info("device successfully initialized\n");
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hdaps_resume(struct device *dev)
+{
+ return hdaps_device_init();
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
+
+static struct platform_driver hdaps_driver = {
+ .probe = hdaps_probe,
+ .driver = {
+ .name = "hdaps",
+ .owner = THIS_MODULE,
+ .pm = &hdaps_pm,
+ },
+};
+
+/*
+ * hdaps_calibrate - Set our "resting" values. Callers must hold hdaps_mtx.
+ */
+static void hdaps_calibrate(void)
+{
+ __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
+}
+
+static void hdaps_mousedev_poll(struct input_polled_dev *dev)
+{
+ struct input_dev *input_dev = dev->input;
+ int x, y;
+
+ mutex_lock(&hdaps_mtx);
+
+ if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
+ goto out;
+
+ input_report_abs(input_dev, ABS_X, x - rest_x);
+ input_report_abs(input_dev, ABS_Y, y - rest_y);
+ input_sync(input_dev);
+
+out:
+ mutex_unlock(&hdaps_mtx);
+}
+
+
+/* Sysfs Files */
+
+static ssize_t hdaps_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret, x, y;
+
+ ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "(%d,%d)\n", x, y);
+}
+
+static ssize_t hdaps_variance_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret, x, y;
+
+ ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "(%d,%d)\n", x, y);
+}
+
+static ssize_t hdaps_temp1_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 uninitialized_var(temp);
+ int ret;
+
+ ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%u\n", temp);
+}
+
+static ssize_t hdaps_temp2_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 uninitialized_var(temp);
+ int ret;
+
+ ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%u\n", temp);
+}
+
+static ssize_t hdaps_keyboard_activity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
+}
+
+static ssize_t hdaps_mouse_activity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
+}
+
+static ssize_t hdaps_calibrate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
+}
+
+static ssize_t hdaps_calibrate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ mutex_lock(&hdaps_mtx);
+ hdaps_calibrate();
+ mutex_unlock(&hdaps_mtx);
+
+ return count;
+}
+
+static ssize_t hdaps_invert_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%u\n", hdaps_invert);
+}
+
+static ssize_t hdaps_invert_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int invert;
+
+ if (sscanf(buf, "%d", &invert) != 1 ||
+ invert < 0 || invert > HDAPS_BOTH_AXES)
+ return -EINVAL;
+
+ hdaps_invert = invert;
+ hdaps_calibrate();
+
+ return count;
+}
+
+static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
+static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
+static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
+static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
+static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
+static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
+static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
+static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
+
+static struct attribute *hdaps_attributes[] = {
+ &dev_attr_position.attr,
+ &dev_attr_variance.attr,
+ &dev_attr_temp1.attr,
+ &dev_attr_temp2.attr,
+ &dev_attr_keyboard_activity.attr,
+ &dev_attr_mouse_activity.attr,
+ &dev_attr_calibrate.attr,
+ &dev_attr_invert.attr,
+ NULL,
+};
+
+static struct attribute_group hdaps_attribute_group = {
+ .attrs = hdaps_attributes,
+};
+
+
+/* Module stuff */
+
+/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
+static int __init hdaps_dmi_match(const struct dmi_system_id *id)
+{
+ pr_info("%s detected\n", id->ident);
+ return 1;
+}
+
+/* hdaps_dmi_match_invert - found an inverted match. */
+static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
+{
+ hdaps_invert = (unsigned long)id->driver_data;
+ pr_info("inverting axis (%u) readings\n", hdaps_invert);
+ return hdaps_dmi_match(id);
+}
+
+#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) { \
+ .ident = vendor " " model, \
+ .callback = hdaps_dmi_match_invert, \
+ .driver_data = (void *)axes, \
+ .matches = { \
+ DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
+ DMI_MATCH(DMI_PRODUCT_VERSION, model) \
+ } \
+}
+
+#define HDAPS_DMI_MATCH_NORMAL(vendor, model) \
+ HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
+
+/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
+ "ThinkPad T42p", so the order of the entries matters.
+ If your ThinkPad is not recognized, please update to latest
+ BIOS. This is especially the case for some R52 ThinkPads. */
+static struct dmi_system_id __initdata hdaps_whitelist[] = {
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
+ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
+ HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
+ { .ident = NULL }
+};
+
+static int __init hdaps_init(void)
+{
+ struct input_dev *idev;
+ int ret;
+
+ if (!dmi_check_system(hdaps_whitelist)) {
+ pr_warn("supported laptop not found!\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
+ ret = -ENXIO;
+ goto out;
+ }
+
+ ret = platform_driver_register(&hdaps_driver);
+ if (ret)
+ goto out_region;
+
+ pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ ret = PTR_ERR(pdev);
+ goto out_driver;
+ }
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
+ if (ret)
+ goto out_device;
+
+ hdaps_idev = input_allocate_polled_device();
+ if (!hdaps_idev) {
+ ret = -ENOMEM;
+ goto out_group;
+ }
+
+ hdaps_idev->poll = hdaps_mousedev_poll;
+ hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
+
+ /* initial calibrate for the input device */
+ hdaps_calibrate();
+
+ /* initialize the input class */
+ idev = hdaps_idev->input;
+ idev->name = "hdaps";
+ idev->phys = "isa1600/input0";
+ idev->id.bustype = BUS_ISA;
+ idev->dev.parent = &pdev->dev;
+ idev->evbit[0] = BIT_MASK(EV_ABS);
+ input_set_abs_params(idev, ABS_X,
+ -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
+ input_set_abs_params(idev, ABS_Y,
+ -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
+
+ ret = input_register_polled_device(hdaps_idev);
+ if (ret)
+ goto out_idev;
+
+ pr_info("driver successfully loaded\n");
+ return 0;
+
+out_idev:
+ input_free_polled_device(hdaps_idev);
+out_group:
+ sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
+out_device:
+ platform_device_unregister(pdev);
+out_driver:
+ platform_driver_unregister(&hdaps_driver);
+out_region:
+ release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
+out:
+ pr_warn("driver init failed (ret=%d)!\n", ret);
+ return ret;
+}
+
+static void __exit hdaps_exit(void)
+{
+ input_unregister_polled_device(hdaps_idev);
+ input_free_polled_device(hdaps_idev);
+ sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
+ platform_device_unregister(pdev);
+ platform_driver_unregister(&hdaps_driver);
+ release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
+
+ pr_info("driver unloaded\n");
+}
+
+module_init(hdaps_init);
+module_exit(hdaps_exit);
+
+module_param_named(invert, hdaps_invert, int, 0);
+MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
+ "2 invert y-axis, 3 invert both axes.");
+
+MODULE_AUTHOR("Robert Love");
+MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
new file mode 100644
index 00000000..d111c868
--- /dev/null
+++ b/drivers/platform/x86/hp-wmi.c
@@ -0,0 +1,988 @@
+/*
+ * HP WMI hotkeys
+ *
+ * Copyright (C) 2008 Red Hat <mjg@redhat.com>
+ * Copyright (C) 2010, 2011 Anssi Hannula <anssi.hannula@iki.fi>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+#include <linux/string.h>
+
+MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
+MODULE_DESCRIPTION("HP laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
+MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4");
+
+#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
+#define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
+
+#define HPWMI_DISPLAY_QUERY 0x1
+#define HPWMI_HDDTEMP_QUERY 0x2
+#define HPWMI_ALS_QUERY 0x3
+#define HPWMI_HARDWARE_QUERY 0x4
+#define HPWMI_WIRELESS_QUERY 0x5
+#define HPWMI_HOTKEY_QUERY 0xc
+#define HPWMI_WIRELESS2_QUERY 0x1b
+
+enum hp_wmi_radio {
+ HPWMI_WIFI = 0,
+ HPWMI_BLUETOOTH = 1,
+ HPWMI_WWAN = 2,
+ HPWMI_GPS = 3,
+};
+
+enum hp_wmi_event_ids {
+ HPWMI_DOCK_EVENT = 1,
+ HPWMI_PARK_HDD = 2,
+ HPWMI_SMART_ADAPTER = 3,
+ HPWMI_BEZEL_BUTTON = 4,
+ HPWMI_WIRELESS = 5,
+ HPWMI_CPU_BATTERY_THROTTLE = 6,
+ HPWMI_LOCK_SWITCH = 7,
+ HPWMI_LID_SWITCH = 8,
+ HPWMI_SCREEN_ROTATION = 9,
+ HPWMI_COOLSENSE_SYSTEM_MOBILE = 0x0A,
+ HPWMI_COOLSENSE_SYSTEM_HOT = 0x0B,
+ HPWMI_PROXIMITY_SENSOR = 0x0C,
+ HPWMI_BACKLIT_KB_BRIGHTNESS = 0x0D,
+ HPWMI_PEAKSHIFT_PERIOD = 0x0F,
+ HPWMI_BATTERY_CHARGE_PERIOD = 0x10,
+};
+
+struct bios_args {
+ u32 signature;
+ u32 command;
+ u32 commandtype;
+ u32 datasize;
+ u32 data;
+};
+
+struct bios_return {
+ u32 sigpass;
+ u32 return_code;
+};
+
+enum hp_return_value {
+ HPWMI_RET_WRONG_SIGNATURE = 0x02,
+ HPWMI_RET_UNKNOWN_COMMAND = 0x03,
+ HPWMI_RET_UNKNOWN_CMDTYPE = 0x04,
+ HPWMI_RET_INVALID_PARAMETERS = 0x05,
+};
+
+enum hp_wireless2_bits {
+ HPWMI_POWER_STATE = 0x01,
+ HPWMI_POWER_SOFT = 0x02,
+ HPWMI_POWER_BIOS = 0x04,
+ HPWMI_POWER_HARD = 0x08,
+};
+
+#define IS_HWBLOCKED(x) ((x & (HPWMI_POWER_BIOS | HPWMI_POWER_HARD)) \
+ != (HPWMI_POWER_BIOS | HPWMI_POWER_HARD))
+#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
+
+struct bios_rfkill2_device_state {
+ u8 radio_type;
+ u8 bus_type;
+ u16 vendor_id;
+ u16 product_id;
+ u16 subsys_vendor_id;
+ u16 subsys_product_id;
+ u8 rfkill_id;
+ u8 power;
+ u8 unknown[4];
+};
+
+/* 7 devices fit into the 128 byte buffer */
+#define HPWMI_MAX_RFKILL2_DEVICES 7
+
+struct bios_rfkill2_state {
+ u8 unknown[7];
+ u8 count;
+ u8 pad[8];
+ struct bios_rfkill2_device_state device[HPWMI_MAX_RFKILL2_DEVICES];
+};
+
+static const struct key_entry hp_wmi_keymap[] = {
+ { KE_KEY, 0x02, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 0x03, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, 0x20e6, { KEY_PROG1 } },
+ { KE_KEY, 0x20e8, { KEY_MEDIA } },
+ { KE_KEY, 0x2142, { KEY_MEDIA } },
+ { KE_KEY, 0x213b, { KEY_INFO } },
+ { KE_KEY, 0x2169, { KEY_DIRECTION } },
+ { KE_KEY, 0x231b, { KEY_HELP } },
+ { KE_END, 0 }
+};
+
+static struct input_dev *hp_wmi_input_dev;
+static struct platform_device *hp_wmi_platform_dev;
+
+static struct rfkill *wifi_rfkill;
+static struct rfkill *bluetooth_rfkill;
+static struct rfkill *wwan_rfkill;
+static struct rfkill *gps_rfkill;
+
+struct rfkill2_device {
+ u8 id;
+ int num;
+ struct rfkill *rfkill;
+};
+
+static int rfkill2_count;
+static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES];
+
+/*
+ * hp_wmi_perform_query
+ *
+ * query: The commandtype -> What should be queried
+ * write: The command -> 0 read, 1 write, 3 ODM specific
+ * buffer: Buffer used as input and/or output
+ * insize: Size of input buffer
+ * outsize: Size of output buffer
+ *
+ * returns zero on success
+ * an HP WMI query specific error code (which is positive)
+ * -EINVAL if the query was not successful at all
+ * -EINVAL if the output buffer size exceeds buffersize
+ *
+ * Note: The buffersize must at least be the maximum of the input and output
+ * size. E.g. Battery info query (0x7) is defined to have 1 byte input
+ * and 128 byte output. The caller would do:
+ * buffer = kzalloc(128, GFP_KERNEL);
+ * ret = hp_wmi_perform_query(0x7, 0, buffer, 1, 128)
+ */
+static int hp_wmi_perform_query(int query, int write, void *buffer,
+ int insize, int outsize)
+{
+ struct bios_return *bios_return;
+ int actual_outsize;
+ union acpi_object *obj;
+ struct bios_args args = {
+ .signature = 0x55434553,
+ .command = write ? 0x2 : 0x1,
+ .commandtype = query,
+ .datasize = insize,
+ .data = 0,
+ };
+ struct acpi_buffer input = { sizeof(struct bios_args), &args };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ u32 rc;
+
+ if (WARN_ON(insize > sizeof(args.data)))
+ return -EINVAL;
+ memcpy(&args.data, buffer, insize);
+
+ wmi_evaluate_method(HPWMI_BIOS_GUID, 0, 0x3, &input, &output);
+
+ obj = output.pointer;
+
+ if (!obj)
+ return -EINVAL;
+ else if (obj->type != ACPI_TYPE_BUFFER) {
+ kfree(obj);
+ return -EINVAL;
+ }
+
+ bios_return = (struct bios_return *)obj->buffer.pointer;
+ rc = bios_return->return_code;
+
+ if (rc) {
+ if (rc != HPWMI_RET_UNKNOWN_CMDTYPE)
+ pr_warn("query 0x%x returned error 0x%x\n", query, rc);
+ kfree(obj);
+ return rc;
+ }
+
+ if (!outsize) {
+ /* ignore output data */
+ kfree(obj);
+ return 0;
+ }
+
+ actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
+ memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
+ memset(buffer + actual_outsize, 0, outsize - actual_outsize);
+ kfree(obj);
+ return 0;
+}
+
+static int hp_wmi_display_state(void)
+{
+ int state = 0;
+ int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state,
+ sizeof(state), sizeof(state));
+ if (ret)
+ return -EINVAL;
+ return state;
+}
+
+static int hp_wmi_hddtemp_state(void)
+{
+ int state = 0;
+ int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, &state,
+ sizeof(state), sizeof(state));
+ if (ret)
+ return -EINVAL;
+ return state;
+}
+
+static int hp_wmi_als_state(void)
+{
+ int state = 0;
+ int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, &state,
+ sizeof(state), sizeof(state));
+ if (ret)
+ return -EINVAL;
+ return state;
+}
+
+static int hp_wmi_dock_state(void)
+{
+ int state = 0;
+ int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state,
+ sizeof(state), sizeof(state));
+
+ if (ret)
+ return -EINVAL;
+
+ return state & 0x1;
+}
+
+static int hp_wmi_tablet_state(void)
+{
+ int state = 0;
+ int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state,
+ sizeof(state), sizeof(state));
+ if (ret)
+ return ret;
+
+ return (state & 0x4) ? 1 : 0;
+}
+
+static int hp_wmi_set_block(void *data, bool blocked)
+{
+ enum hp_wmi_radio r = (enum hp_wmi_radio) data;
+ int query = BIT(r + 8) | ((!blocked) << r);
+ int ret;
+
+ ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1,
+ &query, sizeof(query), 0);
+ if (ret)
+ return -EINVAL;
+ return 0;
+}
+
+static const struct rfkill_ops hp_wmi_rfkill_ops = {
+ .set_block = hp_wmi_set_block,
+};
+
+static bool hp_wmi_get_sw_state(enum hp_wmi_radio r)
+{
+ int wireless = 0;
+ int mask;
+ hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0,
+ &wireless, sizeof(wireless),
+ sizeof(wireless));
+ /* TBD: Pass error */
+
+ mask = 0x200 << (r * 8);
+
+ if (wireless & mask)
+ return false;
+ else
+ return true;
+}
+
+static bool hp_wmi_get_hw_state(enum hp_wmi_radio r)
+{
+ int wireless = 0;
+ int mask;
+ hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0,
+ &wireless, sizeof(wireless),
+ sizeof(wireless));
+ /* TBD: Pass error */
+
+ mask = 0x800 << (r * 8);
+
+ if (wireless & mask)
+ return false;
+ else
+ return true;
+}
+
+static int hp_wmi_rfkill2_set_block(void *data, bool blocked)
+{
+ int rfkill_id = (int)(long)data;
+ char buffer[4] = { 0x01, 0x00, rfkill_id, !blocked };
+
+ if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1,
+ buffer, sizeof(buffer), 0))
+ return -EINVAL;
+ return 0;
+}
+
+static const struct rfkill_ops hp_wmi_rfkill2_ops = {
+ .set_block = hp_wmi_rfkill2_set_block,
+};
+
+static int hp_wmi_rfkill2_refresh(void)
+{
+ int err, i;
+ struct bios_rfkill2_state state;
+
+ err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state,
+ 0, sizeof(state));
+ if (err)
+ return err;
+
+ for (i = 0; i < rfkill2_count; i++) {
+ int num = rfkill2[i].num;
+ struct bios_rfkill2_device_state *devstate;
+ devstate = &state.device[num];
+
+ if (num >= state.count ||
+ devstate->rfkill_id != rfkill2[i].id) {
+ pr_warn("power configuration of the wireless devices unexpectedly changed\n");
+ continue;
+ }
+
+ rfkill_set_states(rfkill2[i].rfkill,
+ IS_SWBLOCKED(devstate->power),
+ IS_HWBLOCKED(devstate->power));
+ }
+
+ return 0;
+}
+
+static ssize_t show_display(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int value = hp_wmi_display_state();
+ if (value < 0)
+ return -EINVAL;
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_hddtemp(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int value = hp_wmi_hddtemp_state();
+ if (value < 0)
+ return -EINVAL;
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_als(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int value = hp_wmi_als_state();
+ if (value < 0)
+ return -EINVAL;
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_dock(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int value = hp_wmi_dock_state();
+ if (value < 0)
+ return -EINVAL;
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t show_tablet(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int value = hp_wmi_tablet_state();
+ if (value < 0)
+ return -EINVAL;
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t set_als(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 tmp = simple_strtoul(buf, NULL, 10);
+ int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, &tmp,
+ sizeof(tmp), sizeof(tmp));
+ if (ret)
+ return -EINVAL;
+
+ return count;
+}
+
+static DEVICE_ATTR(display, S_IRUGO, show_display, NULL);
+static DEVICE_ATTR(hddtemp, S_IRUGO, show_hddtemp, NULL);
+static DEVICE_ATTR(als, S_IRUGO | S_IWUSR, show_als, set_als);
+static DEVICE_ATTR(dock, S_IRUGO, show_dock, NULL);
+static DEVICE_ATTR(tablet, S_IRUGO, show_tablet, NULL);
+
+static void hp_wmi_notify(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ u32 event_id, event_data;
+ int key_code = 0, ret;
+ u32 *location;
+ acpi_status status;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_info("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+
+ if (!obj)
+ return;
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ pr_info("Unknown response received %d\n", obj->type);
+ kfree(obj);
+ return;
+ }
+
+ /*
+ * Depending on ACPI version the concatenation of id and event data
+ * inside _WED function will result in a 8 or 16 byte buffer.
+ */
+ location = (u32 *)obj->buffer.pointer;
+ if (obj->buffer.length == 8) {
+ event_id = *location;
+ event_data = *(location + 1);
+ } else if (obj->buffer.length == 16) {
+ event_id = *location;
+ event_data = *(location + 2);
+ } else {
+ pr_info("Unknown buffer length %d\n", obj->buffer.length);
+ kfree(obj);
+ return;
+ }
+ kfree(obj);
+
+ switch (event_id) {
+ case HPWMI_DOCK_EVENT:
+ input_report_switch(hp_wmi_input_dev, SW_DOCK,
+ hp_wmi_dock_state());
+ input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
+ hp_wmi_tablet_state());
+ input_sync(hp_wmi_input_dev);
+ break;
+ case HPWMI_PARK_HDD:
+ break;
+ case HPWMI_SMART_ADAPTER:
+ break;
+ case HPWMI_BEZEL_BUTTON:
+ ret = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0,
+ &key_code,
+ sizeof(key_code),
+ sizeof(key_code));
+ if (ret)
+ break;
+
+ if (!sparse_keymap_report_event(hp_wmi_input_dev,
+ key_code, 1, true))
+ pr_info("Unknown key code - 0x%x\n", key_code);
+ break;
+ case HPWMI_WIRELESS:
+ if (rfkill2_count) {
+ hp_wmi_rfkill2_refresh();
+ break;
+ }
+
+ if (wifi_rfkill)
+ rfkill_set_states(wifi_rfkill,
+ hp_wmi_get_sw_state(HPWMI_WIFI),
+ hp_wmi_get_hw_state(HPWMI_WIFI));
+ if (bluetooth_rfkill)
+ rfkill_set_states(bluetooth_rfkill,
+ hp_wmi_get_sw_state(HPWMI_BLUETOOTH),
+ hp_wmi_get_hw_state(HPWMI_BLUETOOTH));
+ if (wwan_rfkill)
+ rfkill_set_states(wwan_rfkill,
+ hp_wmi_get_sw_state(HPWMI_WWAN),
+ hp_wmi_get_hw_state(HPWMI_WWAN));
+ if (gps_rfkill)
+ rfkill_set_states(gps_rfkill,
+ hp_wmi_get_sw_state(HPWMI_GPS),
+ hp_wmi_get_hw_state(HPWMI_GPS));
+ break;
+ case HPWMI_CPU_BATTERY_THROTTLE:
+ pr_info("Unimplemented CPU throttle because of 3 Cell battery event detected\n");
+ break;
+ case HPWMI_LOCK_SWITCH:
+ break;
+ case HPWMI_LID_SWITCH:
+ break;
+ case HPWMI_SCREEN_ROTATION:
+ break;
+ case HPWMI_COOLSENSE_SYSTEM_MOBILE:
+ break;
+ case HPWMI_COOLSENSE_SYSTEM_HOT:
+ break;
+ case HPWMI_PROXIMITY_SENSOR:
+ break;
+ case HPWMI_BACKLIT_KB_BRIGHTNESS:
+ break;
+ case HPWMI_PEAKSHIFT_PERIOD:
+ break;
+ case HPWMI_BATTERY_CHARGE_PERIOD:
+ break;
+ default:
+ pr_info("Unknown event_id - %d - 0x%x\n", event_id, event_data);
+ break;
+ }
+}
+
+static int __init hp_wmi_input_setup(void)
+{
+ acpi_status status;
+ int err;
+
+ hp_wmi_input_dev = input_allocate_device();
+ if (!hp_wmi_input_dev)
+ return -ENOMEM;
+
+ hp_wmi_input_dev->name = "HP WMI hotkeys";
+ hp_wmi_input_dev->phys = "wmi/input0";
+ hp_wmi_input_dev->id.bustype = BUS_HOST;
+
+ __set_bit(EV_SW, hp_wmi_input_dev->evbit);
+ __set_bit(SW_DOCK, hp_wmi_input_dev->swbit);
+ __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit);
+
+ err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL);
+ if (err)
+ goto err_free_dev;
+
+ /* Set initial hardware state */
+ input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state());
+ input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
+ hp_wmi_tablet_state());
+ input_sync(hp_wmi_input_dev);
+
+ status = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL);
+ if (ACPI_FAILURE(status)) {
+ err = -EIO;
+ goto err_free_keymap;
+ }
+
+ err = input_register_device(hp_wmi_input_dev);
+ if (err)
+ goto err_uninstall_notifier;
+
+ return 0;
+
+ err_uninstall_notifier:
+ wmi_remove_notify_handler(HPWMI_EVENT_GUID);
+ err_free_keymap:
+ sparse_keymap_free(hp_wmi_input_dev);
+ err_free_dev:
+ input_free_device(hp_wmi_input_dev);
+ return err;
+}
+
+static void hp_wmi_input_destroy(void)
+{
+ wmi_remove_notify_handler(HPWMI_EVENT_GUID);
+ sparse_keymap_free(hp_wmi_input_dev);
+ input_unregister_device(hp_wmi_input_dev);
+}
+
+static void cleanup_sysfs(struct platform_device *device)
+{
+ device_remove_file(&device->dev, &dev_attr_display);
+ device_remove_file(&device->dev, &dev_attr_hddtemp);
+ device_remove_file(&device->dev, &dev_attr_als);
+ device_remove_file(&device->dev, &dev_attr_dock);
+ device_remove_file(&device->dev, &dev_attr_tablet);
+}
+
+static int hp_wmi_rfkill_setup(struct platform_device *device)
+{
+ int err;
+ int wireless = 0;
+
+ err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless,
+ sizeof(wireless), sizeof(wireless));
+ if (err)
+ return err;
+
+ if (wireless & 0x1) {
+ wifi_rfkill = rfkill_alloc("hp-wifi", &device->dev,
+ RFKILL_TYPE_WLAN,
+ &hp_wmi_rfkill_ops,
+ (void *) HPWMI_WIFI);
+ if (!wifi_rfkill)
+ return -ENOMEM;
+ rfkill_init_sw_state(wifi_rfkill,
+ hp_wmi_get_sw_state(HPWMI_WIFI));
+ rfkill_set_hw_state(wifi_rfkill,
+ hp_wmi_get_hw_state(HPWMI_WIFI));
+ err = rfkill_register(wifi_rfkill);
+ if (err)
+ goto register_wifi_error;
+ }
+
+ if (wireless & 0x2) {
+ bluetooth_rfkill = rfkill_alloc("hp-bluetooth", &device->dev,
+ RFKILL_TYPE_BLUETOOTH,
+ &hp_wmi_rfkill_ops,
+ (void *) HPWMI_BLUETOOTH);
+ if (!bluetooth_rfkill) {
+ err = -ENOMEM;
+ goto register_wifi_error;
+ }
+ rfkill_init_sw_state(bluetooth_rfkill,
+ hp_wmi_get_sw_state(HPWMI_BLUETOOTH));
+ rfkill_set_hw_state(bluetooth_rfkill,
+ hp_wmi_get_hw_state(HPWMI_BLUETOOTH));
+ err = rfkill_register(bluetooth_rfkill);
+ if (err)
+ goto register_bluetooth_error;
+ }
+
+ if (wireless & 0x4) {
+ wwan_rfkill = rfkill_alloc("hp-wwan", &device->dev,
+ RFKILL_TYPE_WWAN,
+ &hp_wmi_rfkill_ops,
+ (void *) HPWMI_WWAN);
+ if (!wwan_rfkill) {
+ err = -ENOMEM;
+ goto register_gps_error;
+ }
+ rfkill_init_sw_state(wwan_rfkill,
+ hp_wmi_get_sw_state(HPWMI_WWAN));
+ rfkill_set_hw_state(wwan_rfkill,
+ hp_wmi_get_hw_state(HPWMI_WWAN));
+ err = rfkill_register(wwan_rfkill);
+ if (err)
+ goto register_wwan_err;
+ }
+
+ if (wireless & 0x8) {
+ gps_rfkill = rfkill_alloc("hp-gps", &device->dev,
+ RFKILL_TYPE_GPS,
+ &hp_wmi_rfkill_ops,
+ (void *) HPWMI_GPS);
+ if (!gps_rfkill) {
+ err = -ENOMEM;
+ goto register_bluetooth_error;
+ }
+ rfkill_init_sw_state(gps_rfkill,
+ hp_wmi_get_sw_state(HPWMI_GPS));
+ rfkill_set_hw_state(gps_rfkill,
+ hp_wmi_get_hw_state(HPWMI_GPS));
+ err = rfkill_register(gps_rfkill);
+ if (err)
+ goto register_gps_error;
+ }
+
+ return 0;
+register_wwan_err:
+ rfkill_destroy(wwan_rfkill);
+ wwan_rfkill = NULL;
+ if (gps_rfkill)
+ rfkill_unregister(gps_rfkill);
+register_gps_error:
+ rfkill_destroy(gps_rfkill);
+ gps_rfkill = NULL;
+ if (bluetooth_rfkill)
+ rfkill_unregister(bluetooth_rfkill);
+register_bluetooth_error:
+ rfkill_destroy(bluetooth_rfkill);
+ bluetooth_rfkill = NULL;
+ if (wifi_rfkill)
+ rfkill_unregister(wifi_rfkill);
+register_wifi_error:
+ rfkill_destroy(wifi_rfkill);
+ wifi_rfkill = NULL;
+ return err;
+}
+
+static int hp_wmi_rfkill2_setup(struct platform_device *device)
+{
+ int err, i;
+ struct bios_rfkill2_state state;
+ err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state,
+ 0, sizeof(state));
+ if (err)
+ return err;
+
+ if (state.count > HPWMI_MAX_RFKILL2_DEVICES) {
+ pr_warn("unable to parse 0x1b query output\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < state.count; i++) {
+ struct rfkill *rfkill;
+ enum rfkill_type type;
+ char *name;
+ switch (state.device[i].radio_type) {
+ case HPWMI_WIFI:
+ type = RFKILL_TYPE_WLAN;
+ name = "hp-wifi";
+ break;
+ case HPWMI_BLUETOOTH:
+ type = RFKILL_TYPE_BLUETOOTH;
+ name = "hp-bluetooth";
+ break;
+ case HPWMI_WWAN:
+ type = RFKILL_TYPE_WWAN;
+ name = "hp-wwan";
+ break;
+ case HPWMI_GPS:
+ type = RFKILL_TYPE_GPS;
+ name = "hp-gps";
+ break;
+ default:
+ pr_warn("unknown device type 0x%x\n",
+ state.device[i].radio_type);
+ continue;
+ }
+
+ if (!state.device[i].vendor_id) {
+ pr_warn("zero device %d while %d reported\n",
+ i, state.count);
+ continue;
+ }
+
+ rfkill = rfkill_alloc(name, &device->dev, type,
+ &hp_wmi_rfkill2_ops, (void *)(long)i);
+ if (!rfkill) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ rfkill2[rfkill2_count].id = state.device[i].rfkill_id;
+ rfkill2[rfkill2_count].num = i;
+ rfkill2[rfkill2_count].rfkill = rfkill;
+
+ rfkill_init_sw_state(rfkill,
+ IS_SWBLOCKED(state.device[i].power));
+ rfkill_set_hw_state(rfkill,
+ IS_HWBLOCKED(state.device[i].power));
+
+ if (!(state.device[i].power & HPWMI_POWER_BIOS))
+ pr_info("device %s blocked by BIOS\n", name);
+
+ err = rfkill_register(rfkill);
+ if (err) {
+ rfkill_destroy(rfkill);
+ goto fail;
+ }
+
+ rfkill2_count++;
+ }
+
+ return 0;
+fail:
+ for (; rfkill2_count > 0; rfkill2_count--) {
+ rfkill_unregister(rfkill2[rfkill2_count - 1].rfkill);
+ rfkill_destroy(rfkill2[rfkill2_count - 1].rfkill);
+ }
+ return err;
+}
+
+static int __init hp_wmi_bios_setup(struct platform_device *device)
+{
+ int err;
+
+ /* clear detected rfkill devices */
+ wifi_rfkill = NULL;
+ bluetooth_rfkill = NULL;
+ wwan_rfkill = NULL;
+ gps_rfkill = NULL;
+ rfkill2_count = 0;
+
+ if (hp_wmi_rfkill_setup(device))
+ hp_wmi_rfkill2_setup(device);
+
+ err = device_create_file(&device->dev, &dev_attr_display);
+ if (err)
+ goto add_sysfs_error;
+ err = device_create_file(&device->dev, &dev_attr_hddtemp);
+ if (err)
+ goto add_sysfs_error;
+ err = device_create_file(&device->dev, &dev_attr_als);
+ if (err)
+ goto add_sysfs_error;
+ err = device_create_file(&device->dev, &dev_attr_dock);
+ if (err)
+ goto add_sysfs_error;
+ err = device_create_file(&device->dev, &dev_attr_tablet);
+ if (err)
+ goto add_sysfs_error;
+ return 0;
+
+add_sysfs_error:
+ cleanup_sysfs(device);
+ return err;
+}
+
+static int __exit hp_wmi_bios_remove(struct platform_device *device)
+{
+ int i;
+ cleanup_sysfs(device);
+
+ for (i = 0; i < rfkill2_count; i++) {
+ rfkill_unregister(rfkill2[i].rfkill);
+ rfkill_destroy(rfkill2[i].rfkill);
+ }
+
+ if (wifi_rfkill) {
+ rfkill_unregister(wifi_rfkill);
+ rfkill_destroy(wifi_rfkill);
+ }
+ if (bluetooth_rfkill) {
+ rfkill_unregister(bluetooth_rfkill);
+ rfkill_destroy(bluetooth_rfkill);
+ }
+ if (wwan_rfkill) {
+ rfkill_unregister(wwan_rfkill);
+ rfkill_destroy(wwan_rfkill);
+ }
+ if (gps_rfkill) {
+ rfkill_unregister(gps_rfkill);
+ rfkill_destroy(gps_rfkill);
+ }
+
+ return 0;
+}
+
+static int hp_wmi_resume_handler(struct device *device)
+{
+ /*
+ * Hardware state may have changed while suspended, so trigger
+ * input events for the current state. As this is a switch,
+ * the input layer will only actually pass it on if the state
+ * changed.
+ */
+ if (hp_wmi_input_dev) {
+ input_report_switch(hp_wmi_input_dev, SW_DOCK,
+ hp_wmi_dock_state());
+ input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE,
+ hp_wmi_tablet_state());
+ input_sync(hp_wmi_input_dev);
+ }
+
+ if (rfkill2_count)
+ hp_wmi_rfkill2_refresh();
+
+ if (wifi_rfkill)
+ rfkill_set_states(wifi_rfkill,
+ hp_wmi_get_sw_state(HPWMI_WIFI),
+ hp_wmi_get_hw_state(HPWMI_WIFI));
+ if (bluetooth_rfkill)
+ rfkill_set_states(bluetooth_rfkill,
+ hp_wmi_get_sw_state(HPWMI_BLUETOOTH),
+ hp_wmi_get_hw_state(HPWMI_BLUETOOTH));
+ if (wwan_rfkill)
+ rfkill_set_states(wwan_rfkill,
+ hp_wmi_get_sw_state(HPWMI_WWAN),
+ hp_wmi_get_hw_state(HPWMI_WWAN));
+ if (gps_rfkill)
+ rfkill_set_states(gps_rfkill,
+ hp_wmi_get_sw_state(HPWMI_GPS),
+ hp_wmi_get_hw_state(HPWMI_GPS));
+
+ return 0;
+}
+
+static const struct dev_pm_ops hp_wmi_pm_ops = {
+ .resume = hp_wmi_resume_handler,
+ .restore = hp_wmi_resume_handler,
+};
+
+static struct platform_driver hp_wmi_driver = {
+ .driver = {
+ .name = "hp-wmi",
+ .owner = THIS_MODULE,
+ .pm = &hp_wmi_pm_ops,
+ },
+ .remove = __exit_p(hp_wmi_bios_remove),
+};
+
+static int __init hp_wmi_init(void)
+{
+ int err;
+ int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
+ int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID);
+
+ if (!bios_capable && !event_capable)
+ return -ENODEV;
+
+ if (event_capable) {
+ err = hp_wmi_input_setup();
+ if (err)
+ return err;
+ }
+
+ if (bios_capable) {
+ hp_wmi_platform_dev =
+ platform_device_register_simple("hp-wmi", -1, NULL, 0);
+ if (IS_ERR(hp_wmi_platform_dev)) {
+ err = PTR_ERR(hp_wmi_platform_dev);
+ goto err_destroy_input;
+ }
+
+ err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
+ if (err)
+ goto err_unregister_device;
+ }
+
+ return 0;
+
+err_unregister_device:
+ platform_device_unregister(hp_wmi_platform_dev);
+err_destroy_input:
+ if (event_capable)
+ hp_wmi_input_destroy();
+
+ return err;
+}
+module_init(hp_wmi_init);
+
+static void __exit hp_wmi_exit(void)
+{
+ if (wmi_has_guid(HPWMI_EVENT_GUID))
+ hp_wmi_input_destroy();
+
+ if (hp_wmi_platform_dev) {
+ platform_device_unregister(hp_wmi_platform_dev);
+ platform_driver_unregister(&hp_wmi_driver);
+ }
+}
+module_exit(hp_wmi_exit);
diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c
new file mode 100644
index 00000000..0ed96df2
--- /dev/null
+++ b/drivers/platform/x86/hp_accel.c
@@ -0,0 +1,391 @@
+/*
+ * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS
+ *
+ * Copyright (C) 2007-2008 Yan Burman
+ * Copyright (C) 2008 Eric Piel
+ * Copyright (C) 2008-2009 Pavel Machek
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/dmi.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/freezer.h>
+#include <linux/uaccess.h>
+#include <linux/leds.h>
+#include <linux/atomic.h>
+#include <acpi/acpi_drivers.h>
+#include "../../misc/lis3lv02d/lis3lv02d.h"
+
+#define DRIVER_NAME "hp_accel"
+#define ACPI_MDPS_CLASS "accelerometer"
+
+/* Delayed LEDs infrastructure ------------------------------------ */
+
+/* Special LED class that can defer work */
+struct delayed_led_classdev {
+ struct led_classdev led_classdev;
+ struct work_struct work;
+ enum led_brightness new_brightness;
+
+ unsigned int led; /* For driver */
+ void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value);
+};
+
+static inline void delayed_set_status_worker(struct work_struct *work)
+{
+ struct delayed_led_classdev *data =
+ container_of(work, struct delayed_led_classdev, work);
+
+ data->set_brightness(data, data->new_brightness);
+}
+
+static inline void delayed_sysfs_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct delayed_led_classdev *data = container_of(led_cdev,
+ struct delayed_led_classdev, led_classdev);
+ data->new_brightness = brightness;
+ schedule_work(&data->work);
+}
+
+/* HP-specific accelerometer driver ------------------------------------ */
+
+/* For automatic insertion of the module */
+static struct acpi_device_id lis3lv02d_device_ids[] = {
+ {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */
+ {"HPQ6000", 0}, /* HP Mobile Data Protection System PNP */
+ {"HPQ6007", 0}, /* HP Mobile Data Protection System PNP */
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids);
+
+
+/**
+ * lis3lv02d_acpi_init - ACPI _INI method: initialize the device.
+ * @lis3: pointer to the device struct
+ *
+ * Returns 0 on success.
+ */
+int lis3lv02d_acpi_init(struct lis3lv02d *lis3)
+{
+ struct acpi_device *dev = lis3->bus_priv;
+ if (acpi_evaluate_object(dev->handle, METHOD_NAME__INI,
+ NULL, NULL) != AE_OK)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * lis3lv02d_acpi_read - ACPI ALRD method: read a register
+ * @lis3: pointer to the device struct
+ * @reg: the register to read
+ * @ret: result of the operation
+ *
+ * Returns 0 on success.
+ */
+int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret)
+{
+ struct acpi_device *dev = lis3->bus_priv;
+ union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+ struct acpi_object_list args = { 1, &arg0 };
+ unsigned long long lret;
+ acpi_status status;
+
+ arg0.integer.value = reg;
+
+ status = acpi_evaluate_integer(dev->handle, "ALRD", &args, &lret);
+ *ret = lret;
+ return (status != AE_OK) ? -EINVAL : 0;
+}
+
+/**
+ * lis3lv02d_acpi_write - ACPI ALWR method: write to a register
+ * @lis3: pointer to the device struct
+ * @reg: the register to write to
+ * @val: the value to write
+ *
+ * Returns 0 on success.
+ */
+int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val)
+{
+ struct acpi_device *dev = lis3->bus_priv;
+ unsigned long long ret; /* Not used when writting */
+ union acpi_object in_obj[2];
+ struct acpi_object_list args = { 2, in_obj };
+
+ in_obj[0].type = ACPI_TYPE_INTEGER;
+ in_obj[0].integer.value = reg;
+ in_obj[1].type = ACPI_TYPE_INTEGER;
+ in_obj[1].integer.value = val;
+
+ if (acpi_evaluate_integer(dev->handle, "ALWR", &args, &ret) != AE_OK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi)
+{
+ lis3_dev.ac = *((union axis_conversion *)dmi->driver_data);
+ pr_info("hardware type %s found\n", dmi->ident);
+
+ return 1;
+}
+
+/* Represents, for each axis seen by userspace, the corresponding hw axis (+1).
+ * If the value is negative, the opposite of the hw value is used. */
+#define DEFINE_CONV(name, x, y, z) \
+ static union axis_conversion lis3lv02d_axis_##name = \
+ { .as_array = { x, y, z } }
+DEFINE_CONV(normal, 1, 2, 3);
+DEFINE_CONV(y_inverted, 1, -2, 3);
+DEFINE_CONV(x_inverted, -1, 2, 3);
+DEFINE_CONV(z_inverted, 1, 2, -3);
+DEFINE_CONV(xy_swap, 2, 1, 3);
+DEFINE_CONV(xy_rotated_left, -2, 1, 3);
+DEFINE_CONV(xy_rotated_left_usd, -2, 1, -3);
+DEFINE_CONV(xy_swap_inverted, -2, -1, 3);
+DEFINE_CONV(xy_rotated_right, 2, -1, 3);
+DEFINE_CONV(xy_swap_yz_inverted, 2, -1, -3);
+
+#define AXIS_DMI_MATCH(_ident, _name, _axis) { \
+ .ident = _ident, \
+ .callback = lis3lv02d_dmi_matched, \
+ .matches = { \
+ DMI_MATCH(DMI_PRODUCT_NAME, _name) \
+ }, \
+ .driver_data = &lis3lv02d_axis_##_axis \
+}
+
+#define AXIS_DMI_MATCH2(_ident, _class1, _name1, \
+ _class2, _name2, \
+ _axis) { \
+ .ident = _ident, \
+ .callback = lis3lv02d_dmi_matched, \
+ .matches = { \
+ DMI_MATCH(DMI_##_class1, _name1), \
+ DMI_MATCH(DMI_##_class2, _name2), \
+ }, \
+ .driver_data = &lis3lv02d_axis_##_axis \
+}
+static struct dmi_system_id lis3lv02d_dmi_ids[] = {
+ /* product names are truncated to match all kinds of a same model */
+ AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted),
+ AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted),
+ AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted),
+ AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted),
+ AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted),
+ AXIS_DMI_MATCH("NC2710", "HP Compaq 2710", xy_swap),
+ AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted),
+ AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left),
+ AXIS_DMI_MATCH("HP2140", "HP 2140", xy_swap_inverted),
+ AXIS_DMI_MATCH("NC653x", "HP Compaq 653", xy_rotated_left_usd),
+ AXIS_DMI_MATCH("NC6730b", "HP Compaq 6730b", xy_rotated_left_usd),
+ AXIS_DMI_MATCH("NC6730s", "HP Compaq 6730s", xy_swap),
+ AXIS_DMI_MATCH("NC651xx", "HP Compaq 651", xy_rotated_right),
+ AXIS_DMI_MATCH("NC6710x", "HP Compaq 6710", xy_swap_yz_inverted),
+ AXIS_DMI_MATCH("NC6715x", "HP Compaq 6715", y_inverted),
+ AXIS_DMI_MATCH("NC693xx", "HP EliteBook 693", xy_rotated_right),
+ AXIS_DMI_MATCH("NC693xx", "HP EliteBook 853", xy_swap),
+ AXIS_DMI_MATCH("NC854xx", "HP EliteBook 854", y_inverted),
+ AXIS_DMI_MATCH("NC273xx", "HP EliteBook 273", y_inverted),
+ /* Intel-based HP Pavilion dv5 */
+ AXIS_DMI_MATCH2("HPDV5_I",
+ PRODUCT_NAME, "HP Pavilion dv5",
+ BOARD_NAME, "3603",
+ x_inverted),
+ /* AMD-based HP Pavilion dv5 */
+ AXIS_DMI_MATCH2("HPDV5_A",
+ PRODUCT_NAME, "HP Pavilion dv5",
+ BOARD_NAME, "3600",
+ y_inverted),
+ AXIS_DMI_MATCH("DV7", "HP Pavilion dv7", x_inverted),
+ AXIS_DMI_MATCH("HP8710", "HP Compaq 8710", y_inverted),
+ AXIS_DMI_MATCH("HDX18", "HP HDX 18", x_inverted),
+ AXIS_DMI_MATCH("HPB432x", "HP ProBook 432", xy_rotated_left),
+ AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left),
+ AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted),
+ AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap),
+ AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted),
+ AXIS_DMI_MATCH("HPB655x", "HP ProBook 655", xy_swap_inverted),
+ AXIS_DMI_MATCH("Mini510x", "HP Mini 510", xy_rotated_left_usd),
+ AXIS_DMI_MATCH("HPB63xx", "HP ProBook 63", xy_swap),
+ AXIS_DMI_MATCH("HPB64xx", "HP ProBook 64", xy_swap),
+ AXIS_DMI_MATCH("HPB64xx", "HP EliteBook 84", xy_swap),
+ AXIS_DMI_MATCH("HPB65xx", "HP ProBook 65", x_inverted),
+ { NULL, }
+/* Laptop models without axis info (yet):
+ * "NC6910" "HP Compaq 6910"
+ * "NC2400" "HP Compaq nc2400"
+ * "NX74x0" "HP Compaq nx74"
+ * "NX6325" "HP Compaq nx6325"
+ * "NC4400" "HP Compaq nc4400"
+ */
+};
+
+static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value)
+{
+ struct acpi_device *dev = lis3_dev.bus_priv;
+ unsigned long long ret; /* Not used when writing */
+ union acpi_object in_obj[1];
+ struct acpi_object_list args = { 1, in_obj };
+
+ in_obj[0].type = ACPI_TYPE_INTEGER;
+ in_obj[0].integer.value = !!value;
+
+ acpi_evaluate_integer(dev->handle, "ALED", &args, &ret);
+}
+
+static struct delayed_led_classdev hpled_led = {
+ .led_classdev = {
+ .name = "hp::hddprotect",
+ .default_trigger = "none",
+ .brightness_set = delayed_sysfs_set,
+ .flags = LED_CORE_SUSPENDRESUME,
+ },
+ .set_brightness = hpled_set,
+};
+
+static acpi_status
+lis3lv02d_get_resource(struct acpi_resource *resource, void *context)
+{
+ if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) {
+ struct acpi_resource_extended_irq *irq;
+ u32 *device_irq = context;
+
+ irq = &resource->data.extended_irq;
+ *device_irq = irq->interrupts[0];
+ }
+
+ return AE_OK;
+}
+
+static void lis3lv02d_enum_resources(struct acpi_device *device)
+{
+ acpi_status status;
+
+ status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+ lis3lv02d_get_resource, &lis3_dev.irq);
+ if (ACPI_FAILURE(status))
+ printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n");
+}
+
+static int lis3lv02d_add(struct acpi_device *device)
+{
+ int ret;
+
+ if (!device)
+ return -EINVAL;
+
+ lis3_dev.bus_priv = device;
+ lis3_dev.init = lis3lv02d_acpi_init;
+ lis3_dev.read = lis3lv02d_acpi_read;
+ lis3_dev.write = lis3lv02d_acpi_write;
+ strcpy(acpi_device_name(device), DRIVER_NAME);
+ strcpy(acpi_device_class(device), ACPI_MDPS_CLASS);
+ device->driver_data = &lis3_dev;
+
+ /* obtain IRQ number of our device from ACPI */
+ lis3lv02d_enum_resources(device);
+
+ /* If possible use a "standard" axes order */
+ if (lis3_dev.ac.x && lis3_dev.ac.y && lis3_dev.ac.z) {
+ pr_info("Using custom axes %d,%d,%d\n",
+ lis3_dev.ac.x, lis3_dev.ac.y, lis3_dev.ac.z);
+ } else if (dmi_check_system(lis3lv02d_dmi_ids) == 0) {
+ pr_info("laptop model unknown, using default axes configuration\n");
+ lis3_dev.ac = lis3lv02d_axis_normal;
+ }
+
+ /* call the core layer do its init */
+ ret = lis3lv02d_init_device(&lis3_dev);
+ if (ret)
+ return ret;
+
+ INIT_WORK(&hpled_led.work, delayed_set_status_worker);
+ ret = led_classdev_register(NULL, &hpled_led.led_classdev);
+ if (ret) {
+ lis3lv02d_joystick_disable(&lis3_dev);
+ lis3lv02d_poweroff(&lis3_dev);
+ flush_work(&hpled_led.work);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int lis3lv02d_remove(struct acpi_device *device)
+{
+ if (!device)
+ return -EINVAL;
+
+ lis3lv02d_joystick_disable(&lis3_dev);
+ lis3lv02d_poweroff(&lis3_dev);
+
+ led_classdev_unregister(&hpled_led.led_classdev);
+ flush_work(&hpled_led.work);
+
+ return lis3lv02d_remove_fs(&lis3_dev);
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+static int lis3lv02d_suspend(struct device *dev)
+{
+ /* make sure the device is off when we suspend */
+ lis3lv02d_poweroff(&lis3_dev);
+ return 0;
+}
+
+static int lis3lv02d_resume(struct device *dev)
+{
+ lis3lv02d_poweron(&lis3_dev);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(hp_accel_pm, lis3lv02d_suspend, lis3lv02d_resume);
+#define HP_ACCEL_PM (&hp_accel_pm)
+#else
+#define HP_ACCEL_PM NULL
+#endif
+
+/* For the HP MDPS aka 3D Driveguard */
+static struct acpi_driver lis3lv02d_driver = {
+ .name = DRIVER_NAME,
+ .class = ACPI_MDPS_CLASS,
+ .ids = lis3lv02d_device_ids,
+ .ops = {
+ .add = lis3lv02d_add,
+ .remove = lis3lv02d_remove,
+ },
+ .drv.pm = HP_ACCEL_PM,
+};
+module_acpi_driver(lis3lv02d_driver);
+
+MODULE_DESCRIPTION("Glue between LIS3LV02Dx and HP ACPI BIOS and support for disk protection LED.");
+MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c
new file mode 100644
index 00000000..97c2be19
--- /dev/null
+++ b/drivers/platform/x86/ibm_rtl.c
@@ -0,0 +1,328 @@
+/*
+ * IBM Real-Time Linux driver
+ *
+ * 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.
+ *
+ * Copyright (C) IBM Corporation, 2010
+ *
+ * Author: Keith Mannthey <kmannth@us.ibm.com>
+ * Vernon Mauery <vernux@us.ibm.com>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/dmi.h>
+#include <linux/efi.h>
+#include <linux/mutex.h>
+#include <asm/bios_ebda.h>
+
+#include <asm-generic/io-64-nonatomic-lo-hi.h>
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static bool debug;
+module_param(debug, bool, 0644);
+MODULE_PARM_DESC(debug, "Show debug output");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>");
+MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>");
+
+#define RTL_ADDR_TYPE_IO 1
+#define RTL_ADDR_TYPE_MMIO 2
+
+#define RTL_CMD_ENTER_PRTM 1
+#define RTL_CMD_EXIT_PRTM 2
+
+/* The RTL table as presented by the EBDA: */
+struct ibm_rtl_table {
+ char signature[5]; /* signature should be "_RTL_" */
+ u8 version;
+ u8 rt_status;
+ u8 command;
+ u8 command_status;
+ u8 cmd_address_type;
+ u8 cmd_granularity;
+ u8 cmd_offset;
+ u16 reserve1;
+ u32 cmd_port_address; /* platform dependent address */
+ u32 cmd_port_value; /* platform dependent value */
+} __attribute__((packed));
+
+/* to locate "_RTL_" signature do a masked 5-byte integer compare */
+#define RTL_SIGNATURE 0x0000005f4c54525fULL
+#define RTL_MASK 0x000000ffffffffffULL
+
+#define RTL_DEBUG(fmt, ...) \
+do { \
+ if (debug) \
+ pr_info(fmt, ##__VA_ARGS__); \
+} while (0)
+
+static DEFINE_MUTEX(rtl_lock);
+static struct ibm_rtl_table __iomem *rtl_table;
+static void __iomem *ebda_map;
+static void __iomem *rtl_cmd_addr;
+static u8 rtl_cmd_type;
+static u8 rtl_cmd_width;
+
+static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len)
+{
+ if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
+ return ioremap(addr, len);
+ return ioport_map(addr, len);
+}
+
+static void rtl_port_unmap(void __iomem *addr)
+{
+ if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
+ iounmap(addr);
+ else
+ ioport_unmap(addr);
+}
+
+static int ibm_rtl_write(u8 value)
+{
+ int ret = 0, count = 0;
+ static u32 cmd_port_val;
+
+ RTL_DEBUG("%s(%d)\n", __func__, value);
+
+ value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM;
+
+ mutex_lock(&rtl_lock);
+
+ if (ioread8(&rtl_table->rt_status) != value) {
+ iowrite8(value, &rtl_table->command);
+
+ switch (rtl_cmd_width) {
+ case 8:
+ cmd_port_val = ioread8(&rtl_table->cmd_port_value);
+ RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
+ iowrite8((u8)cmd_port_val, rtl_cmd_addr);
+ break;
+ case 16:
+ cmd_port_val = ioread16(&rtl_table->cmd_port_value);
+ RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
+ iowrite16((u16)cmd_port_val, rtl_cmd_addr);
+ break;
+ case 32:
+ cmd_port_val = ioread32(&rtl_table->cmd_port_value);
+ RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
+ iowrite32(cmd_port_val, rtl_cmd_addr);
+ break;
+ }
+
+ while (ioread8(&rtl_table->command)) {
+ msleep(10);
+ if (count++ > 500) {
+ pr_err("Hardware not responding to "
+ "mode switch request\n");
+ ret = -EIO;
+ break;
+ }
+
+ }
+
+ if (ioread8(&rtl_table->command_status)) {
+ RTL_DEBUG("command_status reports failed command\n");
+ ret = -EIO;
+ }
+ }
+
+ mutex_unlock(&rtl_lock);
+ return ret;
+}
+
+static ssize_t rtl_show_version(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version));
+}
+
+static ssize_t rtl_show_state(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status));
+}
+
+static ssize_t rtl_set_state(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ ssize_t ret;
+
+ if (count < 1 || count > 2)
+ return -EINVAL;
+
+ switch (buf[0]) {
+ case '0':
+ ret = ibm_rtl_write(0);
+ break;
+ case '1':
+ ret = ibm_rtl_write(1);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ if (ret >= 0)
+ ret = count;
+
+ return ret;
+}
+
+static struct bus_type rtl_subsys = {
+ .name = "ibm_rtl",
+ .dev_name = "ibm_rtl",
+};
+
+static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL);
+static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state);
+
+static struct device_attribute *rtl_attributes[] = {
+ &dev_attr_version,
+ &dev_attr_state,
+ NULL
+};
+
+
+static int rtl_setup_sysfs(void) {
+ int ret, i;
+
+ ret = subsys_system_register(&rtl_subsys, NULL);
+ if (!ret) {
+ for (i = 0; rtl_attributes[i]; i ++)
+ device_create_file(rtl_subsys.dev_root, rtl_attributes[i]);
+ }
+ return ret;
+}
+
+static void rtl_teardown_sysfs(void) {
+ int i;
+ for (i = 0; rtl_attributes[i]; i ++)
+ device_remove_file(rtl_subsys.dev_root, rtl_attributes[i]);
+ bus_unregister(&rtl_subsys);
+}
+
+
+static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = {
+ { \
+ .matches = { \
+ DMI_MATCH(DMI_SYS_VENDOR, "IBM"), \
+ }, \
+ },
+ { }
+};
+
+static int __init ibm_rtl_init(void) {
+ unsigned long ebda_addr, ebda_size;
+ unsigned int ebda_kb;
+ int ret = -ENODEV, i;
+
+ if (force)
+ pr_warn("module loaded by force\n");
+ /* first ensure that we are running on IBM HW */
+ else if (efi_enabled(EFI_BOOT) || !dmi_check_system(ibm_rtl_dmi_table))
+ return -ENODEV;
+
+ /* Get the address for the Extended BIOS Data Area */
+ ebda_addr = get_bios_ebda();
+ if (!ebda_addr) {
+ RTL_DEBUG("no BIOS EBDA found\n");
+ return -ENODEV;
+ }
+
+ ebda_map = ioremap(ebda_addr, 4);
+ if (!ebda_map)
+ return -ENOMEM;
+
+ /* First word in the EDBA is the Size in KB */
+ ebda_kb = ioread16(ebda_map);
+ RTL_DEBUG("EBDA is %d kB\n", ebda_kb);
+
+ if (ebda_kb == 0)
+ goto out;
+
+ iounmap(ebda_map);
+ ebda_size = ebda_kb*1024;
+
+ /* Remap the whole table */
+ ebda_map = ioremap(ebda_addr, ebda_size);
+ if (!ebda_map)
+ return -ENOMEM;
+
+ /* search for the _RTL_ signature at the start of the table */
+ for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
+ struct ibm_rtl_table __iomem * tmp;
+ tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i);
+ if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
+ phys_addr_t addr;
+ unsigned int plen;
+ RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp);
+ rtl_table = tmp;
+ /* The address, value, width and offset are platform
+ * dependent and found in the ibm_rtl_table */
+ rtl_cmd_width = ioread8(&rtl_table->cmd_granularity);
+ rtl_cmd_type = ioread8(&rtl_table->cmd_address_type);
+ RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n",
+ rtl_cmd_width, rtl_cmd_type);
+ addr = ioread32(&rtl_table->cmd_port_address);
+ RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr);
+ plen = rtl_cmd_width/sizeof(char);
+ rtl_cmd_addr = rtl_port_map(addr, plen);
+ RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr);
+ if (!rtl_cmd_addr) {
+ ret = -ENOMEM;
+ break;
+ }
+ ret = rtl_setup_sysfs();
+ break;
+ }
+ }
+
+out:
+ if (ret) {
+ iounmap(ebda_map);
+ rtl_port_unmap(rtl_cmd_addr);
+ }
+
+ return ret;
+}
+
+static void __exit ibm_rtl_exit(void)
+{
+ if (rtl_table) {
+ RTL_DEBUG("cleaning up");
+ /* do not leave the machine in SMI-free mode */
+ ibm_rtl_write(0);
+ /* unmap, unlink and remove all traces */
+ rtl_teardown_sysfs();
+ iounmap(ebda_map);
+ rtl_port_unmap(rtl_cmd_addr);
+ }
+}
+
+module_init(ibm_rtl_init);
+module_exit(ibm_rtl_exit);
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
new file mode 100644
index 00000000..89c4519d
--- /dev/null
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -0,0 +1,927 @@
+/*
+ * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras
+ *
+ * Copyright © 2010 Intel Corporation
+ * Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/rfkill.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/i8042.h>
+
+#define IDEAPAD_RFKILL_DEV_NUM (3)
+
+#define CFG_BT_BIT (16)
+#define CFG_3G_BIT (17)
+#define CFG_WIFI_BIT (18)
+#define CFG_CAMERA_BIT (19)
+
+enum {
+ VPCCMD_R_VPC1 = 0x10,
+ VPCCMD_R_BL_MAX,
+ VPCCMD_R_BL,
+ VPCCMD_W_BL,
+ VPCCMD_R_WIFI,
+ VPCCMD_W_WIFI,
+ VPCCMD_R_BT,
+ VPCCMD_W_BT,
+ VPCCMD_R_BL_POWER,
+ VPCCMD_R_NOVO,
+ VPCCMD_R_VPC2,
+ VPCCMD_R_TOUCHPAD,
+ VPCCMD_W_TOUCHPAD,
+ VPCCMD_R_CAMERA,
+ VPCCMD_W_CAMERA,
+ VPCCMD_R_3G,
+ VPCCMD_W_3G,
+ VPCCMD_R_ODD, /* 0x21 */
+ VPCCMD_W_FAN,
+ VPCCMD_R_RF,
+ VPCCMD_W_RF,
+ VPCCMD_R_FAN = 0x2B,
+ VPCCMD_R_SPECIAL_BUTTONS = 0x31,
+ VPCCMD_W_BL_POWER = 0x33,
+};
+
+struct ideapad_private {
+ struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
+ struct platform_device *platform_device;
+ struct input_dev *inputdev;
+ struct backlight_device *blightdev;
+ struct dentry *debug;
+ unsigned long cfg;
+};
+
+static acpi_handle ideapad_handle;
+static struct ideapad_private *ideapad_priv;
+static bool no_bt_rfkill;
+module_param(no_bt_rfkill, bool, 0444);
+MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
+
+/*
+ * ACPI Helpers
+ */
+#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
+
+static int read_method_int(acpi_handle handle, const char *method, int *val)
+{
+ acpi_status status;
+ unsigned long long result;
+
+ status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
+ if (ACPI_FAILURE(status)) {
+ *val = -1;
+ return -1;
+ } else {
+ *val = result;
+ return 0;
+ }
+}
+
+static int method_vpcr(acpi_handle handle, int cmd, int *ret)
+{
+ acpi_status status;
+ unsigned long long result;
+ struct acpi_object_list params;
+ union acpi_object in_obj;
+
+ params.count = 1;
+ params.pointer = &in_obj;
+ in_obj.type = ACPI_TYPE_INTEGER;
+ in_obj.integer.value = cmd;
+
+ status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
+
+ if (ACPI_FAILURE(status)) {
+ *ret = -1;
+ return -1;
+ } else {
+ *ret = result;
+ return 0;
+ }
+}
+
+static int method_vpcw(acpi_handle handle, int cmd, int data)
+{
+ struct acpi_object_list params;
+ union acpi_object in_obj[2];
+ acpi_status status;
+
+ params.count = 2;
+ params.pointer = in_obj;
+ in_obj[0].type = ACPI_TYPE_INTEGER;
+ in_obj[0].integer.value = cmd;
+ in_obj[1].type = ACPI_TYPE_INTEGER;
+ in_obj[1].integer.value = data;
+
+ status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
+ if (status != AE_OK)
+ return -1;
+ return 0;
+}
+
+static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
+{
+ int val;
+ unsigned long int end_jiffies;
+
+ if (method_vpcw(handle, 1, cmd))
+ return -1;
+
+ for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
+ time_before(jiffies, end_jiffies);) {
+ schedule();
+ if (method_vpcr(handle, 1, &val))
+ return -1;
+ if (val == 0) {
+ if (method_vpcr(handle, 0, &val))
+ return -1;
+ *data = val;
+ return 0;
+ }
+ }
+ pr_err("timeout in read_ec_cmd\n");
+ return -1;
+}
+
+static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
+{
+ int val;
+ unsigned long int end_jiffies;
+
+ if (method_vpcw(handle, 0, data))
+ return -1;
+ if (method_vpcw(handle, 1, cmd))
+ return -1;
+
+ for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
+ time_before(jiffies, end_jiffies);) {
+ schedule();
+ if (method_vpcr(handle, 1, &val))
+ return -1;
+ if (val == 0)
+ return 0;
+ }
+ pr_err("timeout in write_ec_cmd\n");
+ return -1;
+}
+
+/*
+ * debugfs
+ */
+static int debugfs_status_show(struct seq_file *s, void *data)
+{
+ unsigned long value;
+
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &value))
+ seq_printf(s, "Backlight max:\t%lu\n", value);
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_BL, &value))
+ seq_printf(s, "Backlight now:\t%lu\n", value);
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &value))
+ seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off");
+ seq_printf(s, "=====================\n");
+
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_RF, &value))
+ seq_printf(s, "Radio status:\t%s(%lu)\n",
+ value ? "On" : "Off", value);
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_WIFI, &value))
+ seq_printf(s, "Wifi status:\t%s(%lu)\n",
+ value ? "On" : "Off", value);
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_BT, &value))
+ seq_printf(s, "BT status:\t%s(%lu)\n",
+ value ? "On" : "Off", value);
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_3G, &value))
+ seq_printf(s, "3G status:\t%s(%lu)\n",
+ value ? "On" : "Off", value);
+ seq_printf(s, "=====================\n");
+
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_TOUCHPAD, &value))
+ seq_printf(s, "Touchpad status:%s(%lu)\n",
+ value ? "On" : "Off", value);
+ if (!read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &value))
+ seq_printf(s, "Camera status:\t%s(%lu)\n",
+ value ? "On" : "Off", value);
+
+ return 0;
+}
+
+static int debugfs_status_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debugfs_status_show, NULL);
+}
+
+static const struct file_operations debugfs_status_fops = {
+ .owner = THIS_MODULE,
+ .open = debugfs_status_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int debugfs_cfg_show(struct seq_file *s, void *data)
+{
+ if (!ideapad_priv) {
+ seq_printf(s, "cfg: N/A\n");
+ } else {
+ seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ",
+ ideapad_priv->cfg);
+ if (test_bit(CFG_BT_BIT, &ideapad_priv->cfg))
+ seq_printf(s, "Bluetooth ");
+ if (test_bit(CFG_3G_BIT, &ideapad_priv->cfg))
+ seq_printf(s, "3G ");
+ if (test_bit(CFG_WIFI_BIT, &ideapad_priv->cfg))
+ seq_printf(s, "Wireless ");
+ if (test_bit(CFG_CAMERA_BIT, &ideapad_priv->cfg))
+ seq_printf(s, "Camera ");
+ seq_printf(s, "\nGraphic: ");
+ switch ((ideapad_priv->cfg)&0x700) {
+ case 0x100:
+ seq_printf(s, "Intel");
+ break;
+ case 0x200:
+ seq_printf(s, "ATI");
+ break;
+ case 0x300:
+ seq_printf(s, "Nvidia");
+ break;
+ case 0x400:
+ seq_printf(s, "Intel and ATI");
+ break;
+ case 0x500:
+ seq_printf(s, "Intel and Nvidia");
+ break;
+ }
+ seq_printf(s, "\n");
+ }
+ return 0;
+}
+
+static int debugfs_cfg_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debugfs_cfg_show, NULL);
+}
+
+static const struct file_operations debugfs_cfg_fops = {
+ .owner = THIS_MODULE,
+ .open = debugfs_cfg_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int ideapad_debugfs_init(struct ideapad_private *priv)
+{
+ struct dentry *node;
+
+ priv->debug = debugfs_create_dir("ideapad", NULL);
+ if (priv->debug == NULL) {
+ pr_err("failed to create debugfs directory");
+ goto errout;
+ }
+
+ node = debugfs_create_file("cfg", S_IRUGO, priv->debug, NULL,
+ &debugfs_cfg_fops);
+ if (!node) {
+ pr_err("failed to create cfg in debugfs");
+ goto errout;
+ }
+
+ node = debugfs_create_file("status", S_IRUGO, priv->debug, NULL,
+ &debugfs_status_fops);
+ if (!node) {
+ pr_err("failed to create status in debugfs");
+ goto errout;
+ }
+
+ return 0;
+
+errout:
+ return -ENOMEM;
+}
+
+static void ideapad_debugfs_exit(struct ideapad_private *priv)
+{
+ debugfs_remove_recursive(priv->debug);
+ priv->debug = NULL;
+}
+
+/*
+ * sysfs
+ */
+static ssize_t show_ideapad_cam(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned long result;
+
+ if (read_ec_data(ideapad_handle, VPCCMD_R_CAMERA, &result))
+ return sprintf(buf, "-1\n");
+ return sprintf(buf, "%lu\n", result);
+}
+
+static ssize_t store_ideapad_cam(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret, state;
+
+ if (!count)
+ return 0;
+ if (sscanf(buf, "%i", &state) != 1)
+ return -EINVAL;
+ ret = write_ec_cmd(ideapad_handle, VPCCMD_W_CAMERA, state);
+ if (ret < 0)
+ return -EIO;
+ return count;
+}
+
+static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
+
+static ssize_t show_ideapad_fan(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned long result;
+
+ if (read_ec_data(ideapad_handle, VPCCMD_R_FAN, &result))
+ return sprintf(buf, "-1\n");
+ return sprintf(buf, "%lu\n", result);
+}
+
+static ssize_t store_ideapad_fan(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret, state;
+
+ if (!count)
+ return 0;
+ if (sscanf(buf, "%i", &state) != 1)
+ return -EINVAL;
+ if (state < 0 || state > 4 || state == 3)
+ return -EINVAL;
+ ret = write_ec_cmd(ideapad_handle, VPCCMD_W_FAN, state);
+ if (ret < 0)
+ return -EIO;
+ return count;
+}
+
+static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan);
+
+static struct attribute *ideapad_attributes[] = {
+ &dev_attr_camera_power.attr,
+ &dev_attr_fan_mode.attr,
+ NULL
+};
+
+static umode_t ideapad_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct ideapad_private *priv = dev_get_drvdata(dev);
+ bool supported;
+
+ if (attr == &dev_attr_camera_power.attr)
+ supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg));
+ else if (attr == &dev_attr_fan_mode.attr) {
+ unsigned long value;
+ supported = !read_ec_data(ideapad_handle, VPCCMD_R_FAN, &value);
+ } else
+ supported = true;
+
+ return supported ? attr->mode : 0;
+}
+
+static struct attribute_group ideapad_attribute_group = {
+ .is_visible = ideapad_is_visible,
+ .attrs = ideapad_attributes
+};
+
+/*
+ * Rfkill
+ */
+struct ideapad_rfk_data {
+ char *name;
+ int cfgbit;
+ int opcode;
+ int type;
+};
+
+const struct ideapad_rfk_data ideapad_rfk_data[] = {
+ { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN },
+ { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH },
+ { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN },
+};
+
+static int ideapad_rfk_set(void *data, bool blocked)
+{
+ unsigned long opcode = (unsigned long)data;
+
+ return write_ec_cmd(ideapad_handle, opcode, !blocked);
+}
+
+static struct rfkill_ops ideapad_rfk_ops = {
+ .set_block = ideapad_rfk_set,
+};
+
+static void ideapad_sync_rfk_state(struct ideapad_private *priv)
+{
+ unsigned long hw_blocked;
+ int i;
+
+ if (read_ec_data(ideapad_handle, VPCCMD_R_RF, &hw_blocked))
+ return;
+ hw_blocked = !hw_blocked;
+
+ for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
+ if (priv->rfk[i])
+ rfkill_set_hw_state(priv->rfk[i], hw_blocked);
+}
+
+static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
+{
+ struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
+ int ret;
+ unsigned long sw_blocked;
+
+ if (no_bt_rfkill &&
+ (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
+ /* Force to enable bluetooth when no_bt_rfkill=1 */
+ write_ec_cmd(ideapad_handle,
+ ideapad_rfk_data[dev].opcode, 1);
+ return 0;
+ }
+
+ priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, &adevice->dev,
+ ideapad_rfk_data[dev].type, &ideapad_rfk_ops,
+ (void *)(long)dev);
+ if (!priv->rfk[dev])
+ return -ENOMEM;
+
+ if (read_ec_data(ideapad_handle, ideapad_rfk_data[dev].opcode-1,
+ &sw_blocked)) {
+ rfkill_init_sw_state(priv->rfk[dev], 0);
+ } else {
+ sw_blocked = !sw_blocked;
+ rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
+ }
+
+ ret = rfkill_register(priv->rfk[dev]);
+ if (ret) {
+ rfkill_destroy(priv->rfk[dev]);
+ return ret;
+ }
+ return 0;
+}
+
+static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
+{
+ struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
+
+ if (!priv->rfk[dev])
+ return;
+
+ rfkill_unregister(priv->rfk[dev]);
+ rfkill_destroy(priv->rfk[dev]);
+}
+
+/*
+ * Platform device
+ */
+static int ideapad_platform_init(struct ideapad_private *priv)
+{
+ int result;
+
+ priv->platform_device = platform_device_alloc("ideapad", -1);
+ if (!priv->platform_device)
+ return -ENOMEM;
+ platform_set_drvdata(priv->platform_device, priv);
+
+ result = platform_device_add(priv->platform_device);
+ if (result)
+ goto fail_platform_device;
+
+ result = sysfs_create_group(&priv->platform_device->dev.kobj,
+ &ideapad_attribute_group);
+ if (result)
+ goto fail_sysfs;
+ return 0;
+
+fail_sysfs:
+ platform_device_del(priv->platform_device);
+fail_platform_device:
+ platform_device_put(priv->platform_device);
+ return result;
+}
+
+static void ideapad_platform_exit(struct ideapad_private *priv)
+{
+ sysfs_remove_group(&priv->platform_device->dev.kobj,
+ &ideapad_attribute_group);
+ platform_device_unregister(priv->platform_device);
+}
+
+/*
+ * input device
+ */
+static const struct key_entry ideapad_keymap[] = {
+ { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 7, { KEY_CAMERA } },
+ { KE_KEY, 11, { KEY_F16 } },
+ { KE_KEY, 13, { KEY_WLAN } },
+ { KE_KEY, 16, { KEY_PROG1 } },
+ { KE_KEY, 17, { KEY_PROG2 } },
+ { KE_KEY, 64, { KEY_PROG3 } },
+ { KE_KEY, 65, { KEY_PROG4 } },
+ { KE_KEY, 66, { KEY_TOUCHPAD_OFF } },
+ { KE_KEY, 67, { KEY_TOUCHPAD_ON } },
+ { KE_END, 0 },
+};
+
+static int ideapad_input_init(struct ideapad_private *priv)
+{
+ struct input_dev *inputdev;
+ int error;
+
+ inputdev = input_allocate_device();
+ if (!inputdev) {
+ pr_info("Unable to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ inputdev->name = "Ideapad extra buttons";
+ inputdev->phys = "ideapad/input0";
+ inputdev->id.bustype = BUS_HOST;
+ inputdev->dev.parent = &priv->platform_device->dev;
+
+ error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
+ if (error) {
+ pr_err("Unable to setup input device keymap\n");
+ goto err_free_dev;
+ }
+
+ error = input_register_device(inputdev);
+ if (error) {
+ pr_err("Unable to register input device\n");
+ goto err_free_keymap;
+ }
+
+ priv->inputdev = inputdev;
+ return 0;
+
+err_free_keymap:
+ sparse_keymap_free(inputdev);
+err_free_dev:
+ input_free_device(inputdev);
+ return error;
+}
+
+static void ideapad_input_exit(struct ideapad_private *priv)
+{
+ sparse_keymap_free(priv->inputdev);
+ input_unregister_device(priv->inputdev);
+ priv->inputdev = NULL;
+}
+
+static void ideapad_input_report(struct ideapad_private *priv,
+ unsigned long scancode)
+{
+ sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
+}
+
+static void ideapad_input_novokey(struct ideapad_private *priv)
+{
+ unsigned long long_pressed;
+
+ if (read_ec_data(ideapad_handle, VPCCMD_R_NOVO, &long_pressed))
+ return;
+ if (long_pressed)
+ ideapad_input_report(priv, 17);
+ else
+ ideapad_input_report(priv, 16);
+}
+
+static void ideapad_check_special_buttons(struct ideapad_private *priv)
+{
+ unsigned long bit, value;
+
+ read_ec_data(ideapad_handle, VPCCMD_R_SPECIAL_BUTTONS, &value);
+
+ for (bit = 0; bit < 16; bit++) {
+ if (test_bit(bit, &value)) {
+ switch (bit) {
+ case 0: /* Z580 */
+ case 6: /* Z570 */
+ /* Thermal Management button */
+ ideapad_input_report(priv, 65);
+ break;
+ case 1:
+ /* OneKey Theater button */
+ ideapad_input_report(priv, 64);
+ break;
+ default:
+ pr_info("Unknown special button: %lu\n", bit);
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * backlight
+ */
+static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
+{
+ unsigned long now;
+
+ if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
+ return -EIO;
+ return now;
+}
+
+static int ideapad_backlight_update_status(struct backlight_device *blightdev)
+{
+ if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL,
+ blightdev->props.brightness))
+ return -EIO;
+ if (write_ec_cmd(ideapad_handle, VPCCMD_W_BL_POWER,
+ blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1))
+ return -EIO;
+
+ return 0;
+}
+
+static const struct backlight_ops ideapad_backlight_ops = {
+ .get_brightness = ideapad_backlight_get_brightness,
+ .update_status = ideapad_backlight_update_status,
+};
+
+static int ideapad_backlight_init(struct ideapad_private *priv)
+{
+ struct backlight_device *blightdev;
+ struct backlight_properties props;
+ unsigned long max, now, power;
+
+ if (read_ec_data(ideapad_handle, VPCCMD_R_BL_MAX, &max))
+ return -EIO;
+ if (read_ec_data(ideapad_handle, VPCCMD_R_BL, &now))
+ return -EIO;
+ if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
+ return -EIO;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = max;
+ props.type = BACKLIGHT_PLATFORM;
+ blightdev = backlight_device_register("ideapad",
+ &priv->platform_device->dev,
+ priv,
+ &ideapad_backlight_ops,
+ &props);
+ if (IS_ERR(blightdev)) {
+ pr_err("Could not register backlight device\n");
+ return PTR_ERR(blightdev);
+ }
+
+ priv->blightdev = blightdev;
+ blightdev->props.brightness = now;
+ blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+ backlight_update_status(blightdev);
+
+ return 0;
+}
+
+static void ideapad_backlight_exit(struct ideapad_private *priv)
+{
+ if (priv->blightdev)
+ backlight_device_unregister(priv->blightdev);
+ priv->blightdev = NULL;
+}
+
+static void ideapad_backlight_notify_power(struct ideapad_private *priv)
+{
+ unsigned long power;
+ struct backlight_device *blightdev = priv->blightdev;
+
+ if (!blightdev)
+ return;
+ if (read_ec_data(ideapad_handle, VPCCMD_R_BL_POWER, &power))
+ return;
+ blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+}
+
+static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
+{
+ unsigned long now;
+
+ /* if we control brightness via acpi video driver */
+ if (priv->blightdev == NULL) {
+ read_ec_data(ideapad_handle, VPCCMD_R_BL, &now);
+ return;
+ }
+
+ backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
+}
+
+/*
+ * module init/exit
+ */
+static const struct acpi_device_id ideapad_device_ids[] = {
+ { "VPC2004", 0},
+ { "", 0},
+};
+MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
+
+static void ideapad_sync_touchpad_state(struct acpi_device *adevice)
+{
+ struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
+ unsigned long value;
+
+ /* Without reading from EC touchpad LED doesn't switch state */
+ if (!read_ec_data(adevice->handle, VPCCMD_R_TOUCHPAD, &value)) {
+ /* Some IdeaPads don't really turn off touchpad - they only
+ * switch the LED state. We (de)activate KBC AUX port to turn
+ * touchpad off and on. We send KEY_TOUCHPAD_OFF and
+ * KEY_TOUCHPAD_ON to not to get out of sync with LED */
+ unsigned char param;
+ i8042_command(&param, value ? I8042_CMD_AUX_ENABLE :
+ I8042_CMD_AUX_DISABLE);
+ ideapad_input_report(priv, value ? 67 : 66);
+ }
+}
+
+static int ideapad_acpi_add(struct acpi_device *adevice)
+{
+ int ret, i;
+ int cfg;
+ struct ideapad_private *priv;
+
+ if (read_method_int(adevice->handle, "_CFG", &cfg))
+ return -ENODEV;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ dev_set_drvdata(&adevice->dev, priv);
+ ideapad_priv = priv;
+ ideapad_handle = adevice->handle;
+ priv->cfg = cfg;
+
+ ret = ideapad_platform_init(priv);
+ if (ret)
+ goto platform_failed;
+
+ ret = ideapad_debugfs_init(priv);
+ if (ret)
+ goto debugfs_failed;
+
+ ret = ideapad_input_init(priv);
+ if (ret)
+ goto input_failed;
+
+ for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) {
+ if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg))
+ ideapad_register_rfkill(adevice, i);
+ else
+ priv->rfk[i] = NULL;
+ }
+ ideapad_sync_rfk_state(priv);
+ ideapad_sync_touchpad_state(adevice);
+
+ if (!acpi_video_backlight_support()) {
+ ret = ideapad_backlight_init(priv);
+ if (ret && ret != -ENODEV)
+ goto backlight_failed;
+ }
+
+ return 0;
+
+backlight_failed:
+ for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
+ ideapad_unregister_rfkill(adevice, i);
+ ideapad_input_exit(priv);
+input_failed:
+ ideapad_debugfs_exit(priv);
+debugfs_failed:
+ ideapad_platform_exit(priv);
+platform_failed:
+ kfree(priv);
+ return ret;
+}
+
+static int ideapad_acpi_remove(struct acpi_device *adevice)
+{
+ struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
+ int i;
+
+ ideapad_backlight_exit(priv);
+ for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
+ ideapad_unregister_rfkill(adevice, i);
+ ideapad_input_exit(priv);
+ ideapad_debugfs_exit(priv);
+ ideapad_platform_exit(priv);
+ dev_set_drvdata(&adevice->dev, NULL);
+ kfree(priv);
+
+ return 0;
+}
+
+static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
+{
+ struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
+ acpi_handle handle = adevice->handle;
+ unsigned long vpc1, vpc2, vpc_bit;
+
+ if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
+ return;
+ if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
+ return;
+
+ vpc1 = (vpc2 << 8) | vpc1;
+ for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
+ if (test_bit(vpc_bit, &vpc1)) {
+ switch (vpc_bit) {
+ case 9:
+ ideapad_sync_rfk_state(priv);
+ break;
+ case 13:
+ case 11:
+ case 7:
+ case 6:
+ ideapad_input_report(priv, vpc_bit);
+ break;
+ case 5:
+ ideapad_sync_touchpad_state(adevice);
+ break;
+ case 4:
+ ideapad_backlight_notify_brightness(priv);
+ break;
+ case 3:
+ ideapad_input_novokey(priv);
+ break;
+ case 2:
+ ideapad_backlight_notify_power(priv);
+ break;
+ case 0:
+ ideapad_check_special_buttons(priv);
+ break;
+ default:
+ pr_info("Unknown event: %lu\n", vpc_bit);
+ }
+ }
+ }
+}
+
+static int ideapad_acpi_resume(struct device *device)
+{
+ ideapad_sync_rfk_state(ideapad_priv);
+ ideapad_sync_touchpad_state(to_acpi_device(device));
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume);
+
+static struct acpi_driver ideapad_acpi_driver = {
+ .name = "ideapad_acpi",
+ .class = "IdeaPad",
+ .ids = ideapad_device_ids,
+ .ops.add = ideapad_acpi_add,
+ .ops.remove = ideapad_acpi_remove,
+ .ops.notify = ideapad_acpi_notify,
+ .drv.pm = &ideapad_pm,
+ .owner = THIS_MODULE,
+};
+module_acpi_driver(ideapad_acpi_driver);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("IdeaPad ACPI Extras");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c
new file mode 100644
index 00000000..5051aa97
--- /dev/null
+++ b/drivers/platform/x86/intel_ips.c
@@ -0,0 +1,1749 @@
+/*
+ * Copyright (c) 2009-2010 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ *
+ * Authors:
+ * Jesse Barnes <jbarnes@virtuousgeek.org>
+ */
+
+/*
+ * Some Intel Ibex Peak based platforms support so-called "intelligent
+ * power sharing", which allows the CPU and GPU to cooperate to maximize
+ * performance within a given TDP (thermal design point). This driver
+ * performs the coordination between the CPU and GPU, monitors thermal and
+ * power statistics in the platform, and initializes power monitoring
+ * hardware. It also provides a few tunables to control behavior. Its
+ * primary purpose is to safely allow CPU and GPU turbo modes to be enabled
+ * by tracking power and thermal budget; secondarily it can boost turbo
+ * performance by allocating more power or thermal budget to the CPU or GPU
+ * based on available headroom and activity.
+ *
+ * The basic algorithm is driven by a 5s moving average of tempurature. If
+ * thermal headroom is available, the CPU and/or GPU power clamps may be
+ * adjusted upwards. If we hit the thermal ceiling or a thermal trigger,
+ * we scale back the clamp. Aside from trigger events (when we're critically
+ * close or over our TDP) we don't adjust the clamps more than once every
+ * five seconds.
+ *
+ * The thermal device (device 31, function 6) has a set of registers that
+ * are updated by the ME firmware. The ME should also take the clamp values
+ * written to those registers and write them to the CPU, but we currently
+ * bypass that functionality and write the CPU MSR directly.
+ *
+ * UNSUPPORTED:
+ * - dual MCP configs
+ *
+ * TODO:
+ * - handle CPU hotplug
+ * - provide turbo enable/disable api
+ *
+ * Related documents:
+ * - CDI 403777, 403778 - Auburndale EDS vol 1 & 2
+ * - CDI 401376 - Ibex Peak EDS
+ * - ref 26037, 26641 - IPS BIOS spec
+ * - ref 26489 - Nehalem BIOS writer's guide
+ * - ref 26921 - Ibex Peak BIOS Specification
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/string.h>
+#include <linux/tick.h>
+#include <linux/timer.h>
+#include <linux/dmi.h>
+#include <drm/i915_drm.h>
+#include <asm/msr.h>
+#include <asm/processor.h>
+#include "intel_ips.h"
+
+#include <asm-generic/io-64-nonatomic-lo-hi.h>
+
+#define PCI_DEVICE_ID_INTEL_THERMAL_SENSOR 0x3b32
+
+/*
+ * Package level MSRs for monitor/control
+ */
+#define PLATFORM_INFO 0xce
+#define PLATFORM_TDP (1<<29)
+#define PLATFORM_RATIO (1<<28)
+
+#define IA32_MISC_ENABLE 0x1a0
+#define IA32_MISC_TURBO_EN (1ULL<<38)
+
+#define TURBO_POWER_CURRENT_LIMIT 0x1ac
+#define TURBO_TDC_OVR_EN (1UL<<31)
+#define TURBO_TDC_MASK (0x000000007fff0000UL)
+#define TURBO_TDC_SHIFT (16)
+#define TURBO_TDP_OVR_EN (1UL<<15)
+#define TURBO_TDP_MASK (0x0000000000003fffUL)
+
+/*
+ * Core/thread MSRs for monitoring
+ */
+#define IA32_PERF_CTL 0x199
+#define IA32_PERF_TURBO_DIS (1ULL<<32)
+
+/*
+ * Thermal PCI device regs
+ */
+#define THM_CFG_TBAR 0x10
+#define THM_CFG_TBAR_HI 0x14
+
+#define THM_TSIU 0x00
+#define THM_TSE 0x01
+#define TSE_EN 0xb8
+#define THM_TSS 0x02
+#define THM_TSTR 0x03
+#define THM_TSTTP 0x04
+#define THM_TSCO 0x08
+#define THM_TSES 0x0c
+#define THM_TSGPEN 0x0d
+#define TSGPEN_HOT_LOHI (1<<1)
+#define TSGPEN_CRIT_LOHI (1<<2)
+#define THM_TSPC 0x0e
+#define THM_PPEC 0x10
+#define THM_CTA 0x12
+#define THM_PTA 0x14
+#define PTA_SLOPE_MASK (0xff00)
+#define PTA_SLOPE_SHIFT 8
+#define PTA_OFFSET_MASK (0x00ff)
+#define THM_MGTA 0x16
+#define MGTA_SLOPE_MASK (0xff00)
+#define MGTA_SLOPE_SHIFT 8
+#define MGTA_OFFSET_MASK (0x00ff)
+#define THM_TRC 0x1a
+#define TRC_CORE2_EN (1<<15)
+#define TRC_THM_EN (1<<12)
+#define TRC_C6_WAR (1<<8)
+#define TRC_CORE1_EN (1<<7)
+#define TRC_CORE_PWR (1<<6)
+#define TRC_PCH_EN (1<<5)
+#define TRC_MCH_EN (1<<4)
+#define TRC_DIMM4 (1<<3)
+#define TRC_DIMM3 (1<<2)
+#define TRC_DIMM2 (1<<1)
+#define TRC_DIMM1 (1<<0)
+#define THM_TES 0x20
+#define THM_TEN 0x21
+#define TEN_UPDATE_EN 1
+#define THM_PSC 0x24
+#define PSC_NTG (1<<0) /* No GFX turbo support */
+#define PSC_NTPC (1<<1) /* No CPU turbo support */
+#define PSC_PP_DEF (0<<2) /* Perf policy up to driver */
+#define PSP_PP_PC (1<<2) /* BIOS prefers CPU perf */
+#define PSP_PP_BAL (2<<2) /* BIOS wants balanced perf */
+#define PSP_PP_GFX (3<<2) /* BIOS prefers GFX perf */
+#define PSP_PBRT (1<<4) /* BIOS run time support */
+#define THM_CTV1 0x30
+#define CTV_TEMP_ERROR (1<<15)
+#define CTV_TEMP_MASK 0x3f
+#define CTV_
+#define THM_CTV2 0x32
+#define THM_CEC 0x34 /* undocumented power accumulator in joules */
+#define THM_AE 0x3f
+#define THM_HTS 0x50 /* 32 bits */
+#define HTS_PCPL_MASK (0x7fe00000)
+#define HTS_PCPL_SHIFT 21
+#define HTS_GPL_MASK (0x001ff000)
+#define HTS_GPL_SHIFT 12
+#define HTS_PP_MASK (0x00000c00)
+#define HTS_PP_SHIFT 10
+#define HTS_PP_DEF 0
+#define HTS_PP_PROC 1
+#define HTS_PP_BAL 2
+#define HTS_PP_GFX 3
+#define HTS_PCTD_DIS (1<<9)
+#define HTS_GTD_DIS (1<<8)
+#define HTS_PTL_MASK (0x000000fe)
+#define HTS_PTL_SHIFT 1
+#define HTS_NVV (1<<0)
+#define THM_HTSHI 0x54 /* 16 bits */
+#define HTS2_PPL_MASK (0x03ff)
+#define HTS2_PRST_MASK (0x3c00)
+#define HTS2_PRST_SHIFT 10
+#define HTS2_PRST_UNLOADED 0
+#define HTS2_PRST_RUNNING 1
+#define HTS2_PRST_TDISOP 2 /* turbo disabled due to power */
+#define HTS2_PRST_TDISHT 3 /* turbo disabled due to high temp */
+#define HTS2_PRST_TDISUSR 4 /* user disabled turbo */
+#define HTS2_PRST_TDISPLAT 5 /* platform disabled turbo */
+#define HTS2_PRST_TDISPM 6 /* power management disabled turbo */
+#define HTS2_PRST_TDISERR 7 /* some kind of error disabled turbo */
+#define THM_PTL 0x56
+#define THM_MGTV 0x58
+#define TV_MASK 0x000000000000ff00
+#define TV_SHIFT 8
+#define THM_PTV 0x60
+#define PTV_MASK 0x00ff
+#define THM_MMGPC 0x64
+#define THM_MPPC 0x66
+#define THM_MPCPC 0x68
+#define THM_TSPIEN 0x82
+#define TSPIEN_AUX_LOHI (1<<0)
+#define TSPIEN_HOT_LOHI (1<<1)
+#define TSPIEN_CRIT_LOHI (1<<2)
+#define TSPIEN_AUX2_LOHI (1<<3)
+#define THM_TSLOCK 0x83
+#define THM_ATR 0x84
+#define THM_TOF 0x87
+#define THM_STS 0x98
+#define STS_PCPL_MASK (0x7fe00000)
+#define STS_PCPL_SHIFT 21
+#define STS_GPL_MASK (0x001ff000)
+#define STS_GPL_SHIFT 12
+#define STS_PP_MASK (0x00000c00)
+#define STS_PP_SHIFT 10
+#define STS_PP_DEF 0
+#define STS_PP_PROC 1
+#define STS_PP_BAL 2
+#define STS_PP_GFX 3
+#define STS_PCTD_DIS (1<<9)
+#define STS_GTD_DIS (1<<8)
+#define STS_PTL_MASK (0x000000fe)
+#define STS_PTL_SHIFT 1
+#define STS_NVV (1<<0)
+#define THM_SEC 0x9c
+#define SEC_ACK (1<<0)
+#define THM_TC3 0xa4
+#define THM_TC1 0xa8
+#define STS_PPL_MASK (0x0003ff00)
+#define STS_PPL_SHIFT 16
+#define THM_TC2 0xac
+#define THM_DTV 0xb0
+#define THM_ITV 0xd8
+#define ITV_ME_SEQNO_MASK 0x00ff0000 /* ME should update every ~200ms */
+#define ITV_ME_SEQNO_SHIFT (16)
+#define ITV_MCH_TEMP_MASK 0x0000ff00
+#define ITV_MCH_TEMP_SHIFT (8)
+#define ITV_PCH_TEMP_MASK 0x000000ff
+
+#define thm_readb(off) readb(ips->regmap + (off))
+#define thm_readw(off) readw(ips->regmap + (off))
+#define thm_readl(off) readl(ips->regmap + (off))
+#define thm_readq(off) readq(ips->regmap + (off))
+
+#define thm_writeb(off, val) writeb((val), ips->regmap + (off))
+#define thm_writew(off, val) writew((val), ips->regmap + (off))
+#define thm_writel(off, val) writel((val), ips->regmap + (off))
+
+static const int IPS_ADJUST_PERIOD = 5000; /* ms */
+static bool late_i915_load = false;
+
+/* For initial average collection */
+static const int IPS_SAMPLE_PERIOD = 200; /* ms */
+static const int IPS_SAMPLE_WINDOW = 5000; /* 5s moving window of samples */
+#define IPS_SAMPLE_COUNT (IPS_SAMPLE_WINDOW / IPS_SAMPLE_PERIOD)
+
+/* Per-SKU limits */
+struct ips_mcp_limits {
+ int cpu_family;
+ int cpu_model; /* includes extended model... */
+ int mcp_power_limit; /* mW units */
+ int core_power_limit;
+ int mch_power_limit;
+ int core_temp_limit; /* degrees C */
+ int mch_temp_limit;
+};
+
+/* Max temps are -10 degrees C to avoid PROCHOT# */
+
+struct ips_mcp_limits ips_sv_limits = {
+ .mcp_power_limit = 35000,
+ .core_power_limit = 29000,
+ .mch_power_limit = 20000,
+ .core_temp_limit = 95,
+ .mch_temp_limit = 90
+};
+
+struct ips_mcp_limits ips_lv_limits = {
+ .mcp_power_limit = 25000,
+ .core_power_limit = 21000,
+ .mch_power_limit = 13000,
+ .core_temp_limit = 95,
+ .mch_temp_limit = 90
+};
+
+struct ips_mcp_limits ips_ulv_limits = {
+ .mcp_power_limit = 18000,
+ .core_power_limit = 14000,
+ .mch_power_limit = 11000,
+ .core_temp_limit = 95,
+ .mch_temp_limit = 90
+};
+
+struct ips_driver {
+ struct pci_dev *dev;
+ void *regmap;
+ struct task_struct *monitor;
+ struct task_struct *adjust;
+ struct dentry *debug_root;
+
+ /* Average CPU core temps (all averages in .01 degrees C for precision) */
+ u16 ctv1_avg_temp;
+ u16 ctv2_avg_temp;
+ /* GMCH average */
+ u16 mch_avg_temp;
+ /* Average for the CPU (both cores?) */
+ u16 mcp_avg_temp;
+ /* Average power consumption (in mW) */
+ u32 cpu_avg_power;
+ u32 mch_avg_power;
+
+ /* Offset values */
+ u16 cta_val;
+ u16 pta_val;
+ u16 mgta_val;
+
+ /* Maximums & prefs, protected by turbo status lock */
+ spinlock_t turbo_status_lock;
+ u16 mcp_temp_limit;
+ u16 mcp_power_limit;
+ u16 core_power_limit;
+ u16 mch_power_limit;
+ bool cpu_turbo_enabled;
+ bool __cpu_turbo_on;
+ bool gpu_turbo_enabled;
+ bool __gpu_turbo_on;
+ bool gpu_preferred;
+ bool poll_turbo_status;
+ bool second_cpu;
+ bool turbo_toggle_allowed;
+ struct ips_mcp_limits *limits;
+
+ /* Optional MCH interfaces for if i915 is in use */
+ unsigned long (*read_mch_val)(void);
+ bool (*gpu_raise)(void);
+ bool (*gpu_lower)(void);
+ bool (*gpu_busy)(void);
+ bool (*gpu_turbo_disable)(void);
+
+ /* For restoration at unload */
+ u64 orig_turbo_limit;
+ u64 orig_turbo_ratios;
+};
+
+static bool
+ips_gpu_turbo_enabled(struct ips_driver *ips);
+
+/**
+ * ips_cpu_busy - is CPU busy?
+ * @ips: IPS driver struct
+ *
+ * Check CPU for load to see whether we should increase its thermal budget.
+ *
+ * RETURNS:
+ * True if the CPU could use more power, false otherwise.
+ */
+static bool ips_cpu_busy(struct ips_driver *ips)
+{
+ if ((avenrun[0] >> FSHIFT) > 1)
+ return true;
+
+ return false;
+}
+
+/**
+ * ips_cpu_raise - raise CPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Raise the CPU power clamp by %IPS_CPU_STEP, in accordance with TDP for
+ * this platform.
+ *
+ * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR upwards (as
+ * long as we haven't hit the TDP limit for the SKU).
+ */
+static void ips_cpu_raise(struct ips_driver *ips)
+{
+ u64 turbo_override;
+ u16 cur_tdp_limit, new_tdp_limit;
+
+ if (!ips->cpu_turbo_enabled)
+ return;
+
+ rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+ cur_tdp_limit = turbo_override & TURBO_TDP_MASK;
+ new_tdp_limit = cur_tdp_limit + 8; /* 1W increase */
+
+ /* Clamp to SKU TDP limit */
+ if (((new_tdp_limit * 10) / 8) > ips->core_power_limit)
+ new_tdp_limit = cur_tdp_limit;
+
+ thm_writew(THM_MPCPC, (new_tdp_limit * 10) / 8);
+
+ turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;
+ wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+ turbo_override &= ~TURBO_TDP_MASK;
+ turbo_override |= new_tdp_limit;
+
+ wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+}
+
+/**
+ * ips_cpu_lower - lower CPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Lower CPU power clamp b %IPS_CPU_STEP if possible.
+ *
+ * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR down, going
+ * as low as the platform limits will allow (though we could go lower there
+ * wouldn't be much point).
+ */
+static void ips_cpu_lower(struct ips_driver *ips)
+{
+ u64 turbo_override;
+ u16 cur_limit, new_limit;
+
+ rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+ cur_limit = turbo_override & TURBO_TDP_MASK;
+ new_limit = cur_limit - 8; /* 1W decrease */
+
+ /* Clamp to SKU TDP limit */
+ if (new_limit < (ips->orig_turbo_limit & TURBO_TDP_MASK))
+ new_limit = ips->orig_turbo_limit & TURBO_TDP_MASK;
+
+ thm_writew(THM_MPCPC, (new_limit * 10) / 8);
+
+ turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;
+ wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+ turbo_override &= ~TURBO_TDP_MASK;
+ turbo_override |= new_limit;
+
+ wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+}
+
+/**
+ * do_enable_cpu_turbo - internal turbo enable function
+ * @data: unused
+ *
+ * Internal function for actually updating MSRs. When we enable/disable
+ * turbo, we need to do it on each CPU; this function is the one called
+ * by on_each_cpu() when needed.
+ */
+static void do_enable_cpu_turbo(void *data)
+{
+ u64 perf_ctl;
+
+ rdmsrl(IA32_PERF_CTL, perf_ctl);
+ if (perf_ctl & IA32_PERF_TURBO_DIS) {
+ perf_ctl &= ~IA32_PERF_TURBO_DIS;
+ wrmsrl(IA32_PERF_CTL, perf_ctl);
+ }
+}
+
+/**
+ * ips_enable_cpu_turbo - enable turbo mode on all CPUs
+ * @ips: IPS driver struct
+ *
+ * Enable turbo mode by clearing the disable bit in IA32_PERF_CTL on
+ * all logical threads.
+ */
+static void ips_enable_cpu_turbo(struct ips_driver *ips)
+{
+ /* Already on, no need to mess with MSRs */
+ if (ips->__cpu_turbo_on)
+ return;
+
+ if (ips->turbo_toggle_allowed)
+ on_each_cpu(do_enable_cpu_turbo, ips, 1);
+
+ ips->__cpu_turbo_on = true;
+}
+
+/**
+ * do_disable_cpu_turbo - internal turbo disable function
+ * @data: unused
+ *
+ * Internal function for actually updating MSRs. When we enable/disable
+ * turbo, we need to do it on each CPU; this function is the one called
+ * by on_each_cpu() when needed.
+ */
+static void do_disable_cpu_turbo(void *data)
+{
+ u64 perf_ctl;
+
+ rdmsrl(IA32_PERF_CTL, perf_ctl);
+ if (!(perf_ctl & IA32_PERF_TURBO_DIS)) {
+ perf_ctl |= IA32_PERF_TURBO_DIS;
+ wrmsrl(IA32_PERF_CTL, perf_ctl);
+ }
+}
+
+/**
+ * ips_disable_cpu_turbo - disable turbo mode on all CPUs
+ * @ips: IPS driver struct
+ *
+ * Disable turbo mode by setting the disable bit in IA32_PERF_CTL on
+ * all logical threads.
+ */
+static void ips_disable_cpu_turbo(struct ips_driver *ips)
+{
+ /* Already off, leave it */
+ if (!ips->__cpu_turbo_on)
+ return;
+
+ if (ips->turbo_toggle_allowed)
+ on_each_cpu(do_disable_cpu_turbo, ips, 1);
+
+ ips->__cpu_turbo_on = false;
+}
+
+/**
+ * ips_gpu_busy - is GPU busy?
+ * @ips: IPS driver struct
+ *
+ * Check GPU for load to see whether we should increase its thermal budget.
+ * We need to call into the i915 driver in this case.
+ *
+ * RETURNS:
+ * True if the GPU could use more power, false otherwise.
+ */
+static bool ips_gpu_busy(struct ips_driver *ips)
+{
+ if (!ips_gpu_turbo_enabled(ips))
+ return false;
+
+ return ips->gpu_busy();
+}
+
+/**
+ * ips_gpu_raise - raise GPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Raise the GPU frequency/power if possible. We need to call into the
+ * i915 driver in this case.
+ */
+static void ips_gpu_raise(struct ips_driver *ips)
+{
+ if (!ips_gpu_turbo_enabled(ips))
+ return;
+
+ if (!ips->gpu_raise())
+ ips->gpu_turbo_enabled = false;
+
+ return;
+}
+
+/**
+ * ips_gpu_lower - lower GPU power clamp
+ * @ips: IPS driver struct
+ *
+ * Lower GPU frequency/power if possible. Need to call i915.
+ */
+static void ips_gpu_lower(struct ips_driver *ips)
+{
+ if (!ips_gpu_turbo_enabled(ips))
+ return;
+
+ if (!ips->gpu_lower())
+ ips->gpu_turbo_enabled = false;
+
+ return;
+}
+
+/**
+ * ips_enable_gpu_turbo - notify the gfx driver turbo is available
+ * @ips: IPS driver struct
+ *
+ * Call into the graphics driver indicating that it can safely use
+ * turbo mode.
+ */
+static void ips_enable_gpu_turbo(struct ips_driver *ips)
+{
+ if (ips->__gpu_turbo_on)
+ return;
+ ips->__gpu_turbo_on = true;
+}
+
+/**
+ * ips_disable_gpu_turbo - notify the gfx driver to disable turbo mode
+ * @ips: IPS driver struct
+ *
+ * Request that the graphics driver disable turbo mode.
+ */
+static void ips_disable_gpu_turbo(struct ips_driver *ips)
+{
+ /* Avoid calling i915 if turbo is already disabled */
+ if (!ips->__gpu_turbo_on)
+ return;
+
+ if (!ips->gpu_turbo_disable())
+ dev_err(&ips->dev->dev, "failed to disable graphis turbo\n");
+ else
+ ips->__gpu_turbo_on = false;
+}
+
+/**
+ * mcp_exceeded - check whether we're outside our thermal & power limits
+ * @ips: IPS driver struct
+ *
+ * Check whether the MCP is over its thermal or power budget.
+ */
+static bool mcp_exceeded(struct ips_driver *ips)
+{
+ unsigned long flags;
+ bool ret = false;
+ u32 temp_limit;
+ u32 avg_power;
+
+ spin_lock_irqsave(&ips->turbo_status_lock, flags);
+
+ temp_limit = ips->mcp_temp_limit * 100;
+ if (ips->mcp_avg_temp > temp_limit)
+ ret = true;
+
+ avg_power = ips->cpu_avg_power + ips->mch_avg_power;
+ if (avg_power > ips->mcp_power_limit)
+ ret = true;
+
+ spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+ return ret;
+}
+
+/**
+ * cpu_exceeded - check whether a CPU core is outside its limits
+ * @ips: IPS driver struct
+ * @cpu: CPU number to check
+ *
+ * Check a given CPU's average temp or power is over its limit.
+ */
+static bool cpu_exceeded(struct ips_driver *ips, int cpu)
+{
+ unsigned long flags;
+ int avg;
+ bool ret = false;
+
+ spin_lock_irqsave(&ips->turbo_status_lock, flags);
+ avg = cpu ? ips->ctv2_avg_temp : ips->ctv1_avg_temp;
+ if (avg > (ips->limits->core_temp_limit * 100))
+ ret = true;
+ if (ips->cpu_avg_power > ips->core_power_limit * 100)
+ ret = true;
+ spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+ if (ret)
+ dev_info(&ips->dev->dev,
+ "CPU power or thermal limit exceeded\n");
+
+ return ret;
+}
+
+/**
+ * mch_exceeded - check whether the GPU is over budget
+ * @ips: IPS driver struct
+ *
+ * Check the MCH temp & power against their maximums.
+ */
+static bool mch_exceeded(struct ips_driver *ips)
+{
+ unsigned long flags;
+ bool ret = false;
+
+ spin_lock_irqsave(&ips->turbo_status_lock, flags);
+ if (ips->mch_avg_temp > (ips->limits->mch_temp_limit * 100))
+ ret = true;
+ if (ips->mch_avg_power > ips->mch_power_limit)
+ ret = true;
+ spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+ return ret;
+}
+
+/**
+ * verify_limits - verify BIOS provided limits
+ * @ips: IPS structure
+ *
+ * BIOS can optionally provide non-default limits for power and temp. Check
+ * them here and use the defaults if the BIOS values are not provided or
+ * are otherwise unusable.
+ */
+static void verify_limits(struct ips_driver *ips)
+{
+ if (ips->mcp_power_limit < ips->limits->mcp_power_limit ||
+ ips->mcp_power_limit > 35000)
+ ips->mcp_power_limit = ips->limits->mcp_power_limit;
+
+ if (ips->mcp_temp_limit < ips->limits->core_temp_limit ||
+ ips->mcp_temp_limit < ips->limits->mch_temp_limit ||
+ ips->mcp_temp_limit > 150)
+ ips->mcp_temp_limit = min(ips->limits->core_temp_limit,
+ ips->limits->mch_temp_limit);
+}
+
+/**
+ * update_turbo_limits - get various limits & settings from regs
+ * @ips: IPS driver struct
+ *
+ * Update the IPS power & temp limits, along with turbo enable flags,
+ * based on latest register contents.
+ *
+ * Used at init time and for runtime BIOS support, which requires polling
+ * the regs for updates (as a result of AC->DC transition for example).
+ *
+ * LOCKING:
+ * Caller must hold turbo_status_lock (outside of init)
+ */
+static void update_turbo_limits(struct ips_driver *ips)
+{
+ u32 hts = thm_readl(THM_HTS);
+
+ ips->cpu_turbo_enabled = !(hts & HTS_PCTD_DIS);
+ /*
+ * Disable turbo for now, until we can figure out why the power figures
+ * are wrong
+ */
+ ips->cpu_turbo_enabled = false;
+
+ if (ips->gpu_busy)
+ ips->gpu_turbo_enabled = !(hts & HTS_GTD_DIS);
+
+ ips->core_power_limit = thm_readw(THM_MPCPC);
+ ips->mch_power_limit = thm_readw(THM_MMGPC);
+ ips->mcp_temp_limit = thm_readw(THM_PTL);
+ ips->mcp_power_limit = thm_readw(THM_MPPC);
+
+ verify_limits(ips);
+ /* Ignore BIOS CPU vs GPU pref */
+}
+
+/**
+ * ips_adjust - adjust power clamp based on thermal state
+ * @data: ips driver structure
+ *
+ * Wake up every 5s or so and check whether we should adjust the power clamp.
+ * Check CPU and GPU load to determine which needs adjustment. There are
+ * several things to consider here:
+ * - do we need to adjust up or down?
+ * - is CPU busy?
+ * - is GPU busy?
+ * - is CPU in turbo?
+ * - is GPU in turbo?
+ * - is CPU or GPU preferred? (CPU is default)
+ *
+ * So, given the above, we do the following:
+ * - up (TDP available)
+ * - CPU not busy, GPU not busy - nothing
+ * - CPU busy, GPU not busy - adjust CPU up
+ * - CPU not busy, GPU busy - adjust GPU up
+ * - CPU busy, GPU busy - adjust preferred unit up, taking headroom from
+ * non-preferred unit if necessary
+ * - down (at TDP limit)
+ * - adjust both CPU and GPU down if possible
+ *
+ cpu+ gpu+ cpu+gpu- cpu-gpu+ cpu-gpu-
+cpu < gpu < cpu+gpu+ cpu+ gpu+ nothing
+cpu < gpu >= cpu+gpu-(mcp<) cpu+gpu-(mcp<) gpu- gpu-
+cpu >= gpu < cpu-gpu+(mcp<) cpu- cpu-gpu+(mcp<) cpu-
+cpu >= gpu >= cpu-gpu- cpu-gpu- cpu-gpu- cpu-gpu-
+ *
+ */
+static int ips_adjust(void *data)
+{
+ struct ips_driver *ips = data;
+ unsigned long flags;
+
+ dev_dbg(&ips->dev->dev, "starting ips-adjust thread\n");
+
+ /*
+ * Adjust CPU and GPU clamps every 5s if needed. Doing it more
+ * often isn't recommended due to ME interaction.
+ */
+ do {
+ bool cpu_busy = ips_cpu_busy(ips);
+ bool gpu_busy = ips_gpu_busy(ips);
+
+ spin_lock_irqsave(&ips->turbo_status_lock, flags);
+ if (ips->poll_turbo_status)
+ update_turbo_limits(ips);
+ spin_unlock_irqrestore(&ips->turbo_status_lock, flags);
+
+ /* Update turbo status if necessary */
+ if (ips->cpu_turbo_enabled)
+ ips_enable_cpu_turbo(ips);
+ else
+ ips_disable_cpu_turbo(ips);
+
+ if (ips->gpu_turbo_enabled)
+ ips_enable_gpu_turbo(ips);
+ else
+ ips_disable_gpu_turbo(ips);
+
+ /* We're outside our comfort zone, crank them down */
+ if (mcp_exceeded(ips)) {
+ ips_cpu_lower(ips);
+ ips_gpu_lower(ips);
+ goto sleep;
+ }
+
+ if (!cpu_exceeded(ips, 0) && cpu_busy)
+ ips_cpu_raise(ips);
+ else
+ ips_cpu_lower(ips);
+
+ if (!mch_exceeded(ips) && gpu_busy)
+ ips_gpu_raise(ips);
+ else
+ ips_gpu_lower(ips);
+
+sleep:
+ schedule_timeout_interruptible(msecs_to_jiffies(IPS_ADJUST_PERIOD));
+ } while (!kthread_should_stop());
+
+ dev_dbg(&ips->dev->dev, "ips-adjust thread stopped\n");
+
+ return 0;
+}
+
+/*
+ * Helpers for reading out temp/power values and calculating their
+ * averages for the decision making and monitoring functions.
+ */
+
+static u16 calc_avg_temp(struct ips_driver *ips, u16 *array)
+{
+ u64 total = 0;
+ int i;
+ u16 avg;
+
+ for (i = 0; i < IPS_SAMPLE_COUNT; i++)
+ total += (u64)(array[i] * 100);
+
+ do_div(total, IPS_SAMPLE_COUNT);
+
+ avg = (u16)total;
+
+ return avg;
+}
+
+static u16 read_mgtv(struct ips_driver *ips)
+{
+ u16 ret;
+ u64 slope, offset;
+ u64 val;
+
+ val = thm_readq(THM_MGTV);
+ val = (val & TV_MASK) >> TV_SHIFT;
+
+ slope = offset = thm_readw(THM_MGTA);
+ slope = (slope & MGTA_SLOPE_MASK) >> MGTA_SLOPE_SHIFT;
+ offset = offset & MGTA_OFFSET_MASK;
+
+ ret = ((val * slope + 0x40) >> 7) + offset;
+
+ return 0; /* MCH temp reporting buggy */
+}
+
+static u16 read_ptv(struct ips_driver *ips)
+{
+ u16 val, slope, offset;
+
+ slope = (ips->pta_val & PTA_SLOPE_MASK) >> PTA_SLOPE_SHIFT;
+ offset = ips->pta_val & PTA_OFFSET_MASK;
+
+ val = thm_readw(THM_PTV) & PTV_MASK;
+
+ return val;
+}
+
+static u16 read_ctv(struct ips_driver *ips, int cpu)
+{
+ int reg = cpu ? THM_CTV2 : THM_CTV1;
+ u16 val;
+
+ val = thm_readw(reg);
+ if (!(val & CTV_TEMP_ERROR))
+ val = (val) >> 6; /* discard fractional component */
+ else
+ val = 0;
+
+ return val;
+}
+
+static u32 get_cpu_power(struct ips_driver *ips, u32 *last, int period)
+{
+ u32 val;
+ u32 ret;
+
+ /*
+ * CEC is in joules/65535. Take difference over time to
+ * get watts.
+ */
+ val = thm_readl(THM_CEC);
+
+ /* period is in ms and we want mW */
+ ret = (((val - *last) * 1000) / period);
+ ret = (ret * 1000) / 65535;
+ *last = val;
+
+ return 0;
+}
+
+static const u16 temp_decay_factor = 2;
+static u16 update_average_temp(u16 avg, u16 val)
+{
+ u16 ret;
+
+ /* Multiply by 100 for extra precision */
+ ret = (val * 100 / temp_decay_factor) +
+ (((temp_decay_factor - 1) * avg) / temp_decay_factor);
+ return ret;
+}
+
+static const u16 power_decay_factor = 2;
+static u16 update_average_power(u32 avg, u32 val)
+{
+ u32 ret;
+
+ ret = (val / power_decay_factor) +
+ (((power_decay_factor - 1) * avg) / power_decay_factor);
+
+ return ret;
+}
+
+static u32 calc_avg_power(struct ips_driver *ips, u32 *array)
+{
+ u64 total = 0;
+ u32 avg;
+ int i;
+
+ for (i = 0; i < IPS_SAMPLE_COUNT; i++)
+ total += array[i];
+
+ do_div(total, IPS_SAMPLE_COUNT);
+ avg = (u32)total;
+
+ return avg;
+}
+
+static void monitor_timeout(unsigned long arg)
+{
+ wake_up_process((struct task_struct *)arg);
+}
+
+/**
+ * ips_monitor - temp/power monitoring thread
+ * @data: ips driver structure
+ *
+ * This is the main function for the IPS driver. It monitors power and
+ * tempurature in the MCP and adjusts CPU and GPU power clams accordingly.
+ *
+ * We keep a 5s moving average of power consumption and tempurature. Using
+ * that data, along with CPU vs GPU preference, we adjust the power clamps
+ * up or down.
+ */
+static int ips_monitor(void *data)
+{
+ struct ips_driver *ips = data;
+ struct timer_list timer;
+ unsigned long seqno_timestamp, expire, last_msecs, last_sample_period;
+ int i;
+ u32 *cpu_samples, *mchp_samples, old_cpu_power;
+ u16 *mcp_samples, *ctv1_samples, *ctv2_samples, *mch_samples;
+ u8 cur_seqno, last_seqno;
+
+ mcp_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+ ctv1_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+ ctv2_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+ mch_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+ cpu_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+ mchp_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL);
+ if (!mcp_samples || !ctv1_samples || !ctv2_samples || !mch_samples ||
+ !cpu_samples || !mchp_samples) {
+ dev_err(&ips->dev->dev,
+ "failed to allocate sample array, ips disabled\n");
+ kfree(mcp_samples);
+ kfree(ctv1_samples);
+ kfree(ctv2_samples);
+ kfree(mch_samples);
+ kfree(cpu_samples);
+ kfree(mchp_samples);
+ return -ENOMEM;
+ }
+
+ last_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >>
+ ITV_ME_SEQNO_SHIFT;
+ seqno_timestamp = get_jiffies_64();
+
+ old_cpu_power = thm_readl(THM_CEC);
+ schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD));
+
+ /* Collect an initial average */
+ for (i = 0; i < IPS_SAMPLE_COUNT; i++) {
+ u32 mchp, cpu_power;
+ u16 val;
+
+ mcp_samples[i] = read_ptv(ips);
+
+ val = read_ctv(ips, 0);
+ ctv1_samples[i] = val;
+
+ val = read_ctv(ips, 1);
+ ctv2_samples[i] = val;
+
+ val = read_mgtv(ips);
+ mch_samples[i] = val;
+
+ cpu_power = get_cpu_power(ips, &old_cpu_power,
+ IPS_SAMPLE_PERIOD);
+ cpu_samples[i] = cpu_power;
+
+ if (ips->read_mch_val) {
+ mchp = ips->read_mch_val();
+ mchp_samples[i] = mchp;
+ }
+
+ schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD));
+ if (kthread_should_stop())
+ break;
+ }
+
+ ips->mcp_avg_temp = calc_avg_temp(ips, mcp_samples);
+ ips->ctv1_avg_temp = calc_avg_temp(ips, ctv1_samples);
+ ips->ctv2_avg_temp = calc_avg_temp(ips, ctv2_samples);
+ ips->mch_avg_temp = calc_avg_temp(ips, mch_samples);
+ ips->cpu_avg_power = calc_avg_power(ips, cpu_samples);
+ ips->mch_avg_power = calc_avg_power(ips, mchp_samples);
+ kfree(mcp_samples);
+ kfree(ctv1_samples);
+ kfree(ctv2_samples);
+ kfree(mch_samples);
+ kfree(cpu_samples);
+ kfree(mchp_samples);
+
+ /* Start the adjustment thread now that we have data */
+ wake_up_process(ips->adjust);
+
+ /*
+ * Ok, now we have an initial avg. From here on out, we track the
+ * running avg using a decaying average calculation. This allows
+ * us to reduce the sample frequency if the CPU and GPU are idle.
+ */
+ old_cpu_power = thm_readl(THM_CEC);
+ schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD));
+ last_sample_period = IPS_SAMPLE_PERIOD;
+
+ setup_deferrable_timer_on_stack(&timer, monitor_timeout,
+ (unsigned long)current);
+ do {
+ u32 cpu_val, mch_val;
+ u16 val;
+
+ /* MCP itself */
+ val = read_ptv(ips);
+ ips->mcp_avg_temp = update_average_temp(ips->mcp_avg_temp, val);
+
+ /* Processor 0 */
+ val = read_ctv(ips, 0);
+ ips->ctv1_avg_temp =
+ update_average_temp(ips->ctv1_avg_temp, val);
+ /* Power */
+ cpu_val = get_cpu_power(ips, &old_cpu_power,
+ last_sample_period);
+ ips->cpu_avg_power =
+ update_average_power(ips->cpu_avg_power, cpu_val);
+
+ if (ips->second_cpu) {
+ /* Processor 1 */
+ val = read_ctv(ips, 1);
+ ips->ctv2_avg_temp =
+ update_average_temp(ips->ctv2_avg_temp, val);
+ }
+
+ /* MCH */
+ val = read_mgtv(ips);
+ ips->mch_avg_temp = update_average_temp(ips->mch_avg_temp, val);
+ /* Power */
+ if (ips->read_mch_val) {
+ mch_val = ips->read_mch_val();
+ ips->mch_avg_power =
+ update_average_power(ips->mch_avg_power,
+ mch_val);
+ }
+
+ /*
+ * Make sure ME is updating thermal regs.
+ * Note:
+ * If it's been more than a second since the last update,
+ * the ME is probably hung.
+ */
+ cur_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >>
+ ITV_ME_SEQNO_SHIFT;
+ if (cur_seqno == last_seqno &&
+ time_after(jiffies, seqno_timestamp + HZ)) {
+ dev_warn(&ips->dev->dev, "ME failed to update for more than 1s, likely hung\n");
+ } else {
+ seqno_timestamp = get_jiffies_64();
+ last_seqno = cur_seqno;
+ }
+
+ last_msecs = jiffies_to_msecs(jiffies);
+ expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD);
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+ mod_timer(&timer, expire);
+ schedule();
+
+ /* Calculate actual sample period for power averaging */
+ last_sample_period = jiffies_to_msecs(jiffies) - last_msecs;
+ if (!last_sample_period)
+ last_sample_period = 1;
+ } while (!kthread_should_stop());
+
+ del_timer_sync(&timer);
+ destroy_timer_on_stack(&timer);
+
+ dev_dbg(&ips->dev->dev, "ips-monitor thread stopped\n");
+
+ return 0;
+}
+
+#if 0
+#define THM_DUMPW(reg) \
+ { \
+ u16 val = thm_readw(reg); \
+ dev_dbg(&ips->dev->dev, #reg ": 0x%04x\n", val); \
+ }
+#define THM_DUMPL(reg) \
+ { \
+ u32 val = thm_readl(reg); \
+ dev_dbg(&ips->dev->dev, #reg ": 0x%08x\n", val); \
+ }
+#define THM_DUMPQ(reg) \
+ { \
+ u64 val = thm_readq(reg); \
+ dev_dbg(&ips->dev->dev, #reg ": 0x%016x\n", val); \
+ }
+
+static void dump_thermal_info(struct ips_driver *ips)
+{
+ u16 ptl;
+
+ ptl = thm_readw(THM_PTL);
+ dev_dbg(&ips->dev->dev, "Processor temp limit: %d\n", ptl);
+
+ THM_DUMPW(THM_CTA);
+ THM_DUMPW(THM_TRC);
+ THM_DUMPW(THM_CTV1);
+ THM_DUMPL(THM_STS);
+ THM_DUMPW(THM_PTV);
+ THM_DUMPQ(THM_MGTV);
+}
+#endif
+
+/**
+ * ips_irq_handler - handle temperature triggers and other IPS events
+ * @irq: irq number
+ * @arg: unused
+ *
+ * Handle temperature limit trigger events, generally by lowering the clamps.
+ * If we're at a critical limit, we clamp back to the lowest possible value
+ * to prevent emergency shutdown.
+ */
+static irqreturn_t ips_irq_handler(int irq, void *arg)
+{
+ struct ips_driver *ips = arg;
+ u8 tses = thm_readb(THM_TSES);
+ u8 tes = thm_readb(THM_TES);
+
+ if (!tses && !tes)
+ return IRQ_NONE;
+
+ dev_info(&ips->dev->dev, "TSES: 0x%02x\n", tses);
+ dev_info(&ips->dev->dev, "TES: 0x%02x\n", tes);
+
+ /* STS update from EC? */
+ if (tes & 1) {
+ u32 sts, tc1;
+
+ sts = thm_readl(THM_STS);
+ tc1 = thm_readl(THM_TC1);
+
+ if (sts & STS_NVV) {
+ spin_lock(&ips->turbo_status_lock);
+ ips->core_power_limit = (sts & STS_PCPL_MASK) >>
+ STS_PCPL_SHIFT;
+ ips->mch_power_limit = (sts & STS_GPL_MASK) >>
+ STS_GPL_SHIFT;
+ /* ignore EC CPU vs GPU pref */
+ ips->cpu_turbo_enabled = !(sts & STS_PCTD_DIS);
+ /*
+ * Disable turbo for now, until we can figure
+ * out why the power figures are wrong
+ */
+ ips->cpu_turbo_enabled = false;
+ if (ips->gpu_busy)
+ ips->gpu_turbo_enabled = !(sts & STS_GTD_DIS);
+ ips->mcp_temp_limit = (sts & STS_PTL_MASK) >>
+ STS_PTL_SHIFT;
+ ips->mcp_power_limit = (tc1 & STS_PPL_MASK) >>
+ STS_PPL_SHIFT;
+ verify_limits(ips);
+ spin_unlock(&ips->turbo_status_lock);
+
+ thm_writeb(THM_SEC, SEC_ACK);
+ }
+ thm_writeb(THM_TES, tes);
+ }
+
+ /* Thermal trip */
+ if (tses) {
+ dev_warn(&ips->dev->dev,
+ "thermal trip occurred, tses: 0x%04x\n", tses);
+ thm_writeb(THM_TSES, tses);
+ }
+
+ return IRQ_HANDLED;
+}
+
+#ifndef CONFIG_DEBUG_FS
+static void ips_debugfs_init(struct ips_driver *ips) { return; }
+static void ips_debugfs_cleanup(struct ips_driver *ips) { return; }
+#else
+
+/* Expose current state and limits in debugfs if possible */
+
+struct ips_debugfs_node {
+ struct ips_driver *ips;
+ char *name;
+ int (*show)(struct seq_file *m, void *data);
+};
+
+static int show_cpu_temp(struct seq_file *m, void *data)
+{
+ struct ips_driver *ips = m->private;
+
+ seq_printf(m, "%d.%02d\n", ips->ctv1_avg_temp / 100,
+ ips->ctv1_avg_temp % 100);
+
+ return 0;
+}
+
+static int show_cpu_power(struct seq_file *m, void *data)
+{
+ struct ips_driver *ips = m->private;
+
+ seq_printf(m, "%dmW\n", ips->cpu_avg_power);
+
+ return 0;
+}
+
+static int show_cpu_clamp(struct seq_file *m, void *data)
+{
+ u64 turbo_override;
+ int tdp, tdc;
+
+ rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+
+ tdp = (int)(turbo_override & TURBO_TDP_MASK);
+ tdc = (int)((turbo_override & TURBO_TDC_MASK) >> TURBO_TDC_SHIFT);
+
+ /* Convert to .1W/A units */
+ tdp = tdp * 10 / 8;
+ tdc = tdc * 10 / 8;
+
+ /* Watts Amperes */
+ seq_printf(m, "%d.%dW %d.%dA\n", tdp / 10, tdp % 10,
+ tdc / 10, tdc % 10);
+
+ return 0;
+}
+
+static int show_mch_temp(struct seq_file *m, void *data)
+{
+ struct ips_driver *ips = m->private;
+
+ seq_printf(m, "%d.%02d\n", ips->mch_avg_temp / 100,
+ ips->mch_avg_temp % 100);
+
+ return 0;
+}
+
+static int show_mch_power(struct seq_file *m, void *data)
+{
+ struct ips_driver *ips = m->private;
+
+ seq_printf(m, "%dmW\n", ips->mch_avg_power);
+
+ return 0;
+}
+
+static struct ips_debugfs_node ips_debug_files[] = {
+ { NULL, "cpu_temp", show_cpu_temp },
+ { NULL, "cpu_power", show_cpu_power },
+ { NULL, "cpu_clamp", show_cpu_clamp },
+ { NULL, "mch_temp", show_mch_temp },
+ { NULL, "mch_power", show_mch_power },
+};
+
+static int ips_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct ips_debugfs_node *node = inode->i_private;
+
+ return single_open(file, node->show, node->ips);
+}
+
+static const struct file_operations ips_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = ips_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void ips_debugfs_cleanup(struct ips_driver *ips)
+{
+ if (ips->debug_root)
+ debugfs_remove_recursive(ips->debug_root);
+ return;
+}
+
+static void ips_debugfs_init(struct ips_driver *ips)
+{
+ int i;
+
+ ips->debug_root = debugfs_create_dir("ips", NULL);
+ if (!ips->debug_root) {
+ dev_err(&ips->dev->dev,
+ "failed to create debugfs entries: %ld\n",
+ PTR_ERR(ips->debug_root));
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ips_debug_files); i++) {
+ struct dentry *ent;
+ struct ips_debugfs_node *node = &ips_debug_files[i];
+
+ node->ips = ips;
+ ent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
+ ips->debug_root, node,
+ &ips_debugfs_ops);
+ if (!ent) {
+ dev_err(&ips->dev->dev,
+ "failed to create debug file: %ld\n",
+ PTR_ERR(ent));
+ goto err_cleanup;
+ }
+ }
+
+ return;
+
+err_cleanup:
+ ips_debugfs_cleanup(ips);
+ return;
+}
+#endif /* CONFIG_DEBUG_FS */
+
+/**
+ * ips_detect_cpu - detect whether CPU supports IPS
+ *
+ * Walk our list and see if we're on a supported CPU. If we find one,
+ * return the limits for it.
+ */
+static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips)
+{
+ u64 turbo_power, misc_en;
+ struct ips_mcp_limits *limits = NULL;
+ u16 tdp;
+
+ if (!(boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 37)) {
+ dev_info(&ips->dev->dev, "Non-IPS CPU detected.\n");
+ goto out;
+ }
+
+ rdmsrl(IA32_MISC_ENABLE, misc_en);
+ /*
+ * If the turbo enable bit isn't set, we shouldn't try to enable/disable
+ * turbo manually or we'll get an illegal MSR access, even though
+ * turbo will still be available.
+ */
+ if (misc_en & IA32_MISC_TURBO_EN)
+ ips->turbo_toggle_allowed = true;
+ else
+ ips->turbo_toggle_allowed = false;
+
+ if (strstr(boot_cpu_data.x86_model_id, "CPU M"))
+ limits = &ips_sv_limits;
+ else if (strstr(boot_cpu_data.x86_model_id, "CPU L"))
+ limits = &ips_lv_limits;
+ else if (strstr(boot_cpu_data.x86_model_id, "CPU U"))
+ limits = &ips_ulv_limits;
+ else {
+ dev_info(&ips->dev->dev, "No CPUID match found.\n");
+ goto out;
+ }
+
+ rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_power);
+ tdp = turbo_power & TURBO_TDP_MASK;
+
+ /* Sanity check TDP against CPU */
+ if (limits->core_power_limit != (tdp / 8) * 1000) {
+ dev_info(&ips->dev->dev, "CPU TDP doesn't match expected value (found %d, expected %d)\n",
+ tdp / 8, limits->core_power_limit / 1000);
+ limits->core_power_limit = (tdp / 8) * 1000;
+ }
+
+out:
+ return limits;
+}
+
+/**
+ * ips_get_i915_syms - try to get GPU control methods from i915 driver
+ * @ips: IPS driver
+ *
+ * The i915 driver exports several interfaces to allow the IPS driver to
+ * monitor and control graphics turbo mode. If we can find them, we can
+ * enable graphics turbo, otherwise we must disable it to avoid exceeding
+ * thermal and power limits in the MCP.
+ */
+static bool ips_get_i915_syms(struct ips_driver *ips)
+{
+ ips->read_mch_val = symbol_get(i915_read_mch_val);
+ if (!ips->read_mch_val)
+ goto out_err;
+ ips->gpu_raise = symbol_get(i915_gpu_raise);
+ if (!ips->gpu_raise)
+ goto out_put_mch;
+ ips->gpu_lower = symbol_get(i915_gpu_lower);
+ if (!ips->gpu_lower)
+ goto out_put_raise;
+ ips->gpu_busy = symbol_get(i915_gpu_busy);
+ if (!ips->gpu_busy)
+ goto out_put_lower;
+ ips->gpu_turbo_disable = symbol_get(i915_gpu_turbo_disable);
+ if (!ips->gpu_turbo_disable)
+ goto out_put_busy;
+
+ return true;
+
+out_put_busy:
+ symbol_put(i915_gpu_busy);
+out_put_lower:
+ symbol_put(i915_gpu_lower);
+out_put_raise:
+ symbol_put(i915_gpu_raise);
+out_put_mch:
+ symbol_put(i915_read_mch_val);
+out_err:
+ return false;
+}
+
+static bool
+ips_gpu_turbo_enabled(struct ips_driver *ips)
+{
+ if (!ips->gpu_busy && late_i915_load) {
+ if (ips_get_i915_syms(ips)) {
+ dev_info(&ips->dev->dev,
+ "i915 driver attached, reenabling gpu turbo\n");
+ ips->gpu_turbo_enabled = !(thm_readl(THM_HTS) & HTS_GTD_DIS);
+ }
+ }
+
+ return ips->gpu_turbo_enabled;
+}
+
+void
+ips_link_to_i915_driver(void)
+{
+ /* We can't cleanly get at the various ips_driver structs from
+ * this caller (the i915 driver), so just set a flag saying
+ * that it's time to try getting the symbols again.
+ */
+ late_i915_load = true;
+}
+EXPORT_SYMBOL_GPL(ips_link_to_i915_driver);
+
+static DEFINE_PCI_DEVICE_TABLE(ips_id_table) = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_THERMAL_SENSOR), },
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, ips_id_table);
+
+static int ips_blacklist_callback(const struct dmi_system_id *id)
+{
+ pr_info("Blacklisted intel_ips for %s\n", id->ident);
+ return 1;
+}
+
+static const struct dmi_system_id ips_blacklist[] = {
+ {
+ .callback = ips_blacklist_callback,
+ .ident = "HP ProBook",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook"),
+ },
+ },
+ { } /* terminating entry */
+};
+
+static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ u64 platform_info;
+ struct ips_driver *ips;
+ u32 hts;
+ int ret = 0;
+ u16 htshi, trc, trc_required_mask;
+ u8 tse;
+
+ if (dmi_check_system(ips_blacklist))
+ return -ENODEV;
+
+ ips = kzalloc(sizeof(struct ips_driver), GFP_KERNEL);
+ if (!ips)
+ return -ENOMEM;
+
+ pci_set_drvdata(dev, ips);
+ ips->dev = dev;
+
+ ips->limits = ips_detect_cpu(ips);
+ if (!ips->limits) {
+ dev_info(&dev->dev, "IPS not supported on this CPU\n");
+ ret = -ENXIO;
+ goto error_free;
+ }
+
+ spin_lock_init(&ips->turbo_status_lock);
+
+ ret = pci_enable_device(dev);
+ if (ret) {
+ dev_err(&dev->dev, "can't enable PCI device, aborting\n");
+ goto error_free;
+ }
+
+ if (!pci_resource_start(dev, 0)) {
+ dev_err(&dev->dev, "TBAR not assigned, aborting\n");
+ ret = -ENXIO;
+ goto error_free;
+ }
+
+ ret = pci_request_regions(dev, "ips thermal sensor");
+ if (ret) {
+ dev_err(&dev->dev, "thermal resource busy, aborting\n");
+ goto error_free;
+ }
+
+
+ ips->regmap = ioremap(pci_resource_start(dev, 0),
+ pci_resource_len(dev, 0));
+ if (!ips->regmap) {
+ dev_err(&dev->dev, "failed to map thermal regs, aborting\n");
+ ret = -EBUSY;
+ goto error_release;
+ }
+
+ tse = thm_readb(THM_TSE);
+ if (tse != TSE_EN) {
+ dev_err(&dev->dev, "thermal device not enabled (0x%02x), aborting\n", tse);
+ ret = -ENXIO;
+ goto error_unmap;
+ }
+
+ trc = thm_readw(THM_TRC);
+ trc_required_mask = TRC_CORE1_EN | TRC_CORE_PWR | TRC_MCH_EN;
+ if ((trc & trc_required_mask) != trc_required_mask) {
+ dev_err(&dev->dev, "thermal reporting for required devices not enabled, aborting\n");
+ ret = -ENXIO;
+ goto error_unmap;
+ }
+
+ if (trc & TRC_CORE2_EN)
+ ips->second_cpu = true;
+
+ update_turbo_limits(ips);
+ dev_dbg(&dev->dev, "max cpu power clamp: %dW\n",
+ ips->mcp_power_limit / 10);
+ dev_dbg(&dev->dev, "max core power clamp: %dW\n",
+ ips->core_power_limit / 10);
+ /* BIOS may update limits at runtime */
+ if (thm_readl(THM_PSC) & PSP_PBRT)
+ ips->poll_turbo_status = true;
+
+ if (!ips_get_i915_syms(ips)) {
+ dev_info(&dev->dev, "failed to get i915 symbols, graphics turbo disabled until i915 loads\n");
+ ips->gpu_turbo_enabled = false;
+ } else {
+ dev_dbg(&dev->dev, "graphics turbo enabled\n");
+ ips->gpu_turbo_enabled = true;
+ }
+
+ /*
+ * Check PLATFORM_INFO MSR to make sure this chip is
+ * turbo capable.
+ */
+ rdmsrl(PLATFORM_INFO, platform_info);
+ if (!(platform_info & PLATFORM_TDP)) {
+ dev_err(&dev->dev, "platform indicates TDP override unavailable, aborting\n");
+ ret = -ENODEV;
+ goto error_unmap;
+ }
+
+ /*
+ * IRQ handler for ME interaction
+ * Note: don't use MSI here as the PCH has bugs.
+ */
+ pci_disable_msi(dev);
+ ret = request_irq(dev->irq, ips_irq_handler, IRQF_SHARED, "ips",
+ ips);
+ if (ret) {
+ dev_err(&dev->dev, "request irq failed, aborting\n");
+ goto error_unmap;
+ }
+
+ /* Enable aux, hot & critical interrupts */
+ thm_writeb(THM_TSPIEN, TSPIEN_AUX2_LOHI | TSPIEN_CRIT_LOHI |
+ TSPIEN_HOT_LOHI | TSPIEN_AUX_LOHI);
+ thm_writeb(THM_TEN, TEN_UPDATE_EN);
+
+ /* Collect adjustment values */
+ ips->cta_val = thm_readw(THM_CTA);
+ ips->pta_val = thm_readw(THM_PTA);
+ ips->mgta_val = thm_readw(THM_MGTA);
+
+ /* Save turbo limits & ratios */
+ rdmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
+
+ ips_disable_cpu_turbo(ips);
+ ips->cpu_turbo_enabled = false;
+
+ /* Create thermal adjust thread */
+ ips->adjust = kthread_create(ips_adjust, ips, "ips-adjust");
+ if (IS_ERR(ips->adjust)) {
+ dev_err(&dev->dev,
+ "failed to create thermal adjust thread, aborting\n");
+ ret = -ENOMEM;
+ goto error_free_irq;
+
+ }
+
+ /*
+ * Set up the work queue and monitor thread. The monitor thread
+ * will wake up ips_adjust thread.
+ */
+ ips->monitor = kthread_run(ips_monitor, ips, "ips-monitor");
+ if (IS_ERR(ips->monitor)) {
+ dev_err(&dev->dev,
+ "failed to create thermal monitor thread, aborting\n");
+ ret = -ENOMEM;
+ goto error_thread_cleanup;
+ }
+
+ hts = (ips->core_power_limit << HTS_PCPL_SHIFT) |
+ (ips->mcp_temp_limit << HTS_PTL_SHIFT) | HTS_NVV;
+ htshi = HTS2_PRST_RUNNING << HTS2_PRST_SHIFT;
+
+ thm_writew(THM_HTSHI, htshi);
+ thm_writel(THM_HTS, hts);
+
+ ips_debugfs_init(ips);
+
+ dev_info(&dev->dev, "IPS driver initialized, MCP temp limit %d\n",
+ ips->mcp_temp_limit);
+ return ret;
+
+error_thread_cleanup:
+ kthread_stop(ips->adjust);
+error_free_irq:
+ free_irq(ips->dev->irq, ips);
+error_unmap:
+ iounmap(ips->regmap);
+error_release:
+ pci_release_regions(dev);
+error_free:
+ kfree(ips);
+ return ret;
+}
+
+static void ips_remove(struct pci_dev *dev)
+{
+ struct ips_driver *ips = pci_get_drvdata(dev);
+ u64 turbo_override;
+
+ if (!ips)
+ return;
+
+ ips_debugfs_cleanup(ips);
+
+ /* Release i915 driver */
+ if (ips->read_mch_val)
+ symbol_put(i915_read_mch_val);
+ if (ips->gpu_raise)
+ symbol_put(i915_gpu_raise);
+ if (ips->gpu_lower)
+ symbol_put(i915_gpu_lower);
+ if (ips->gpu_busy)
+ symbol_put(i915_gpu_busy);
+ if (ips->gpu_turbo_disable)
+ symbol_put(i915_gpu_turbo_disable);
+
+ rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ turbo_override &= ~(TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN);
+ wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ wrmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
+
+ free_irq(ips->dev->irq, ips);
+ if (ips->adjust)
+ kthread_stop(ips->adjust);
+ if (ips->monitor)
+ kthread_stop(ips->monitor);
+ iounmap(ips->regmap);
+ pci_release_regions(dev);
+ kfree(ips);
+ dev_dbg(&dev->dev, "IPS driver removed\n");
+}
+
+static void ips_shutdown(struct pci_dev *dev)
+{
+}
+
+static struct pci_driver ips_pci_driver = {
+ .name = "intel ips",
+ .id_table = ips_id_table,
+ .probe = ips_probe,
+ .remove = ips_remove,
+ .shutdown = ips_shutdown,
+};
+
+static int __init ips_init(void)
+{
+ return pci_register_driver(&ips_pci_driver);
+}
+module_init(ips_init);
+
+static void ips_exit(void)
+{
+ pci_unregister_driver(&ips_pci_driver);
+ return;
+}
+module_exit(ips_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jesse Barnes <jbarnes@virtuousgeek.org>");
+MODULE_DESCRIPTION("Intelligent Power Sharing Driver");
diff --git a/drivers/platform/x86/intel_ips.h b/drivers/platform/x86/intel_ips.h
new file mode 100644
index 00000000..73299bef
--- /dev/null
+++ b/drivers/platform/x86/intel_ips.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * The full GNU General Public License is included in this distribution in
+ * the file called "COPYING".
+ */
+
+void ips_link_to_i915_driver(void);
diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c
new file mode 100644
index 00000000..d6cfc155
--- /dev/null
+++ b/drivers/platform/x86/intel_menlow.c
@@ -0,0 +1,543 @@
+/*
+ * intel_menlow.c - Intel menlow Driver for thermal management extension
+ *
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.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; version 2 of the License.
+ *
+ * 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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This driver creates the sys I/F for programming the sensors.
+ * It also implements the driver for intel menlow memory controller (hardware
+ * id is INT0002) which makes use of the platform specific ACPI methods
+ * to get/set bandwidth.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+
+#include <linux/thermal.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Thomas Sujith");
+MODULE_AUTHOR("Zhang Rui");
+MODULE_DESCRIPTION("Intel Menlow platform specific driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Memory controller device control
+ */
+
+#define MEMORY_GET_BANDWIDTH "GTHS"
+#define MEMORY_SET_BANDWIDTH "STHS"
+#define MEMORY_ARG_CUR_BANDWIDTH 1
+#define MEMORY_ARG_MAX_BANDWIDTH 0
+
+static void intel_menlow_unregister_sensor(void);
+
+/*
+ * GTHS returning 'n' would mean that [0,n-1] states are supported
+ * In that case max_cstate would be n-1
+ * GTHS returning '0' would mean that no bandwidth control states are supported
+ */
+static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev,
+ unsigned long *max_state)
+{
+ struct acpi_device *device = cdev->devdata;
+ acpi_handle handle = device->handle;
+ unsigned long long value;
+ struct acpi_object_list arg_list;
+ union acpi_object arg;
+ acpi_status status = AE_OK;
+
+ arg_list.count = 1;
+ arg_list.pointer = &arg;
+ arg.type = ACPI_TYPE_INTEGER;
+ arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH;
+ status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+ &arg_list, &value);
+ if (ACPI_FAILURE(status))
+ return -EFAULT;
+
+ if (!value)
+ return -EINVAL;
+
+ *max_state = value - 1;
+ return 0;
+}
+
+static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev,
+ unsigned long *value)
+{
+ struct acpi_device *device = cdev->devdata;
+ acpi_handle handle = device->handle;
+ unsigned long long result;
+ struct acpi_object_list arg_list;
+ union acpi_object arg;
+ acpi_status status = AE_OK;
+
+ arg_list.count = 1;
+ arg_list.pointer = &arg;
+ arg.type = ACPI_TYPE_INTEGER;
+ arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH;
+ status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+ &arg_list, &result);
+ if (ACPI_FAILURE(status))
+ return -EFAULT;
+
+ *value = result;
+ return 0;
+}
+
+static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct acpi_device *device = cdev->devdata;
+ acpi_handle handle = device->handle;
+ struct acpi_object_list arg_list;
+ union acpi_object arg;
+ acpi_status status;
+ unsigned long long temp;
+ unsigned long max_state;
+
+ if (memory_get_max_bandwidth(cdev, &max_state))
+ return -EFAULT;
+
+ if (state > max_state)
+ return -EINVAL;
+
+ arg_list.count = 1;
+ arg_list.pointer = &arg;
+ arg.type = ACPI_TYPE_INTEGER;
+ arg.integer.value = state;
+
+ status =
+ acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list,
+ &temp);
+
+ pr_info("Bandwidth value was %ld: status is %d\n", state, status);
+ if (ACPI_FAILURE(status))
+ return -EFAULT;
+
+ return 0;
+}
+
+static struct thermal_cooling_device_ops memory_cooling_ops = {
+ .get_max_state = memory_get_max_bandwidth,
+ .get_cur_state = memory_get_cur_bandwidth,
+ .set_cur_state = memory_set_cur_bandwidth,
+};
+
+/*
+ * Memory Device Management
+ */
+static int intel_menlow_memory_add(struct acpi_device *device)
+{
+ int result = -ENODEV;
+ acpi_status status = AE_OK;
+ acpi_handle dummy;
+ struct thermal_cooling_device *cdev;
+
+ if (!device)
+ return -EINVAL;
+
+ status = acpi_get_handle(device->handle, MEMORY_GET_BANDWIDTH, &dummy);
+ if (ACPI_FAILURE(status))
+ goto end;
+
+ status = acpi_get_handle(device->handle, MEMORY_SET_BANDWIDTH, &dummy);
+ if (ACPI_FAILURE(status))
+ goto end;
+
+ cdev = thermal_cooling_device_register("Memory controller", device,
+ &memory_cooling_ops);
+ if (IS_ERR(cdev)) {
+ result = PTR_ERR(cdev);
+ goto end;
+ }
+
+ device->driver_data = cdev;
+ result = sysfs_create_link(&device->dev.kobj,
+ &cdev->device.kobj, "thermal_cooling");
+ if (result)
+ goto unregister;
+
+ result = sysfs_create_link(&cdev->device.kobj,
+ &device->dev.kobj, "device");
+ if (result) {
+ sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+ goto unregister;
+ }
+
+ end:
+ return result;
+
+ unregister:
+ thermal_cooling_device_unregister(cdev);
+ return result;
+
+}
+
+static int intel_menlow_memory_remove(struct acpi_device *device)
+{
+ struct thermal_cooling_device *cdev = acpi_driver_data(device);
+
+ if (!device || !cdev)
+ return -EINVAL;
+
+ sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+ sysfs_remove_link(&cdev->device.kobj, "device");
+ thermal_cooling_device_unregister(cdev);
+
+ return 0;
+}
+
+static const struct acpi_device_id intel_menlow_memory_ids[] = {
+ {"INT0002", 0},
+ {"", 0},
+};
+
+static struct acpi_driver intel_menlow_memory_driver = {
+ .name = "intel_menlow_thermal_control",
+ .ids = intel_menlow_memory_ids,
+ .ops = {
+ .add = intel_menlow_memory_add,
+ .remove = intel_menlow_memory_remove,
+ },
+};
+
+/*
+ * Sensor control on menlow platform
+ */
+
+#define THERMAL_AUX0 0
+#define THERMAL_AUX1 1
+#define GET_AUX0 "GAX0"
+#define GET_AUX1 "GAX1"
+#define SET_AUX0 "SAX0"
+#define SET_AUX1 "SAX1"
+
+struct intel_menlow_attribute {
+ struct device_attribute attr;
+ struct device *device;
+ acpi_handle handle;
+ struct list_head node;
+};
+
+static LIST_HEAD(intel_menlow_attr_list);
+static DEFINE_MUTEX(intel_menlow_attr_lock);
+
+/*
+ * sensor_get_auxtrip - get the current auxtrip value from sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_get_auxtrip(acpi_handle handle, int index,
+ unsigned long long *value)
+{
+ acpi_status status;
+
+ if ((index != 0 && index != 1) || !value)
+ return -EINVAL;
+
+ status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0,
+ NULL, value);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return 0;
+}
+
+/*
+ * sensor_set_auxtrip - set the new auxtrip value to sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_set_auxtrip(acpi_handle handle, int index, int value)
+{
+ acpi_status status;
+ union acpi_object arg = {
+ ACPI_TYPE_INTEGER
+ };
+ struct acpi_object_list args = {
+ 1, &arg
+ };
+ unsigned long long temp;
+
+ if (index != 0 && index != 1)
+ return -EINVAL;
+
+ status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1,
+ NULL, &temp);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+ if ((index && value < temp) || (!index && value > temp))
+ return -EINVAL;
+
+ arg.integer.value = value;
+ status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0,
+ &args, &temp);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ /* do we need to check the return value of SAX0/SAX1 ? */
+
+ return 0;
+}
+
+#define to_intel_menlow_attr(_attr) \
+ container_of(_attr, struct intel_menlow_attribute, attr)
+
+static ssize_t aux0_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+ unsigned long long value;
+ int result;
+
+ result = sensor_get_auxtrip(attr->handle, 0, &value);
+
+ return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value));
+}
+
+static ssize_t aux1_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+ unsigned long long value;
+ int result;
+
+ result = sensor_get_auxtrip(attr->handle, 1, &value);
+
+ return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value));
+}
+
+static ssize_t aux0_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+ int value;
+ int result;
+
+ /*Sanity check; should be a positive integer */
+ if (!sscanf(buf, "%d", &value))
+ return -EINVAL;
+
+ if (value < 0)
+ return -EINVAL;
+
+ result = sensor_set_auxtrip(attr->handle, 0, CELSIUS_TO_KELVIN(value));
+ return result ? result : count;
+}
+
+static ssize_t aux1_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+ int value;
+ int result;
+
+ /*Sanity check; should be a positive integer */
+ if (!sscanf(buf, "%d", &value))
+ return -EINVAL;
+
+ if (value < 0)
+ return -EINVAL;
+
+ result = sensor_set_auxtrip(attr->handle, 1, CELSIUS_TO_KELVIN(value));
+ return result ? result : count;
+}
+
+/* BIOS can enable/disable the thermal user application in dabney platform */
+#define BIOS_ENABLED "\\_TZ.GSTS"
+static ssize_t bios_enabled_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ acpi_status status;
+ unsigned long long bios_enabled;
+
+ status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled");
+}
+
+static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show,
+ void *store, struct device *dev,
+ acpi_handle handle)
+{
+ struct intel_menlow_attribute *attr;
+ int result;
+
+ attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL);
+ if (!attr)
+ return -ENOMEM;
+
+ sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */
+ attr->attr.attr.name = name;
+ attr->attr.attr.mode = mode;
+ attr->attr.show = show;
+ attr->attr.store = store;
+ attr->device = dev;
+ attr->handle = handle;
+
+ result = device_create_file(dev, &attr->attr);
+ if (result) {
+ kfree(attr);
+ return result;
+ }
+
+ mutex_lock(&intel_menlow_attr_lock);
+ list_add_tail(&attr->node, &intel_menlow_attr_list);
+ mutex_unlock(&intel_menlow_attr_lock);
+
+ return 0;
+}
+
+static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl,
+ void *context, void **rv)
+{
+ acpi_status status;
+ acpi_handle dummy;
+ struct thermal_zone_device *thermal;
+ int result;
+
+ result = acpi_bus_get_private_data(handle, (void **)&thermal);
+ if (result)
+ return 0;
+
+ /* _TZ must have the AUX0/1 methods */
+ status = acpi_get_handle(handle, GET_AUX0, &dummy);
+ if (ACPI_FAILURE(status))
+ return (status == AE_NOT_FOUND) ? AE_OK : status;
+
+ status = acpi_get_handle(handle, SET_AUX0, &dummy);
+ if (ACPI_FAILURE(status))
+ return (status == AE_NOT_FOUND) ? AE_OK : status;
+
+ result = intel_menlow_add_one_attribute("aux0", 0644,
+ aux0_show, aux0_store,
+ &thermal->device, handle);
+ if (result)
+ return AE_ERROR;
+
+ status = acpi_get_handle(handle, GET_AUX1, &dummy);
+ if (ACPI_FAILURE(status))
+ goto aux1_not_found;
+
+ status = acpi_get_handle(handle, SET_AUX1, &dummy);
+ if (ACPI_FAILURE(status))
+ goto aux1_not_found;
+
+ result = intel_menlow_add_one_attribute("aux1", 0644,
+ aux1_show, aux1_store,
+ &thermal->device, handle);
+ if (result) {
+ intel_menlow_unregister_sensor();
+ return AE_ERROR;
+ }
+
+ /*
+ * create the "dabney_enabled" attribute which means the user app
+ * should be loaded or not
+ */
+
+ result = intel_menlow_add_one_attribute("bios_enabled", 0444,
+ bios_enabled_show, NULL,
+ &thermal->device, handle);
+ if (result) {
+ intel_menlow_unregister_sensor();
+ return AE_ERROR;
+ }
+
+ return AE_OK;
+
+ aux1_not_found:
+ if (status == AE_NOT_FOUND)
+ return AE_OK;
+
+ intel_menlow_unregister_sensor();
+ return status;
+}
+
+static void intel_menlow_unregister_sensor(void)
+{
+ struct intel_menlow_attribute *pos, *next;
+
+ mutex_lock(&intel_menlow_attr_lock);
+ list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) {
+ list_del(&pos->node);
+ device_remove_file(pos->device, &pos->attr);
+ kfree(pos);
+ }
+ mutex_unlock(&intel_menlow_attr_lock);
+
+ return;
+}
+
+static int __init intel_menlow_module_init(void)
+{
+ int result = -ENODEV;
+ acpi_status status;
+ unsigned long long enable;
+
+ if (acpi_disabled)
+ return result;
+
+ /* Looking for the \_TZ.GSTS method */
+ status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable);
+ if (ACPI_FAILURE(status) || !enable)
+ return -ENODEV;
+
+ /* Looking for ACPI device MEM0 with hardware id INT0002 */
+ result = acpi_bus_register_driver(&intel_menlow_memory_driver);
+ if (result)
+ return result;
+
+ /* Looking for sensors in each ACPI thermal zone */
+ status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT,
+ ACPI_UINT32_MAX,
+ intel_menlow_register_sensor, NULL, NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ acpi_bus_unregister_driver(&intel_menlow_memory_driver);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit intel_menlow_module_exit(void)
+{
+ acpi_bus_unregister_driver(&intel_menlow_memory_driver);
+ intel_menlow_unregister_sensor();
+}
+
+module_init(intel_menlow_module_init);
+module_exit(intel_menlow_module_exit);
diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c
new file mode 100644
index 00000000..f59683aa
--- /dev/null
+++ b/drivers/platform/x86/intel_mid_powerbtn.c
@@ -0,0 +1,150 @@
+/*
+ * Power button driver for Medfield.
+ *
+ * Copyright (C) 2010 Intel Corp
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/interrupt.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/intel_msic.h>
+
+#define DRIVER_NAME "msic_power_btn"
+
+#define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */
+
+/*
+ * MSIC document ti_datasheet defines the 1st bit reg 0x21 is used to mask
+ * power button interrupt
+ */
+#define MSIC_PWRBTNM (1 << 0)
+
+static irqreturn_t mfld_pb_isr(int irq, void *dev_id)
+{
+ struct input_dev *input = dev_id;
+ int ret;
+ u8 pbstat;
+
+ ret = intel_msic_reg_read(INTEL_MSIC_PBSTATUS, &pbstat);
+ dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat);
+
+ if (ret < 0) {
+ dev_err(input->dev.parent, "Read error %d while reading"
+ " MSIC_PB_STATUS\n", ret);
+ } else {
+ input_event(input, EV_KEY, KEY_POWER,
+ !(pbstat & MSIC_PB_LEVEL));
+ input_sync(input);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mfld_pb_probe(struct platform_device *pdev)
+{
+ struct input_dev *input;
+ int irq = platform_get_irq(pdev, 0);
+ int error;
+
+ if (irq < 0)
+ return -EINVAL;
+
+ input = input_allocate_device();
+ if (!input) {
+ dev_err(&pdev->dev, "Input device allocation error\n");
+ return -ENOMEM;
+ }
+
+ input->name = pdev->name;
+ input->phys = "power-button/input0";
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = &pdev->dev;
+
+ input_set_capability(input, EV_KEY, KEY_POWER);
+
+ error = request_threaded_irq(irq, NULL, mfld_pb_isr, IRQF_NO_SUSPEND,
+ DRIVER_NAME, input);
+ if (error) {
+ dev_err(&pdev->dev, "Unable to request irq %d for mfld power"
+ "button\n", irq);
+ goto err_free_input;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&pdev->dev, "Unable to register input dev, error "
+ "%d\n", error);
+ goto err_free_irq;
+ }
+
+ platform_set_drvdata(pdev, input);
+
+ /*
+ * SCU firmware might send power button interrupts to IA core before
+ * kernel boots and doesn't get EOI from IA core. The first bit of
+ * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new
+ * power interrupt to Android kernel. Unmask the bit when probing
+ * power button in kernel.
+ * There is a very narrow race between irq handler and power button
+ * initialization. The race happens rarely. So we needn't worry
+ * about it.
+ */
+ error = intel_msic_reg_update(INTEL_MSIC_IRQLVL1MSK, 0, MSIC_PWRBTNM);
+ if (error) {
+ dev_err(&pdev->dev, "Unable to clear power button interrupt, "
+ "error: %d\n", error);
+ goto err_free_irq;
+ }
+
+ return 0;
+
+err_free_irq:
+ free_irq(irq, input);
+err_free_input:
+ input_free_device(input);
+ return error;
+}
+
+static int mfld_pb_remove(struct platform_device *pdev)
+{
+ struct input_dev *input = platform_get_drvdata(pdev);
+ int irq = platform_get_irq(pdev, 0);
+
+ free_irq(irq, input);
+ input_unregister_device(input);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver mfld_pb_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = mfld_pb_probe,
+ .remove = mfld_pb_remove,
+};
+
+module_platform_driver(mfld_pb_driver);
+
+MODULE_AUTHOR("Hong Liu <hong.liu@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield Power Button Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c
new file mode 100644
index 00000000..81c491e7
--- /dev/null
+++ b/drivers/platform/x86/intel_mid_thermal.c
@@ -0,0 +1,574 @@
+/*
+ * intel_mid_thermal.c - Intel MID platform thermal driver
+ *
+ * Copyright (C) 2011 Intel 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Durgadoss R <durgadoss.r@intel.com>
+ */
+
+#define pr_fmt(fmt) "intel_mid_thermal: " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/param.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/thermal.h>
+#include <linux/mfd/intel_msic.h>
+
+/* Number of thermal sensors */
+#define MSIC_THERMAL_SENSORS 4
+
+/* ADC1 - thermal registers */
+#define MSIC_ADC_ENBL 0x10
+#define MSIC_ADC_START 0x08
+
+#define MSIC_ADCTHERM_ENBL 0x04
+#define MSIC_ADCRRDATA_ENBL 0x05
+#define MSIC_CHANL_MASK_VAL 0x0F
+
+#define MSIC_STOPBIT_MASK 16
+#define MSIC_ADCTHERM_MASK 4
+/* Number of ADC channels */
+#define ADC_CHANLS_MAX 15
+#define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS)
+
+/* ADC channel code values */
+#define SKIN_SENSOR0_CODE 0x08
+#define SKIN_SENSOR1_CODE 0x09
+#define SYS_SENSOR_CODE 0x0A
+#define MSIC_DIE_SENSOR_CODE 0x03
+
+#define SKIN_THERM_SENSOR0 0
+#define SKIN_THERM_SENSOR1 1
+#define SYS_THERM_SENSOR2 2
+#define MSIC_DIE_THERM_SENSOR3 3
+
+/* ADC code range */
+#define ADC_MAX 977
+#define ADC_MIN 162
+#define ADC_VAL0C 887
+#define ADC_VAL20C 720
+#define ADC_VAL40C 508
+#define ADC_VAL60C 315
+
+/* ADC base addresses */
+#define ADC_CHNL_START_ADDR INTEL_MSIC_ADC1ADDR0 /* increments by 1 */
+#define ADC_DATA_START_ADDR INTEL_MSIC_ADC1SNS0H /* increments by 2 */
+
+/* MSIC die attributes */
+#define MSIC_DIE_ADC_MIN 488
+#define MSIC_DIE_ADC_MAX 1004
+
+/* This holds the address of the first free ADC channel,
+ * among the 15 channels
+ */
+static int channel_index;
+
+struct platform_info {
+ struct platform_device *pdev;
+ struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS];
+};
+
+struct thermal_device_info {
+ unsigned int chnl_addr;
+ int direct;
+ /* This holds the current temperature in millidegree celsius */
+ long curr_temp;
+};
+
+/**
+ * to_msic_die_temp - converts adc_val to msic_die temperature
+ * @adc_val: ADC value to be converted
+ *
+ * Can sleep
+ */
+static int to_msic_die_temp(uint16_t adc_val)
+{
+ return (368 * (adc_val) / 1000) - 220;
+}
+
+/**
+ * is_valid_adc - checks whether the adc code is within the defined range
+ * @min: minimum value for the sensor
+ * @max: maximum value for the sensor
+ *
+ * Can sleep
+ */
+static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max)
+{
+ return (adc_val >= min) && (adc_val <= max);
+}
+
+/**
+ * adc_to_temp - converts the ADC code to temperature in C
+ * @direct: true if ths channel is direct index
+ * @adc_val: the adc_val that needs to be converted
+ * @tp: temperature return value
+ *
+ * Linear approximation is used to covert the skin adc value into temperature.
+ * This technique is used to avoid very long look-up table to get
+ * the appropriate temp value from ADC value.
+ * The adc code vs sensor temp curve is split into five parts
+ * to achieve very close approximate temp value with less than
+ * 0.5C error
+ */
+static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp)
+{
+ int temp;
+
+ /* Direct conversion for die temperature */
+ if (direct) {
+ if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) {
+ *tp = to_msic_die_temp(adc_val) * 1000;
+ return 0;
+ }
+ return -ERANGE;
+ }
+
+ if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX))
+ return -ERANGE;
+
+ /* Linear approximation for skin temperature */
+ if (adc_val > ADC_VAL0C)
+ temp = 177 - (adc_val/5);
+ else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C))
+ temp = 111 - (adc_val/8);
+ else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C))
+ temp = 92 - (adc_val/10);
+ else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C))
+ temp = 91 - (adc_val/10);
+ else
+ temp = 112 - (adc_val/6);
+
+ /* Convert temperature in celsius to milli degree celsius */
+ *tp = temp * 1000;
+ return 0;
+}
+
+/**
+ * mid_read_temp - read sensors for temperature
+ * @temp: holds the current temperature for the sensor after reading
+ *
+ * reads the adc_code from the channel and converts it to real
+ * temperature. The converted value is stored in temp.
+ *
+ * Can sleep
+ */
+static int mid_read_temp(struct thermal_zone_device *tzd, unsigned long *temp)
+{
+ struct thermal_device_info *td_info = tzd->devdata;
+ uint16_t adc_val, addr;
+ uint8_t data = 0;
+ int ret;
+ unsigned long curr_temp;
+
+
+ addr = td_info->chnl_addr;
+
+ /* Enable the msic for conversion before reading */
+ ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCRRDATA_ENBL);
+ if (ret)
+ return ret;
+
+ /* Re-toggle the RRDATARD bit (temporary workaround) */
+ ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCTHERM_ENBL);
+ if (ret)
+ return ret;
+
+ /* Read the higher bits of data */
+ ret = intel_msic_reg_read(addr, &data);
+ if (ret)
+ return ret;
+
+ /* Shift bits to accommodate the lower two data bits */
+ adc_val = (data << 2);
+ addr++;
+
+ ret = intel_msic_reg_read(addr, &data);/* Read lower bits */
+ if (ret)
+ return ret;
+
+ /* Adding lower two bits to the higher bits */
+ data &= 03;
+ adc_val += data;
+
+ /* Convert ADC value to temperature */
+ ret = adc_to_temp(td_info->direct, adc_val, &curr_temp);
+ if (ret == 0)
+ *temp = td_info->curr_temp = curr_temp;
+ return ret;
+}
+
+/**
+ * configure_adc - enables/disables the ADC for conversion
+ * @val: zero: disables the ADC non-zero:enables the ADC
+ *
+ * Enable/Disable the ADC depending on the argument
+ *
+ * Can sleep
+ */
+static int configure_adc(int val)
+{
+ int ret;
+ uint8_t data;
+
+ ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data);
+ if (ret)
+ return ret;
+
+ if (val) {
+ /* Enable and start the ADC */
+ data |= (MSIC_ADC_ENBL | MSIC_ADC_START);
+ } else {
+ /* Just stop the ADC */
+ data &= (~MSIC_ADC_START);
+ }
+ return intel_msic_reg_write(INTEL_MSIC_ADC1CNTL1, data);
+}
+
+/**
+ * set_up_therm_channel - enable thermal channel for conversion
+ * @base_addr: index of free msic ADC channel
+ *
+ * Enable all the three channels for conversion
+ *
+ * Can sleep
+ */
+static int set_up_therm_channel(u16 base_addr)
+{
+ int ret;
+
+ /* Enable all the sensor channels */
+ ret = intel_msic_reg_write(base_addr, SKIN_SENSOR0_CODE);
+ if (ret)
+ return ret;
+
+ ret = intel_msic_reg_write(base_addr + 1, SKIN_SENSOR1_CODE);
+ if (ret)
+ return ret;
+
+ ret = intel_msic_reg_write(base_addr + 2, SYS_SENSOR_CODE);
+ if (ret)
+ return ret;
+
+ /* Since this is the last channel, set the stop bit
+ * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */
+ ret = intel_msic_reg_write(base_addr + 3,
+ (MSIC_DIE_SENSOR_CODE | 0x10));
+ if (ret)
+ return ret;
+
+ /* Enable ADC and start it */
+ return configure_adc(1);
+}
+
+/**
+ * reset_stopbit - sets the stop bit to 0 on the given channel
+ * @addr: address of the channel
+ *
+ * Can sleep
+ */
+static int reset_stopbit(uint16_t addr)
+{
+ int ret;
+ uint8_t data;
+ ret = intel_msic_reg_read(addr, &data);
+ if (ret)
+ return ret;
+ /* Set the stop bit to zero */
+ return intel_msic_reg_write(addr, (data & 0xEF));
+}
+
+/**
+ * find_free_channel - finds an empty channel for conversion
+ *
+ * If the ADC is not enabled then start using 0th channel
+ * itself. Otherwise find an empty channel by looking for a
+ * channel in which the stopbit is set to 1. returns the index
+ * of the first free channel if succeeds or an error code.
+ *
+ * Context: can sleep
+ *
+ * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc
+ * code.
+ */
+static int find_free_channel(void)
+{
+ int ret;
+ int i;
+ uint8_t data;
+
+ /* check whether ADC is enabled */
+ ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data);
+ if (ret)
+ return ret;
+
+ if ((data & MSIC_ADC_ENBL) == 0)
+ return 0;
+
+ /* ADC is already enabled; Looking for an empty channel */
+ for (i = 0; i < ADC_CHANLS_MAX; i++) {
+ ret = intel_msic_reg_read(ADC_CHNL_START_ADDR + i, &data);
+ if (ret)
+ return ret;
+
+ if (data & MSIC_STOPBIT_MASK) {
+ ret = i;
+ break;
+ }
+ }
+ return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret;
+}
+
+/**
+ * mid_initialize_adc - initializing the ADC
+ * @dev: our device structure
+ *
+ * Initialize the ADC for reading thermistor values. Can sleep.
+ */
+static int mid_initialize_adc(struct device *dev)
+{
+ u8 data;
+ u16 base_addr;
+ int ret;
+
+ /*
+ * Ensure that adctherm is disabled before we
+ * initialize the ADC
+ */
+ ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL3, &data);
+ if (ret)
+ return ret;
+
+ data &= ~MSIC_ADCTHERM_MASK;
+ ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, data);
+ if (ret)
+ return ret;
+
+ /* Index of the first channel in which the stop bit is set */
+ channel_index = find_free_channel();
+ if (channel_index < 0) {
+ dev_err(dev, "No free ADC channels");
+ return channel_index;
+ }
+
+ base_addr = ADC_CHNL_START_ADDR + channel_index;
+
+ if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) {
+ /* Reset stop bit for channels other than 0 and 12 */
+ ret = reset_stopbit(base_addr);
+ if (ret)
+ return ret;
+
+ /* Index of the first free channel */
+ base_addr++;
+ channel_index++;
+ }
+
+ ret = set_up_therm_channel(base_addr);
+ if (ret) {
+ dev_err(dev, "unable to enable ADC");
+ return ret;
+ }
+ dev_dbg(dev, "ADC initialization successful");
+ return ret;
+}
+
+/**
+ * initialize_sensor - sets default temp and timer ranges
+ * @index: index of the sensor
+ *
+ * Context: can sleep
+ */
+static struct thermal_device_info *initialize_sensor(int index)
+{
+ struct thermal_device_info *td_info =
+ kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL);
+
+ if (!td_info)
+ return NULL;
+
+ /* Set the base addr of the channel for this sensor */
+ td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index);
+ /* Sensor 3 is direct conversion */
+ if (index == 3)
+ td_info->direct = 1;
+ return td_info;
+}
+
+/**
+ * mid_thermal_resume - resume routine
+ * @dev: device structure
+ *
+ * mid thermal resume: re-initializes the adc. Can sleep.
+ */
+static int mid_thermal_resume(struct device *dev)
+{
+ return mid_initialize_adc(dev);
+}
+
+/**
+ * mid_thermal_suspend - suspend routine
+ * @dev: device structure
+ *
+ * mid thermal suspend implements the suspend functionality
+ * by stopping the ADC. Can sleep.
+ */
+static int mid_thermal_suspend(struct device *dev)
+{
+ /*
+ * This just stops the ADC and does not disable it.
+ * temporary workaround until we have a generic ADC driver.
+ * If 0 is passed, it disables the ADC.
+ */
+ return configure_adc(0);
+}
+
+static SIMPLE_DEV_PM_OPS(mid_thermal_pm,
+ mid_thermal_suspend, mid_thermal_resume);
+
+/**
+ * read_curr_temp - reads the current temperature and stores in temp
+ * @temp: holds the current temperature value after reading
+ *
+ * Can sleep
+ */
+static int read_curr_temp(struct thermal_zone_device *tzd, unsigned long *temp)
+{
+ WARN_ON(tzd == NULL);
+ return mid_read_temp(tzd, temp);
+}
+
+/* Can't be const */
+static struct thermal_zone_device_ops tzd_ops = {
+ .get_temp = read_curr_temp,
+};
+
+/**
+ * mid_thermal_probe - mfld thermal initialize
+ * @pdev: platform device structure
+ *
+ * mid thermal probe initializes the hardware and registers
+ * all the sensors with the generic thermal framework. Can sleep.
+ */
+static int mid_thermal_probe(struct platform_device *pdev)
+{
+ static char *name[MSIC_THERMAL_SENSORS] = {
+ "skin0", "skin1", "sys", "msicdie"
+ };
+
+ int ret;
+ int i;
+ struct platform_info *pinfo;
+
+ pinfo = kzalloc(sizeof(struct platform_info), GFP_KERNEL);
+ if (!pinfo)
+ return -ENOMEM;
+
+ /* Initializing the hardware */
+ ret = mid_initialize_adc(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "ADC init failed");
+ kfree(pinfo);
+ return ret;
+ }
+
+ /* Register each sensor with the generic thermal framework*/
+ for (i = 0; i < MSIC_THERMAL_SENSORS; i++) {
+ struct thermal_device_info *td_info = initialize_sensor(i);
+
+ if (!td_info) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ pinfo->tzd[i] = thermal_zone_device_register(name[i],
+ 0, 0, td_info, &tzd_ops, NULL, 0, 0);
+ if (IS_ERR(pinfo->tzd[i])) {
+ kfree(td_info);
+ ret = PTR_ERR(pinfo->tzd[i]);
+ goto err;
+ }
+ }
+
+ pinfo->pdev = pdev;
+ platform_set_drvdata(pdev, pinfo);
+ return 0;
+
+err:
+ while (--i >= 0) {
+ kfree(pinfo->tzd[i]->devdata);
+ thermal_zone_device_unregister(pinfo->tzd[i]);
+ }
+ configure_adc(0);
+ kfree(pinfo);
+ return ret;
+}
+
+/**
+ * mid_thermal_remove - mfld thermal finalize
+ * @dev: platform device structure
+ *
+ * MLFD thermal remove unregisters all the sensors from the generic
+ * thermal framework. Can sleep.
+ */
+static int mid_thermal_remove(struct platform_device *pdev)
+{
+ int i;
+ struct platform_info *pinfo = platform_get_drvdata(pdev);
+
+ for (i = 0; i < MSIC_THERMAL_SENSORS; i++) {
+ kfree(pinfo->tzd[i]->devdata);
+ thermal_zone_device_unregister(pinfo->tzd[i]);
+ }
+
+ kfree(pinfo);
+ platform_set_drvdata(pdev, NULL);
+
+ /* Stop the ADC */
+ return configure_adc(0);
+}
+
+#define DRIVER_NAME "msic_thermal"
+
+static const struct platform_device_id therm_id_table[] = {
+ { DRIVER_NAME, 1 },
+ { "msic_thermal", 1 },
+ { }
+};
+
+static struct platform_driver mid_thermal_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .pm = &mid_thermal_pm,
+ },
+ .probe = mid_thermal_probe,
+ .remove = mid_thermal_remove,
+ .id_table = therm_id_table,
+};
+
+module_platform_driver(mid_thermal_driver);
+
+MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c
new file mode 100644
index 00000000..f6f18cde
--- /dev/null
+++ b/drivers/platform/x86/intel_oaktrail.c
@@ -0,0 +1,397 @@
+/*
+ * intel_oaktrail.c - Intel OakTrail Platform support.
+ *
+ * Copyright (C) 2010-2011 Intel Corporation
+ * Author: Yin Kangkai (kangkai.yin@intel.com)
+ *
+ * based on Compal driver, Copyright (C) 2008 Cezary Jackiewicz
+ * <cezary.jackiewicz (at) gmail.com>, based on MSI driver
+ * Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
+ *
+ * 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.
+ *
+ * This driver does below things:
+ * 1. registers itself in the Linux backlight control in
+ * /sys/class/backlight/intel_oaktrail/
+ *
+ * 2. registers in the rfkill subsystem here: /sys/class/rfkill/rfkillX/
+ * for these components: wifi, bluetooth, wwan (3g), gps
+ *
+ * This driver might work on other products based on Oaktrail. If you
+ * want to try it you can pass force=1 as argument to the module which
+ * will force it to load even when the DMI data doesn't identify the
+ * product as compatible.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/fb.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/dmi.h>
+#include <linux/rfkill.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+
+#define DRIVER_NAME "intel_oaktrail"
+#define DRIVER_VERSION "0.4ac1"
+
+/*
+ * This is the devices status address in EC space, and the control bits
+ * definition:
+ *
+ * (1 << 0): Camera enable/disable, RW.
+ * (1 << 1): Bluetooth enable/disable, RW.
+ * (1 << 2): GPS enable/disable, RW.
+ * (1 << 3): WiFi enable/disable, RW.
+ * (1 << 4): WWAN (3G) enable/disalbe, RW.
+ * (1 << 5): Touchscreen enable/disable, Read Only.
+ */
+#define OT_EC_DEVICE_STATE_ADDRESS 0xD6
+
+#define OT_EC_CAMERA_MASK (1 << 0)
+#define OT_EC_BT_MASK (1 << 1)
+#define OT_EC_GPS_MASK (1 << 2)
+#define OT_EC_WIFI_MASK (1 << 3)
+#define OT_EC_WWAN_MASK (1 << 4)
+#define OT_EC_TS_MASK (1 << 5)
+
+/*
+ * This is the address in EC space and commands used to control LCD backlight:
+ *
+ * Two steps needed to change the LCD backlight:
+ * 1. write the backlight percentage into OT_EC_BL_BRIGHTNESS_ADDRESS;
+ * 2. write OT_EC_BL_CONTROL_ON_DATA into OT_EC_BL_CONTROL_ADDRESS.
+ *
+ * To read the LCD back light, just read out the value from
+ * OT_EC_BL_BRIGHTNESS_ADDRESS.
+ *
+ * LCD backlight brightness range: 0 - 100 (OT_EC_BL_BRIGHTNESS_MAX)
+ */
+#define OT_EC_BL_BRIGHTNESS_ADDRESS 0x44
+#define OT_EC_BL_BRIGHTNESS_MAX 100
+#define OT_EC_BL_CONTROL_ADDRESS 0x3A
+#define OT_EC_BL_CONTROL_ON_DATA 0x1A
+
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static struct platform_device *oaktrail_device;
+static struct backlight_device *oaktrail_bl_device;
+static struct rfkill *bt_rfkill;
+static struct rfkill *gps_rfkill;
+static struct rfkill *wifi_rfkill;
+static struct rfkill *wwan_rfkill;
+
+
+/* rfkill */
+static int oaktrail_rfkill_set(void *data, bool blocked)
+{
+ u8 value;
+ u8 result;
+ unsigned long radio = (unsigned long) data;
+
+ ec_read(OT_EC_DEVICE_STATE_ADDRESS, &result);
+
+ if (!blocked)
+ value = (u8) (result | radio);
+ else
+ value = (u8) (result & ~radio);
+
+ ec_write(OT_EC_DEVICE_STATE_ADDRESS, value);
+
+ return 0;
+}
+
+static const struct rfkill_ops oaktrail_rfkill_ops = {
+ .set_block = oaktrail_rfkill_set,
+};
+
+static struct rfkill *oaktrail_rfkill_new(char *name, enum rfkill_type type,
+ unsigned long mask)
+{
+ struct rfkill *rfkill_dev;
+ u8 value;
+ int err;
+
+ rfkill_dev = rfkill_alloc(name, &oaktrail_device->dev, type,
+ &oaktrail_rfkill_ops, (void *)mask);
+ if (!rfkill_dev)
+ return ERR_PTR(-ENOMEM);
+
+ ec_read(OT_EC_DEVICE_STATE_ADDRESS, &value);
+ rfkill_init_sw_state(rfkill_dev, (value & mask) != 1);
+
+ err = rfkill_register(rfkill_dev);
+ if (err) {
+ rfkill_destroy(rfkill_dev);
+ return ERR_PTR(err);
+ }
+
+ return rfkill_dev;
+}
+
+static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf)
+{
+ if (rf) {
+ rfkill_unregister(rf);
+ rfkill_destroy(rf);
+ }
+}
+
+static void oaktrail_rfkill_cleanup(void)
+{
+ __oaktrail_rfkill_cleanup(wifi_rfkill);
+ __oaktrail_rfkill_cleanup(bt_rfkill);
+ __oaktrail_rfkill_cleanup(gps_rfkill);
+ __oaktrail_rfkill_cleanup(wwan_rfkill);
+}
+
+static int oaktrail_rfkill_init(void)
+{
+ int ret;
+
+ wifi_rfkill = oaktrail_rfkill_new("oaktrail-wifi",
+ RFKILL_TYPE_WLAN,
+ OT_EC_WIFI_MASK);
+ if (IS_ERR(wifi_rfkill)) {
+ ret = PTR_ERR(wifi_rfkill);
+ wifi_rfkill = NULL;
+ goto cleanup;
+ }
+
+ bt_rfkill = oaktrail_rfkill_new("oaktrail-bluetooth",
+ RFKILL_TYPE_BLUETOOTH,
+ OT_EC_BT_MASK);
+ if (IS_ERR(bt_rfkill)) {
+ ret = PTR_ERR(bt_rfkill);
+ bt_rfkill = NULL;
+ goto cleanup;
+ }
+
+ gps_rfkill = oaktrail_rfkill_new("oaktrail-gps",
+ RFKILL_TYPE_GPS,
+ OT_EC_GPS_MASK);
+ if (IS_ERR(gps_rfkill)) {
+ ret = PTR_ERR(gps_rfkill);
+ gps_rfkill = NULL;
+ goto cleanup;
+ }
+
+ wwan_rfkill = oaktrail_rfkill_new("oaktrail-wwan",
+ RFKILL_TYPE_WWAN,
+ OT_EC_WWAN_MASK);
+ if (IS_ERR(wwan_rfkill)) {
+ ret = PTR_ERR(wwan_rfkill);
+ wwan_rfkill = NULL;
+ goto cleanup;
+ }
+
+ return 0;
+
+cleanup:
+ oaktrail_rfkill_cleanup();
+ return ret;
+}
+
+
+/* backlight */
+static int get_backlight_brightness(struct backlight_device *b)
+{
+ u8 value;
+ ec_read(OT_EC_BL_BRIGHTNESS_ADDRESS, &value);
+
+ return value;
+}
+
+static int set_backlight_brightness(struct backlight_device *b)
+{
+ u8 percent = (u8) b->props.brightness;
+ if (percent < 0 || percent > OT_EC_BL_BRIGHTNESS_MAX)
+ return -EINVAL;
+
+ ec_write(OT_EC_BL_BRIGHTNESS_ADDRESS, percent);
+ ec_write(OT_EC_BL_CONTROL_ADDRESS, OT_EC_BL_CONTROL_ON_DATA);
+
+ return 0;
+}
+
+static const struct backlight_ops oaktrail_bl_ops = {
+ .get_brightness = get_backlight_brightness,
+ .update_status = set_backlight_brightness,
+};
+
+static int oaktrail_backlight_init(void)
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = OT_EC_BL_BRIGHTNESS_MAX;
+ bd = backlight_device_register(DRIVER_NAME,
+ &oaktrail_device->dev, NULL,
+ &oaktrail_bl_ops,
+ &props);
+
+ if (IS_ERR(bd)) {
+ oaktrail_bl_device = NULL;
+ pr_warning("Unable to register backlight device\n");
+ return PTR_ERR(bd);
+ }
+
+ oaktrail_bl_device = bd;
+
+ bd->props.brightness = get_backlight_brightness(bd);
+ bd->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(bd);
+
+ return 0;
+}
+
+static void oaktrail_backlight_exit(void)
+{
+ if (oaktrail_bl_device)
+ backlight_device_unregister(oaktrail_bl_device);
+}
+
+static int oaktrail_probe(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static int oaktrail_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver oaktrail_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = oaktrail_probe,
+ .remove = oaktrail_remove,
+};
+
+static int dmi_check_cb(const struct dmi_system_id *id)
+{
+ pr_info("Identified model '%s'\n", id->ident);
+ return 0;
+}
+
+static struct dmi_system_id __initdata oaktrail_dmi_table[] = {
+ {
+ .ident = "OakTrail platform",
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_NAME, "OakTrail platform"),
+ },
+ .callback = dmi_check_cb
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table);
+
+static int __init oaktrail_init(void)
+{
+ int ret;
+
+ if (acpi_disabled) {
+ pr_err("ACPI needs to be enabled for this driver to work!\n");
+ return -ENODEV;
+ }
+
+ if (!force && !dmi_check_system(oaktrail_dmi_table)) {
+ pr_err("Platform not recognized (You could try the module's force-parameter)");
+ return -ENODEV;
+ }
+
+ ret = platform_driver_register(&oaktrail_driver);
+ if (ret) {
+ pr_warning("Unable to register platform driver\n");
+ goto err_driver_reg;
+ }
+
+ oaktrail_device = platform_device_alloc(DRIVER_NAME, -1);
+ if (!oaktrail_device) {
+ pr_warning("Unable to allocate platform device\n");
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+
+ ret = platform_device_add(oaktrail_device);
+ if (ret) {
+ pr_warning("Unable to add platform device\n");
+ goto err_device_add;
+ }
+
+ if (!acpi_video_backlight_support()) {
+ ret = oaktrail_backlight_init();
+ if (ret)
+ goto err_backlight;
+
+ } else
+ pr_info("Backlight controlled by ACPI video driver\n");
+
+ ret = oaktrail_rfkill_init();
+ if (ret) {
+ pr_warning("Setup rfkill failed\n");
+ goto err_rfkill;
+ }
+
+ pr_info("Driver "DRIVER_VERSION" successfully loaded\n");
+ return 0;
+
+err_rfkill:
+ oaktrail_backlight_exit();
+err_backlight:
+ platform_device_del(oaktrail_device);
+err_device_add:
+ platform_device_put(oaktrail_device);
+err_device_alloc:
+ platform_driver_unregister(&oaktrail_driver);
+err_driver_reg:
+
+ return ret;
+}
+
+static void __exit oaktrail_cleanup(void)
+{
+ oaktrail_backlight_exit();
+ oaktrail_rfkill_cleanup();
+ platform_device_unregister(oaktrail_device);
+ platform_driver_unregister(&oaktrail_driver);
+
+ pr_info("Driver unloaded\n");
+}
+
+module_init(oaktrail_init);
+module_exit(oaktrail_cleanup);
+
+MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)");
+MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c
new file mode 100644
index 00000000..6f4b7289
--- /dev/null
+++ b/drivers/platform/x86/intel_pmic_gpio.c
@@ -0,0 +1,328 @@
+/* Moorestown PMIC GPIO (access through IPC) driver
+ * Copyright (c) 2008 - 2009, Intel Corporation.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Supports:
+ * Moorestown platform PMIC chip
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/stddef.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <asm/intel_scu_ipc.h>
+#include <linux/device.h>
+#include <linux/intel_pmic_gpio.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "pmic_gpio"
+
+/* register offset that IPC driver should use
+ * 8 GPIO + 8 GPOSW (6 controllable) + 8GPO
+ */
+enum pmic_gpio_register {
+ GPIO0 = 0xE0,
+ GPIO7 = 0xE7,
+ GPIOINT = 0xE8,
+ GPOSWCTL0 = 0xEC,
+ GPOSWCTL5 = 0xF1,
+ GPO = 0xF4,
+};
+
+/* bits definition for GPIO & GPOSW */
+#define GPIO_DRV 0x01
+#define GPIO_DIR 0x02
+#define GPIO_DIN 0x04
+#define GPIO_DOU 0x08
+#define GPIO_INTCTL 0x30
+#define GPIO_DBC 0xc0
+
+#define GPOSW_DRV 0x01
+#define GPOSW_DOU 0x08
+#define GPOSW_RDRV 0x30
+
+#define GPIO_UPDATE_TYPE 0x80000000
+
+#define NUM_GPIO 24
+
+struct pmic_gpio {
+ struct mutex buslock;
+ struct gpio_chip chip;
+ void *gpiointr;
+ int irq;
+ unsigned irq_base;
+ unsigned int update_type;
+ u32 trigger_type;
+};
+
+static void pmic_program_irqtype(int gpio, int type)
+{
+ if (type & IRQ_TYPE_EDGE_RISING)
+ intel_scu_ipc_update_register(GPIO0 + gpio, 0x20, 0x20);
+ else
+ intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x20);
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ intel_scu_ipc_update_register(GPIO0 + gpio, 0x10, 0x10);
+ else
+ intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x10);
+};
+
+static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ if (offset > 8) {
+ pr_err("only pin 0-7 support input\n");
+ return -1;/* we only have 8 GPIO can use as input */
+ }
+ return intel_scu_ipc_update_register(GPIO0 + offset,
+ GPIO_DIR, GPIO_DIR);
+}
+
+static int pmic_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ int rc = 0;
+
+ if (offset < 8)/* it is GPIO */
+ rc = intel_scu_ipc_update_register(GPIO0 + offset,
+ GPIO_DRV | (value ? GPIO_DOU : 0),
+ GPIO_DRV | GPIO_DOU | GPIO_DIR);
+ else if (offset < 16)/* it is GPOSW */
+ rc = intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8,
+ GPOSW_DRV | (value ? GPOSW_DOU : 0),
+ GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV);
+ else if (offset > 15 && offset < 24)/* it is GPO */
+ rc = intel_scu_ipc_update_register(GPO,
+ value ? 1 << (offset - 16) : 0,
+ 1 << (offset - 16));
+ else {
+ pr_err("invalid PMIC GPIO pin %d!\n", offset);
+ WARN_ON(1);
+ }
+
+ return rc;
+}
+
+static int pmic_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ u8 r;
+ int ret;
+
+ /* we only have 8 GPIO pins we can use as input */
+ if (offset > 8)
+ return -EOPNOTSUPP;
+ ret = intel_scu_ipc_ioread8(GPIO0 + offset, &r);
+ if (ret < 0)
+ return ret;
+ return r & GPIO_DIN;
+}
+
+static void pmic_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ if (offset < 8)/* it is GPIO */
+ intel_scu_ipc_update_register(GPIO0 + offset,
+ GPIO_DRV | (value ? GPIO_DOU : 0),
+ GPIO_DRV | GPIO_DOU);
+ else if (offset < 16)/* it is GPOSW */
+ intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8,
+ GPOSW_DRV | (value ? GPOSW_DOU : 0),
+ GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV);
+ else if (offset > 15 && offset < 24) /* it is GPO */
+ intel_scu_ipc_update_register(GPO,
+ value ? 1 << (offset - 16) : 0,
+ 1 << (offset - 16));
+}
+
+/*
+ * This is called from genirq with pg->buslock locked and
+ * irq_desc->lock held. We can not access the scu bus here, so we
+ * store the change and update in the bus_sync_unlock() function below
+ */
+static int pmic_irq_type(struct irq_data *data, unsigned type)
+{
+ struct pmic_gpio *pg = irq_data_get_irq_chip_data(data);
+ u32 gpio = data->irq - pg->irq_base;
+
+ if (gpio >= pg->chip.ngpio)
+ return -EINVAL;
+
+ pg->trigger_type = type;
+ pg->update_type = gpio | GPIO_UPDATE_TYPE;
+ return 0;
+}
+
+static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct pmic_gpio *pg = container_of(chip, struct pmic_gpio, chip);
+
+ return pg->irq_base + offset;
+}
+
+static void pmic_bus_lock(struct irq_data *data)
+{
+ struct pmic_gpio *pg = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&pg->buslock);
+}
+
+static void pmic_bus_sync_unlock(struct irq_data *data)
+{
+ struct pmic_gpio *pg = irq_data_get_irq_chip_data(data);
+
+ if (pg->update_type) {
+ unsigned int gpio = pg->update_type & ~GPIO_UPDATE_TYPE;
+
+ pmic_program_irqtype(gpio, pg->trigger_type);
+ pg->update_type = 0;
+ }
+ mutex_unlock(&pg->buslock);
+}
+
+/* the gpiointr register is read-clear, so just do nothing. */
+static void pmic_irq_unmask(struct irq_data *data) { }
+
+static void pmic_irq_mask(struct irq_data *data) { }
+
+static struct irq_chip pmic_irqchip = {
+ .name = "PMIC-GPIO",
+ .irq_mask = pmic_irq_mask,
+ .irq_unmask = pmic_irq_unmask,
+ .irq_set_type = pmic_irq_type,
+ .irq_bus_lock = pmic_bus_lock,
+ .irq_bus_sync_unlock = pmic_bus_sync_unlock,
+};
+
+static irqreturn_t pmic_irq_handler(int irq, void *data)
+{
+ struct pmic_gpio *pg = data;
+ u8 intsts = *((u8 *)pg->gpiointr + 4);
+ int gpio;
+ irqreturn_t ret = IRQ_NONE;
+
+ for (gpio = 0; gpio < 8; gpio++) {
+ if (intsts & (1 << gpio)) {
+ pr_debug("pmic pin %d triggered\n", gpio);
+ generic_handle_irq(pg->irq_base + gpio);
+ ret = IRQ_HANDLED;
+ }
+ }
+ return ret;
+}
+
+static int platform_pmic_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int irq = platform_get_irq(pdev, 0);
+ struct intel_pmic_gpio_platform_data *pdata = dev->platform_data;
+
+ struct pmic_gpio *pg;
+ int retval;
+ int i;
+
+ if (irq < 0) {
+ dev_dbg(dev, "no IRQ line\n");
+ return -EINVAL;
+ }
+
+ if (!pdata || !pdata->gpio_base || !pdata->irq_base) {
+ dev_dbg(dev, "incorrect or missing platform data\n");
+ return -EINVAL;
+ }
+
+ pg = kzalloc(sizeof(*pg), GFP_KERNEL);
+ if (!pg)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, pg);
+
+ pg->irq = irq;
+ /* setting up SRAM mapping for GPIOINT register */
+ pg->gpiointr = ioremap_nocache(pdata->gpiointr, 8);
+ if (!pg->gpiointr) {
+ pr_err("Can not map GPIOINT\n");
+ retval = -EINVAL;
+ goto err2;
+ }
+ pg->irq_base = pdata->irq_base;
+ pg->chip.label = "intel_pmic";
+ pg->chip.direction_input = pmic_gpio_direction_input;
+ pg->chip.direction_output = pmic_gpio_direction_output;
+ pg->chip.get = pmic_gpio_get;
+ pg->chip.set = pmic_gpio_set;
+ pg->chip.to_irq = pmic_gpio_to_irq;
+ pg->chip.base = pdata->gpio_base;
+ pg->chip.ngpio = NUM_GPIO;
+ pg->chip.can_sleep = 1;
+ pg->chip.dev = dev;
+
+ mutex_init(&pg->buslock);
+
+ pg->chip.dev = dev;
+ retval = gpiochip_add(&pg->chip);
+ if (retval) {
+ pr_err("Can not add pmic gpio chip\n");
+ goto err;
+ }
+
+ retval = request_irq(pg->irq, pmic_irq_handler, 0, "pmic", pg);
+ if (retval) {
+ pr_warn("Interrupt request failed\n");
+ goto err;
+ }
+
+ for (i = 0; i < 8; i++) {
+ irq_set_chip_and_handler_name(i + pg->irq_base,
+ &pmic_irqchip,
+ handle_simple_irq,
+ "demux");
+ irq_set_chip_data(i + pg->irq_base, pg);
+ }
+ return 0;
+err:
+ iounmap(pg->gpiointr);
+err2:
+ kfree(pg);
+ return retval;
+}
+
+/* at the same time, register a platform driver
+ * this supports the sfi 0.81 fw */
+static struct platform_driver platform_pmic_gpio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = platform_pmic_gpio_probe,
+};
+
+static int __init platform_pmic_gpio_init(void)
+{
+ return platform_driver_register(&platform_pmic_gpio_driver);
+}
+
+subsys_initcall(platform_pmic_gpio_init);
+
+MODULE_AUTHOR("Alek Du <alek.du@intel.com>");
+MODULE_DESCRIPTION("Intel Moorestown PMIC GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
new file mode 100644
index 00000000..9215ed72
--- /dev/null
+++ b/drivers/platform/x86/intel_scu_ipc.c
@@ -0,0 +1,598 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@intel.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; version 2
+ * of the License.
+ *
+ * SCU running in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad SCU.
+ * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
+ * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
+ * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
+ * along with other APIs.
+ */
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/sfi.h>
+#include <linux/module.h>
+#include <asm/mrst.h>
+#include <asm/intel_scu_ipc.h>
+
+/* IPC defines the following message types */
+#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */
+#define IPCMSG_BATTERY 0xEF /* Coulomb Counter Accumulator */
+#define IPCMSG_FW_UPDATE 0xFE /* Firmware update */
+#define IPCMSG_PCNTRL 0xFF /* Power controller unit read/write */
+#define IPCMSG_FW_REVISION 0xF4 /* Get firmware revision */
+
+/* Command id associated with message IPCMSG_PCNTRL */
+#define IPC_CMD_PCNTRL_W 0 /* Register write */
+#define IPC_CMD_PCNTRL_R 1 /* Register read */
+#define IPC_CMD_PCNTRL_M 2 /* Register read-modify-write */
+
+/*
+ * IPC register summary
+ *
+ * IPC register blocks are memory mapped at fixed address of 0xFF11C000
+ * To read or write information to the SCU, driver writes to IPC-1 memory
+ * mapped registers (base address 0xFF11C000). The following is the IPC
+ * mechanism
+ *
+ * 1. IA core cDMI interface claims this transaction and converts it to a
+ * Transaction Layer Packet (TLP) message which is sent across the cDMI.
+ *
+ * 2. South Complex cDMI block receives this message and writes it to
+ * the IPC-1 register block, causing an interrupt to the SCU
+ *
+ * 3. SCU firmware decodes this interrupt and IPC message and the appropriate
+ * message handler is called within firmware.
+ */
+
+#define IPC_BASE_ADDR 0xFF11C000 /* IPC1 base register address */
+#define IPC_MAX_ADDR 0x100 /* Maximum IPC regisers */
+#define IPC_WWBUF_SIZE 20 /* IPC Write buffer Size */
+#define IPC_RWBUF_SIZE 20 /* IPC Read buffer Size */
+#define IPC_I2C_BASE 0xFF12B000 /* I2C control register base address */
+#define IPC_I2C_MAX_ADDR 0x10 /* Maximum I2C regisers */
+
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id);
+static void ipc_remove(struct pci_dev *pdev);
+
+struct intel_scu_ipc_dev {
+ struct pci_dev *pdev;
+ void __iomem *ipc_base;
+ void __iomem *i2c_base;
+};
+
+static struct intel_scu_ipc_dev ipcdev; /* Only one for now */
+
+static int platform; /* Platform type */
+
+/*
+ * IPC Read Buffer (Read Only):
+ * 16 byte buffer for receiving data from SCU, if IPC command
+ * processing results in response data
+ */
+#define IPC_READ_BUFFER 0x90
+
+#define IPC_I2C_CNTRL_ADDR 0
+#define I2C_DATA_ADDR 0x04
+
+static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
+
+/*
+ * Command Register (Write Only):
+ * A write to this register results in an interrupt to the SCU core processor
+ * Format:
+ * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
+ */
+static inline void ipc_command(u32 cmd) /* Send ipc command */
+{
+ writel(cmd, ipcdev.ipc_base);
+}
+
+/*
+ * IPC Write Buffer (Write Only):
+ * 16-byte buffer for sending data associated with IPC command to
+ * SCU. Size of the data is specified in the IPC_COMMAND_REG register
+ */
+static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */
+{
+ writel(data, ipcdev.ipc_base + 0x80 + offset);
+}
+
+/*
+ * Status Register (Read Only):
+ * Driver will read this register to get the ready/busy status of the IPC
+ * block and error status of the IPC command that was just processed by SCU
+ * Format:
+ * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
+ */
+
+static inline u8 ipc_read_status(void)
+{
+ return __raw_readl(ipcdev.ipc_base + 0x04);
+}
+
+static inline u8 ipc_data_readb(u32 offset) /* Read ipc byte data */
+{
+ return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline u32 ipc_data_readl(u32 offset) /* Read ipc u32 data */
+{
+ return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline int busy_loop(void) /* Wait till scu status is busy */
+{
+ u32 status = 0;
+ u32 loop_count = 0;
+
+ status = ipc_read_status();
+ while (status & 1) {
+ udelay(1); /* scu processing time is in few u secods */
+ status = ipc_read_status();
+ loop_count++;
+ /* break if scu doesn't reset busy bit after huge retry */
+ if (loop_count > 100000) {
+ dev_err(&ipcdev.pdev->dev, "IPC timed out");
+ return -ETIMEDOUT;
+ }
+ }
+ if ((status >> 1) & 1)
+ return -EIO;
+
+ return 0;
+}
+
+/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
+static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
+{
+ int nc;
+ u32 offset = 0;
+ int err;
+ u8 cbuf[IPC_WWBUF_SIZE] = { };
+ u32 *wbuf = (u32 *)&cbuf;
+
+ mutex_lock(&ipclock);
+
+ memset(cbuf, 0, sizeof(cbuf));
+
+ if (ipcdev.pdev == NULL) {
+ mutex_unlock(&ipclock);
+ return -ENODEV;
+ }
+
+ for (nc = 0; nc < count; nc++, offset += 2) {
+ cbuf[offset] = addr[nc];
+ cbuf[offset + 1] = addr[nc] >> 8;
+ }
+
+ if (id == IPC_CMD_PCNTRL_R) {
+ for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+ ipc_data_writel(wbuf[nc], offset);
+ ipc_command((count*2) << 16 | id << 12 | 0 << 8 | op);
+ } else if (id == IPC_CMD_PCNTRL_W) {
+ for (nc = 0; nc < count; nc++, offset += 1)
+ cbuf[offset] = data[nc];
+ for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+ ipc_data_writel(wbuf[nc], offset);
+ ipc_command((count*3) << 16 | id << 12 | 0 << 8 | op);
+ } else if (id == IPC_CMD_PCNTRL_M) {
+ cbuf[offset] = data[0];
+ cbuf[offset + 1] = data[1];
+ ipc_data_writel(wbuf[0], 0); /* Write wbuff */
+ ipc_command(4 << 16 | id << 12 | 0 << 8 | op);
+ }
+
+ err = busy_loop();
+ if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
+ /* Workaround: values are read as 0 without memcpy_fromio */
+ memcpy_fromio(cbuf, ipcdev.ipc_base + 0x90, 16);
+ for (nc = 0; nc < count; nc++)
+ data[nc] = ipc_data_readb(nc);
+ }
+ mutex_unlock(&ipclock);
+ return err;
+}
+
+/**
+ * intel_scu_ipc_ioread8 - read a word via the SCU
+ * @addr: register on SCU
+ * @data: return pointer for read byte
+ *
+ * Read a single register. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * This function may sleep.
+ */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data)
+{
+ return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread8);
+
+/**
+ * intel_scu_ipc_ioread16 - read a word via the SCU
+ * @addr: register on SCU
+ * @data: return pointer for read word
+ *
+ * Read a register pair. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * This function may sleep.
+ */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data)
+{
+ u16 x[2] = {addr, addr + 1 };
+ return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread16);
+
+/**
+ * intel_scu_ipc_ioread32 - read a dword via the SCU
+ * @addr: register on SCU
+ * @data: return pointer for read dword
+ *
+ * Read four registers. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * This function may sleep.
+ */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data)
+{
+ u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+ return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread32);
+
+/**
+ * intel_scu_ipc_iowrite8 - write a byte via the SCU
+ * @addr: register on SCU
+ * @data: byte to write
+ *
+ * Write a single register. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * This function may sleep.
+ */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data)
+{
+ return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
+
+/**
+ * intel_scu_ipc_iowrite16 - write a word via the SCU
+ * @addr: register on SCU
+ * @data: word to write
+ *
+ * Write two registers. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * This function may sleep.
+ */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data)
+{
+ u16 x[2] = {addr, addr + 1 };
+ return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite16);
+
+/**
+ * intel_scu_ipc_iowrite32 - write a dword via the SCU
+ * @addr: register on SCU
+ * @data: dword to write
+ *
+ * Write four registers. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * This function may sleep.
+ */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data)
+{
+ u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+ return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite32);
+
+/**
+ * intel_scu_ipc_readvv - read a set of registers
+ * @addr: register list
+ * @data: bytes to return
+ * @len: length of array
+ *
+ * Read registers. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * The largest array length permitted by the hardware is 5 items.
+ *
+ * This function may sleep.
+ */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
+{
+ return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_readv);
+
+/**
+ * intel_scu_ipc_writev - write a set of registers
+ * @addr: register list
+ * @data: bytes to write
+ * @len: length of array
+ *
+ * Write registers. Returns 0 on success or an error code. All
+ * locking between SCU accesses is handled for the caller.
+ *
+ * The largest array length permitted by the hardware is 5 items.
+ *
+ * This function may sleep.
+ *
+ */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
+{
+ return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_writev);
+
+
+/**
+ * intel_scu_ipc_update_register - r/m/w a register
+ * @addr: register address
+ * @bits: bits to update
+ * @mask: mask of bits to update
+ *
+ * Read-modify-write power control unit register. The first data argument
+ * must be register value and second is mask value
+ * mask is a bitmap that indicates which bits to update.
+ * 0 = masked. Don't modify this bit, 1 = modify this bit.
+ * returns 0 on success or an error code.
+ *
+ * This function may sleep. Locking between SCU accesses is handled
+ * for the caller.
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 bits, u8 mask)
+{
+ u8 data[2] = { bits, mask };
+ return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
+}
+EXPORT_SYMBOL(intel_scu_ipc_update_register);
+
+/**
+ * intel_scu_ipc_simple_command - send a simple command
+ * @cmd: command
+ * @sub: sub type
+ *
+ * Issue a simple command to the SCU. Do not use this interface if
+ * you must then access data as any data values may be overwritten
+ * by another SCU access by the time this function returns.
+ *
+ * This function may sleep. Locking for SCU accesses is handled for
+ * the caller.
+ */
+int intel_scu_ipc_simple_command(int cmd, int sub)
+{
+ int err;
+
+ mutex_lock(&ipclock);
+ if (ipcdev.pdev == NULL) {
+ mutex_unlock(&ipclock);
+ return -ENODEV;
+ }
+ ipc_command(sub << 12 | cmd);
+ err = busy_loop();
+ mutex_unlock(&ipclock);
+ return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_simple_command);
+
+/**
+ * intel_scu_ipc_command - command with data
+ * @cmd: command
+ * @sub: sub type
+ * @in: input data
+ * @inlen: input length in dwords
+ * @out: output data
+ * @outlein: output length in dwords
+ *
+ * Issue a command to the SCU which involves data transfers. Do the
+ * data copies under the lock but leave it for the caller to interpret
+ */
+
+int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
+ u32 *out, int outlen)
+{
+ int i, err;
+
+ mutex_lock(&ipclock);
+ if (ipcdev.pdev == NULL) {
+ mutex_unlock(&ipclock);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < inlen; i++)
+ ipc_data_writel(*in++, 4 * i);
+
+ ipc_command((inlen << 16) | (sub << 12) | cmd);
+ err = busy_loop();
+
+ for (i = 0; i < outlen; i++)
+ *out++ = ipc_data_readl(4 * i);
+
+ mutex_unlock(&ipclock);
+ return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_command);
+
+/*I2C commands */
+#define IPC_I2C_WRITE 1 /* I2C Write command */
+#define IPC_I2C_READ 2 /* I2C Read command */
+
+/**
+ * intel_scu_ipc_i2c_cntrl - I2C read/write operations
+ * @addr: I2C address + command bits
+ * @data: data to read/write
+ *
+ * Perform an an I2C read/write operation via the SCU. All locking is
+ * handled for the caller. This function may sleep.
+ *
+ * Returns an error code or 0 on success.
+ *
+ * This has to be in the IPC driver for the locking.
+ */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
+{
+ u32 cmd = 0;
+
+ mutex_lock(&ipclock);
+ if (ipcdev.pdev == NULL) {
+ mutex_unlock(&ipclock);
+ return -ENODEV;
+ }
+ cmd = (addr >> 24) & 0xFF;
+ if (cmd == IPC_I2C_READ) {
+ writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+ /* Write not getting updated without delay */
+ mdelay(1);
+ *data = readl(ipcdev.i2c_base + I2C_DATA_ADDR);
+ } else if (cmd == IPC_I2C_WRITE) {
+ writel(*data, ipcdev.i2c_base + I2C_DATA_ADDR);
+ mdelay(1);
+ writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+ } else {
+ dev_err(&ipcdev.pdev->dev,
+ "intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
+
+ mutex_unlock(&ipclock);
+ return -EIO;
+ }
+ mutex_unlock(&ipclock);
+ return 0;
+}
+EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
+
+/*
+ * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
+ * When ioc bit is set to 1, caller api must wait for interrupt handler called
+ * which in turn unlocks the caller api. Currently this is not used
+ *
+ * This is edge triggered so we need take no action to clear anything
+ */
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+ return IRQ_HANDLED;
+}
+
+/**
+ * ipc_probe - probe an Intel SCU IPC
+ * @dev: the PCI device matching
+ * @id: entry in the match table
+ *
+ * Enable and install an intel SCU IPC. This appears in the PCI space
+ * but uses some hard coded addresses as well.
+ */
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ int err;
+ resource_size_t pci_resource;
+
+ if (ipcdev.pdev) /* We support only one SCU */
+ return -EBUSY;
+
+ ipcdev.pdev = pci_dev_get(dev);
+
+ err = pci_enable_device(dev);
+ if (err)
+ return err;
+
+ err = pci_request_regions(dev, "intel_scu_ipc");
+ if (err)
+ return err;
+
+ pci_resource = pci_resource_start(dev, 0);
+ if (!pci_resource)
+ return -ENOMEM;
+
+ if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev))
+ return -EBUSY;
+
+ ipcdev.ipc_base = ioremap_nocache(IPC_BASE_ADDR, IPC_MAX_ADDR);
+ if (!ipcdev.ipc_base)
+ return -ENOMEM;
+
+ ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR);
+ if (!ipcdev.i2c_base) {
+ iounmap(ipcdev.ipc_base);
+ return -ENOMEM;
+ }
+
+ intel_scu_devices_create();
+
+ return 0;
+}
+
+/**
+ * ipc_remove - remove a bound IPC device
+ * @pdev: PCI device
+ *
+ * In practice the SCU is not removable but this function is also
+ * called for each device on a module unload or cleanup which is the
+ * path that will get used.
+ *
+ * Free up the mappings and release the PCI resources
+ */
+static void ipc_remove(struct pci_dev *pdev)
+{
+ free_irq(pdev->irq, &ipcdev);
+ pci_release_regions(pdev);
+ pci_dev_put(ipcdev.pdev);
+ iounmap(ipcdev.ipc_base);
+ iounmap(ipcdev.i2c_base);
+ ipcdev.pdev = NULL;
+ intel_scu_devices_destroy();
+}
+
+static DEFINE_PCI_DEVICE_TABLE(pci_ids) = {
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x082a)},
+ { 0,}
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver ipc_driver = {
+ .name = "intel_scu_ipc",
+ .id_table = pci_ids,
+ .probe = ipc_probe,
+ .remove = ipc_remove,
+};
+
+
+static int __init intel_scu_ipc_init(void)
+{
+ platform = mrst_identify_cpu();
+ if (platform == 0)
+ return -ENODEV;
+ return pci_register_driver(&ipc_driver);
+}
+
+static void __exit intel_scu_ipc_exit(void)
+{
+ pci_unregister_driver(&ipc_driver);
+}
+
+MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
+MODULE_DESCRIPTION("Intel SCU IPC driver");
+MODULE_LICENSE("GPL");
+
+module_init(intel_scu_ipc_init);
+module_exit(intel_scu_ipc_exit);
diff --git a/drivers/platform/x86/intel_scu_ipcutil.c b/drivers/platform/x86/intel_scu_ipcutil.c
new file mode 100644
index 00000000..02bc5a63
--- /dev/null
+++ b/drivers/platform/x86/intel_scu_ipcutil.c
@@ -0,0 +1,121 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@intel.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; version 2
+ * of the License.
+ *
+ * This driver provides ioctl interfaces to call intel scu ipc driver api
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/intel_scu_ipc.h>
+
+static int major;
+
+/* ioctl commnds */
+#define INTE_SCU_IPC_REGISTER_READ 0
+#define INTE_SCU_IPC_REGISTER_WRITE 1
+#define INTE_SCU_IPC_REGISTER_UPDATE 2
+
+struct scu_ipc_data {
+ u32 count; /* No. of registers */
+ u16 addr[5]; /* Register addresses */
+ u8 data[5]; /* Register data */
+ u8 mask; /* Valid for read-modify-write */
+};
+
+/**
+ * scu_reg_access - implement register access ioctls
+ * @cmd: command we are doing (read/write/update)
+ * @data: kernel copy of ioctl data
+ *
+ * Allow the user to perform register accesses on the SCU via the
+ * kernel interface
+ */
+
+static int scu_reg_access(u32 cmd, struct scu_ipc_data *data)
+{
+ int count = data->count;
+
+ if (count == 0 || count == 3 || count > 4)
+ return -EINVAL;
+
+ switch (cmd) {
+ case INTE_SCU_IPC_REGISTER_READ:
+ return intel_scu_ipc_readv(data->addr, data->data, count);
+ case INTE_SCU_IPC_REGISTER_WRITE:
+ return intel_scu_ipc_writev(data->addr, data->data, count);
+ case INTE_SCU_IPC_REGISTER_UPDATE:
+ return intel_scu_ipc_update_register(data->addr[0],
+ data->data[0], data->mask);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/**
+ * scu_ipc_ioctl - control ioctls for the SCU
+ * @fp: file handle of the SCU device
+ * @cmd: ioctl coce
+ * @arg: pointer to user passed structure
+ *
+ * Support the I/O and firmware flashing interfaces of the SCU
+ */
+static long scu_ipc_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ struct scu_ipc_data data;
+ void __user *argp = (void __user *)arg;
+
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+
+ if (copy_from_user(&data, argp, sizeof(struct scu_ipc_data)))
+ return -EFAULT;
+ ret = scu_reg_access(cmd, &data);
+ if (ret < 0)
+ return ret;
+ if (copy_to_user(argp, &data, sizeof(struct scu_ipc_data)))
+ return -EFAULT;
+ return 0;
+}
+
+static const struct file_operations scu_ipc_fops = {
+ .unlocked_ioctl = scu_ipc_ioctl,
+};
+
+static int __init ipc_module_init(void)
+{
+ major = register_chrdev(0, "intel_mid_scu", &scu_ipc_fops);
+ if (major < 0)
+ return major;
+
+ return 0;
+}
+
+static void __exit ipc_module_exit(void)
+{
+ unregister_chrdev(major, "intel_mid_scu");
+}
+
+module_init(ipc_module_init);
+module_exit(ipc_module_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Utility driver for intel scu ipc");
+MODULE_AUTHOR("Sreedhara <sreedhara.ds@intel.com>");
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c
new file mode 100644
index 00000000..6b229387
--- /dev/null
+++ b/drivers/platform/x86/msi-laptop.c
@@ -0,0 +1,1205 @@
+/*-*-linux-c-*-*/
+
+/*
+ Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
+
+ 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.
+ */
+
+/*
+ * msi-laptop.c - MSI S270 laptop support. This laptop is sold under
+ * various brands, including "Cytron/TCM/Medion/Tchibo MD96100".
+ *
+ * Driver also supports S271, S420 models.
+ *
+ * This driver exports a few files in /sys/devices/platform/msi-laptop-pf/:
+ *
+ * lcd_level - Screen brightness: contains a single integer in the
+ * range 0..8. (rw)
+ *
+ * auto_brightness - Enable automatic brightness control: contains
+ * either 0 or 1. If set to 1 the hardware adjusts the screen
+ * brightness automatically when the power cord is
+ * plugged/unplugged. (rw)
+ *
+ * wlan - WLAN subsystem enabled: contains either 0 or 1. (ro)
+ *
+ * bluetooth - Bluetooth subsystem enabled: contains either 0 or 1
+ * Please note that this file is constantly 0 if no Bluetooth
+ * hardware is available. (ro)
+ *
+ * In addition to these platform device attributes the driver
+ * registers itself in the Linux backlight control subsystem and is
+ * available to userspace under /sys/class/backlight/msi-laptop-bl/.
+ *
+ * This driver might work on other laptops produced by MSI. If you
+ * want to try it you can pass force=1 as argument to the module which
+ * will force it to load even when the DMI data doesn't identify the
+ * laptop as MSI S270. YMMV.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/i8042.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+
+#define MSI_DRIVER_VERSION "0.5"
+
+#define MSI_LCD_LEVEL_MAX 9
+
+#define MSI_EC_COMMAND_WIRELESS 0x10
+#define MSI_EC_COMMAND_LCD_LEVEL 0x11
+
+#define MSI_STANDARD_EC_COMMAND_ADDRESS 0x2e
+#define MSI_STANDARD_EC_BLUETOOTH_MASK (1 << 0)
+#define MSI_STANDARD_EC_WEBCAM_MASK (1 << 1)
+#define MSI_STANDARD_EC_WLAN_MASK (1 << 3)
+#define MSI_STANDARD_EC_3G_MASK (1 << 4)
+
+/* For set SCM load flag to disable BIOS fn key */
+#define MSI_STANDARD_EC_SCM_LOAD_ADDRESS 0x2d
+#define MSI_STANDARD_EC_SCM_LOAD_MASK (1 << 0)
+
+#define MSI_STANDARD_EC_FUNCTIONS_ADDRESS 0xe4
+/* Power LED is orange - Turbo mode */
+#define MSI_STANDARD_EC_TURBO_MASK (1 << 1)
+/* Power LED is green - ECO mode */
+#define MSI_STANDARD_EC_ECO_MASK (1 << 3)
+/* Touchpad is turned on */
+#define MSI_STANDARD_EC_TOUCHPAD_MASK (1 << 4)
+/* If this bit != bit 1, turbo mode can't be toggled */
+#define MSI_STANDARD_EC_TURBO_COOLDOWN_MASK (1 << 7)
+
+#define MSI_STANDARD_EC_FAN_ADDRESS 0x33
+/* If zero, fan rotates at maximal speed */
+#define MSI_STANDARD_EC_AUTOFAN_MASK (1 << 0)
+
+#ifdef CONFIG_PM_SLEEP
+static int msi_laptop_resume(struct device *device);
+#endif
+static SIMPLE_DEV_PM_OPS(msi_laptop_pm, NULL, msi_laptop_resume);
+
+#define MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS 0x2f
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static int auto_brightness;
+module_param(auto_brightness, int, 0);
+MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
+
+static const struct key_entry msi_laptop_keymap[] = {
+ {KE_KEY, KEY_TOUCHPAD_ON, {KEY_TOUCHPAD_ON} }, /* Touch Pad On */
+ {KE_KEY, KEY_TOUCHPAD_OFF, {KEY_TOUCHPAD_OFF} },/* Touch Pad On */
+ {KE_END, 0}
+};
+
+static struct input_dev *msi_laptop_input_dev;
+
+static int wlan_s, bluetooth_s, threeg_s;
+static int threeg_exists;
+static struct rfkill *rfk_wlan, *rfk_bluetooth, *rfk_threeg;
+
+/* MSI laptop quirks */
+struct quirk_entry {
+ bool old_ec_model;
+
+ /* Some MSI 3G netbook only have one fn key to control
+ * Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to
+ * disable the original Wlan/Bluetooth control by BIOS when user press
+ * fn key, then control Wlan/Bluetooth/3G by SCM (software control by
+ * OS). Without SCM, user cann't on/off 3G module on those 3G netbook.
+ * On Linux, msi-laptop driver will do the same thing to disable the
+ * original BIOS control, then might need use HAL or other userland
+ * application to do the software control that simulate with SCM.
+ * e.g. MSI N034 netbook
+ */
+ bool load_scm_model;
+
+ /* Some MSI laptops need delay before reading from EC */
+ bool ec_delay;
+
+ /* Some MSI Wind netbooks (e.g. MSI Wind U100) need loading SCM to get
+ * some features working (e.g. ECO mode), but we cannot change
+ * Wlan/Bluetooth state in software and we can only read its state.
+ */
+ bool ec_read_only;
+};
+
+static struct quirk_entry *quirks;
+
+/* Hardware access */
+
+static int set_lcd_level(int level)
+{
+ u8 buf[2];
+
+ if (level < 0 || level >= MSI_LCD_LEVEL_MAX)
+ return -EINVAL;
+
+ buf[0] = 0x80;
+ buf[1] = (u8) (level*31);
+
+ return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf),
+ NULL, 0);
+}
+
+static int get_lcd_level(void)
+{
+ u8 wdata = 0, rdata;
+ int result;
+
+ result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
+ &rdata, 1);
+ if (result < 0)
+ return result;
+
+ return (int) rdata / 31;
+}
+
+static int get_auto_brightness(void)
+{
+ u8 wdata = 4, rdata;
+ int result;
+
+ result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1,
+ &rdata, 1);
+ if (result < 0)
+ return result;
+
+ return !!(rdata & 8);
+}
+
+static int set_auto_brightness(int enable)
+{
+ u8 wdata[2], rdata;
+ int result;
+
+ wdata[0] = 4;
+
+ result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1,
+ &rdata, 1);
+ if (result < 0)
+ return result;
+
+ wdata[0] = 0x84;
+ wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);
+
+ return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2,
+ NULL, 0);
+}
+
+static ssize_t set_device_state(const char *buf, size_t count, u8 mask)
+{
+ int status;
+ u8 wdata = 0, rdata;
+ int result;
+
+ if (sscanf(buf, "%i", &status) != 1 || (status < 0 || status > 1))
+ return -EINVAL;
+
+ if (quirks->ec_read_only)
+ return -EOPNOTSUPP;
+
+ /* read current device state */
+ result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ if (!!(rdata & mask) != status) {
+ /* reverse device bit */
+ if (rdata & mask)
+ wdata = rdata & ~mask;
+ else
+ wdata = rdata | mask;
+
+ result = ec_write(MSI_STANDARD_EC_COMMAND_ADDRESS, wdata);
+ if (result < 0)
+ return result;
+ }
+
+ return count;
+}
+
+static int get_wireless_state(int *wlan, int *bluetooth)
+{
+ u8 wdata = 0, rdata;
+ int result;
+
+ result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1);
+ if (result < 0)
+ return result;
+
+ if (wlan)
+ *wlan = !!(rdata & 8);
+
+ if (bluetooth)
+ *bluetooth = !!(rdata & 128);
+
+ return 0;
+}
+
+static int get_wireless_state_ec_standard(void)
+{
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_COMMAND_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ wlan_s = !!(rdata & MSI_STANDARD_EC_WLAN_MASK);
+
+ bluetooth_s = !!(rdata & MSI_STANDARD_EC_BLUETOOTH_MASK);
+
+ threeg_s = !!(rdata & MSI_STANDARD_EC_3G_MASK);
+
+ return 0;
+}
+
+static int get_threeg_exists(void)
+{
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_DEVICES_EXISTS_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ threeg_exists = !!(rdata & MSI_STANDARD_EC_3G_MASK);
+
+ return 0;
+}
+
+/* Backlight device stuff */
+
+static int bl_get_brightness(struct backlight_device *b)
+{
+ return get_lcd_level();
+}
+
+
+static int bl_update_status(struct backlight_device *b)
+{
+ return set_lcd_level(b->props.brightness);
+}
+
+static const struct backlight_ops msibl_ops = {
+ .get_brightness = bl_get_brightness,
+ .update_status = bl_update_status,
+};
+
+static struct backlight_device *msibl_device;
+
+/* Platform device */
+
+static ssize_t show_wlan(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret, enabled = 0;
+
+ if (quirks->old_ec_model) {
+ ret = get_wireless_state(&enabled, NULL);
+ } else {
+ ret = get_wireless_state_ec_standard();
+ enabled = wlan_s;
+ }
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", enabled);
+}
+
+static ssize_t store_wlan(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return set_device_state(buf, count, MSI_STANDARD_EC_WLAN_MASK);
+}
+
+static ssize_t show_bluetooth(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret, enabled = 0;
+
+ if (quirks->old_ec_model) {
+ ret = get_wireless_state(NULL, &enabled);
+ } else {
+ ret = get_wireless_state_ec_standard();
+ enabled = bluetooth_s;
+ }
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", enabled);
+}
+
+static ssize_t store_bluetooth(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return set_device_state(buf, count, MSI_STANDARD_EC_BLUETOOTH_MASK);
+}
+
+static ssize_t show_threeg(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret;
+
+ /* old msi ec not support 3G */
+ if (quirks->old_ec_model)
+ return -ENODEV;
+
+ ret = get_wireless_state_ec_standard();
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", threeg_s);
+}
+
+static ssize_t store_threeg(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return set_device_state(buf, count, MSI_STANDARD_EC_3G_MASK);
+}
+
+static ssize_t show_lcd_level(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret;
+
+ ret = get_lcd_level();
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", ret);
+}
+
+static ssize_t store_lcd_level(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+
+ int level, ret;
+
+ if (sscanf(buf, "%i", &level) != 1 ||
+ (level < 0 || level >= MSI_LCD_LEVEL_MAX))
+ return -EINVAL;
+
+ ret = set_lcd_level(level);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t show_auto_brightness(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ int ret;
+
+ ret = get_auto_brightness();
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%i\n", ret);
+}
+
+static ssize_t store_auto_brightness(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+
+ int enable, ret;
+
+ if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
+ return -EINVAL;
+
+ ret = set_auto_brightness(enable);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t show_touchpad(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TOUCHPAD_MASK));
+}
+
+static ssize_t show_turbo(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_TURBO_MASK));
+}
+
+static ssize_t show_eco(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_ECO_MASK));
+}
+
+static ssize_t show_turbo_cooldown(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ return sprintf(buf, "%i\n", (!!(rdata & MSI_STANDARD_EC_TURBO_MASK)) |
+ (!!(rdata & MSI_STANDARD_EC_TURBO_COOLDOWN_MASK) << 1));
+}
+
+static ssize_t show_auto_fan(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_FAN_ADDRESS, &rdata);
+ if (result < 0)
+ return result;
+
+ return sprintf(buf, "%i\n", !!(rdata & MSI_STANDARD_EC_AUTOFAN_MASK));
+}
+
+static ssize_t store_auto_fan(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+
+ int enable, result;
+
+ if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
+ return -EINVAL;
+
+ result = ec_write(MSI_STANDARD_EC_FAN_ADDRESS, enable);
+ if (result < 0)
+ return result;
+
+ return count;
+}
+
+static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
+static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness,
+ store_auto_brightness);
+static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
+static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
+static DEVICE_ATTR(threeg, 0444, show_threeg, NULL);
+static DEVICE_ATTR(touchpad, 0444, show_touchpad, NULL);
+static DEVICE_ATTR(turbo_mode, 0444, show_turbo, NULL);
+static DEVICE_ATTR(eco_mode, 0444, show_eco, NULL);
+static DEVICE_ATTR(turbo_cooldown, 0444, show_turbo_cooldown, NULL);
+static DEVICE_ATTR(auto_fan, 0644, show_auto_fan, store_auto_fan);
+
+static struct attribute *msipf_attributes[] = {
+ &dev_attr_bluetooth.attr,
+ &dev_attr_wlan.attr,
+ &dev_attr_touchpad.attr,
+ &dev_attr_turbo_mode.attr,
+ &dev_attr_eco_mode.attr,
+ &dev_attr_turbo_cooldown.attr,
+ &dev_attr_auto_fan.attr,
+ NULL
+};
+
+static struct attribute *msipf_old_attributes[] = {
+ &dev_attr_lcd_level.attr,
+ &dev_attr_auto_brightness.attr,
+ NULL
+};
+
+static struct attribute_group msipf_attribute_group = {
+ .attrs = msipf_attributes
+};
+
+static struct attribute_group msipf_old_attribute_group = {
+ .attrs = msipf_old_attributes
+};
+
+static struct platform_driver msipf_driver = {
+ .driver = {
+ .name = "msi-laptop-pf",
+ .owner = THIS_MODULE,
+ .pm = &msi_laptop_pm,
+ },
+};
+
+static struct platform_device *msipf_device;
+
+/* Initialization */
+
+static struct quirk_entry quirk_old_ec_model = {
+ .old_ec_model = true,
+};
+
+static struct quirk_entry quirk_load_scm_model = {
+ .load_scm_model = true,
+ .ec_delay = true,
+};
+
+static struct quirk_entry quirk_load_scm_ro_model = {
+ .load_scm_model = true,
+ .ec_read_only = true,
+};
+
+static int dmi_check_cb(const struct dmi_system_id *dmi)
+{
+ pr_info("Identified laptop model '%s'\n", dmi->ident);
+
+ quirks = dmi->driver_data;
+
+ return 1;
+}
+
+static struct dmi_system_id __initdata msi_dmi_table[] = {
+ {
+ .ident = "MSI S270",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
+ DMI_MATCH(DMI_CHASSIS_VENDOR,
+ "MICRO-STAR INT'L CO.,LTD")
+ },
+ .driver_data = &quirk_old_ec_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI S271",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MS-1058"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "0581"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-1058")
+ },
+ .driver_data = &quirk_old_ec_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI S420",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MS-1412"),
+ DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-1412")
+ },
+ .driver_data = &quirk_old_ec_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "Medion MD96100",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "0131"),
+ DMI_MATCH(DMI_CHASSIS_VENDOR,
+ "MICRO-STAR INT'L CO.,LTD")
+ },
+ .driver_data = &quirk_old_ec_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI N034",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "MICRO-STAR INTERNATIONAL CO., LTD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MS-N034"),
+ DMI_MATCH(DMI_CHASSIS_VENDOR,
+ "MICRO-STAR INTERNATIONAL CO., LTD")
+ },
+ .driver_data = &quirk_load_scm_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI N051",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "MICRO-STAR INTERNATIONAL CO., LTD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MS-N051"),
+ DMI_MATCH(DMI_CHASSIS_VENDOR,
+ "MICRO-STAR INTERNATIONAL CO., LTD")
+ },
+ .driver_data = &quirk_load_scm_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI N014",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "MICRO-STAR INTERNATIONAL CO., LTD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "MS-N014"),
+ },
+ .driver_data = &quirk_load_scm_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI CR620",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "Micro-Star International"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "CR620"),
+ },
+ .driver_data = &quirk_load_scm_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI U270",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "U270 series"),
+ },
+ .driver_data = &quirk_load_scm_model,
+ .callback = dmi_check_cb
+ },
+ {
+ .ident = "MSI U90/U100",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "MICRO-STAR INTERNATIONAL CO., LTD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "U90/U100"),
+ },
+ .driver_data = &quirk_load_scm_ro_model,
+ .callback = dmi_check_cb
+ },
+ { }
+};
+
+static int rfkill_bluetooth_set(void *data, bool blocked)
+{
+ /* Do something with blocked...*/
+ /*
+ * blocked == false is on
+ * blocked == true is off
+ */
+ int result = set_device_state(blocked ? "0" : "1", 0,
+ MSI_STANDARD_EC_BLUETOOTH_MASK);
+
+ return min(result, 0);
+}
+
+static int rfkill_wlan_set(void *data, bool blocked)
+{
+ int result = set_device_state(blocked ? "0" : "1", 0,
+ MSI_STANDARD_EC_WLAN_MASK);
+
+ return min(result, 0);
+}
+
+static int rfkill_threeg_set(void *data, bool blocked)
+{
+ int result = set_device_state(blocked ? "0" : "1", 0,
+ MSI_STANDARD_EC_3G_MASK);
+
+ return min(result, 0);
+}
+
+static const struct rfkill_ops rfkill_bluetooth_ops = {
+ .set_block = rfkill_bluetooth_set
+};
+
+static const struct rfkill_ops rfkill_wlan_ops = {
+ .set_block = rfkill_wlan_set
+};
+
+static const struct rfkill_ops rfkill_threeg_ops = {
+ .set_block = rfkill_threeg_set
+};
+
+static void rfkill_cleanup(void)
+{
+ if (rfk_bluetooth) {
+ rfkill_unregister(rfk_bluetooth);
+ rfkill_destroy(rfk_bluetooth);
+ }
+
+ if (rfk_threeg) {
+ rfkill_unregister(rfk_threeg);
+ rfkill_destroy(rfk_threeg);
+ }
+
+ if (rfk_wlan) {
+ rfkill_unregister(rfk_wlan);
+ rfkill_destroy(rfk_wlan);
+ }
+}
+
+static bool msi_rfkill_set_state(struct rfkill *rfkill, bool blocked)
+{
+ if (quirks->ec_read_only)
+ return rfkill_set_hw_state(rfkill, blocked);
+ else
+ return rfkill_set_sw_state(rfkill, blocked);
+}
+
+static void msi_update_rfkill(struct work_struct *ignored)
+{
+ get_wireless_state_ec_standard();
+
+ if (rfk_wlan)
+ msi_rfkill_set_state(rfk_wlan, !wlan_s);
+ if (rfk_bluetooth)
+ msi_rfkill_set_state(rfk_bluetooth, !bluetooth_s);
+ if (rfk_threeg)
+ msi_rfkill_set_state(rfk_threeg, !threeg_s);
+}
+static DECLARE_DELAYED_WORK(msi_rfkill_dwork, msi_update_rfkill);
+static DECLARE_WORK(msi_rfkill_work, msi_update_rfkill);
+
+static void msi_send_touchpad_key(struct work_struct *ignored)
+{
+ u8 rdata;
+ int result;
+
+ result = ec_read(MSI_STANDARD_EC_FUNCTIONS_ADDRESS, &rdata);
+ if (result < 0)
+ return;
+
+ sparse_keymap_report_event(msi_laptop_input_dev,
+ (rdata & MSI_STANDARD_EC_TOUCHPAD_MASK) ?
+ KEY_TOUCHPAD_ON : KEY_TOUCHPAD_OFF, 1, true);
+}
+static DECLARE_DELAYED_WORK(msi_touchpad_dwork, msi_send_touchpad_key);
+static DECLARE_WORK(msi_touchpad_work, msi_send_touchpad_key);
+
+static bool msi_laptop_i8042_filter(unsigned char data, unsigned char str,
+ struct serio *port)
+{
+ static bool extended;
+
+ if (str & 0x20)
+ return false;
+
+ /* 0x54 wwan, 0x62 bluetooth, 0x76 wlan, 0xE4 touchpad toggle*/
+ if (unlikely(data == 0xe0)) {
+ extended = true;
+ return false;
+ } else if (unlikely(extended)) {
+ extended = false;
+ switch (data) {
+ case 0xE4:
+ if (quirks->ec_delay) {
+ schedule_delayed_work(&msi_touchpad_dwork,
+ round_jiffies_relative(0.5 * HZ));
+ } else
+ schedule_work(&msi_touchpad_work);
+ break;
+ case 0x54:
+ case 0x62:
+ case 0x76:
+ if (quirks->ec_delay) {
+ schedule_delayed_work(&msi_rfkill_dwork,
+ round_jiffies_relative(0.5 * HZ));
+ } else
+ schedule_work(&msi_rfkill_work);
+ break;
+ }
+ }
+
+ return false;
+}
+
+static void msi_init_rfkill(struct work_struct *ignored)
+{
+ if (rfk_wlan) {
+ rfkill_set_sw_state(rfk_wlan, !wlan_s);
+ rfkill_wlan_set(NULL, !wlan_s);
+ }
+ if (rfk_bluetooth) {
+ rfkill_set_sw_state(rfk_bluetooth, !bluetooth_s);
+ rfkill_bluetooth_set(NULL, !bluetooth_s);
+ }
+ if (rfk_threeg) {
+ rfkill_set_sw_state(rfk_threeg, !threeg_s);
+ rfkill_threeg_set(NULL, !threeg_s);
+ }
+}
+static DECLARE_DELAYED_WORK(msi_rfkill_init, msi_init_rfkill);
+
+static int rfkill_init(struct platform_device *sdev)
+{
+ /* add rfkill */
+ int retval;
+
+ /* keep the hardware wireless state */
+ get_wireless_state_ec_standard();
+
+ rfk_bluetooth = rfkill_alloc("msi-bluetooth", &sdev->dev,
+ RFKILL_TYPE_BLUETOOTH,
+ &rfkill_bluetooth_ops, NULL);
+ if (!rfk_bluetooth) {
+ retval = -ENOMEM;
+ goto err_bluetooth;
+ }
+ retval = rfkill_register(rfk_bluetooth);
+ if (retval)
+ goto err_bluetooth;
+
+ rfk_wlan = rfkill_alloc("msi-wlan", &sdev->dev, RFKILL_TYPE_WLAN,
+ &rfkill_wlan_ops, NULL);
+ if (!rfk_wlan) {
+ retval = -ENOMEM;
+ goto err_wlan;
+ }
+ retval = rfkill_register(rfk_wlan);
+ if (retval)
+ goto err_wlan;
+
+ if (threeg_exists) {
+ rfk_threeg = rfkill_alloc("msi-threeg", &sdev->dev,
+ RFKILL_TYPE_WWAN, &rfkill_threeg_ops, NULL);
+ if (!rfk_threeg) {
+ retval = -ENOMEM;
+ goto err_threeg;
+ }
+ retval = rfkill_register(rfk_threeg);
+ if (retval)
+ goto err_threeg;
+ }
+
+ /* schedule to run rfkill state initial */
+ if (quirks->ec_delay) {
+ schedule_delayed_work(&msi_rfkill_init,
+ round_jiffies_relative(1 * HZ));
+ } else
+ schedule_work(&msi_rfkill_work);
+
+ return 0;
+
+err_threeg:
+ rfkill_destroy(rfk_threeg);
+ if (rfk_wlan)
+ rfkill_unregister(rfk_wlan);
+err_wlan:
+ rfkill_destroy(rfk_wlan);
+ if (rfk_bluetooth)
+ rfkill_unregister(rfk_bluetooth);
+err_bluetooth:
+ rfkill_destroy(rfk_bluetooth);
+
+ return retval;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int msi_laptop_resume(struct device *device)
+{
+ u8 data;
+ int result;
+
+ if (!quirks->load_scm_model)
+ return 0;
+
+ /* set load SCM to disable hardware control by fn key */
+ result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
+ if (result < 0)
+ return result;
+
+ result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
+ data | MSI_STANDARD_EC_SCM_LOAD_MASK);
+ if (result < 0)
+ return result;
+
+ return 0;
+}
+#endif
+
+static int __init msi_laptop_input_setup(void)
+{
+ int err;
+
+ msi_laptop_input_dev = input_allocate_device();
+ if (!msi_laptop_input_dev)
+ return -ENOMEM;
+
+ msi_laptop_input_dev->name = "MSI Laptop hotkeys";
+ msi_laptop_input_dev->phys = "msi-laptop/input0";
+ msi_laptop_input_dev->id.bustype = BUS_HOST;
+
+ err = sparse_keymap_setup(msi_laptop_input_dev,
+ msi_laptop_keymap, NULL);
+ if (err)
+ goto err_free_dev;
+
+ err = input_register_device(msi_laptop_input_dev);
+ if (err)
+ goto err_free_keymap;
+
+ return 0;
+
+err_free_keymap:
+ sparse_keymap_free(msi_laptop_input_dev);
+err_free_dev:
+ input_free_device(msi_laptop_input_dev);
+ return err;
+}
+
+static void msi_laptop_input_destroy(void)
+{
+ sparse_keymap_free(msi_laptop_input_dev);
+ input_unregister_device(msi_laptop_input_dev);
+}
+
+static int __init load_scm_model_init(struct platform_device *sdev)
+{
+ u8 data;
+ int result;
+
+ if (!quirks->ec_read_only) {
+ /* allow userland write sysfs file */
+ dev_attr_bluetooth.store = store_bluetooth;
+ dev_attr_wlan.store = store_wlan;
+ dev_attr_threeg.store = store_threeg;
+ dev_attr_bluetooth.attr.mode |= S_IWUSR;
+ dev_attr_wlan.attr.mode |= S_IWUSR;
+ dev_attr_threeg.attr.mode |= S_IWUSR;
+ }
+
+ /* disable hardware control by fn key */
+ result = ec_read(MSI_STANDARD_EC_SCM_LOAD_ADDRESS, &data);
+ if (result < 0)
+ return result;
+
+ result = ec_write(MSI_STANDARD_EC_SCM_LOAD_ADDRESS,
+ data | MSI_STANDARD_EC_SCM_LOAD_MASK);
+ if (result < 0)
+ return result;
+
+ /* initial rfkill */
+ result = rfkill_init(sdev);
+ if (result < 0)
+ goto fail_rfkill;
+
+ /* setup input device */
+ result = msi_laptop_input_setup();
+ if (result)
+ goto fail_input;
+
+ result = i8042_install_filter(msi_laptop_i8042_filter);
+ if (result) {
+ pr_err("Unable to install key filter\n");
+ goto fail_filter;
+ }
+
+ return 0;
+
+fail_filter:
+ msi_laptop_input_destroy();
+
+fail_input:
+ rfkill_cleanup();
+
+fail_rfkill:
+
+ return result;
+
+}
+
+static int __init msi_init(void)
+{
+ int ret;
+
+ if (acpi_disabled)
+ return -ENODEV;
+
+ dmi_check_system(msi_dmi_table);
+ if (!quirks)
+ /* quirks may be NULL if no match in DMI table */
+ quirks = &quirk_load_scm_model;
+ if (force)
+ quirks = &quirk_old_ec_model;
+
+ if (!quirks->old_ec_model)
+ get_threeg_exists();
+
+ if (auto_brightness < 0 || auto_brightness > 2)
+ return -EINVAL;
+
+ /* Register backlight stuff */
+
+ if (!quirks->old_ec_model || acpi_video_backlight_support()) {
+ pr_info("Brightness ignored, must be controlled by ACPI video driver\n");
+ } else {
+ struct backlight_properties props;
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = MSI_LCD_LEVEL_MAX - 1;
+ msibl_device = backlight_device_register("msi-laptop-bl", NULL,
+ NULL, &msibl_ops,
+ &props);
+ if (IS_ERR(msibl_device))
+ return PTR_ERR(msibl_device);
+ }
+
+ ret = platform_driver_register(&msipf_driver);
+ if (ret)
+ goto fail_backlight;
+
+ /* Register platform stuff */
+
+ msipf_device = platform_device_alloc("msi-laptop-pf", -1);
+ if (!msipf_device) {
+ ret = -ENOMEM;
+ goto fail_platform_driver;
+ }
+
+ ret = platform_device_add(msipf_device);
+ if (ret)
+ goto fail_platform_device1;
+
+ if (quirks->load_scm_model && (load_scm_model_init(msipf_device) < 0)) {
+ ret = -EINVAL;
+ goto fail_platform_device1;
+ }
+
+ ret = sysfs_create_group(&msipf_device->dev.kobj,
+ &msipf_attribute_group);
+ if (ret)
+ goto fail_platform_device2;
+
+ if (!quirks->old_ec_model) {
+ if (threeg_exists)
+ ret = device_create_file(&msipf_device->dev,
+ &dev_attr_threeg);
+ if (ret)
+ goto fail_platform_device2;
+ } else {
+ ret = sysfs_create_group(&msipf_device->dev.kobj,
+ &msipf_old_attribute_group);
+ if (ret)
+ goto fail_platform_device2;
+
+ /* Disable automatic brightness control by default because
+ * this module was probably loaded to do brightness control in
+ * software. */
+
+ if (auto_brightness != 2)
+ set_auto_brightness(auto_brightness);
+ }
+
+ pr_info("driver " MSI_DRIVER_VERSION " successfully loaded\n");
+
+ return 0;
+
+fail_platform_device2:
+
+ if (quirks->load_scm_model) {
+ i8042_remove_filter(msi_laptop_i8042_filter);
+ cancel_delayed_work_sync(&msi_rfkill_dwork);
+ cancel_work_sync(&msi_rfkill_work);
+ rfkill_cleanup();
+ }
+ platform_device_del(msipf_device);
+
+fail_platform_device1:
+
+ platform_device_put(msipf_device);
+
+fail_platform_driver:
+
+ platform_driver_unregister(&msipf_driver);
+
+fail_backlight:
+
+ backlight_device_unregister(msibl_device);
+
+ return ret;
+}
+
+static void __exit msi_cleanup(void)
+{
+ if (quirks->load_scm_model) {
+ i8042_remove_filter(msi_laptop_i8042_filter);
+ msi_laptop_input_destroy();
+ cancel_delayed_work_sync(&msi_rfkill_dwork);
+ cancel_work_sync(&msi_rfkill_work);
+ rfkill_cleanup();
+ }
+
+ sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
+ if (!quirks->old_ec_model && threeg_exists)
+ device_remove_file(&msipf_device->dev, &dev_attr_threeg);
+ platform_device_unregister(msipf_device);
+ platform_driver_unregister(&msipf_driver);
+ backlight_device_unregister(msibl_device);
+
+ if (quirks->old_ec_model) {
+ /* Enable automatic brightness control again */
+ if (auto_brightness != 2)
+ set_auto_brightness(1);
+ }
+
+ pr_info("driver unloaded\n");
+}
+
+module_init(msi_init);
+module_exit(msi_cleanup);
+
+MODULE_AUTHOR("Lennart Poettering");
+MODULE_DESCRIPTION("MSI Laptop Support");
+MODULE_VERSION(MSI_DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
+MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N034:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N051:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnMS-N014:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnCR620:*");
+MODULE_ALIAS("dmi:*:svnMicro-StarInternational*:pnU270series:*");
+MODULE_ALIAS("dmi:*:svnMICRO-STARINTERNATIONAL*:pnU90/U100:*");
diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c
new file mode 100644
index 00000000..70222f26
--- /dev/null
+++ b/drivers/platform/x86/msi-wmi.c
@@ -0,0 +1,362 @@
+/*
+ * MSI WMI hotkeys
+ *
+ * Copyright (C) 2009 Novell <trenn@suse.de>
+ *
+ * Most stuff taken over from hp-wmi
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/acpi.h>
+#include <linux/backlight.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>");
+MODULE_DESCRIPTION("MSI laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+#define DRV_NAME "msi-wmi"
+
+#define MSIWMI_BIOS_GUID "551A1F84-FBDD-4125-91DB-3EA8F44F1D45"
+#define MSIWMI_MSI_EVENT_GUID "B6F3EEF2-3D2F-49DC-9DE3-85BCE18C62F2"
+#define MSIWMI_WIND_EVENT_GUID "5B3CC38A-40D9-7245-8AE6-1145B751BE3F"
+
+MODULE_ALIAS("wmi:" MSIWMI_BIOS_GUID);
+MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID);
+MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID);
+
+enum msi_scancodes {
+ /* Generic MSI keys (not present on MSI Wind) */
+ MSI_KEY_BRIGHTNESSUP = 0xD0,
+ MSI_KEY_BRIGHTNESSDOWN,
+ MSI_KEY_VOLUMEUP,
+ MSI_KEY_VOLUMEDOWN,
+ MSI_KEY_MUTE,
+ /* MSI Wind keys */
+ WIND_KEY_TOUCHPAD = 0x08, /* Fn+F3 touchpad toggle */
+ WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */
+ WIND_KEY_CAMERA, /* Fn+F6 webcam toggle */
+ WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */
+ WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */
+ WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */
+};
+static struct key_entry msi_wmi_keymap[] = {
+ { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} },
+ { KE_KEY, MSI_KEY_BRIGHTNESSDOWN, {KEY_BRIGHTNESSDOWN} },
+ { KE_KEY, MSI_KEY_VOLUMEUP, {KEY_VOLUMEUP} },
+ { KE_KEY, MSI_KEY_VOLUMEDOWN, {KEY_VOLUMEDOWN} },
+ { KE_KEY, MSI_KEY_MUTE, {KEY_MUTE} },
+
+ /* These keys work without WMI. Ignore them to avoid double keycodes */
+ { KE_IGNORE, WIND_KEY_TOUCHPAD, {KEY_TOUCHPAD_TOGGLE} },
+ { KE_IGNORE, WIND_KEY_BLUETOOTH, {KEY_BLUETOOTH} },
+ { KE_IGNORE, WIND_KEY_CAMERA, {KEY_CAMERA} },
+ { KE_IGNORE, WIND_KEY_WLAN, {KEY_WLAN} },
+
+ /* These are unknown WMI events found on MSI Wind */
+ { KE_IGNORE, 0x00 },
+ { KE_IGNORE, 0x62 },
+ { KE_IGNORE, 0x63 },
+
+ /* These are MSI Wind keys that should be handled via WMI */
+ { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} },
+ { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} },
+
+ { KE_END, 0 }
+};
+
+static ktime_t last_pressed;
+
+static const struct {
+ const char *guid;
+ bool quirk_last_pressed;
+} *event_wmi, event_wmis[] = {
+ { MSIWMI_MSI_EVENT_GUID, true },
+ { MSIWMI_WIND_EVENT_GUID, false },
+};
+
+static struct backlight_device *backlight;
+
+static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF };
+
+static struct input_dev *msi_wmi_input_dev;
+
+static int msi_wmi_query_block(int instance, int *ret)
+{
+ acpi_status status;
+ union acpi_object *obj;
+
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output);
+
+ obj = output.pointer;
+
+ if (!obj || obj->type != ACPI_TYPE_INTEGER) {
+ if (obj) {
+ pr_err("query block returned object "
+ "type: %d - buffer length:%d\n", obj->type,
+ obj->type == ACPI_TYPE_BUFFER ?
+ obj->buffer.length : 0);
+ }
+ kfree(obj);
+ return -EINVAL;
+ }
+ *ret = obj->integer.value;
+ kfree(obj);
+ return 0;
+}
+
+static int msi_wmi_set_block(int instance, int value)
+{
+ acpi_status status;
+
+ struct acpi_buffer input = { sizeof(int), &value };
+
+ pr_debug("Going to set block of instance: %d - value: %d\n",
+ instance, value);
+
+ status = wmi_set_block(MSIWMI_BIOS_GUID, instance, &input);
+
+ return ACPI_SUCCESS(status) ? 0 : 1;
+}
+
+static int bl_get(struct backlight_device *bd)
+{
+ int level, err, ret;
+
+ /* Instance 1 is "get backlight", cmp with DSDT */
+ err = msi_wmi_query_block(1, &ret);
+ if (err) {
+ pr_err("Could not query backlight: %d\n", err);
+ return -EINVAL;
+ }
+ pr_debug("Get: Query block returned: %d\n", ret);
+ for (level = 0; level < ARRAY_SIZE(backlight_map); level++) {
+ if (backlight_map[level] == ret) {
+ pr_debug("Current backlight level: 0x%X - index: %d\n",
+ backlight_map[level], level);
+ break;
+ }
+ }
+ if (level == ARRAY_SIZE(backlight_map)) {
+ pr_err("get: Invalid brightness value: 0x%X\n", ret);
+ return -EINVAL;
+ }
+ return level;
+}
+
+static int bl_set_status(struct backlight_device *bd)
+{
+ int bright = bd->props.brightness;
+ if (bright >= ARRAY_SIZE(backlight_map) || bright < 0)
+ return -EINVAL;
+
+ /* Instance 0 is "set backlight" */
+ return msi_wmi_set_block(0, backlight_map[bright]);
+}
+
+static const struct backlight_ops msi_backlight_ops = {
+ .get_brightness = bl_get,
+ .update_status = bl_set_status,
+};
+
+static void msi_wmi_notify(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ static struct key_entry *key;
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_info("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+
+ if (obj && obj->type == ACPI_TYPE_INTEGER) {
+ int eventcode = obj->integer.value;
+ pr_debug("Eventcode: 0x%x\n", eventcode);
+ key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,
+ eventcode);
+ if (!key) {
+ pr_info("Unknown key pressed - %x\n", eventcode);
+ goto msi_wmi_notify_exit;
+ }
+
+ if (event_wmi->quirk_last_pressed) {
+ ktime_t cur = ktime_get_real();
+ ktime_t diff = ktime_sub(cur, last_pressed);
+ /* Ignore event if any event happened in a 50 ms
+ timeframe -> Key press may result in 10-20 GPEs */
+ if (ktime_to_us(diff) < 1000 * 50) {
+ pr_debug("Suppressed key event 0x%X - "
+ "Last press was %lld us ago\n",
+ key->code, ktime_to_us(diff));
+ goto msi_wmi_notify_exit;
+ }
+ last_pressed = cur;
+ }
+
+ if (key->type == KE_KEY &&
+ /* Brightness is served via acpi video driver */
+ (backlight ||
+ (key->code != MSI_KEY_BRIGHTNESSUP &&
+ key->code != MSI_KEY_BRIGHTNESSDOWN))) {
+ pr_debug("Send key: 0x%X - Input layer keycode: %d\n",
+ key->code, key->keycode);
+ sparse_keymap_report_entry(msi_wmi_input_dev, key, 1,
+ true);
+ }
+ } else
+ pr_info("Unknown event received\n");
+
+msi_wmi_notify_exit:
+ kfree(response.pointer);
+}
+
+static int __init msi_wmi_backlight_setup(void)
+{
+ int err;
+ struct backlight_properties props;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = ARRAY_SIZE(backlight_map) - 1;
+ backlight = backlight_device_register(DRV_NAME, NULL, NULL,
+ &msi_backlight_ops,
+ &props);
+ if (IS_ERR(backlight))
+ return PTR_ERR(backlight);
+
+ err = bl_get(NULL);
+ if (err < 0) {
+ backlight_device_unregister(backlight);
+ return err;
+ }
+
+ backlight->props.brightness = err;
+
+ return 0;
+}
+
+static int __init msi_wmi_input_setup(void)
+{
+ int err;
+
+ msi_wmi_input_dev = input_allocate_device();
+ if (!msi_wmi_input_dev)
+ return -ENOMEM;
+
+ msi_wmi_input_dev->name = "MSI WMI hotkeys";
+ msi_wmi_input_dev->phys = "wmi/input0";
+ msi_wmi_input_dev->id.bustype = BUS_HOST;
+
+ err = sparse_keymap_setup(msi_wmi_input_dev, msi_wmi_keymap, NULL);
+ if (err)
+ goto err_free_dev;
+
+ err = input_register_device(msi_wmi_input_dev);
+
+ if (err)
+ goto err_free_keymap;
+
+ last_pressed = ktime_set(0, 0);
+
+ return 0;
+
+err_free_keymap:
+ sparse_keymap_free(msi_wmi_input_dev);
+err_free_dev:
+ input_free_device(msi_wmi_input_dev);
+ return err;
+}
+
+static int __init msi_wmi_init(void)
+{
+ int err;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(event_wmis); i++) {
+ if (!wmi_has_guid(event_wmis[i].guid))
+ continue;
+
+ err = msi_wmi_input_setup();
+ if (err) {
+ pr_err("Unable to setup input device\n");
+ return err;
+ }
+
+ err = wmi_install_notify_handler(event_wmis[i].guid,
+ msi_wmi_notify, NULL);
+ if (ACPI_FAILURE(err)) {
+ pr_err("Unable to setup WMI notify handler\n");
+ goto err_free_input;
+ }
+
+ pr_debug("Event handler installed\n");
+ event_wmi = &event_wmis[i];
+ break;
+ }
+
+ if (wmi_has_guid(MSIWMI_BIOS_GUID) && !acpi_video_backlight_support()) {
+ err = msi_wmi_backlight_setup();
+ if (err) {
+ pr_err("Unable to setup backlight device\n");
+ goto err_uninstall_handler;
+ }
+ pr_debug("Backlight device created\n");
+ }
+
+ if (!event_wmi && !backlight) {
+ pr_err("This machine doesn't have neither MSI-hotkeys nor backlight through WMI\n");
+ return -ENODEV;
+ }
+
+ return 0;
+
+err_uninstall_handler:
+ if (event_wmi)
+ wmi_remove_notify_handler(event_wmi->guid);
+err_free_input:
+ if (event_wmi) {
+ sparse_keymap_free(msi_wmi_input_dev);
+ input_unregister_device(msi_wmi_input_dev);
+ }
+ return err;
+}
+
+static void __exit msi_wmi_exit(void)
+{
+ if (event_wmi) {
+ wmi_remove_notify_handler(event_wmi->guid);
+ sparse_keymap_free(msi_wmi_input_dev);
+ input_unregister_device(msi_wmi_input_dev);
+ }
+ if (backlight)
+ backlight_device_unregister(backlight);
+}
+
+module_init(msi_wmi_init);
+module_exit(msi_wmi_exit);
diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c
new file mode 100644
index 00000000..0aea63b3
--- /dev/null
+++ b/drivers/platform/x86/mxm-wmi.c
@@ -0,0 +1,111 @@
+/*
+ * MXM WMI driver
+ *
+ * Copyright(C) 2010 Red Hat.
+ *
+ * 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/init.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Dave Airlie");
+MODULE_DESCRIPTION("MXM WMI Driver");
+MODULE_LICENSE("GPL");
+
+#define MXM_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0"
+
+MODULE_ALIAS("wmi:"MXM_WMMX_GUID);
+
+#define MXM_WMMX_FUNC_MXDS 0x5344584D /* "MXDS" */
+#define MXM_WMMX_FUNC_MXMX 0x53445344 /* "MXMX" */
+
+struct mxds_args {
+ u32 func;
+ u32 args;
+ u32 xarg;
+};
+
+int mxm_wmi_call_mxds(int adapter)
+{
+ struct mxds_args args = {
+ .func = MXM_WMMX_FUNC_MXDS,
+ .args = 0,
+ .xarg = 1,
+ };
+ struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ printk("calling mux switch %d\n", adapter);
+
+ status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input,
+ &output);
+
+ if (ACPI_FAILURE(status))
+ return status;
+
+ printk("mux switched %d\n", status);
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(mxm_wmi_call_mxds);
+
+int mxm_wmi_call_mxmx(int adapter)
+{
+ struct mxds_args args = {
+ .func = MXM_WMMX_FUNC_MXMX,
+ .args = 0,
+ .xarg = 1,
+ };
+ struct acpi_buffer input = { (acpi_size)sizeof(args), &args };
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ printk("calling mux switch %d\n", adapter);
+
+ status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input,
+ &output);
+
+ if (ACPI_FAILURE(status))
+ return status;
+
+ printk("mux mutex set switched %d\n", status);
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(mxm_wmi_call_mxmx);
+
+bool mxm_wmi_supported(void)
+{
+ bool guid_valid;
+ guid_valid = wmi_has_guid(MXM_WMMX_GUID);
+ return guid_valid;
+}
+EXPORT_SYMBOL_GPL(mxm_wmi_supported);
+
+static int __init mxm_wmi_init(void)
+{
+ return 0;
+}
+
+static void __exit mxm_wmi_exit(void)
+{
+}
+
+module_init(mxm_wmi_init);
+module_exit(mxm_wmi_exit);
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
new file mode 100644
index 00000000..4add9a31
--- /dev/null
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -0,0 +1,691 @@
+/*
+ * Panasonic HotKey and LCD brightness control driver
+ * (C) 2004 Hiroshi Miura <miura@da-cha.org>
+ * (C) 2004 NTT DATA Intellilink Co. http://www.intellilink.co.jp/
+ * (C) YOKOTA Hiroshi <yokota (at) netlab. is. tsukuba. ac. jp>
+ * (C) 2004 David Bronaugh <dbronaugh>
+ * (C) 2006-2008 Harald Welte <laforge@gnumonks.org>
+ *
+ * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte
+ *
+ * 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
+ * publicshed by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *---------------------------------------------------------------------------
+ *
+ * ChangeLog:
+ * Sep.23, 2008 Harald Welte <laforge@gnumonks.org>
+ * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to
+ * drivers/misc/panasonic-laptop.c
+ *
+ * Jul.04, 2008 Harald Welte <laforge@gnumonks.org>
+ * -v0.94 replace /proc interface with device attributes
+ * support {set,get}keycode on th input device
+ *
+ * Jun.27, 2008 Harald Welte <laforge@gnumonks.org>
+ * -v0.92 merge with 2.6.26-rc6 input API changes
+ * remove broken <= 2.6.15 kernel support
+ * resolve all compiler warnings
+ * various coding style fixes (checkpatch.pl)
+ * add support for backlight api
+ * major code restructuring
+ *
+ * Dac.28, 2007 Harald Welte <laforge@gnumonks.org>
+ * -v0.91 merge with 2.6.24-rc6 ACPI changes
+ *
+ * Nov.04, 2006 Hiroshi Miura <miura@da-cha.org>
+ * -v0.9 remove warning about section reference.
+ * remove acpi_os_free
+ * add /proc/acpi/pcc/brightness interface for HAL access
+ * merge dbronaugh's enhancement
+ * Aug.17, 2004 David Bronaugh (dbronaugh)
+ * - Added screen brightness setting interface
+ * Thanks to FreeBSD crew (acpi_panasonic.c)
+ * for the ideas I needed to accomplish it
+ *
+ * May.29, 2006 Hiroshi Miura <miura@da-cha.org>
+ * -v0.8.4 follow to change keyinput structure
+ * thanks Fabian Yamaguchi <fabs@cs.tu-berlin.de>,
+ * Jacob Bower <jacob.bower@ic.ac.uk> and
+ * Hiroshi Yokota for providing solutions.
+ *
+ * Oct.02, 2004 Hiroshi Miura <miura@da-cha.org>
+ * -v0.8.2 merge code of YOKOTA Hiroshi
+ * <yokota@netlab.is.tsukuba.ac.jp>.
+ * Add sticky key mode interface.
+ * Refactoring acpi_pcc_generate_keyinput().
+ *
+ * Sep.15, 2004 Hiroshi Miura <miura@da-cha.org>
+ * -v0.8 Generate key input event on input subsystem.
+ * This is based on yet another driver written by
+ * Ryuta Nakanishi.
+ *
+ * Sep.10, 2004 Hiroshi Miura <miura@da-cha.org>
+ * -v0.7 Change proc interface functions using seq_file
+ * facility as same as other ACPI drivers.
+ *
+ * Aug.28, 2004 Hiroshi Miura <miura@da-cha.org>
+ * -v0.6.4 Fix a silly error with status checking
+ *
+ * Aug.25, 2004 Hiroshi Miura <miura@da-cha.org>
+ * -v0.6.3 replace read_acpi_int by standard function
+ * acpi_evaluate_integer
+ * some clean up and make smart copyright notice.
+ * fix return value of pcc_acpi_get_key()
+ * fix checking return value of acpi_bus_register_driver()
+ *
+ * Aug.22, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
+ * -v0.6.2 Add check on ACPI data (num_sifr)
+ * Coding style cleanups, better error messages/handling
+ * Fixed an off-by-one error in memory allocation
+ *
+ * Aug.21, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
+ * -v0.6.1 Fix a silly error with status checking
+ *
+ * Aug.20, 2004 David Bronaugh <dbronaugh@linuxboxen.org>
+ * - v0.6 Correct brightness controls to reflect reality
+ * based on information gleaned by Hiroshi Miura
+ * and discussions with Hiroshi Miura
+ *
+ * Aug.10, 2004 Hiroshi Miura <miura@da-cha.org>
+ * - v0.5 support LCD brightness control
+ * based on the disclosed information by MEI.
+ *
+ * Jul.25, 2004 Hiroshi Miura <miura@da-cha.org>
+ * - v0.4 first post version
+ * add function to retrive SIFR
+ *
+ * Jul.24, 2004 Hiroshi Miura <miura@da-cha.org>
+ * - v0.3 get proper status of hotkey
+ *
+ * Jul.22, 2004 Hiroshi Miura <miura@da-cha.org>
+ * - v0.2 add HotKey handler
+ *
+ * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org>
+ * - v0.1 start from toshiba_acpi driver written by John Belmonte
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/backlight.h>
+#include <linux/ctype.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+
+
+#ifndef ACPI_HOTKEY_COMPONENT
+#define ACPI_HOTKEY_COMPONENT 0x10000000
+#endif
+
+#define _COMPONENT ACPI_HOTKEY_COMPONENT
+
+MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte");
+MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops");
+MODULE_LICENSE("GPL");
+
+#define LOGPREFIX "pcc_acpi: "
+
+/* Define ACPI PATHs */
+/* Lets note hotkeys */
+#define METHOD_HKEY_QUERY "HINF"
+#define METHOD_HKEY_SQTY "SQTY"
+#define METHOD_HKEY_SINF "SINF"
+#define METHOD_HKEY_SSET "SSET"
+#define HKEY_NOTIFY 0x80
+
+#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
+#define ACPI_PCC_DEVICE_NAME "Hotkey"
+#define ACPI_PCC_CLASS "pcc"
+
+#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
+
+/* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent
+ ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00
+*/
+enum SINF_BITS { SINF_NUM_BATTERIES = 0,
+ SINF_LCD_TYPE,
+ SINF_AC_MAX_BRIGHT,
+ SINF_AC_MIN_BRIGHT,
+ SINF_AC_CUR_BRIGHT,
+ SINF_DC_MAX_BRIGHT,
+ SINF_DC_MIN_BRIGHT,
+ SINF_DC_CUR_BRIGHT,
+ SINF_MUTE,
+ SINF_RESERVED,
+ SINF_ENV_STATE,
+ SINF_STICKY_KEY = 0x80,
+ };
+/* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
+
+static int acpi_pcc_hotkey_add(struct acpi_device *device);
+static int acpi_pcc_hotkey_remove(struct acpi_device *device);
+static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id pcc_device_ids[] = {
+ { "MAT0012", 0},
+ { "MAT0013", 0},
+ { "MAT0018", 0},
+ { "MAT0019", 0},
+ { "", 0},
+};
+MODULE_DEVICE_TABLE(acpi, pcc_device_ids);
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_pcc_hotkey_resume(struct device *dev);
+#endif
+static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume);
+
+static struct acpi_driver acpi_pcc_driver = {
+ .name = ACPI_PCC_DRIVER_NAME,
+ .class = ACPI_PCC_CLASS,
+ .ids = pcc_device_ids,
+ .ops = {
+ .add = acpi_pcc_hotkey_add,
+ .remove = acpi_pcc_hotkey_remove,
+ .notify = acpi_pcc_hotkey_notify,
+ },
+ .drv.pm = &acpi_pcc_hotkey_pm,
+};
+
+static const struct key_entry panasonic_keymap[] = {
+ { KE_KEY, 0, { KEY_RESERVED } },
+ { KE_KEY, 1, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, 2, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 3, { KEY_DISPLAYTOGGLE } },
+ { KE_KEY, 4, { KEY_MUTE } },
+ { KE_KEY, 5, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 6, { KEY_VOLUMEUP } },
+ { KE_KEY, 7, { KEY_SLEEP } },
+ { KE_KEY, 8, { KEY_PROG1 } }, /* Change CPU boost */
+ { KE_KEY, 9, { KEY_BATTERY } },
+ { KE_KEY, 10, { KEY_SUSPEND } },
+ { KE_END, 0 }
+};
+
+struct pcc_acpi {
+ acpi_handle handle;
+ unsigned long num_sifr;
+ int sticky_mode;
+ u32 *sinf;
+ struct acpi_device *device;
+ struct input_dev *input_dev;
+ struct backlight_device *backlight;
+};
+
+struct pcc_keyinput {
+ struct acpi_hotkey *hotkey;
+};
+
+/* method access functions */
+static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val)
+{
+ union acpi_object in_objs[] = {
+ { .integer.type = ACPI_TYPE_INTEGER,
+ .integer.value = func, },
+ { .integer.type = ACPI_TYPE_INTEGER,
+ .integer.value = val, },
+ };
+ struct acpi_object_list params = {
+ .count = ARRAY_SIZE(in_objs),
+ .pointer = in_objs,
+ };
+ acpi_status status = AE_OK;
+
+ status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET,
+ &params, NULL);
+
+ return (status == AE_OK) ? 0 : -EIO;
+}
+
+static inline int acpi_pcc_get_sqty(struct acpi_device *device)
+{
+ unsigned long long s;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, METHOD_HKEY_SQTY,
+ NULL, &s);
+ if (ACPI_SUCCESS(status))
+ return s;
+ else {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "evaluation error HKEY.SQTY\n"));
+ return -EINVAL;
+ }
+}
+
+static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
+{
+ acpi_status status;
+ struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+ union acpi_object *hkey = NULL;
+ int i;
+
+ status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, NULL,
+ &buffer);
+ if (ACPI_FAILURE(status)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "evaluation error HKEY.SINF\n"));
+ return 0;
+ }
+
+ hkey = buffer.pointer;
+ if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n"));
+ status = AE_ERROR;
+ goto end;
+ }
+
+ if (pcc->num_sifr < hkey->package.count) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "SQTY reports bad SINF length\n"));
+ status = AE_ERROR;
+ goto end;
+ }
+
+ for (i = 0; i < hkey->package.count; i++) {
+ union acpi_object *element = &(hkey->package.elements[i]);
+ if (likely(element->type == ACPI_TYPE_INTEGER)) {
+ pcc->sinf[i] = element->integer.value;
+ } else
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Invalid HKEY.SINF data\n"));
+ }
+ pcc->sinf[hkey->package.count] = -1;
+
+end:
+ kfree(buffer.pointer);
+ return status == AE_OK;
+}
+
+/* backlight API interface functions */
+
+/* This driver currently treats AC and DC brightness identical,
+ * since we don't need to invent an interface to the core ACPI
+ * logic to receive events in case a power supply is plugged in
+ * or removed */
+
+static int bl_get(struct backlight_device *bd)
+{
+ struct pcc_acpi *pcc = bl_get_data(bd);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return pcc->sinf[SINF_AC_CUR_BRIGHT];
+}
+
+static int bl_set_status(struct backlight_device *bd)
+{
+ struct pcc_acpi *pcc = bl_get_data(bd);
+ int bright = bd->props.brightness;
+ int rc;
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT])
+ bright = pcc->sinf[SINF_AC_MIN_BRIGHT];
+
+ if (bright < pcc->sinf[SINF_DC_MIN_BRIGHT])
+ bright = pcc->sinf[SINF_DC_MIN_BRIGHT];
+
+ if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT] ||
+ bright > pcc->sinf[SINF_AC_MAX_BRIGHT])
+ return -EINVAL;
+
+ rc = acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, bright);
+ if (rc < 0)
+ return rc;
+
+ return acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, bright);
+}
+
+static const struct backlight_ops pcc_backlight_ops = {
+ .get_brightness = bl_get,
+ .update_status = bl_set_status,
+};
+
+
+/* sysfs user interface functions */
+
+static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]);
+}
+
+static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_LCD_TYPE]);
+}
+
+static ssize_t show_mute(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_MUTE]);
+}
+
+static ssize_t show_sticky(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+
+ if (!acpi_pcc_retrieve_biosdata(pcc))
+ return -EIO;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", pcc->sinf[SINF_STICKY_KEY]);
+}
+
+static ssize_t set_sticky(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *acpi = to_acpi_device(dev);
+ struct pcc_acpi *pcc = acpi_driver_data(acpi);
+ int val;
+
+ if (count && sscanf(buf, "%i", &val) == 1 &&
+ (val == 0 || val == 1)) {
+ acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val);
+ pcc->sticky_mode = val;
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL);
+static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL);
+static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL);
+static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky);
+
+static struct attribute *pcc_sysfs_entries[] = {
+ &dev_attr_numbatt.attr,
+ &dev_attr_lcdtype.attr,
+ &dev_attr_mute.attr,
+ &dev_attr_sticky_key.attr,
+ NULL,
+};
+
+static struct attribute_group pcc_attr_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = pcc_sysfs_entries,
+};
+
+
+/* hotkey input device driver */
+
+static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
+{
+ struct input_dev *hotk_input_dev = pcc->input_dev;
+ int rc;
+ unsigned long long result;
+
+ rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY,
+ NULL, &result);
+ if (!ACPI_SUCCESS(rc)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "error getting hotkey status\n"));
+ return;
+ }
+
+ acpi_bus_generate_proc_event(pcc->device, HKEY_NOTIFY, result);
+
+ if (!sparse_keymap_report_event(hotk_input_dev,
+ result & 0xf, result & 0x80, false))
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Unknown hotkey event: %d\n", result));
+}
+
+static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
+{
+ struct pcc_acpi *pcc = acpi_driver_data(device);
+
+ switch (event) {
+ case HKEY_NOTIFY:
+ acpi_pcc_generate_keyinput(pcc);
+ break;
+ default:
+ /* nothing to do */
+ break;
+ }
+}
+
+static int acpi_pcc_init_input(struct pcc_acpi *pcc)
+{
+ struct input_dev *input_dev;
+ int error;
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Couldn't allocate input device for hotkey"));
+ return -ENOMEM;
+ }
+
+ input_dev->name = ACPI_PCC_DRIVER_NAME;
+ input_dev->phys = ACPI_PCC_INPUT_PHYS;
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->id.vendor = 0x0001;
+ input_dev->id.product = 0x0001;
+ input_dev->id.version = 0x0100;
+
+ error = sparse_keymap_setup(input_dev, panasonic_keymap, NULL);
+ if (error) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Unable to setup input device keymap\n"));
+ goto err_free_dev;
+ }
+
+ error = input_register_device(input_dev);
+ if (error) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Unable to register input device\n"));
+ goto err_free_keymap;
+ }
+
+ pcc->input_dev = input_dev;
+ return 0;
+
+ err_free_keymap:
+ sparse_keymap_free(input_dev);
+ err_free_dev:
+ input_free_device(input_dev);
+ return error;
+}
+
+static void acpi_pcc_destroy_input(struct pcc_acpi *pcc)
+{
+ sparse_keymap_free(pcc->input_dev);
+ input_unregister_device(pcc->input_dev);
+ /*
+ * No need to input_free_device() since core input API refcounts
+ * and free()s the device.
+ */
+}
+
+/* kernel module interface */
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_pcc_hotkey_resume(struct device *dev)
+{
+ struct pcc_acpi *pcc;
+
+ if (!dev)
+ return -EINVAL;
+
+ pcc = acpi_driver_data(to_acpi_device(dev));
+ if (!pcc)
+ return -EINVAL;
+
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n",
+ pcc->sticky_mode));
+
+ return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode);
+}
+#endif
+
+static int acpi_pcc_hotkey_add(struct acpi_device *device)
+{
+ struct backlight_properties props;
+ struct pcc_acpi *pcc;
+ int num_sifr, result;
+
+ if (!device)
+ return -EINVAL;
+
+ num_sifr = acpi_pcc_get_sqty(device);
+
+ if (num_sifr < 0 || num_sifr > 255) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr out of range"));
+ return -ENODEV;
+ }
+
+ pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
+ if (!pcc) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Couldn't allocate mem for pcc"));
+ return -ENOMEM;
+ }
+
+ pcc->sinf = kzalloc(sizeof(u32) * (num_sifr + 1), GFP_KERNEL);
+ if (!pcc->sinf) {
+ result = -ENOMEM;
+ goto out_hotkey;
+ }
+
+ pcc->device = device;
+ pcc->handle = device->handle;
+ pcc->num_sifr = num_sifr;
+ device->driver_data = pcc;
+ strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
+ strcpy(acpi_device_class(device), ACPI_PCC_CLASS);
+
+ result = acpi_pcc_init_input(pcc);
+ if (result) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Error installing keyinput handler\n"));
+ goto out_sinf;
+ }
+
+ if (!acpi_pcc_retrieve_biosdata(pcc)) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Couldn't retrieve BIOS data\n"));
+ result = -EIO;
+ goto out_input;
+ }
+ /* initialize backlight */
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT];
+ pcc->backlight = backlight_device_register("panasonic", NULL, pcc,
+ &pcc_backlight_ops, &props);
+ if (IS_ERR(pcc->backlight)) {
+ result = PTR_ERR(pcc->backlight);
+ goto out_input;
+ }
+
+ /* read the initial brightness setting from the hardware */
+ pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
+
+ /* read the initial sticky key mode from the hardware */
+ pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY];
+
+ /* add sysfs attributes */
+ result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
+ if (result)
+ goto out_backlight;
+
+ return 0;
+
+out_backlight:
+ backlight_device_unregister(pcc->backlight);
+out_input:
+ acpi_pcc_destroy_input(pcc);
+out_sinf:
+ kfree(pcc->sinf);
+out_hotkey:
+ kfree(pcc);
+
+ return result;
+}
+
+static int __init acpi_pcc_init(void)
+{
+ int result = 0;
+
+ if (acpi_disabled)
+ return -ENODEV;
+
+ result = acpi_bus_register_driver(&acpi_pcc_driver);
+ if (result < 0) {
+ ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+ "Error registering hotkey driver\n"));
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int acpi_pcc_hotkey_remove(struct acpi_device *device)
+{
+ struct pcc_acpi *pcc = acpi_driver_data(device);
+
+ if (!device || !pcc)
+ return -EINVAL;
+
+ sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
+
+ backlight_device_unregister(pcc->backlight);
+
+ acpi_pcc_destroy_input(pcc);
+
+ kfree(pcc->sinf);
+ kfree(pcc);
+
+ return 0;
+}
+
+static void __exit acpi_pcc_exit(void)
+{
+ acpi_bus_unregister_driver(&acpi_pcc_driver);
+}
+
+module_init(acpi_pcc_init);
+module_exit(acpi_pcc_exit);
diff --git a/drivers/platform/x86/pvpanic.c b/drivers/platform/x86/pvpanic.c
new file mode 100644
index 00000000..47ae0c47
--- /dev/null
+++ b/drivers/platform/x86/pvpanic.c
@@ -0,0 +1,124 @@
+/*
+ * pvpanic.c - pvpanic Device Support
+ *
+ * Copyright (C) 2013 Fujitsu.
+ *
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Hu Tao <hutao@cn.fujitsu.com>");
+MODULE_DESCRIPTION("pvpanic device driver");
+MODULE_LICENSE("GPL");
+
+static int pvpanic_add(struct acpi_device *device);
+static int pvpanic_remove(struct acpi_device *device);
+
+static const struct acpi_device_id pvpanic_device_ids[] = {
+ { "QEMU0001", 0 },
+ { "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, pvpanic_device_ids);
+
+#define PVPANIC_PANICKED (1 << 0)
+
+static u16 port;
+
+static struct acpi_driver pvpanic_driver = {
+ .name = "pvpanic",
+ .class = "QEMU",
+ .ids = pvpanic_device_ids,
+ .ops = {
+ .add = pvpanic_add,
+ .remove = pvpanic_remove,
+ },
+ .owner = THIS_MODULE,
+};
+
+static void
+pvpanic_send_event(unsigned int event)
+{
+ outb(event, port);
+}
+
+static int
+pvpanic_panic_notify(struct notifier_block *nb, unsigned long code,
+ void *unused)
+{
+ pvpanic_send_event(PVPANIC_PANICKED);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block pvpanic_panic_nb = {
+ .notifier_call = pvpanic_panic_notify,
+};
+
+
+static acpi_status
+pvpanic_walk_resources(struct acpi_resource *res, void *context)
+{
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_IO:
+ port = res->data.io.minimum;
+ return AE_OK;
+
+ default:
+ return AE_ERROR;
+ }
+}
+
+static int pvpanic_add(struct acpi_device *device)
+{
+ acpi_status status;
+ u64 ret;
+
+ status = acpi_evaluate_integer(device->handle, "_STA", NULL,
+ &ret);
+
+ if (ACPI_FAILURE(status) || (ret & 0x0B) != 0x0B)
+ return -ENODEV;
+
+ acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+ pvpanic_walk_resources, NULL);
+
+ if (!port)
+ return -ENODEV;
+
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &pvpanic_panic_nb);
+
+ return 0;
+}
+
+static int pvpanic_remove(struct acpi_device *device)
+{
+
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &pvpanic_panic_nb);
+ return 0;
+}
+
+module_acpi_driver(pvpanic_driver);
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
new file mode 100644
index 00000000..d1f03005
--- /dev/null
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -0,0 +1,1654 @@
+/*
+ * Samsung Laptop driver
+ *
+ * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de)
+ * Copyright (C) 2009,2011 Novell Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/fb.h>
+#include <linux/dmi.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/acpi.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/ctype.h>
+#include <linux/efi.h>
+#include <acpi/video.h>
+
+/*
+ * This driver is needed because a number of Samsung laptops do not hook
+ * their control settings through ACPI. So we have to poke around in the
+ * BIOS to do things like brightness values, and "special" key controls.
+ */
+
+/*
+ * We have 0 - 8 as valid brightness levels. The specs say that level 0 should
+ * be reserved by the BIOS (which really doesn't make much sense), we tell
+ * userspace that the value is 0 - 7 and then just tell the hardware 1 - 8
+ */
+#define MAX_BRIGHT 0x07
+
+
+#define SABI_IFACE_MAIN 0x00
+#define SABI_IFACE_SUB 0x02
+#define SABI_IFACE_COMPLETE 0x04
+#define SABI_IFACE_DATA 0x05
+
+#define WL_STATUS_WLAN 0x0
+#define WL_STATUS_BT 0x2
+
+/* Structure get/set data using sabi */
+struct sabi_data {
+ union {
+ struct {
+ u32 d0;
+ u32 d1;
+ u16 d2;
+ u8 d3;
+ };
+ u8 data[11];
+ };
+};
+
+struct sabi_header_offsets {
+ u8 port;
+ u8 re_mem;
+ u8 iface_func;
+ u8 en_mem;
+ u8 data_offset;
+ u8 data_segment;
+};
+
+struct sabi_commands {
+ /*
+ * Brightness is 0 - 8, as described above.
+ * Value 0 is for the BIOS to use
+ */
+ u16 get_brightness;
+ u16 set_brightness;
+
+ /*
+ * first byte:
+ * 0x00 - wireless is off
+ * 0x01 - wireless is on
+ * second byte:
+ * 0x02 - 3G is off
+ * 0x03 - 3G is on
+ * TODO, verify 3G is correct, that doesn't seem right...
+ */
+ u16 get_wireless_button;
+ u16 set_wireless_button;
+
+ /* 0 is off, 1 is on */
+ u16 get_backlight;
+ u16 set_backlight;
+
+ /*
+ * 0x80 or 0x00 - no action
+ * 0x81 - recovery key pressed
+ */
+ u16 get_recovery_mode;
+ u16 set_recovery_mode;
+
+ /*
+ * on seclinux: 0 is low, 1 is high,
+ * on swsmi: 0 is normal, 1 is silent, 2 is turbo
+ */
+ u16 get_performance_level;
+ u16 set_performance_level;
+
+ /* 0x80 is off, 0x81 is on */
+ u16 get_battery_life_extender;
+ u16 set_battery_life_extender;
+
+ /* 0x80 is off, 0x81 is on */
+ u16 get_usb_charge;
+ u16 set_usb_charge;
+
+ /* the first byte is for bluetooth and the third one is for wlan */
+ u16 get_wireless_status;
+ u16 set_wireless_status;
+
+ /* 0x81 to read, (0x82 | level << 8) to set, 0xaabb to enable */
+ u16 kbd_backlight;
+
+ /*
+ * Tell the BIOS that Linux is running on this machine.
+ * 81 is on, 80 is off
+ */
+ u16 set_linux;
+};
+
+struct sabi_performance_level {
+ const char *name;
+ u16 value;
+};
+
+struct sabi_config {
+ int sabi_version;
+ const char *test_string;
+ u16 main_function;
+ const struct sabi_header_offsets header_offsets;
+ const struct sabi_commands commands;
+ const struct sabi_performance_level performance_levels[4];
+ u8 min_brightness;
+ u8 max_brightness;
+};
+
+static const struct sabi_config sabi_configs[] = {
+ {
+ /* I don't know if it is really 2, but it it is
+ * less than 3 anyway */
+ .sabi_version = 2,
+
+ .test_string = "SECLINUX",
+
+ .main_function = 0x4c49,
+
+ .header_offsets = {
+ .port = 0x00,
+ .re_mem = 0x02,
+ .iface_func = 0x03,
+ .en_mem = 0x04,
+ .data_offset = 0x05,
+ .data_segment = 0x07,
+ },
+
+ .commands = {
+ .get_brightness = 0x00,
+ .set_brightness = 0x01,
+
+ .get_wireless_button = 0x02,
+ .set_wireless_button = 0x03,
+
+ .get_backlight = 0x04,
+ .set_backlight = 0x05,
+
+ .get_recovery_mode = 0x06,
+ .set_recovery_mode = 0x07,
+
+ .get_performance_level = 0x08,
+ .set_performance_level = 0x09,
+
+ .get_battery_life_extender = 0xFFFF,
+ .set_battery_life_extender = 0xFFFF,
+
+ .get_usb_charge = 0xFFFF,
+ .set_usb_charge = 0xFFFF,
+
+ .get_wireless_status = 0xFFFF,
+ .set_wireless_status = 0xFFFF,
+
+ .kbd_backlight = 0xFFFF,
+
+ .set_linux = 0x0a,
+ },
+
+ .performance_levels = {
+ {
+ .name = "silent",
+ .value = 0,
+ },
+ {
+ .name = "normal",
+ .value = 1,
+ },
+ { },
+ },
+ .min_brightness = 1,
+ .max_brightness = 8,
+ },
+ {
+ .sabi_version = 3,
+
+ .test_string = "SwSmi@",
+
+ .main_function = 0x5843,
+
+ .header_offsets = {
+ .port = 0x00,
+ .re_mem = 0x04,
+ .iface_func = 0x02,
+ .en_mem = 0x03,
+ .data_offset = 0x05,
+ .data_segment = 0x07,
+ },
+
+ .commands = {
+ .get_brightness = 0x10,
+ .set_brightness = 0x11,
+
+ .get_wireless_button = 0x12,
+ .set_wireless_button = 0x13,
+
+ .get_backlight = 0x2d,
+ .set_backlight = 0x2e,
+
+ .get_recovery_mode = 0xff,
+ .set_recovery_mode = 0xff,
+
+ .get_performance_level = 0x31,
+ .set_performance_level = 0x32,
+
+ .get_battery_life_extender = 0x65,
+ .set_battery_life_extender = 0x66,
+
+ .get_usb_charge = 0x67,
+ .set_usb_charge = 0x68,
+
+ .get_wireless_status = 0x69,
+ .set_wireless_status = 0x6a,
+
+ .kbd_backlight = 0x78,
+
+ .set_linux = 0xff,
+ },
+
+ .performance_levels = {
+ {
+ .name = "normal",
+ .value = 0,
+ },
+ {
+ .name = "silent",
+ .value = 1,
+ },
+ {
+ .name = "overclock",
+ .value = 2,
+ },
+ { },
+ },
+ .min_brightness = 0,
+ .max_brightness = 8,
+ },
+ { },
+};
+
+/*
+ * samsung-laptop/ - debugfs root directory
+ * f0000_segment - dump f0000 segment
+ * command - current command
+ * data - current data
+ * d0, d1, d2, d3 - data fields
+ * call - call SABI using command and data
+ *
+ * This allow to call arbitrary sabi commands wihout
+ * modifying the driver at all.
+ * For example, setting the keyboard backlight brightness to 5
+ *
+ * echo 0x78 > command
+ * echo 0x0582 > d0
+ * echo 0 > d1
+ * echo 0 > d2
+ * echo 0 > d3
+ * cat call
+ */
+
+struct samsung_laptop_debug {
+ struct dentry *root;
+ struct sabi_data data;
+ u16 command;
+
+ struct debugfs_blob_wrapper f0000_wrapper;
+ struct debugfs_blob_wrapper data_wrapper;
+ struct debugfs_blob_wrapper sdiag_wrapper;
+};
+
+struct samsung_laptop;
+
+struct samsung_rfkill {
+ struct samsung_laptop *samsung;
+ struct rfkill *rfkill;
+ enum rfkill_type type;
+};
+
+struct samsung_laptop {
+ const struct sabi_config *config;
+
+ void __iomem *sabi;
+ void __iomem *sabi_iface;
+ void __iomem *f0000_segment;
+
+ struct mutex sabi_mutex;
+
+ struct platform_device *platform_device;
+ struct backlight_device *backlight_device;
+
+ struct samsung_rfkill wlan;
+ struct samsung_rfkill bluetooth;
+
+ struct led_classdev kbd_led;
+ int kbd_led_wk;
+ struct workqueue_struct *led_workqueue;
+ struct work_struct kbd_led_work;
+
+ struct samsung_laptop_debug debug;
+ struct samsung_quirks *quirks;
+
+ bool handle_backlight;
+ bool has_stepping_quirk;
+
+ char sdiag[64];
+};
+
+struct samsung_quirks {
+ bool broken_acpi_video;
+};
+
+static struct samsung_quirks samsung_unknown = {};
+
+static struct samsung_quirks samsung_broken_acpi_video = {
+ .broken_acpi_video = true,
+};
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force,
+ "Disable the DMI check and forces the driver to be loaded");
+
+static bool debug;
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
+static int sabi_command(struct samsung_laptop *samsung, u16 command,
+ struct sabi_data *in,
+ struct sabi_data *out)
+{
+ const struct sabi_config *config = samsung->config;
+ int ret = 0;
+ u16 port = readw(samsung->sabi + config->header_offsets.port);
+ u8 complete, iface_data;
+
+ mutex_lock(&samsung->sabi_mutex);
+
+ if (debug) {
+ if (in)
+ pr_info("SABI command:0x%04x "
+ "data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
+ command, in->d0, in->d1, in->d2, in->d3);
+ else
+ pr_info("SABI command:0x%04x", command);
+ }
+
+ /* enable memory to be able to write to it */
+ outb(readb(samsung->sabi + config->header_offsets.en_mem), port);
+
+ /* write out the command */
+ writew(config->main_function, samsung->sabi_iface + SABI_IFACE_MAIN);
+ writew(command, samsung->sabi_iface + SABI_IFACE_SUB);
+ writeb(0, samsung->sabi_iface + SABI_IFACE_COMPLETE);
+ if (in) {
+ writel(in->d0, samsung->sabi_iface + SABI_IFACE_DATA);
+ writel(in->d1, samsung->sabi_iface + SABI_IFACE_DATA + 4);
+ writew(in->d2, samsung->sabi_iface + SABI_IFACE_DATA + 8);
+ writeb(in->d3, samsung->sabi_iface + SABI_IFACE_DATA + 10);
+ }
+ outb(readb(samsung->sabi + config->header_offsets.iface_func), port);
+
+ /* write protect memory to make it safe */
+ outb(readb(samsung->sabi + config->header_offsets.re_mem), port);
+
+ /* see if the command actually succeeded */
+ complete = readb(samsung->sabi_iface + SABI_IFACE_COMPLETE);
+ iface_data = readb(samsung->sabi_iface + SABI_IFACE_DATA);
+
+ /* iface_data = 0xFF happens when a command is not known
+ * so we only add a warning in debug mode since we will
+ * probably issue some unknown command at startup to find
+ * out which features are supported */
+ if (complete != 0xaa || (iface_data == 0xff && debug))
+ pr_warn("SABI command 0x%04x failed with"
+ " completion flag 0x%02x and interface data 0x%02x",
+ command, complete, iface_data);
+
+ if (complete != 0xaa || iface_data == 0xff) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ if (out) {
+ out->d0 = readl(samsung->sabi_iface + SABI_IFACE_DATA);
+ out->d1 = readl(samsung->sabi_iface + SABI_IFACE_DATA + 4);
+ out->d2 = readw(samsung->sabi_iface + SABI_IFACE_DATA + 2);
+ out->d3 = readb(samsung->sabi_iface + SABI_IFACE_DATA + 1);
+ }
+
+ if (debug && out) {
+ pr_info("SABI return data:{0x%08x, 0x%08x, 0x%04x, 0x%02x}",
+ out->d0, out->d1, out->d2, out->d3);
+ }
+
+exit:
+ mutex_unlock(&samsung->sabi_mutex);
+ return ret;
+}
+
+/* simple wrappers usable with most commands */
+static int sabi_set_commandb(struct samsung_laptop *samsung,
+ u16 command, u8 data)
+{
+ struct sabi_data in = { { { .d0 = 0, .d1 = 0, .d2 = 0, .d3 = 0 } } };
+
+ in.data[0] = data;
+ return sabi_command(samsung, command, &in, NULL);
+}
+
+static int read_brightness(struct samsung_laptop *samsung)
+{
+ const struct sabi_config *config = samsung->config;
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data sretval;
+ int user_brightness = 0;
+ int retval;
+
+ retval = sabi_command(samsung, commands->get_brightness,
+ NULL, &sretval);
+ if (retval)
+ return retval;
+
+ user_brightness = sretval.data[0];
+ if (user_brightness > config->min_brightness)
+ user_brightness -= config->min_brightness;
+ else
+ user_brightness = 0;
+
+ return user_brightness;
+}
+
+static void set_brightness(struct samsung_laptop *samsung, u8 user_brightness)
+{
+ const struct sabi_config *config = samsung->config;
+ const struct sabi_commands *commands = &samsung->config->commands;
+ u8 user_level = user_brightness + config->min_brightness;
+
+ if (samsung->has_stepping_quirk && user_level != 0) {
+ /*
+ * short circuit if the specified level is what's already set
+ * to prevent the screen from flickering needlessly
+ */
+ if (user_brightness == read_brightness(samsung))
+ return;
+
+ sabi_set_commandb(samsung, commands->set_brightness, 0);
+ }
+
+ sabi_set_commandb(samsung, commands->set_brightness, user_level);
+}
+
+static int get_brightness(struct backlight_device *bd)
+{
+ struct samsung_laptop *samsung = bl_get_data(bd);
+
+ return read_brightness(samsung);
+}
+
+static void check_for_stepping_quirk(struct samsung_laptop *samsung)
+{
+ int initial_level;
+ int check_level;
+ int orig_level = read_brightness(samsung);
+
+ /*
+ * Some laptops exhibit the strange behaviour of stepping toward
+ * (rather than setting) the brightness except when changing to/from
+ * brightness level 0. This behaviour is checked for here and worked
+ * around in set_brightness.
+ */
+
+ if (orig_level == 0)
+ set_brightness(samsung, 1);
+
+ initial_level = read_brightness(samsung);
+
+ if (initial_level <= 2)
+ check_level = initial_level + 2;
+ else
+ check_level = initial_level - 2;
+
+ samsung->has_stepping_quirk = false;
+ set_brightness(samsung, check_level);
+
+ if (read_brightness(samsung) != check_level) {
+ samsung->has_stepping_quirk = true;
+ pr_info("enabled workaround for brightness stepping quirk\n");
+ }
+
+ set_brightness(samsung, orig_level);
+}
+
+static int update_status(struct backlight_device *bd)
+{
+ struct samsung_laptop *samsung = bl_get_data(bd);
+ const struct sabi_commands *commands = &samsung->config->commands;
+
+ set_brightness(samsung, bd->props.brightness);
+
+ if (bd->props.power == FB_BLANK_UNBLANK)
+ sabi_set_commandb(samsung, commands->set_backlight, 1);
+ else
+ sabi_set_commandb(samsung, commands->set_backlight, 0);
+
+ return 0;
+}
+
+static const struct backlight_ops backlight_ops = {
+ .get_brightness = get_brightness,
+ .update_status = update_status,
+};
+
+static int seclinux_rfkill_set(void *data, bool blocked)
+{
+ struct samsung_rfkill *srfkill = data;
+ struct samsung_laptop *samsung = srfkill->samsung;
+ const struct sabi_commands *commands = &samsung->config->commands;
+
+ return sabi_set_commandb(samsung, commands->set_wireless_button,
+ !blocked);
+}
+
+static struct rfkill_ops seclinux_rfkill_ops = {
+ .set_block = seclinux_rfkill_set,
+};
+
+static int swsmi_wireless_status(struct samsung_laptop *samsung,
+ struct sabi_data *data)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+
+ return sabi_command(samsung, commands->get_wireless_status,
+ NULL, data);
+}
+
+static int swsmi_rfkill_set(void *priv, bool blocked)
+{
+ struct samsung_rfkill *srfkill = priv;
+ struct samsung_laptop *samsung = srfkill->samsung;
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int ret, i;
+
+ ret = swsmi_wireless_status(samsung, &data);
+ if (ret)
+ return ret;
+
+ /* Don't set the state for non-present devices */
+ for (i = 0; i < 4; i++)
+ if (data.data[i] == 0x02)
+ data.data[1] = 0;
+
+ if (srfkill->type == RFKILL_TYPE_WLAN)
+ data.data[WL_STATUS_WLAN] = !blocked;
+ else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
+ data.data[WL_STATUS_BT] = !blocked;
+
+ return sabi_command(samsung, commands->set_wireless_status,
+ &data, &data);
+}
+
+static void swsmi_rfkill_query(struct rfkill *rfkill, void *priv)
+{
+ struct samsung_rfkill *srfkill = priv;
+ struct samsung_laptop *samsung = srfkill->samsung;
+ struct sabi_data data;
+ int ret;
+
+ ret = swsmi_wireless_status(samsung, &data);
+ if (ret)
+ return ;
+
+ if (srfkill->type == RFKILL_TYPE_WLAN)
+ ret = data.data[WL_STATUS_WLAN];
+ else if (srfkill->type == RFKILL_TYPE_BLUETOOTH)
+ ret = data.data[WL_STATUS_BT];
+ else
+ return ;
+
+ rfkill_set_sw_state(rfkill, !ret);
+}
+
+static struct rfkill_ops swsmi_rfkill_ops = {
+ .set_block = swsmi_rfkill_set,
+ .query = swsmi_rfkill_query,
+};
+
+static ssize_t get_performance_level(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ const struct sabi_config *config = samsung->config;
+ const struct sabi_commands *commands = &config->commands;
+ struct sabi_data sretval;
+ int retval;
+ int i;
+
+ /* Read the state */
+ retval = sabi_command(samsung, commands->get_performance_level,
+ NULL, &sretval);
+ if (retval)
+ return retval;
+
+ /* The logic is backwards, yeah, lots of fun... */
+ for (i = 0; config->performance_levels[i].name; ++i) {
+ if (sretval.data[0] == config->performance_levels[i].value)
+ return sprintf(buf, "%s\n", config->performance_levels[i].name);
+ }
+ return sprintf(buf, "%s\n", "unknown");
+}
+
+static ssize_t set_performance_level(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ const struct sabi_config *config = samsung->config;
+ const struct sabi_commands *commands = &config->commands;
+ int i;
+
+ if (count < 1)
+ return count;
+
+ for (i = 0; config->performance_levels[i].name; ++i) {
+ const struct sabi_performance_level *level =
+ &config->performance_levels[i];
+ if (!strncasecmp(level->name, buf, strlen(level->name))) {
+ sabi_set_commandb(samsung,
+ commands->set_performance_level,
+ level->value);
+ break;
+ }
+ }
+
+ if (!config->performance_levels[i].name)
+ return -EINVAL;
+
+ return count;
+}
+
+static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
+ get_performance_level, set_performance_level);
+
+static int read_battery_life_extender(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int retval;
+
+ if (commands->get_battery_life_extender == 0xFFFF)
+ return -ENODEV;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80;
+ retval = sabi_command(samsung, commands->get_battery_life_extender,
+ &data, &data);
+
+ if (retval)
+ return retval;
+
+ if (data.data[0] != 0 && data.data[0] != 1)
+ return -ENODEV;
+
+ return data.data[0];
+}
+
+static int write_battery_life_extender(struct samsung_laptop *samsung,
+ int enabled)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80 | enabled;
+ return sabi_command(samsung, commands->set_battery_life_extender,
+ &data, NULL);
+}
+
+static ssize_t get_battery_life_extender(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret;
+
+ ret = read_battery_life_extender(samsung);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t set_battery_life_extender(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret, value;
+
+ if (!count || sscanf(buf, "%i", &value) != 1)
+ return -EINVAL;
+
+ ret = write_battery_life_extender(samsung, !!value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
+ get_battery_life_extender, set_battery_life_extender);
+
+static int read_usb_charge(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int retval;
+
+ if (commands->get_usb_charge == 0xFFFF)
+ return -ENODEV;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80;
+ retval = sabi_command(samsung, commands->get_usb_charge,
+ &data, &data);
+
+ if (retval)
+ return retval;
+
+ if (data.data[0] != 0 && data.data[0] != 1)
+ return -ENODEV;
+
+ return data.data[0];
+}
+
+static int write_usb_charge(struct samsung_laptop *samsung,
+ int enabled)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x80 | enabled;
+ return sabi_command(samsung, commands->set_usb_charge,
+ &data, NULL);
+}
+
+static ssize_t get_usb_charge(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret;
+
+ ret = read_usb_charge(samsung);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t set_usb_charge(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct samsung_laptop *samsung = dev_get_drvdata(dev);
+ int ret, value;
+
+ if (!count || sscanf(buf, "%i", &value) != 1)
+ return -EINVAL;
+
+ ret = write_usb_charge(samsung, !!value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
+ get_usb_charge, set_usb_charge);
+
+static struct attribute *platform_attributes[] = {
+ &dev_attr_performance_level.attr,
+ &dev_attr_battery_life_extender.attr,
+ &dev_attr_usb_charge.attr,
+ NULL
+};
+
+static int find_signature(void __iomem *memcheck, const char *testStr)
+{
+ int i = 0;
+ int loca;
+
+ for (loca = 0; loca < 0xffff; loca++) {
+ char temp = readb(memcheck + loca);
+
+ if (temp == testStr[i]) {
+ if (i == strlen(testStr)-1)
+ break;
+ ++i;
+ } else {
+ i = 0;
+ }
+ }
+ return loca;
+}
+
+static void samsung_rfkill_exit(struct samsung_laptop *samsung)
+{
+ if (samsung->wlan.rfkill) {
+ rfkill_unregister(samsung->wlan.rfkill);
+ rfkill_destroy(samsung->wlan.rfkill);
+ samsung->wlan.rfkill = NULL;
+ }
+ if (samsung->bluetooth.rfkill) {
+ rfkill_unregister(samsung->bluetooth.rfkill);
+ rfkill_destroy(samsung->bluetooth.rfkill);
+ samsung->bluetooth.rfkill = NULL;
+ }
+}
+
+static int samsung_new_rfkill(struct samsung_laptop *samsung,
+ struct samsung_rfkill *arfkill,
+ const char *name, enum rfkill_type type,
+ const struct rfkill_ops *ops,
+ int blocked)
+{
+ struct rfkill **rfkill = &arfkill->rfkill;
+ int ret;
+
+ arfkill->type = type;
+ arfkill->samsung = samsung;
+
+ *rfkill = rfkill_alloc(name, &samsung->platform_device->dev,
+ type, ops, arfkill);
+
+ if (!*rfkill)
+ return -EINVAL;
+
+ if (blocked != -1)
+ rfkill_init_sw_state(*rfkill, blocked);
+
+ ret = rfkill_register(*rfkill);
+ if (ret) {
+ rfkill_destroy(*rfkill);
+ *rfkill = NULL;
+ return ret;
+ }
+ return 0;
+}
+
+static int __init samsung_rfkill_init_seclinux(struct samsung_laptop *samsung)
+{
+ return samsung_new_rfkill(samsung, &samsung->wlan, "samsung-wlan",
+ RFKILL_TYPE_WLAN, &seclinux_rfkill_ops, -1);
+}
+
+static int __init samsung_rfkill_init_swsmi(struct samsung_laptop *samsung)
+{
+ struct sabi_data data;
+ int ret;
+
+ ret = swsmi_wireless_status(samsung, &data);
+ if (ret) {
+ /* Some swsmi laptops use the old seclinux way to control
+ * wireless devices */
+ if (ret == -EINVAL)
+ ret = samsung_rfkill_init_seclinux(samsung);
+ return ret;
+ }
+
+ /* 0x02 seems to mean that the device is no present/available */
+
+ if (data.data[WL_STATUS_WLAN] != 0x02)
+ ret = samsung_new_rfkill(samsung, &samsung->wlan,
+ "samsung-wlan",
+ RFKILL_TYPE_WLAN,
+ &swsmi_rfkill_ops,
+ !data.data[WL_STATUS_WLAN]);
+ if (ret)
+ goto exit;
+
+ if (data.data[WL_STATUS_BT] != 0x02)
+ ret = samsung_new_rfkill(samsung, &samsung->bluetooth,
+ "samsung-bluetooth",
+ RFKILL_TYPE_BLUETOOTH,
+ &swsmi_rfkill_ops,
+ !data.data[WL_STATUS_BT]);
+ if (ret)
+ goto exit;
+
+exit:
+ if (ret)
+ samsung_rfkill_exit(samsung);
+
+ return ret;
+}
+
+static int __init samsung_rfkill_init(struct samsung_laptop *samsung)
+{
+ if (samsung->config->sabi_version == 2)
+ return samsung_rfkill_init_seclinux(samsung);
+ if (samsung->config->sabi_version == 3)
+ return samsung_rfkill_init_swsmi(samsung);
+ return 0;
+}
+
+static int kbd_backlight_enable(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int retval;
+
+ if (commands->kbd_backlight == 0xFFFF)
+ return -ENODEV;
+
+ memset(&data, 0, sizeof(data));
+ data.d0 = 0xaabb;
+ retval = sabi_command(samsung, commands->kbd_backlight,
+ &data, &data);
+
+ if (retval)
+ return retval;
+
+ if (data.d0 != 0xccdd)
+ return -ENODEV;
+ return 0;
+}
+
+static int kbd_backlight_read(struct samsung_laptop *samsung)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+ int retval;
+
+ memset(&data, 0, sizeof(data));
+ data.data[0] = 0x81;
+ retval = sabi_command(samsung, commands->kbd_backlight,
+ &data, &data);
+
+ if (retval)
+ return retval;
+
+ return data.data[0];
+}
+
+static int kbd_backlight_write(struct samsung_laptop *samsung, int brightness)
+{
+ const struct sabi_commands *commands = &samsung->config->commands;
+ struct sabi_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.d0 = 0x82 | ((brightness & 0xFF) << 8);
+ return sabi_command(samsung, commands->kbd_backlight,
+ &data, NULL);
+}
+
+static void kbd_led_update(struct work_struct *work)
+{
+ struct samsung_laptop *samsung;
+
+ samsung = container_of(work, struct samsung_laptop, kbd_led_work);
+ kbd_backlight_write(samsung, samsung->kbd_led_wk);
+}
+
+static void kbd_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct samsung_laptop *samsung;
+
+ samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
+
+ if (value > samsung->kbd_led.max_brightness)
+ value = samsung->kbd_led.max_brightness;
+ else if (value < 0)
+ value = 0;
+
+ samsung->kbd_led_wk = value;
+ queue_work(samsung->led_workqueue, &samsung->kbd_led_work);
+}
+
+static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
+{
+ struct samsung_laptop *samsung;
+
+ samsung = container_of(led_cdev, struct samsung_laptop, kbd_led);
+ return kbd_backlight_read(samsung);
+}
+
+static void samsung_leds_exit(struct samsung_laptop *samsung)
+{
+ if (!IS_ERR_OR_NULL(samsung->kbd_led.dev))
+ led_classdev_unregister(&samsung->kbd_led);
+ if (samsung->led_workqueue)
+ destroy_workqueue(samsung->led_workqueue);
+}
+
+static int __init samsung_leds_init(struct samsung_laptop *samsung)
+{
+ int ret = 0;
+
+ samsung->led_workqueue = create_singlethread_workqueue("led_workqueue");
+ if (!samsung->led_workqueue)
+ return -ENOMEM;
+
+ if (kbd_backlight_enable(samsung) >= 0) {
+ INIT_WORK(&samsung->kbd_led_work, kbd_led_update);
+
+ samsung->kbd_led.name = "samsung::kbd_backlight";
+ samsung->kbd_led.brightness_set = kbd_led_set;
+ samsung->kbd_led.brightness_get = kbd_led_get;
+ samsung->kbd_led.max_brightness = 8;
+
+ ret = led_classdev_register(&samsung->platform_device->dev,
+ &samsung->kbd_led);
+ }
+
+ if (ret)
+ samsung_leds_exit(samsung);
+
+ return ret;
+}
+
+static void samsung_backlight_exit(struct samsung_laptop *samsung)
+{
+ if (samsung->backlight_device) {
+ backlight_device_unregister(samsung->backlight_device);
+ samsung->backlight_device = NULL;
+ }
+}
+
+static int __init samsung_backlight_init(struct samsung_laptop *samsung)
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
+
+ if (!samsung->handle_backlight)
+ return 0;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = samsung->config->max_brightness -
+ samsung->config->min_brightness;
+
+ bd = backlight_device_register("samsung",
+ &samsung->platform_device->dev,
+ samsung, &backlight_ops,
+ &props);
+ if (IS_ERR(bd))
+ return PTR_ERR(bd);
+
+ samsung->backlight_device = bd;
+ samsung->backlight_device->props.brightness = read_brightness(samsung);
+ samsung->backlight_device->props.power = FB_BLANK_UNBLANK;
+ backlight_update_status(samsung->backlight_device);
+
+ return 0;
+}
+
+static umode_t samsung_sysfs_is_visible(struct kobject *kobj,
+ struct attribute *attr, int idx)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct platform_device *pdev = to_platform_device(dev);
+ struct samsung_laptop *samsung = platform_get_drvdata(pdev);
+ bool ok = true;
+
+ if (attr == &dev_attr_performance_level.attr)
+ ok = !!samsung->config->performance_levels[0].name;
+ if (attr == &dev_attr_battery_life_extender.attr)
+ ok = !!(read_battery_life_extender(samsung) >= 0);
+ if (attr == &dev_attr_usb_charge.attr)
+ ok = !!(read_usb_charge(samsung) >= 0);
+
+ return ok ? attr->mode : 0;
+}
+
+static struct attribute_group platform_attribute_group = {
+ .is_visible = samsung_sysfs_is_visible,
+ .attrs = platform_attributes
+};
+
+static void samsung_sysfs_exit(struct samsung_laptop *samsung)
+{
+ struct platform_device *device = samsung->platform_device;
+
+ sysfs_remove_group(&device->dev.kobj, &platform_attribute_group);
+}
+
+static int __init samsung_sysfs_init(struct samsung_laptop *samsung)
+{
+ struct platform_device *device = samsung->platform_device;
+
+ return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
+
+}
+
+static int show_call(struct seq_file *m, void *data)
+{
+ struct samsung_laptop *samsung = m->private;
+ struct sabi_data *sdata = &samsung->debug.data;
+ int ret;
+
+ seq_printf(m, "SABI 0x%04x {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
+ samsung->debug.command,
+ sdata->d0, sdata->d1, sdata->d2, sdata->d3);
+
+ ret = sabi_command(samsung, samsung->debug.command, sdata, sdata);
+
+ if (ret) {
+ seq_printf(m, "SABI command 0x%04x failed\n",
+ samsung->debug.command);
+ return ret;
+ }
+
+ seq_printf(m, "SABI {0x%08x, 0x%08x, 0x%04x, 0x%02x}\n",
+ sdata->d0, sdata->d1, sdata->d2, sdata->d3);
+ return 0;
+}
+
+static int samsung_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, show_call, inode->i_private);
+}
+
+static const struct file_operations samsung_laptop_call_io_ops = {
+ .owner = THIS_MODULE,
+ .open = samsung_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void samsung_debugfs_exit(struct samsung_laptop *samsung)
+{
+ debugfs_remove_recursive(samsung->debug.root);
+}
+
+static int samsung_debugfs_init(struct samsung_laptop *samsung)
+{
+ struct dentry *dent;
+
+ samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
+ if (!samsung->debug.root) {
+ pr_err("failed to create debugfs directory");
+ goto error_debugfs;
+ }
+
+ samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
+ samsung->debug.f0000_wrapper.size = 0xffff;
+
+ samsung->debug.data_wrapper.data = &samsung->debug.data;
+ samsung->debug.data_wrapper.size = sizeof(samsung->debug.data);
+
+ samsung->debug.sdiag_wrapper.data = samsung->sdiag;
+ samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
+
+ dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
+ samsung->debug.root, &samsung->debug.command);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d0);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d1);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d2);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
+ &samsung->debug.data.d3);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
+ samsung->debug.root,
+ &samsung->debug.data_wrapper);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
+ samsung->debug.root,
+ &samsung->debug.f0000_wrapper);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
+ samsung->debug.root, samsung,
+ &samsung_laptop_call_io_ops);
+ if (!dent)
+ goto error_debugfs;
+
+ dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR,
+ samsung->debug.root,
+ &samsung->debug.sdiag_wrapper);
+ if (!dent)
+ goto error_debugfs;
+
+ return 0;
+
+error_debugfs:
+ samsung_debugfs_exit(samsung);
+ return -ENOMEM;
+}
+
+static void samsung_sabi_exit(struct samsung_laptop *samsung)
+{
+ const struct sabi_config *config = samsung->config;
+
+ /* Turn off "Linux" mode in the BIOS */
+ if (config && config->commands.set_linux != 0xff)
+ sabi_set_commandb(samsung, config->commands.set_linux, 0x80);
+
+ if (samsung->sabi_iface) {
+ iounmap(samsung->sabi_iface);
+ samsung->sabi_iface = NULL;
+ }
+ if (samsung->f0000_segment) {
+ iounmap(samsung->f0000_segment);
+ samsung->f0000_segment = NULL;
+ }
+
+ samsung->config = NULL;
+}
+
+static __init void samsung_sabi_infos(struct samsung_laptop *samsung, int loca,
+ unsigned int ifaceP)
+{
+ const struct sabi_config *config = samsung->config;
+
+ printk(KERN_DEBUG "This computer supports SABI==%x\n",
+ loca + 0xf0000 - 6);
+
+ printk(KERN_DEBUG "SABI header:\n");
+ printk(KERN_DEBUG " SMI Port Number = 0x%04x\n",
+ readw(samsung->sabi + config->header_offsets.port));
+ printk(KERN_DEBUG " SMI Interface Function = 0x%02x\n",
+ readb(samsung->sabi + config->header_offsets.iface_func));
+ printk(KERN_DEBUG " SMI enable memory buffer = 0x%02x\n",
+ readb(samsung->sabi + config->header_offsets.en_mem));
+ printk(KERN_DEBUG " SMI restore memory buffer = 0x%02x\n",
+ readb(samsung->sabi + config->header_offsets.re_mem));
+ printk(KERN_DEBUG " SABI data offset = 0x%04x\n",
+ readw(samsung->sabi + config->header_offsets.data_offset));
+ printk(KERN_DEBUG " SABI data segment = 0x%04x\n",
+ readw(samsung->sabi + config->header_offsets.data_segment));
+
+ printk(KERN_DEBUG " SABI pointer = 0x%08x\n", ifaceP);
+}
+
+static void __init samsung_sabi_diag(struct samsung_laptop *samsung)
+{
+ int loca = find_signature(samsung->f0000_segment, "SDiaG@");
+ int i;
+
+ if (loca == 0xffff)
+ return ;
+
+ /* Example:
+ * Ident: @SDiaG@686XX-N90X3A/966-SEC-07HL-S90X3A
+ *
+ * Product name: 90X3A
+ * BIOS Version: 07HL
+ */
+ loca += 1;
+ for (i = 0; loca < 0xffff && i < sizeof(samsung->sdiag) - 1; loca++) {
+ char temp = readb(samsung->f0000_segment + loca);
+
+ if (isalnum(temp) || temp == '/' || temp == '-')
+ samsung->sdiag[i++] = temp;
+ else
+ break ;
+ }
+
+ if (debug && samsung->sdiag[0])
+ pr_info("sdiag: %s", samsung->sdiag);
+}
+
+static int __init samsung_sabi_init(struct samsung_laptop *samsung)
+{
+ const struct sabi_config *config = NULL;
+ const struct sabi_commands *commands;
+ unsigned int ifaceP;
+ int ret = 0;
+ int i;
+ int loca;
+
+ samsung->f0000_segment = ioremap_nocache(0xf0000, 0xffff);
+ if (!samsung->f0000_segment) {
+ if (debug || force)
+ pr_err("Can't map the segment at 0xf0000\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ samsung_sabi_diag(samsung);
+
+ /* Try to find one of the signatures in memory to find the header */
+ for (i = 0; sabi_configs[i].test_string != 0; ++i) {
+ samsung->config = &sabi_configs[i];
+ loca = find_signature(samsung->f0000_segment,
+ samsung->config->test_string);
+ if (loca != 0xffff)
+ break;
+ }
+
+ if (loca == 0xffff) {
+ if (debug || force)
+ pr_err("This computer does not support SABI\n");
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ config = samsung->config;
+ commands = &config->commands;
+
+ /* point to the SMI port Number */
+ loca += 1;
+ samsung->sabi = (samsung->f0000_segment + loca);
+
+ /* Get a pointer to the SABI Interface */
+ ifaceP = (readw(samsung->sabi + config->header_offsets.data_segment) & 0x0ffff) << 4;
+ ifaceP += readw(samsung->sabi + config->header_offsets.data_offset) & 0x0ffff;
+
+ if (debug)
+ samsung_sabi_infos(samsung, loca, ifaceP);
+
+ samsung->sabi_iface = ioremap_nocache(ifaceP, 16);
+ if (!samsung->sabi_iface) {
+ pr_err("Can't remap %x\n", ifaceP);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Turn on "Linux" mode in the BIOS */
+ if (commands->set_linux != 0xff) {
+ int retval = sabi_set_commandb(samsung,
+ commands->set_linux, 0x81);
+ if (retval) {
+ pr_warn("Linux mode was not set!\n");
+ ret = -ENODEV;
+ goto exit;
+ }
+ }
+
+ /* Check for stepping quirk */
+ if (samsung->handle_backlight)
+ check_for_stepping_quirk(samsung);
+
+ pr_info("detected SABI interface: %s\n",
+ samsung->config->test_string);
+
+exit:
+ if (ret)
+ samsung_sabi_exit(samsung);
+
+ return ret;
+}
+
+static void samsung_platform_exit(struct samsung_laptop *samsung)
+{
+ if (samsung->platform_device) {
+ platform_device_unregister(samsung->platform_device);
+ samsung->platform_device = NULL;
+ }
+}
+
+static int __init samsung_platform_init(struct samsung_laptop *samsung)
+{
+ struct platform_device *pdev;
+
+ pdev = platform_device_register_simple("samsung", -1, NULL, 0);
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ samsung->platform_device = pdev;
+ platform_set_drvdata(samsung->platform_device, samsung);
+ return 0;
+}
+
+static struct samsung_quirks *quirks;
+
+static int __init samsung_dmi_matched(const struct dmi_system_id *d)
+{
+ quirks = d->driver_data;
+ return 0;
+}
+
+static struct dmi_system_id __initdata samsung_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /* Laptop */
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /* Notebook */
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR,
+ "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_CHASSIS_TYPE, "14"), /* Sub-Notebook */
+ },
+ },
+ /* DMI ids for laptops with bad Chassis Type */
+ {
+ .ident = "R40/R41",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "R40/R41"),
+ DMI_MATCH(DMI_BOARD_NAME, "R40/R41"),
+ },
+ },
+ /* Specific DMI ids for laptop with quirks */
+ {
+ .callback = samsung_dmi_matched,
+ .ident = "N150P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N150P"),
+ DMI_MATCH(DMI_BOARD_NAME, "N150P"),
+ },
+ .driver_data = &samsung_broken_acpi_video,
+ },
+ {
+ .callback = samsung_dmi_matched,
+ .ident = "N145P/N250P/N260P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N145P/N250P/N260P"),
+ DMI_MATCH(DMI_BOARD_NAME, "N145P/N250P/N260P"),
+ },
+ .driver_data = &samsung_broken_acpi_video,
+ },
+ {
+ .callback = samsung_dmi_matched,
+ .ident = "N150/N210/N220",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N150/N210/N220"),
+ DMI_MATCH(DMI_BOARD_NAME, "N150/N210/N220"),
+ },
+ .driver_data = &samsung_broken_acpi_video,
+ },
+ {
+ .callback = samsung_dmi_matched,
+ .ident = "NF110/NF210/NF310",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "NF110/NF210/NF310"),
+ DMI_MATCH(DMI_BOARD_NAME, "NF110/NF210/NF310"),
+ },
+ .driver_data = &samsung_broken_acpi_video,
+ },
+ {
+ .callback = samsung_dmi_matched,
+ .ident = "X360",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X360"),
+ DMI_MATCH(DMI_BOARD_NAME, "X360"),
+ },
+ .driver_data = &samsung_broken_acpi_video,
+ },
+ {
+ .callback = samsung_dmi_matched,
+ .ident = "N250P",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "N250P"),
+ DMI_MATCH(DMI_BOARD_NAME, "N250P"),
+ },
+ .driver_data = &samsung_broken_acpi_video,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(dmi, samsung_dmi_table);
+
+static struct platform_device *samsung_platform_device;
+
+static int __init samsung_init(void)
+{
+ struct samsung_laptop *samsung;
+ int ret;
+
+ if (efi_enabled(EFI_BOOT))
+ return -ENODEV;
+
+ quirks = &samsung_unknown;
+ if (!force && !dmi_check_system(samsung_dmi_table))
+ return -ENODEV;
+
+ samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
+ if (!samsung)
+ return -ENOMEM;
+
+ mutex_init(&samsung->sabi_mutex);
+ samsung->handle_backlight = true;
+ samsung->quirks = quirks;
+
+
+#ifdef CONFIG_ACPI
+ if (samsung->quirks->broken_acpi_video)
+ acpi_video_dmi_promote_vendor();
+
+ /* Don't handle backlight here if the acpi video already handle it */
+ if (acpi_video_backlight_support()) {
+ samsung->handle_backlight = false;
+ } else if (samsung->quirks->broken_acpi_video) {
+ pr_info("Disabling ACPI video driver\n");
+ acpi_video_unregister();
+ }
+#endif
+
+ ret = samsung_platform_init(samsung);
+ if (ret)
+ goto error_platform;
+
+ ret = samsung_sabi_init(samsung);
+ if (ret)
+ goto error_sabi;
+
+#ifdef CONFIG_ACPI
+ /* Only log that if we are really on a sabi platform */
+ if (acpi_video_backlight_support())
+ pr_info("Backlight controlled by ACPI video driver\n");
+#endif
+
+ ret = samsung_sysfs_init(samsung);
+ if (ret)
+ goto error_sysfs;
+
+ ret = samsung_backlight_init(samsung);
+ if (ret)
+ goto error_backlight;
+
+ ret = samsung_rfkill_init(samsung);
+ if (ret)
+ goto error_rfkill;
+
+ ret = samsung_leds_init(samsung);
+ if (ret)
+ goto error_leds;
+
+ ret = samsung_debugfs_init(samsung);
+ if (ret)
+ goto error_debugfs;
+
+ samsung_platform_device = samsung->platform_device;
+ return ret;
+
+error_debugfs:
+ samsung_leds_exit(samsung);
+error_leds:
+ samsung_rfkill_exit(samsung);
+error_rfkill:
+ samsung_backlight_exit(samsung);
+error_backlight:
+ samsung_sysfs_exit(samsung);
+error_sysfs:
+ samsung_sabi_exit(samsung);
+error_sabi:
+ samsung_platform_exit(samsung);
+error_platform:
+ kfree(samsung);
+ return ret;
+}
+
+static void __exit samsung_exit(void)
+{
+ struct samsung_laptop *samsung;
+
+ samsung = platform_get_drvdata(samsung_platform_device);
+
+ samsung_debugfs_exit(samsung);
+ samsung_leds_exit(samsung);
+ samsung_rfkill_exit(samsung);
+ samsung_backlight_exit(samsung);
+ samsung_sysfs_exit(samsung);
+ samsung_sabi_exit(samsung);
+ samsung_platform_exit(samsung);
+
+ kfree(samsung);
+ samsung_platform_device = NULL;
+}
+
+module_init(samsung_init);
+module_exit(samsung_exit);
+
+MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@suse.de>");
+MODULE_DESCRIPTION("Samsung Backlight driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/samsung-q10.c b/drivers/platform/x86/samsung-q10.c
new file mode 100644
index 00000000..1a90b62a
--- /dev/null
+++ b/drivers/platform/x86/samsung-q10.c
@@ -0,0 +1,193 @@
+/*
+ * Driver for Samsung Q10 and related laptops: controls the backlight
+ *
+ * Copyright (c) 2011 Frederick van der Wyck <fvanderwyck@gmail.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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/i8042.h>
+#include <linux/dmi.h>
+
+#define SAMSUNGQ10_BL_MAX_INTENSITY 255
+#define SAMSUNGQ10_BL_DEFAULT_INTENSITY 185
+
+#define SAMSUNGQ10_BL_8042_CMD 0xbe
+#define SAMSUNGQ10_BL_8042_DATA { 0x89, 0x91 }
+
+static int samsungq10_bl_brightness;
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force,
+ "Disable the DMI check and force the driver to be loaded");
+
+static int samsungq10_bl_set_intensity(struct backlight_device *bd)
+{
+
+ int brightness = bd->props.brightness;
+ unsigned char c[3] = SAMSUNGQ10_BL_8042_DATA;
+
+ c[2] = (unsigned char)brightness;
+ i8042_lock_chip();
+ i8042_command(c, (0x30 << 8) | SAMSUNGQ10_BL_8042_CMD);
+ i8042_unlock_chip();
+ samsungq10_bl_brightness = brightness;
+
+ return 0;
+}
+
+static int samsungq10_bl_get_intensity(struct backlight_device *bd)
+{
+ return samsungq10_bl_brightness;
+}
+
+static const struct backlight_ops samsungq10_bl_ops = {
+ .get_brightness = samsungq10_bl_get_intensity,
+ .update_status = samsungq10_bl_set_intensity,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int samsungq10_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int samsungq10_resume(struct device *dev)
+{
+
+ struct backlight_device *bd = dev_get_drvdata(dev);
+
+ samsungq10_bl_set_intensity(bd);
+ return 0;
+}
+#else
+#define samsungq10_suspend NULL
+#define samsungq10_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(samsungq10_pm_ops,
+ samsungq10_suspend, samsungq10_resume);
+
+static int samsungq10_probe(struct platform_device *pdev)
+{
+
+ struct backlight_properties props;
+ struct backlight_device *bd;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = SAMSUNGQ10_BL_MAX_INTENSITY;
+ bd = backlight_device_register("samsung", &pdev->dev, NULL,
+ &samsungq10_bl_ops, &props);
+ if (IS_ERR(bd))
+ return PTR_ERR(bd);
+
+ platform_set_drvdata(pdev, bd);
+
+ bd->props.brightness = SAMSUNGQ10_BL_DEFAULT_INTENSITY;
+ samsungq10_bl_set_intensity(bd);
+
+ return 0;
+}
+
+static int samsungq10_remove(struct platform_device *pdev)
+{
+
+ struct backlight_device *bd = platform_get_drvdata(pdev);
+
+ bd->props.brightness = SAMSUNGQ10_BL_DEFAULT_INTENSITY;
+ samsungq10_bl_set_intensity(bd);
+
+ backlight_device_unregister(bd);
+
+ return 0;
+}
+
+static struct platform_driver samsungq10_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ .pm = &samsungq10_pm_ops,
+ },
+ .probe = samsungq10_probe,
+ .remove = samsungq10_remove,
+};
+
+static struct platform_device *samsungq10_device;
+
+static int __init dmi_check_callback(const struct dmi_system_id *id)
+{
+ printk(KERN_INFO KBUILD_MODNAME ": found model '%s'\n", id->ident);
+ return 1;
+}
+
+static struct dmi_system_id __initdata samsungq10_dmi_table[] = {
+ {
+ .ident = "Samsung Q10",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Samsung"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SQ10"),
+ },
+ .callback = dmi_check_callback,
+ },
+ {
+ .ident = "Samsung Q20",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG Electronics"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SENS Q20"),
+ },
+ .callback = dmi_check_callback,
+ },
+ {
+ .ident = "Samsung Q25",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG Electronics"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "NQ25"),
+ },
+ .callback = dmi_check_callback,
+ },
+ {
+ .ident = "Dell Latitude X200",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "X200"),
+ },
+ .callback = dmi_check_callback,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(dmi, samsungq10_dmi_table);
+
+static int __init samsungq10_init(void)
+{
+ if (!force && !dmi_check_system(samsungq10_dmi_table))
+ return -ENODEV;
+
+ samsungq10_device = platform_create_bundle(&samsungq10_driver,
+ samsungq10_probe,
+ NULL, 0, NULL, 0);
+
+ return PTR_RET(samsungq10_device);
+}
+
+static void __exit samsungq10_exit(void)
+{
+ platform_device_unregister(samsungq10_device);
+ platform_driver_unregister(&samsungq10_driver);
+}
+
+module_init(samsungq10_init);
+module_exit(samsungq10_exit);
+
+MODULE_AUTHOR("Frederick van der Wyck <fvanderwyck@gmail.com>");
+MODULE_DESCRIPTION("Samsung Q10 Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
new file mode 100644
index 00000000..2ac045f2
--- /dev/null
+++ b/drivers/platform/x86/sony-laptop.c
@@ -0,0 +1,4530 @@
+/*
+ * ACPI Sony Notebook Control Driver (SNC and SPIC)
+ *
+ * Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net>
+ * Copyright (C) 2007-2009 Mattia Dongili <malattia@linux.it>
+ *
+ * Parts of this driver inspired from asus_acpi.c and ibm_acpi.c
+ * which are copyrighted by their respective authors.
+ *
+ * The SNY6001 driver part is based on the sonypi driver which includes
+ * material from:
+ *
+ * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net>
+ *
+ * Copyright (C) 2005 Narayanan R S <nars@kadamba.org>
+ *
+ * Copyright (C) 2001-2002 Alcôve <www.alcove.com>
+ *
+ * Copyright (C) 2001 Michael Ashley <m.ashley@unsw.edu.au>
+ *
+ * Copyright (C) 2001 Junichi Morita <jun1m@mars.dti.ne.jp>
+ *
+ * Copyright (C) 2000 Takaya Kinjo <t-kinjo@tc4.so-net.ne.jp>
+ *
+ * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com>
+ *
+ * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/dmi.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/kfifo.h>
+#include <linux/workqueue.h>
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+#include <asm/uaccess.h>
+#include <linux/sonypi.h>
+#include <linux/sony-laptop.h>
+#include <linux/rfkill.h>
+#ifdef CONFIG_SONYPI_COMPAT
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#endif
+
+#define dprintk(fmt, ...) \
+do { \
+ if (debug) \
+ pr_warn(fmt, ##__VA_ARGS__); \
+} while (0)
+
+#define SONY_LAPTOP_DRIVER_VERSION "0.6"
+
+#define SONY_NC_CLASS "sony-nc"
+#define SONY_NC_HID "SNY5001"
+#define SONY_NC_DRIVER_NAME "Sony Notebook Control Driver"
+
+#define SONY_PIC_CLASS "sony-pic"
+#define SONY_PIC_HID "SNY6001"
+#define SONY_PIC_DRIVER_NAME "Sony Programmable IO Control Driver"
+
+MODULE_AUTHOR("Stelian Pop, Mattia Dongili");
+MODULE_DESCRIPTION("Sony laptop extras driver (SPIC and SNC ACPI device)");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(SONY_LAPTOP_DRIVER_VERSION);
+
+static int debug;
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug, "set this to 1 (and RTFM) if you want to help "
+ "the development of this driver");
+
+static int no_spic; /* = 0 */
+module_param(no_spic, int, 0444);
+MODULE_PARM_DESC(no_spic,
+ "set this if you don't want to enable the SPIC device");
+
+static int compat; /* = 0 */
+module_param(compat, int, 0444);
+MODULE_PARM_DESC(compat,
+ "set this if you want to enable backward compatibility mode");
+
+static unsigned long mask = 0xffffffff;
+module_param(mask, ulong, 0644);
+MODULE_PARM_DESC(mask,
+ "set this to the mask of event you want to enable (see doc)");
+
+static int camera; /* = 0 */
+module_param(camera, int, 0444);
+MODULE_PARM_DESC(camera,
+ "set this to 1 to enable Motion Eye camera controls "
+ "(only use it if you have a C1VE or C1VN model)");
+
+#ifdef CONFIG_SONYPI_COMPAT
+static int minor = -1;
+module_param(minor, int, 0);
+MODULE_PARM_DESC(minor,
+ "minor number of the misc device for the SPIC compatibility code, "
+ "default is -1 (automatic)");
+#endif
+
+static int kbd_backlight = 1;
+module_param(kbd_backlight, int, 0444);
+MODULE_PARM_DESC(kbd_backlight,
+ "set this to 0 to disable keyboard backlight, "
+ "1 to enable it (default: 0)");
+
+static int kbd_backlight_timeout; /* = 0 */
+module_param(kbd_backlight_timeout, int, 0444);
+MODULE_PARM_DESC(kbd_backlight_timeout,
+ "set this to 0 to set the default 10 seconds timeout, "
+ "1 for 30 seconds, 2 for 60 seconds and 3 to disable timeout "
+ "(default: 0)");
+
+#ifdef CONFIG_PM_SLEEP
+static void sony_nc_kbd_backlight_resume(void);
+static void sony_nc_thermal_resume(void);
+#endif
+static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
+ unsigned int handle);
+static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd);
+
+static int sony_nc_battery_care_setup(struct platform_device *pd,
+ unsigned int handle);
+static void sony_nc_battery_care_cleanup(struct platform_device *pd);
+
+static int sony_nc_thermal_setup(struct platform_device *pd);
+static void sony_nc_thermal_cleanup(struct platform_device *pd);
+
+static int sony_nc_lid_resume_setup(struct platform_device *pd);
+static void sony_nc_lid_resume_cleanup(struct platform_device *pd);
+
+static int sony_nc_gfx_switch_setup(struct platform_device *pd,
+ unsigned int handle);
+static void sony_nc_gfx_switch_cleanup(struct platform_device *pd);
+static int __sony_nc_gfx_switch_status_get(void);
+
+static int sony_nc_highspeed_charging_setup(struct platform_device *pd);
+static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd);
+
+static int sony_nc_touchpad_setup(struct platform_device *pd,
+ unsigned int handle);
+static void sony_nc_touchpad_cleanup(struct platform_device *pd);
+
+enum sony_nc_rfkill {
+ SONY_WIFI,
+ SONY_BLUETOOTH,
+ SONY_WWAN,
+ SONY_WIMAX,
+ N_SONY_RFKILL,
+};
+
+static int sony_rfkill_handle;
+static struct rfkill *sony_rfkill_devices[N_SONY_RFKILL];
+static int sony_rfkill_address[N_SONY_RFKILL] = {0x300, 0x500, 0x700, 0x900};
+static int sony_nc_rfkill_setup(struct acpi_device *device,
+ unsigned int handle);
+static void sony_nc_rfkill_cleanup(void);
+static void sony_nc_rfkill_update(void);
+
+/*********** Input Devices ***********/
+
+#define SONY_LAPTOP_BUF_SIZE 128
+struct sony_laptop_input_s {
+ atomic_t users;
+ struct input_dev *jog_dev;
+ struct input_dev *key_dev;
+ struct kfifo fifo;
+ spinlock_t fifo_lock;
+ struct timer_list release_key_timer;
+};
+
+static struct sony_laptop_input_s sony_laptop_input = {
+ .users = ATOMIC_INIT(0),
+};
+
+struct sony_laptop_keypress {
+ struct input_dev *dev;
+ int key;
+};
+
+/* Correspondance table between sonypi events
+ * and input layer indexes in the keymap
+ */
+static int sony_laptop_input_index[] = {
+ -1, /* 0 no event */
+ -1, /* 1 SONYPI_EVENT_JOGDIAL_DOWN */
+ -1, /* 2 SONYPI_EVENT_JOGDIAL_UP */
+ -1, /* 3 SONYPI_EVENT_JOGDIAL_DOWN_PRESSED */
+ -1, /* 4 SONYPI_EVENT_JOGDIAL_UP_PRESSED */
+ -1, /* 5 SONYPI_EVENT_JOGDIAL_PRESSED */
+ -1, /* 6 SONYPI_EVENT_JOGDIAL_RELEASED */
+ 0, /* 7 SONYPI_EVENT_CAPTURE_PRESSED */
+ 1, /* 8 SONYPI_EVENT_CAPTURE_RELEASED */
+ 2, /* 9 SONYPI_EVENT_CAPTURE_PARTIALPRESSED */
+ 3, /* 10 SONYPI_EVENT_CAPTURE_PARTIALRELEASED */
+ 4, /* 11 SONYPI_EVENT_FNKEY_ESC */
+ 5, /* 12 SONYPI_EVENT_FNKEY_F1 */
+ 6, /* 13 SONYPI_EVENT_FNKEY_F2 */
+ 7, /* 14 SONYPI_EVENT_FNKEY_F3 */
+ 8, /* 15 SONYPI_EVENT_FNKEY_F4 */
+ 9, /* 16 SONYPI_EVENT_FNKEY_F5 */
+ 10, /* 17 SONYPI_EVENT_FNKEY_F6 */
+ 11, /* 18 SONYPI_EVENT_FNKEY_F7 */
+ 12, /* 19 SONYPI_EVENT_FNKEY_F8 */
+ 13, /* 20 SONYPI_EVENT_FNKEY_F9 */
+ 14, /* 21 SONYPI_EVENT_FNKEY_F10 */
+ 15, /* 22 SONYPI_EVENT_FNKEY_F11 */
+ 16, /* 23 SONYPI_EVENT_FNKEY_F12 */
+ 17, /* 24 SONYPI_EVENT_FNKEY_1 */
+ 18, /* 25 SONYPI_EVENT_FNKEY_2 */
+ 19, /* 26 SONYPI_EVENT_FNKEY_D */
+ 20, /* 27 SONYPI_EVENT_FNKEY_E */
+ 21, /* 28 SONYPI_EVENT_FNKEY_F */
+ 22, /* 29 SONYPI_EVENT_FNKEY_S */
+ 23, /* 30 SONYPI_EVENT_FNKEY_B */
+ 24, /* 31 SONYPI_EVENT_BLUETOOTH_PRESSED */
+ 25, /* 32 SONYPI_EVENT_PKEY_P1 */
+ 26, /* 33 SONYPI_EVENT_PKEY_P2 */
+ 27, /* 34 SONYPI_EVENT_PKEY_P3 */
+ 28, /* 35 SONYPI_EVENT_BACK_PRESSED */
+ -1, /* 36 SONYPI_EVENT_LID_CLOSED */
+ -1, /* 37 SONYPI_EVENT_LID_OPENED */
+ 29, /* 38 SONYPI_EVENT_BLUETOOTH_ON */
+ 30, /* 39 SONYPI_EVENT_BLUETOOTH_OFF */
+ 31, /* 40 SONYPI_EVENT_HELP_PRESSED */
+ 32, /* 41 SONYPI_EVENT_FNKEY_ONLY */
+ 33, /* 42 SONYPI_EVENT_JOGDIAL_FAST_DOWN */
+ 34, /* 43 SONYPI_EVENT_JOGDIAL_FAST_UP */
+ 35, /* 44 SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED */
+ 36, /* 45 SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED */
+ 37, /* 46 SONYPI_EVENT_JOGDIAL_VFAST_DOWN */
+ 38, /* 47 SONYPI_EVENT_JOGDIAL_VFAST_UP */
+ 39, /* 48 SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED */
+ 40, /* 49 SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED */
+ 41, /* 50 SONYPI_EVENT_ZOOM_PRESSED */
+ 42, /* 51 SONYPI_EVENT_THUMBPHRASE_PRESSED */
+ 43, /* 52 SONYPI_EVENT_MEYE_FACE */
+ 44, /* 53 SONYPI_EVENT_MEYE_OPPOSITE */
+ 45, /* 54 SONYPI_EVENT_MEMORYSTICK_INSERT */
+ 46, /* 55 SONYPI_EVENT_MEMORYSTICK_EJECT */
+ -1, /* 56 SONYPI_EVENT_ANYBUTTON_RELEASED */
+ -1, /* 57 SONYPI_EVENT_BATTERY_INSERT */
+ -1, /* 58 SONYPI_EVENT_BATTERY_REMOVE */
+ -1, /* 59 SONYPI_EVENT_FNKEY_RELEASED */
+ 47, /* 60 SONYPI_EVENT_WIRELESS_ON */
+ 48, /* 61 SONYPI_EVENT_WIRELESS_OFF */
+ 49, /* 62 SONYPI_EVENT_ZOOM_IN_PRESSED */
+ 50, /* 63 SONYPI_EVENT_ZOOM_OUT_PRESSED */
+ 51, /* 64 SONYPI_EVENT_CD_EJECT_PRESSED */
+ 52, /* 65 SONYPI_EVENT_MODEKEY_PRESSED */
+ 53, /* 66 SONYPI_EVENT_PKEY_P4 */
+ 54, /* 67 SONYPI_EVENT_PKEY_P5 */
+ 55, /* 68 SONYPI_EVENT_SETTINGKEY_PRESSED */
+ 56, /* 69 SONYPI_EVENT_VOLUME_INC_PRESSED */
+ 57, /* 70 SONYPI_EVENT_VOLUME_DEC_PRESSED */
+ -1, /* 71 SONYPI_EVENT_BRIGHTNESS_PRESSED */
+ 58, /* 72 SONYPI_EVENT_MEDIA_PRESSED */
+ 59, /* 72 SONYPI_EVENT_VENDOR_PRESSED */
+};
+
+static int sony_laptop_input_keycode_map[] = {
+ KEY_CAMERA, /* 0 SONYPI_EVENT_CAPTURE_PRESSED */
+ KEY_RESERVED, /* 1 SONYPI_EVENT_CAPTURE_RELEASED */
+ KEY_RESERVED, /* 2 SONYPI_EVENT_CAPTURE_PARTIALPRESSED */
+ KEY_RESERVED, /* 3 SONYPI_EVENT_CAPTURE_PARTIALRELEASED */
+ KEY_FN_ESC, /* 4 SONYPI_EVENT_FNKEY_ESC */
+ KEY_FN_F1, /* 5 SONYPI_EVENT_FNKEY_F1 */
+ KEY_FN_F2, /* 6 SONYPI_EVENT_FNKEY_F2 */
+ KEY_FN_F3, /* 7 SONYPI_EVENT_FNKEY_F3 */
+ KEY_FN_F4, /* 8 SONYPI_EVENT_FNKEY_F4 */
+ KEY_FN_F5, /* 9 SONYPI_EVENT_FNKEY_F5 */
+ KEY_FN_F6, /* 10 SONYPI_EVENT_FNKEY_F6 */
+ KEY_FN_F7, /* 11 SONYPI_EVENT_FNKEY_F7 */
+ KEY_FN_F8, /* 12 SONYPI_EVENT_FNKEY_F8 */
+ KEY_FN_F9, /* 13 SONYPI_EVENT_FNKEY_F9 */
+ KEY_FN_F10, /* 14 SONYPI_EVENT_FNKEY_F10 */
+ KEY_FN_F11, /* 15 SONYPI_EVENT_FNKEY_F11 */
+ KEY_FN_F12, /* 16 SONYPI_EVENT_FNKEY_F12 */
+ KEY_FN_F1, /* 17 SONYPI_EVENT_FNKEY_1 */
+ KEY_FN_F2, /* 18 SONYPI_EVENT_FNKEY_2 */
+ KEY_FN_D, /* 19 SONYPI_EVENT_FNKEY_D */
+ KEY_FN_E, /* 20 SONYPI_EVENT_FNKEY_E */
+ KEY_FN_F, /* 21 SONYPI_EVENT_FNKEY_F */
+ KEY_FN_S, /* 22 SONYPI_EVENT_FNKEY_S */
+ KEY_FN_B, /* 23 SONYPI_EVENT_FNKEY_B */
+ KEY_BLUETOOTH, /* 24 SONYPI_EVENT_BLUETOOTH_PRESSED */
+ KEY_PROG1, /* 25 SONYPI_EVENT_PKEY_P1 */
+ KEY_PROG2, /* 26 SONYPI_EVENT_PKEY_P2 */
+ KEY_PROG3, /* 27 SONYPI_EVENT_PKEY_P3 */
+ KEY_BACK, /* 28 SONYPI_EVENT_BACK_PRESSED */
+ KEY_BLUETOOTH, /* 29 SONYPI_EVENT_BLUETOOTH_ON */
+ KEY_BLUETOOTH, /* 30 SONYPI_EVENT_BLUETOOTH_OFF */
+ KEY_HELP, /* 31 SONYPI_EVENT_HELP_PRESSED */
+ KEY_FN, /* 32 SONYPI_EVENT_FNKEY_ONLY */
+ KEY_RESERVED, /* 33 SONYPI_EVENT_JOGDIAL_FAST_DOWN */
+ KEY_RESERVED, /* 34 SONYPI_EVENT_JOGDIAL_FAST_UP */
+ KEY_RESERVED, /* 35 SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED */
+ KEY_RESERVED, /* 36 SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED */
+ KEY_RESERVED, /* 37 SONYPI_EVENT_JOGDIAL_VFAST_DOWN */
+ KEY_RESERVED, /* 38 SONYPI_EVENT_JOGDIAL_VFAST_UP */
+ KEY_RESERVED, /* 39 SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED */
+ KEY_RESERVED, /* 40 SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED */
+ KEY_ZOOM, /* 41 SONYPI_EVENT_ZOOM_PRESSED */
+ BTN_THUMB, /* 42 SONYPI_EVENT_THUMBPHRASE_PRESSED */
+ KEY_RESERVED, /* 43 SONYPI_EVENT_MEYE_FACE */
+ KEY_RESERVED, /* 44 SONYPI_EVENT_MEYE_OPPOSITE */
+ KEY_RESERVED, /* 45 SONYPI_EVENT_MEMORYSTICK_INSERT */
+ KEY_RESERVED, /* 46 SONYPI_EVENT_MEMORYSTICK_EJECT */
+ KEY_WLAN, /* 47 SONYPI_EVENT_WIRELESS_ON */
+ KEY_WLAN, /* 48 SONYPI_EVENT_WIRELESS_OFF */
+ KEY_ZOOMIN, /* 49 SONYPI_EVENT_ZOOM_IN_PRESSED */
+ KEY_ZOOMOUT, /* 50 SONYPI_EVENT_ZOOM_OUT_PRESSED */
+ KEY_EJECTCD, /* 51 SONYPI_EVENT_CD_EJECT_PRESSED */
+ KEY_F13, /* 52 SONYPI_EVENT_MODEKEY_PRESSED */
+ KEY_PROG4, /* 53 SONYPI_EVENT_PKEY_P4 */
+ KEY_F14, /* 54 SONYPI_EVENT_PKEY_P5 */
+ KEY_F15, /* 55 SONYPI_EVENT_SETTINGKEY_PRESSED */
+ KEY_VOLUMEUP, /* 56 SONYPI_EVENT_VOLUME_INC_PRESSED */
+ KEY_VOLUMEDOWN, /* 57 SONYPI_EVENT_VOLUME_DEC_PRESSED */
+ KEY_MEDIA, /* 58 SONYPI_EVENT_MEDIA_PRESSED */
+ KEY_VENDOR, /* 59 SONYPI_EVENT_VENDOR_PRESSED */
+};
+
+/* release buttons after a short delay if pressed */
+static void do_sony_laptop_release_key(unsigned long unused)
+{
+ struct sony_laptop_keypress kp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sony_laptop_input.fifo_lock, flags);
+
+ if (kfifo_out(&sony_laptop_input.fifo,
+ (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) {
+ input_report_key(kp.dev, kp.key, 0);
+ input_sync(kp.dev);
+ }
+
+ /* If there is something in the fifo schedule next release. */
+ if (kfifo_len(&sony_laptop_input.fifo) != 0)
+ mod_timer(&sony_laptop_input.release_key_timer,
+ jiffies + msecs_to_jiffies(10));
+
+ spin_unlock_irqrestore(&sony_laptop_input.fifo_lock, flags);
+}
+
+/* forward event to the input subsystem */
+static void sony_laptop_report_input_event(u8 event)
+{
+ struct input_dev *jog_dev = sony_laptop_input.jog_dev;
+ struct input_dev *key_dev = sony_laptop_input.key_dev;
+ struct sony_laptop_keypress kp = { NULL };
+ int scancode = -1;
+
+ if (event == SONYPI_EVENT_FNKEY_RELEASED ||
+ event == SONYPI_EVENT_ANYBUTTON_RELEASED) {
+ /* Nothing, not all VAIOs generate this event */
+ return;
+ }
+
+ /* report events */
+ switch (event) {
+ /* jog_dev events */
+ case SONYPI_EVENT_JOGDIAL_UP:
+ case SONYPI_EVENT_JOGDIAL_UP_PRESSED:
+ input_report_rel(jog_dev, REL_WHEEL, 1);
+ input_sync(jog_dev);
+ return;
+
+ case SONYPI_EVENT_JOGDIAL_DOWN:
+ case SONYPI_EVENT_JOGDIAL_DOWN_PRESSED:
+ input_report_rel(jog_dev, REL_WHEEL, -1);
+ input_sync(jog_dev);
+ return;
+
+ /* key_dev events */
+ case SONYPI_EVENT_JOGDIAL_PRESSED:
+ kp.key = BTN_MIDDLE;
+ kp.dev = jog_dev;
+ break;
+
+ default:
+ if (event >= ARRAY_SIZE(sony_laptop_input_index)) {
+ dprintk("sony_laptop_report_input_event, event not known: %d\n", event);
+ break;
+ }
+ if ((scancode = sony_laptop_input_index[event]) != -1) {
+ kp.key = sony_laptop_input_keycode_map[scancode];
+ if (kp.key != KEY_UNKNOWN)
+ kp.dev = key_dev;
+ }
+ break;
+ }
+
+ if (kp.dev) {
+ /* if we have a scancode we emit it so we can always
+ remap the key */
+ if (scancode != -1)
+ input_event(kp.dev, EV_MSC, MSC_SCAN, scancode);
+ input_report_key(kp.dev, kp.key, 1);
+ input_sync(kp.dev);
+
+ /* schedule key release */
+ kfifo_in_locked(&sony_laptop_input.fifo,
+ (unsigned char *)&kp, sizeof(kp),
+ &sony_laptop_input.fifo_lock);
+ mod_timer(&sony_laptop_input.release_key_timer,
+ jiffies + msecs_to_jiffies(10));
+ } else
+ dprintk("unknown input event %.2x\n", event);
+}
+
+static int sony_laptop_setup_input(struct acpi_device *acpi_device)
+{
+ struct input_dev *jog_dev;
+ struct input_dev *key_dev;
+ int i;
+ int error;
+
+ /* don't run again if already initialized */
+ if (atomic_add_return(1, &sony_laptop_input.users) > 1)
+ return 0;
+
+ /* kfifo */
+ spin_lock_init(&sony_laptop_input.fifo_lock);
+ error = kfifo_alloc(&sony_laptop_input.fifo,
+ SONY_LAPTOP_BUF_SIZE, GFP_KERNEL);
+ if (error) {
+ pr_err("kfifo_alloc failed\n");
+ goto err_dec_users;
+ }
+
+ setup_timer(&sony_laptop_input.release_key_timer,
+ do_sony_laptop_release_key, 0);
+
+ /* input keys */
+ key_dev = input_allocate_device();
+ if (!key_dev) {
+ error = -ENOMEM;
+ goto err_free_kfifo;
+ }
+
+ key_dev->name = "Sony Vaio Keys";
+ key_dev->id.bustype = BUS_ISA;
+ key_dev->id.vendor = PCI_VENDOR_ID_SONY;
+ key_dev->dev.parent = &acpi_device->dev;
+
+ /* Initialize the Input Drivers: special keys */
+ input_set_capability(key_dev, EV_MSC, MSC_SCAN);
+
+ __set_bit(EV_KEY, key_dev->evbit);
+ key_dev->keycodesize = sizeof(sony_laptop_input_keycode_map[0]);
+ key_dev->keycodemax = ARRAY_SIZE(sony_laptop_input_keycode_map);
+ key_dev->keycode = &sony_laptop_input_keycode_map;
+ for (i = 0; i < ARRAY_SIZE(sony_laptop_input_keycode_map); i++)
+ __set_bit(sony_laptop_input_keycode_map[i], key_dev->keybit);
+ __clear_bit(KEY_RESERVED, key_dev->keybit);
+
+ error = input_register_device(key_dev);
+ if (error)
+ goto err_free_keydev;
+
+ sony_laptop_input.key_dev = key_dev;
+
+ /* jogdial */
+ jog_dev = input_allocate_device();
+ if (!jog_dev) {
+ error = -ENOMEM;
+ goto err_unregister_keydev;
+ }
+
+ jog_dev->name = "Sony Vaio Jogdial";
+ jog_dev->id.bustype = BUS_ISA;
+ jog_dev->id.vendor = PCI_VENDOR_ID_SONY;
+ jog_dev->dev.parent = &acpi_device->dev;
+
+ input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE);
+ input_set_capability(jog_dev, EV_REL, REL_WHEEL);
+
+ error = input_register_device(jog_dev);
+ if (error)
+ goto err_free_jogdev;
+
+ sony_laptop_input.jog_dev = jog_dev;
+
+ return 0;
+
+err_free_jogdev:
+ input_free_device(jog_dev);
+
+err_unregister_keydev:
+ input_unregister_device(key_dev);
+ /* to avoid kref underflow below at input_free_device */
+ key_dev = NULL;
+
+err_free_keydev:
+ input_free_device(key_dev);
+
+err_free_kfifo:
+ kfifo_free(&sony_laptop_input.fifo);
+
+err_dec_users:
+ atomic_dec(&sony_laptop_input.users);
+ return error;
+}
+
+static void sony_laptop_remove_input(void)
+{
+ struct sony_laptop_keypress kp = { NULL };
+
+ /* Cleanup only after the last user has gone */
+ if (!atomic_dec_and_test(&sony_laptop_input.users))
+ return;
+
+ del_timer_sync(&sony_laptop_input.release_key_timer);
+
+ /*
+ * Generate key-up events for remaining keys. Note that we don't
+ * need locking since nobody is adding new events to the kfifo.
+ */
+ while (kfifo_out(&sony_laptop_input.fifo,
+ (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) {
+ input_report_key(kp.dev, kp.key, 0);
+ input_sync(kp.dev);
+ }
+
+ /* destroy input devs */
+ input_unregister_device(sony_laptop_input.key_dev);
+ sony_laptop_input.key_dev = NULL;
+
+ if (sony_laptop_input.jog_dev) {
+ input_unregister_device(sony_laptop_input.jog_dev);
+ sony_laptop_input.jog_dev = NULL;
+ }
+
+ kfifo_free(&sony_laptop_input.fifo);
+}
+
+/*********** Platform Device ***********/
+
+static atomic_t sony_pf_users = ATOMIC_INIT(0);
+static struct platform_driver sony_pf_driver = {
+ .driver = {
+ .name = "sony-laptop",
+ .owner = THIS_MODULE,
+ }
+};
+static struct platform_device *sony_pf_device;
+
+static int sony_pf_add(void)
+{
+ int ret = 0;
+
+ /* don't run again if already initialized */
+ if (atomic_add_return(1, &sony_pf_users) > 1)
+ return 0;
+
+ ret = platform_driver_register(&sony_pf_driver);
+ if (ret)
+ goto out;
+
+ sony_pf_device = platform_device_alloc("sony-laptop", -1);
+ if (!sony_pf_device) {
+ ret = -ENOMEM;
+ goto out_platform_registered;
+ }
+
+ ret = platform_device_add(sony_pf_device);
+ if (ret)
+ goto out_platform_alloced;
+
+ return 0;
+
+ out_platform_alloced:
+ platform_device_put(sony_pf_device);
+ sony_pf_device = NULL;
+ out_platform_registered:
+ platform_driver_unregister(&sony_pf_driver);
+ out:
+ atomic_dec(&sony_pf_users);
+ return ret;
+}
+
+static void sony_pf_remove(void)
+{
+ /* deregister only after the last user has gone */
+ if (!atomic_dec_and_test(&sony_pf_users))
+ return;
+
+ platform_device_unregister(sony_pf_device);
+ platform_driver_unregister(&sony_pf_driver);
+}
+
+/*********** SNC (SNY5001) Device ***********/
+
+/* the device uses 1-based values, while the backlight subsystem uses
+ 0-based values */
+#define SONY_MAX_BRIGHTNESS 8
+
+#define SNC_VALIDATE_IN 0
+#define SNC_VALIDATE_OUT 1
+
+static ssize_t sony_nc_sysfs_show(struct device *, struct device_attribute *,
+ char *);
+static ssize_t sony_nc_sysfs_store(struct device *, struct device_attribute *,
+ const char *, size_t);
+static int boolean_validate(const int, const int);
+static int brightness_default_validate(const int, const int);
+
+struct sony_nc_value {
+ char *name; /* name of the entry */
+ char **acpiget; /* names of the ACPI get function */
+ char **acpiset; /* names of the ACPI set function */
+ int (*validate)(const int, const int); /* input/output validation */
+ int value; /* current setting */
+ int valid; /* Has ever been set */
+ int debug; /* active only in debug mode ? */
+ struct device_attribute devattr; /* sysfs attribute */
+};
+
+#define SNC_HANDLE_NAMES(_name, _values...) \
+ static char *snc_##_name[] = { _values, NULL }
+
+#define SNC_HANDLE(_name, _getters, _setters, _validate, _debug) \
+ { \
+ .name = __stringify(_name), \
+ .acpiget = _getters, \
+ .acpiset = _setters, \
+ .validate = _validate, \
+ .debug = _debug, \
+ .devattr = __ATTR(_name, 0, sony_nc_sysfs_show, sony_nc_sysfs_store), \
+ }
+
+#define SNC_HANDLE_NULL { .name = NULL }
+
+SNC_HANDLE_NAMES(fnkey_get, "GHKE");
+
+SNC_HANDLE_NAMES(brightness_def_get, "GPBR");
+SNC_HANDLE_NAMES(brightness_def_set, "SPBR");
+
+SNC_HANDLE_NAMES(cdpower_get, "GCDP");
+SNC_HANDLE_NAMES(cdpower_set, "SCDP", "CDPW");
+
+SNC_HANDLE_NAMES(audiopower_get, "GAZP");
+SNC_HANDLE_NAMES(audiopower_set, "AZPW");
+
+SNC_HANDLE_NAMES(lanpower_get, "GLNP");
+SNC_HANDLE_NAMES(lanpower_set, "LNPW");
+
+SNC_HANDLE_NAMES(lidstate_get, "GLID");
+
+SNC_HANDLE_NAMES(indicatorlamp_get, "GILS");
+SNC_HANDLE_NAMES(indicatorlamp_set, "SILS");
+
+SNC_HANDLE_NAMES(gainbass_get, "GMGB");
+SNC_HANDLE_NAMES(gainbass_set, "CMGB");
+
+SNC_HANDLE_NAMES(PID_get, "GPID");
+
+SNC_HANDLE_NAMES(CTR_get, "GCTR");
+SNC_HANDLE_NAMES(CTR_set, "SCTR");
+
+SNC_HANDLE_NAMES(PCR_get, "GPCR");
+SNC_HANDLE_NAMES(PCR_set, "SPCR");
+
+SNC_HANDLE_NAMES(CMI_get, "GCMI");
+SNC_HANDLE_NAMES(CMI_set, "SCMI");
+
+static struct sony_nc_value sony_nc_values[] = {
+ SNC_HANDLE(brightness_default, snc_brightness_def_get,
+ snc_brightness_def_set, brightness_default_validate, 0),
+ SNC_HANDLE(fnkey, snc_fnkey_get, NULL, NULL, 0),
+ SNC_HANDLE(cdpower, snc_cdpower_get, snc_cdpower_set, boolean_validate, 0),
+ SNC_HANDLE(audiopower, snc_audiopower_get, snc_audiopower_set,
+ boolean_validate, 0),
+ SNC_HANDLE(lanpower, snc_lanpower_get, snc_lanpower_set,
+ boolean_validate, 1),
+ SNC_HANDLE(lidstate, snc_lidstate_get, NULL,
+ boolean_validate, 0),
+ SNC_HANDLE(indicatorlamp, snc_indicatorlamp_get, snc_indicatorlamp_set,
+ boolean_validate, 0),
+ SNC_HANDLE(gainbass, snc_gainbass_get, snc_gainbass_set,
+ boolean_validate, 0),
+ /* unknown methods */
+ SNC_HANDLE(PID, snc_PID_get, NULL, NULL, 1),
+ SNC_HANDLE(CTR, snc_CTR_get, snc_CTR_set, NULL, 1),
+ SNC_HANDLE(PCR, snc_PCR_get, snc_PCR_set, NULL, 1),
+ SNC_HANDLE(CMI, snc_CMI_get, snc_CMI_set, NULL, 1),
+ SNC_HANDLE_NULL
+};
+
+static acpi_handle sony_nc_acpi_handle;
+static struct acpi_device *sony_nc_acpi_device = NULL;
+
+/*
+ * acpi_evaluate_object wrappers
+ * all useful calls into SNC methods take one or zero parameters and return
+ * integers or arrays.
+ */
+static union acpi_object *__call_snc_method(acpi_handle handle, char *method,
+ u64 *value)
+{
+ union acpi_object *result = NULL;
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ if (value) {
+ struct acpi_object_list params;
+ union acpi_object in;
+ in.type = ACPI_TYPE_INTEGER;
+ in.integer.value = *value;
+ params.count = 1;
+ params.pointer = &in;
+ status = acpi_evaluate_object(handle, method, &params, &output);
+ dprintk("__call_snc_method: [%s:0x%.8x%.8x]\n", method,
+ (unsigned int)(*value >> 32),
+ (unsigned int)*value & 0xffffffff);
+ } else {
+ status = acpi_evaluate_object(handle, method, NULL, &output);
+ dprintk("__call_snc_method: [%s]\n", method);
+ }
+
+ if (ACPI_FAILURE(status)) {
+ pr_err("Failed to evaluate [%s]\n", method);
+ return NULL;
+ }
+
+ result = (union acpi_object *) output.pointer;
+ if (!result)
+ dprintk("No return object [%s]\n", method);
+
+ return result;
+}
+
+static int sony_nc_int_call(acpi_handle handle, char *name, int *value,
+ int *result)
+{
+ union acpi_object *object = NULL;
+ if (value) {
+ u64 v = *value;
+ object = __call_snc_method(handle, name, &v);
+ } else
+ object = __call_snc_method(handle, name, NULL);
+
+ if (!object)
+ return -EINVAL;
+
+ if (object->type != ACPI_TYPE_INTEGER) {
+ pr_warn("Invalid acpi_object: expected 0x%x got 0x%x\n",
+ ACPI_TYPE_INTEGER, object->type);
+ kfree(object);
+ return -EINVAL;
+ }
+
+ if (result)
+ *result = object->integer.value;
+
+ kfree(object);
+ return 0;
+}
+
+#define MIN(a, b) (a > b ? b : a)
+static int sony_nc_buffer_call(acpi_handle handle, char *name, u64 *value,
+ void *buffer, size_t buflen)
+{
+ int ret = 0;
+ size_t len = len;
+ union acpi_object *object = __call_snc_method(handle, name, value);
+
+ if (!object)
+ return -EINVAL;
+
+ if (object->type == ACPI_TYPE_BUFFER) {
+ len = MIN(buflen, object->buffer.length);
+ memcpy(buffer, object->buffer.pointer, len);
+
+ } else if (object->type == ACPI_TYPE_INTEGER) {
+ len = MIN(buflen, sizeof(object->integer.value));
+ memcpy(buffer, &object->integer.value, len);
+
+ } else {
+ pr_warn("Invalid acpi_object: expected 0x%x got 0x%x\n",
+ ACPI_TYPE_BUFFER, object->type);
+ ret = -EINVAL;
+ }
+
+ kfree(object);
+ return ret;
+}
+
+struct sony_nc_handles {
+ u16 cap[0x10];
+ struct device_attribute devattr;
+};
+
+static struct sony_nc_handles *handles;
+
+static ssize_t sony_nc_handles_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ ssize_t len = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+ len += snprintf(buffer + len, PAGE_SIZE - len, "0x%.4x ",
+ handles->cap[i]);
+ }
+ len += snprintf(buffer + len, PAGE_SIZE - len, "\n");
+
+ return len;
+}
+
+static int sony_nc_handles_setup(struct platform_device *pd)
+{
+ int i, r, result, arg;
+
+ handles = kzalloc(sizeof(*handles), GFP_KERNEL);
+ if (!handles)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+ arg = i + 0x20;
+ r = sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg,
+ &result);
+ if (!r) {
+ dprintk("caching handle 0x%.4x (offset: 0x%.2x)\n",
+ result, i);
+ handles->cap[i] = result;
+ }
+ }
+
+ if (debug) {
+ sysfs_attr_init(&handles->devattr.attr);
+ handles->devattr.attr.name = "handles";
+ handles->devattr.attr.mode = S_IRUGO;
+ handles->devattr.show = sony_nc_handles_show;
+
+ /* allow reading capabilities via sysfs */
+ if (device_create_file(&pd->dev, &handles->devattr)) {
+ kfree(handles);
+ handles = NULL;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int sony_nc_handles_cleanup(struct platform_device *pd)
+{
+ if (handles) {
+ if (debug)
+ device_remove_file(&pd->dev, &handles->devattr);
+ kfree(handles);
+ handles = NULL;
+ }
+ return 0;
+}
+
+static int sony_find_snc_handle(int handle)
+{
+ int i;
+
+ /* not initialized yet, return early */
+ if (!handles || !handle)
+ return -EINVAL;
+
+ for (i = 0; i < 0x10; i++) {
+ if (handles->cap[i] == handle) {
+ dprintk("found handle 0x%.4x (offset: 0x%.2x)\n",
+ handle, i);
+ return i;
+ }
+ }
+ dprintk("handle 0x%.4x not found\n", handle);
+ return -EINVAL;
+}
+
+static int sony_call_snc_handle(int handle, int argument, int *result)
+{
+ int arg, ret = 0;
+ int offset = sony_find_snc_handle(handle);
+
+ if (offset < 0)
+ return offset;
+
+ arg = offset | argument;
+ ret = sony_nc_int_call(sony_nc_acpi_handle, "SN07", &arg, result);
+ dprintk("called SN07 with 0x%.4x (result: 0x%.4x)\n", arg, *result);
+ return ret;
+}
+
+/*
+ * sony_nc_values input/output validate functions
+ */
+
+/* brightness_default_validate:
+ *
+ * manipulate input output values to keep consistency with the
+ * backlight framework for which brightness values are 0-based.
+ */
+static int brightness_default_validate(const int direction, const int value)
+{
+ switch (direction) {
+ case SNC_VALIDATE_OUT:
+ return value - 1;
+ case SNC_VALIDATE_IN:
+ if (value >= 0 && value < SONY_MAX_BRIGHTNESS)
+ return value + 1;
+ }
+ return -EINVAL;
+}
+
+/* boolean_validate:
+ *
+ * on input validate boolean values 0/1, on output just pass the
+ * received value.
+ */
+static int boolean_validate(const int direction, const int value)
+{
+ if (direction == SNC_VALIDATE_IN) {
+ if (value != 0 && value != 1)
+ return -EINVAL;
+ }
+ return value;
+}
+
+/*
+ * Sysfs show/store common to all sony_nc_values
+ */
+static ssize_t sony_nc_sysfs_show(struct device *dev, struct device_attribute *attr,
+ char *buffer)
+{
+ int value, ret = 0;
+ struct sony_nc_value *item =
+ container_of(attr, struct sony_nc_value, devattr);
+
+ if (!*item->acpiget)
+ return -EIO;
+
+ ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiget, NULL,
+ &value);
+ if (ret < 0)
+ return -EIO;
+
+ if (item->validate)
+ value = item->validate(SNC_VALIDATE_OUT, value);
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", value);
+}
+
+static ssize_t sony_nc_sysfs_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ int value;
+ int ret = 0;
+ struct sony_nc_value *item =
+ container_of(attr, struct sony_nc_value, devattr);
+
+ if (!item->acpiset)
+ return -EIO;
+
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoint(buffer, 10, &value))
+ return -EINVAL;
+
+ if (item->validate)
+ value = item->validate(SNC_VALIDATE_IN, value);
+
+ if (value < 0)
+ return value;
+
+ ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiset,
+ &value, NULL);
+ if (ret < 0)
+ return -EIO;
+
+ item->value = value;
+ item->valid = 1;
+ return count;
+}
+
+
+/*
+ * Backlight device
+ */
+struct sony_backlight_props {
+ struct backlight_device *dev;
+ int handle;
+ int cmd_base;
+ u8 offset;
+ u8 maxlvl;
+};
+struct sony_backlight_props sony_bl_props;
+
+static int sony_backlight_update_status(struct backlight_device *bd)
+{
+ int arg = bd->props.brightness + 1;
+ return sony_nc_int_call(sony_nc_acpi_handle, "SBRT", &arg, NULL);
+}
+
+static int sony_backlight_get_brightness(struct backlight_device *bd)
+{
+ int value;
+
+ if (sony_nc_int_call(sony_nc_acpi_handle, "GBRT", NULL, &value))
+ return 0;
+ /* brightness levels are 1-based, while backlight ones are 0-based */
+ return value - 1;
+}
+
+static int sony_nc_get_brightness_ng(struct backlight_device *bd)
+{
+ int result;
+ struct sony_backlight_props *sdev =
+ (struct sony_backlight_props *)bl_get_data(bd);
+
+ sony_call_snc_handle(sdev->handle, sdev->cmd_base + 0x100, &result);
+
+ return (result & 0xff) - sdev->offset;
+}
+
+static int sony_nc_update_status_ng(struct backlight_device *bd)
+{
+ int value, result;
+ struct sony_backlight_props *sdev =
+ (struct sony_backlight_props *)bl_get_data(bd);
+
+ value = bd->props.brightness + sdev->offset;
+ if (sony_call_snc_handle(sdev->handle, sdev->cmd_base | (value << 0x10),
+ &result))
+ return -EIO;
+
+ return value;
+}
+
+static const struct backlight_ops sony_backlight_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = sony_backlight_update_status,
+ .get_brightness = sony_backlight_get_brightness,
+};
+static const struct backlight_ops sony_backlight_ng_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = sony_nc_update_status_ng,
+ .get_brightness = sony_nc_get_brightness_ng,
+};
+
+/*
+ * New SNC-only Vaios event mapping to driver known keys
+ */
+struct sony_nc_event {
+ u8 data;
+ u8 event;
+};
+
+static struct sony_nc_event sony_100_events[] = {
+ { 0x90, SONYPI_EVENT_PKEY_P1 },
+ { 0x10, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x91, SONYPI_EVENT_PKEY_P2 },
+ { 0x11, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x81, SONYPI_EVENT_FNKEY_F1 },
+ { 0x01, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x82, SONYPI_EVENT_FNKEY_F2 },
+ { 0x02, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x83, SONYPI_EVENT_FNKEY_F3 },
+ { 0x03, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x84, SONYPI_EVENT_FNKEY_F4 },
+ { 0x04, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x85, SONYPI_EVENT_FNKEY_F5 },
+ { 0x05, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x86, SONYPI_EVENT_FNKEY_F6 },
+ { 0x06, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x87, SONYPI_EVENT_FNKEY_F7 },
+ { 0x07, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x88, SONYPI_EVENT_FNKEY_F8 },
+ { 0x08, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x89, SONYPI_EVENT_FNKEY_F9 },
+ { 0x09, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x8A, SONYPI_EVENT_FNKEY_F10 },
+ { 0x0A, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x8B, SONYPI_EVENT_FNKEY_F11 },
+ { 0x0B, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x8C, SONYPI_EVENT_FNKEY_F12 },
+ { 0x0C, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x9d, SONYPI_EVENT_ZOOM_PRESSED },
+ { 0x1d, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x9f, SONYPI_EVENT_CD_EJECT_PRESSED },
+ { 0x1f, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0xa1, SONYPI_EVENT_MEDIA_PRESSED },
+ { 0x21, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0xa4, SONYPI_EVENT_CD_EJECT_PRESSED },
+ { 0x24, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0xa5, SONYPI_EVENT_VENDOR_PRESSED },
+ { 0x25, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0xa6, SONYPI_EVENT_HELP_PRESSED },
+ { 0x26, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0, 0 },
+};
+
+static struct sony_nc_event sony_127_events[] = {
+ { 0x81, SONYPI_EVENT_MODEKEY_PRESSED },
+ { 0x01, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x82, SONYPI_EVENT_PKEY_P1 },
+ { 0x02, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x83, SONYPI_EVENT_PKEY_P2 },
+ { 0x03, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x84, SONYPI_EVENT_PKEY_P3 },
+ { 0x04, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x85, SONYPI_EVENT_PKEY_P4 },
+ { 0x05, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x86, SONYPI_EVENT_PKEY_P5 },
+ { 0x06, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0x87, SONYPI_EVENT_SETTINGKEY_PRESSED },
+ { 0x07, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0, 0 },
+};
+
+static int sony_nc_hotkeys_decode(u32 event, unsigned int handle)
+{
+ int ret = -EINVAL;
+ unsigned int result = 0;
+ struct sony_nc_event *key_event;
+
+ if (sony_call_snc_handle(handle, 0x200, &result)) {
+ dprintk("Unable to decode event 0x%.2x 0x%.2x\n", handle,
+ event);
+ return -EINVAL;
+ }
+
+ result &= 0xFF;
+
+ if (handle == 0x0100)
+ key_event = sony_100_events;
+ else
+ key_event = sony_127_events;
+
+ for (; key_event->data; key_event++) {
+ if (key_event->data == result) {
+ ret = key_event->event;
+ break;
+ }
+ }
+
+ if (!key_event->data)
+ pr_info("Unknown hotkey 0x%.2x/0x%.2x (handle 0x%.2x)\n",
+ event, result, handle);
+
+ return ret;
+}
+
+/*
+ * ACPI callbacks
+ */
+enum event_types {
+ HOTKEY = 1,
+ KILLSWITCH,
+ GFX_SWITCH
+};
+static void sony_nc_notify(struct acpi_device *device, u32 event)
+{
+ u32 real_ev = event;
+ u8 ev_type = 0;
+ dprintk("sony_nc_notify, event: 0x%.2x\n", event);
+
+ if (event >= 0x90) {
+ unsigned int result = 0;
+ unsigned int arg = 0;
+ unsigned int handle = 0;
+ unsigned int offset = event - 0x90;
+
+ if (offset >= ARRAY_SIZE(handles->cap)) {
+ pr_err("Event 0x%x outside of capabilities list\n",
+ event);
+ return;
+ }
+ handle = handles->cap[offset];
+
+ /* list of handles known for generating events */
+ switch (handle) {
+ /* hotkey event */
+ case 0x0100:
+ case 0x0127:
+ ev_type = HOTKEY;
+ real_ev = sony_nc_hotkeys_decode(event, handle);
+
+ if (real_ev > 0)
+ sony_laptop_report_input_event(real_ev);
+ else
+ /* restore the original event for reporting */
+ real_ev = event;
+
+ break;
+
+ /* wlan switch */
+ case 0x0124:
+ case 0x0135:
+ /* events on this handle are reported when the
+ * switch changes position or for battery
+ * events. We'll notify both of them but only
+ * update the rfkill device status when the
+ * switch is moved.
+ */
+ ev_type = KILLSWITCH;
+ sony_call_snc_handle(handle, 0x0100, &result);
+ real_ev = result & 0x03;
+
+ /* hw switch event */
+ if (real_ev == 1)
+ sony_nc_rfkill_update();
+
+ break;
+
+ case 0x0128:
+ case 0x0146:
+ /* Hybrid GFX switching */
+ sony_call_snc_handle(handle, 0x0000, &result);
+ dprintk("GFX switch event received (reason: %s)\n",
+ (result == 0x1) ? "switch change" :
+ (result == 0x2) ? "output switch" :
+ (result == 0x3) ? "output switch" :
+ "");
+
+ ev_type = GFX_SWITCH;
+ real_ev = __sony_nc_gfx_switch_status_get();
+ break;
+
+ case 0x015B:
+ /* Hybrid GFX switching SVS151290S */
+ ev_type = GFX_SWITCH;
+ real_ev = __sony_nc_gfx_switch_status_get();
+ break;
+ default:
+ dprintk("Unknown event 0x%x for handle 0x%x\n",
+ event, handle);
+ break;
+ }
+
+ /* clear the event (and the event reason when present) */
+ arg = 1 << offset;
+ sony_nc_int_call(sony_nc_acpi_handle, "SN05", &arg, &result);
+
+ } else {
+ /* old style event */
+ ev_type = HOTKEY;
+ sony_laptop_report_input_event(real_ev);
+ }
+
+ acpi_bus_generate_proc_event(sony_nc_acpi_device, ev_type, real_ev);
+
+ acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class,
+ dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev);
+}
+
+static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
+ void *context, void **return_value)
+{
+ struct acpi_device_info *info;
+
+ if (ACPI_SUCCESS(acpi_get_object_info(handle, &info))) {
+ pr_warn("method: name: %4.4s, args %X\n",
+ (char *)&info->name, info->param_count);
+
+ kfree(info);
+ }
+
+ return AE_OK;
+}
+
+/*
+ * ACPI device
+ */
+static void sony_nc_function_setup(struct acpi_device *device,
+ struct platform_device *pf_device)
+{
+ unsigned int i, result, bitmask, arg;
+
+ if (!handles)
+ return;
+
+ /* setup found handles here */
+ for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+ unsigned int handle = handles->cap[i];
+
+ if (!handle)
+ continue;
+
+ dprintk("setting up handle 0x%.4x\n", handle);
+
+ switch (handle) {
+ case 0x0100:
+ case 0x0101:
+ case 0x0127:
+ /* setup hotkeys */
+ sony_call_snc_handle(handle, 0, &result);
+ break;
+ case 0x0102:
+ /* setup hotkeys */
+ sony_call_snc_handle(handle, 0x100, &result);
+ break;
+ case 0x0105:
+ case 0x0148:
+ /* touchpad enable/disable */
+ result = sony_nc_touchpad_setup(pf_device, handle);
+ if (result)
+ pr_err("couldn't set up touchpad control function (%d)\n",
+ result);
+ break;
+ case 0x0115:
+ case 0x0136:
+ case 0x013f:
+ result = sony_nc_battery_care_setup(pf_device, handle);
+ if (result)
+ pr_err("couldn't set up battery care function (%d)\n",
+ result);
+ break;
+ case 0x0119:
+ result = sony_nc_lid_resume_setup(pf_device);
+ if (result)
+ pr_err("couldn't set up lid resume function (%d)\n",
+ result);
+ break;
+ case 0x0122:
+ result = sony_nc_thermal_setup(pf_device);
+ if (result)
+ pr_err("couldn't set up thermal profile function (%d)\n",
+ result);
+ break;
+ case 0x0128:
+ case 0x0146:
+ case 0x015B:
+ result = sony_nc_gfx_switch_setup(pf_device, handle);
+ if (result)
+ pr_err("couldn't set up GFX Switch status (%d)\n",
+ result);
+ break;
+ case 0x0131:
+ result = sony_nc_highspeed_charging_setup(pf_device);
+ if (result)
+ pr_err("couldn't set up high speed charging function (%d)\n",
+ result);
+ break;
+ case 0x0124:
+ case 0x0135:
+ result = sony_nc_rfkill_setup(device, handle);
+ if (result)
+ pr_err("couldn't set up rfkill support (%d)\n",
+ result);
+ break;
+ case 0x0137:
+ case 0x0143:
+ case 0x014b:
+ case 0x014c:
+ case 0x0163:
+ result = sony_nc_kbd_backlight_setup(pf_device, handle);
+ if (result)
+ pr_err("couldn't set up keyboard backlight function (%d)\n",
+ result);
+ break;
+ default:
+ continue;
+ }
+ }
+
+ /* Enable all events */
+ arg = 0x10;
+ if (!sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg, &bitmask))
+ sony_nc_int_call(sony_nc_acpi_handle, "SN02", &bitmask,
+ &result);
+}
+
+static void sony_nc_function_cleanup(struct platform_device *pd)
+{
+ unsigned int i, result, bitmask, handle;
+
+ /* get enabled events and disable them */
+ sony_nc_int_call(sony_nc_acpi_handle, "SN01", NULL, &bitmask);
+ sony_nc_int_call(sony_nc_acpi_handle, "SN03", &bitmask, &result);
+
+ /* cleanup handles here */
+ for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+
+ handle = handles->cap[i];
+
+ if (!handle)
+ continue;
+
+ switch (handle) {
+ case 0x0105:
+ case 0x0148:
+ sony_nc_touchpad_cleanup(pd);
+ break;
+ case 0x0115:
+ case 0x0136:
+ case 0x013f:
+ sony_nc_battery_care_cleanup(pd);
+ break;
+ case 0x0119:
+ sony_nc_lid_resume_cleanup(pd);
+ break;
+ case 0x0122:
+ sony_nc_thermal_cleanup(pd);
+ break;
+ case 0x0128:
+ case 0x0146:
+ case 0x015B:
+ sony_nc_gfx_switch_cleanup(pd);
+ break;
+ case 0x0131:
+ sony_nc_highspeed_charging_cleanup(pd);
+ break;
+ case 0x0124:
+ case 0x0135:
+ sony_nc_rfkill_cleanup();
+ break;
+ case 0x0137:
+ case 0x0143:
+ case 0x014b:
+ case 0x014c:
+ case 0x0163:
+ sony_nc_kbd_backlight_cleanup(pd);
+ break;
+ default:
+ continue;
+ }
+ }
+
+ /* finally cleanup the handles list */
+ sony_nc_handles_cleanup(pd);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void sony_nc_function_resume(void)
+{
+ unsigned int i, result, bitmask, arg;
+
+ dprintk("Resuming SNC device\n");
+
+ for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+ unsigned int handle = handles->cap[i];
+
+ if (!handle)
+ continue;
+
+ switch (handle) {
+ case 0x0100:
+ case 0x0101:
+ case 0x0127:
+ /* re-enable hotkeys */
+ sony_call_snc_handle(handle, 0, &result);
+ break;
+ case 0x0102:
+ /* re-enable hotkeys */
+ sony_call_snc_handle(handle, 0x100, &result);
+ break;
+ case 0x0122:
+ sony_nc_thermal_resume();
+ break;
+ case 0x0124:
+ case 0x0135:
+ sony_nc_rfkill_update();
+ break;
+ case 0x0137:
+ case 0x0143:
+ case 0x014b:
+ case 0x014c:
+ case 0x0163:
+ sony_nc_kbd_backlight_resume();
+ break;
+ default:
+ continue;
+ }
+ }
+
+ /* Enable all events */
+ arg = 0x10;
+ if (!sony_nc_int_call(sony_nc_acpi_handle, "SN00", &arg, &bitmask))
+ sony_nc_int_call(sony_nc_acpi_handle, "SN02", &bitmask,
+ &result);
+}
+
+static int sony_nc_resume(struct device *dev)
+{
+ struct sony_nc_value *item;
+ acpi_handle handle;
+
+ for (item = sony_nc_values; item->name; item++) {
+ int ret;
+
+ if (!item->valid)
+ continue;
+ ret = sony_nc_int_call(sony_nc_acpi_handle, *item->acpiset,
+ &item->value, NULL);
+ if (ret < 0) {
+ pr_err("%s: %d\n", __func__, ret);
+ break;
+ }
+ }
+
+ if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "ECON",
+ &handle))) {
+ int arg = 1;
+ if (sony_nc_int_call(sony_nc_acpi_handle, "ECON", &arg, NULL))
+ dprintk("ECON Method failed\n");
+ }
+
+ if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00",
+ &handle)))
+ sony_nc_function_resume();
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(sony_nc_pm, NULL, sony_nc_resume);
+
+static void sony_nc_rfkill_cleanup(void)
+{
+ int i;
+
+ for (i = 0; i < N_SONY_RFKILL; i++) {
+ if (sony_rfkill_devices[i]) {
+ rfkill_unregister(sony_rfkill_devices[i]);
+ rfkill_destroy(sony_rfkill_devices[i]);
+ }
+ }
+}
+
+static int sony_nc_rfkill_set(void *data, bool blocked)
+{
+ int result;
+ int argument = sony_rfkill_address[(long) data] + 0x100;
+
+ if (!blocked)
+ argument |= 0x070000;
+
+ return sony_call_snc_handle(sony_rfkill_handle, argument, &result);
+}
+
+static const struct rfkill_ops sony_rfkill_ops = {
+ .set_block = sony_nc_rfkill_set,
+};
+
+static int sony_nc_setup_rfkill(struct acpi_device *device,
+ enum sony_nc_rfkill nc_type)
+{
+ int err = 0;
+ struct rfkill *rfk;
+ enum rfkill_type type;
+ const char *name;
+ int result;
+ bool hwblock, swblock;
+
+ switch (nc_type) {
+ case SONY_WIFI:
+ type = RFKILL_TYPE_WLAN;
+ name = "sony-wifi";
+ break;
+ case SONY_BLUETOOTH:
+ type = RFKILL_TYPE_BLUETOOTH;
+ name = "sony-bluetooth";
+ break;
+ case SONY_WWAN:
+ type = RFKILL_TYPE_WWAN;
+ name = "sony-wwan";
+ break;
+ case SONY_WIMAX:
+ type = RFKILL_TYPE_WIMAX;
+ name = "sony-wimax";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rfk = rfkill_alloc(name, &device->dev, type,
+ &sony_rfkill_ops, (void *)nc_type);
+ if (!rfk)
+ return -ENOMEM;
+
+ if (sony_call_snc_handle(sony_rfkill_handle, 0x200, &result) < 0) {
+ rfkill_destroy(rfk);
+ return -1;
+ }
+ hwblock = !(result & 0x1);
+
+ if (sony_call_snc_handle(sony_rfkill_handle,
+ sony_rfkill_address[nc_type],
+ &result) < 0) {
+ rfkill_destroy(rfk);
+ return -1;
+ }
+ swblock = !(result & 0x2);
+
+ rfkill_init_sw_state(rfk, swblock);
+ rfkill_set_hw_state(rfk, hwblock);
+
+ err = rfkill_register(rfk);
+ if (err) {
+ rfkill_destroy(rfk);
+ return err;
+ }
+ sony_rfkill_devices[nc_type] = rfk;
+ return err;
+}
+
+static void sony_nc_rfkill_update(void)
+{
+ enum sony_nc_rfkill i;
+ int result;
+ bool hwblock;
+
+ sony_call_snc_handle(sony_rfkill_handle, 0x200, &result);
+ hwblock = !(result & 0x1);
+
+ for (i = 0; i < N_SONY_RFKILL; i++) {
+ int argument = sony_rfkill_address[i];
+
+ if (!sony_rfkill_devices[i])
+ continue;
+
+ if (hwblock) {
+ if (rfkill_set_hw_state(sony_rfkill_devices[i], true)) {
+ /* we already know we're blocked */
+ }
+ continue;
+ }
+
+ sony_call_snc_handle(sony_rfkill_handle, argument, &result);
+ rfkill_set_states(sony_rfkill_devices[i],
+ !(result & 0x2), false);
+ }
+}
+
+static int sony_nc_rfkill_setup(struct acpi_device *device,
+ unsigned int handle)
+{
+ u64 offset;
+ int i;
+ unsigned char buffer[32] = { 0 };
+
+ offset = sony_find_snc_handle(handle);
+ sony_rfkill_handle = handle;
+
+ i = sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &offset, buffer,
+ 32);
+ if (i < 0)
+ return i;
+
+ /* The buffer is filled with magic numbers describing the devices
+ * available, 0xff terminates the enumeration.
+ * Known codes:
+ * 0x00 WLAN
+ * 0x10 BLUETOOTH
+ * 0x20 WWAN GPRS-EDGE
+ * 0x21 WWAN HSDPA
+ * 0x22 WWAN EV-DO
+ * 0x23 WWAN GPS
+ * 0x25 Gobi WWAN no GPS
+ * 0x26 Gobi WWAN + GPS
+ * 0x28 Gobi WWAN no GPS
+ * 0x29 Gobi WWAN + GPS
+ * 0x30 WIMAX
+ * 0x50 Gobi WWAN no GPS
+ * 0x51 Gobi WWAN + GPS
+ * 0x70 no SIM card slot
+ * 0x71 SIM card slot
+ */
+ for (i = 0; i < ARRAY_SIZE(buffer); i++) {
+
+ if (buffer[i] == 0xff)
+ break;
+
+ dprintk("Radio devices, found 0x%.2x\n", buffer[i]);
+
+ if (buffer[i] == 0 && !sony_rfkill_devices[SONY_WIFI])
+ sony_nc_setup_rfkill(device, SONY_WIFI);
+
+ if (buffer[i] == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH])
+ sony_nc_setup_rfkill(device, SONY_BLUETOOTH);
+
+ if (((0xf0 & buffer[i]) == 0x20 ||
+ (0xf0 & buffer[i]) == 0x50) &&
+ !sony_rfkill_devices[SONY_WWAN])
+ sony_nc_setup_rfkill(device, SONY_WWAN);
+
+ if (buffer[i] == 0x30 && !sony_rfkill_devices[SONY_WIMAX])
+ sony_nc_setup_rfkill(device, SONY_WIMAX);
+ }
+ return 0;
+}
+
+/* Keyboard backlight feature */
+struct kbd_backlight {
+ unsigned int handle;
+ unsigned int base;
+ unsigned int mode;
+ unsigned int timeout;
+ struct device_attribute mode_attr;
+ struct device_attribute timeout_attr;
+};
+
+static struct kbd_backlight *kbdbl_ctl;
+
+static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value)
+{
+ int result;
+
+ if (value > 1)
+ return -EINVAL;
+
+ if (sony_call_snc_handle(kbdbl_ctl->handle,
+ (value << 0x10) | (kbdbl_ctl->base), &result))
+ return -EIO;
+
+ /* Try to turn the light on/off immediately */
+ sony_call_snc_handle(kbdbl_ctl->handle,
+ (value << 0x10) | (kbdbl_ctl->base + 0x100), &result);
+
+ kbdbl_ctl->mode = value;
+
+ return 0;
+}
+
+static ssize_t sony_nc_kbd_backlight_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ int ret = 0;
+ unsigned long value;
+
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ ret = __sony_nc_kbd_backlight_mode_set(value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t sony_nc_kbd_backlight_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ ssize_t count = 0;
+ count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_ctl->mode);
+ return count;
+}
+
+static int __sony_nc_kbd_backlight_timeout_set(u8 value)
+{
+ int result;
+
+ if (value > 3)
+ return -EINVAL;
+
+ if (sony_call_snc_handle(kbdbl_ctl->handle, (value << 0x10) |
+ (kbdbl_ctl->base + 0x200), &result))
+ return -EIO;
+
+ kbdbl_ctl->timeout = value;
+
+ return 0;
+}
+
+static ssize_t sony_nc_kbd_backlight_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ int ret = 0;
+ unsigned long value;
+
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ ret = __sony_nc_kbd_backlight_timeout_set(value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t sony_nc_kbd_backlight_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ ssize_t count = 0;
+ count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_ctl->timeout);
+ return count;
+}
+
+static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
+ unsigned int handle)
+{
+ int result;
+ int ret = 0;
+
+ /* verify the kbd backlight presence, these handles are not used for
+ * keyboard backlight only
+ */
+ ret = sony_call_snc_handle(handle, handle == 0x0137 ? 0x0B00 : 0x0100,
+ &result);
+ if (ret)
+ return ret;
+
+ if ((handle == 0x0137 && !(result & 0x02)) ||
+ !(result & 0x01)) {
+ dprintk("no backlight keyboard found\n");
+ return 0;
+ }
+
+ kbdbl_ctl = kzalloc(sizeof(*kbdbl_ctl), GFP_KERNEL);
+ if (!kbdbl_ctl)
+ return -ENOMEM;
+
+ kbdbl_ctl->handle = handle;
+ if (handle == 0x0137)
+ kbdbl_ctl->base = 0x0C00;
+ else
+ kbdbl_ctl->base = 0x4000;
+
+ sysfs_attr_init(&kbdbl_ctl->mode_attr.attr);
+ kbdbl_ctl->mode_attr.attr.name = "kbd_backlight";
+ kbdbl_ctl->mode_attr.attr.mode = S_IRUGO | S_IWUSR;
+ kbdbl_ctl->mode_attr.show = sony_nc_kbd_backlight_mode_show;
+ kbdbl_ctl->mode_attr.store = sony_nc_kbd_backlight_mode_store;
+
+ sysfs_attr_init(&kbdbl_ctl->timeout_attr.attr);
+ kbdbl_ctl->timeout_attr.attr.name = "kbd_backlight_timeout";
+ kbdbl_ctl->timeout_attr.attr.mode = S_IRUGO | S_IWUSR;
+ kbdbl_ctl->timeout_attr.show = sony_nc_kbd_backlight_timeout_show;
+ kbdbl_ctl->timeout_attr.store = sony_nc_kbd_backlight_timeout_store;
+
+ ret = device_create_file(&pd->dev, &kbdbl_ctl->mode_attr);
+ if (ret)
+ goto outkzalloc;
+
+ ret = device_create_file(&pd->dev, &kbdbl_ctl->timeout_attr);
+ if (ret)
+ goto outmode;
+
+ __sony_nc_kbd_backlight_mode_set(kbd_backlight);
+ __sony_nc_kbd_backlight_timeout_set(kbd_backlight_timeout);
+
+ return 0;
+
+outmode:
+ device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr);
+outkzalloc:
+ kfree(kbdbl_ctl);
+ kbdbl_ctl = NULL;
+ return ret;
+}
+
+static void sony_nc_kbd_backlight_cleanup(struct platform_device *pd)
+{
+ if (kbdbl_ctl) {
+ int result;
+
+ device_remove_file(&pd->dev, &kbdbl_ctl->mode_attr);
+ device_remove_file(&pd->dev, &kbdbl_ctl->timeout_attr);
+
+ /* restore the default hw behaviour */
+ sony_call_snc_handle(kbdbl_ctl->handle,
+ kbdbl_ctl->base | 0x10000, &result);
+ sony_call_snc_handle(kbdbl_ctl->handle,
+ kbdbl_ctl->base + 0x200, &result);
+
+ kfree(kbdbl_ctl);
+ kbdbl_ctl = NULL;
+ }
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void sony_nc_kbd_backlight_resume(void)
+{
+ int ignore = 0;
+
+ if (!kbdbl_ctl)
+ return;
+
+ if (kbdbl_ctl->mode == 0)
+ sony_call_snc_handle(kbdbl_ctl->handle, kbdbl_ctl->base,
+ &ignore);
+
+ if (kbdbl_ctl->timeout != 0)
+ sony_call_snc_handle(kbdbl_ctl->handle,
+ (kbdbl_ctl->base + 0x200) |
+ (kbdbl_ctl->timeout << 0x10), &ignore);
+}
+#endif
+
+struct battery_care_control {
+ struct device_attribute attrs[2];
+ unsigned int handle;
+};
+static struct battery_care_control *bcare_ctl;
+
+static ssize_t sony_nc_battery_care_limit_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned int result, cmd;
+ unsigned long value;
+
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ /* limit values (2 bits):
+ * 00 - none
+ * 01 - 80%
+ * 10 - 50%
+ * 11 - 100%
+ *
+ * bit 0: 0 disable BCL, 1 enable BCL
+ * bit 1: 1 tell to store the battery limit (see bits 6,7) too
+ * bits 2,3: reserved
+ * bits 4,5: store the limit into the EC
+ * bits 6,7: store the limit into the battery
+ */
+ cmd = 0;
+
+ if (value > 0) {
+ if (value <= 50)
+ cmd = 0x20;
+
+ else if (value <= 80)
+ cmd = 0x10;
+
+ else if (value <= 100)
+ cmd = 0x30;
+
+ else
+ return -EINVAL;
+
+ /*
+ * handle 0x0115 should allow storing on battery too;
+ * handle 0x0136 same as 0x0115 + health status;
+ * handle 0x013f, same as 0x0136 but no storing on the battery
+ */
+ if (bcare_ctl->handle != 0x013f)
+ cmd = cmd | (cmd << 2);
+
+ cmd = (cmd | 0x1) << 0x10;
+ }
+
+ if (sony_call_snc_handle(bcare_ctl->handle, cmd | 0x0100, &result))
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t sony_nc_battery_care_limit_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ unsigned int result, status;
+
+ if (sony_call_snc_handle(bcare_ctl->handle, 0x0000, &result))
+ return -EIO;
+
+ status = (result & 0x01) ? ((result & 0x30) >> 0x04) : 0;
+ switch (status) {
+ case 1:
+ status = 80;
+ break;
+ case 2:
+ status = 50;
+ break;
+ case 3:
+ status = 100;
+ break;
+ default:
+ status = 0;
+ break;
+ }
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t sony_nc_battery_care_health_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ ssize_t count = 0;
+ unsigned int health;
+
+ if (sony_call_snc_handle(bcare_ctl->handle, 0x0200, &health))
+ return -EIO;
+
+ count = snprintf(buffer, PAGE_SIZE, "%d\n", health & 0xff);
+
+ return count;
+}
+
+static int sony_nc_battery_care_setup(struct platform_device *pd,
+ unsigned int handle)
+{
+ int ret = 0;
+
+ bcare_ctl = kzalloc(sizeof(struct battery_care_control), GFP_KERNEL);
+ if (!bcare_ctl)
+ return -ENOMEM;
+
+ bcare_ctl->handle = handle;
+
+ sysfs_attr_init(&bcare_ctl->attrs[0].attr);
+ bcare_ctl->attrs[0].attr.name = "battery_care_limiter";
+ bcare_ctl->attrs[0].attr.mode = S_IRUGO | S_IWUSR;
+ bcare_ctl->attrs[0].show = sony_nc_battery_care_limit_show;
+ bcare_ctl->attrs[0].store = sony_nc_battery_care_limit_store;
+
+ ret = device_create_file(&pd->dev, &bcare_ctl->attrs[0]);
+ if (ret)
+ goto outkzalloc;
+
+ /* 0x0115 is for models with no health reporting capability */
+ if (handle == 0x0115)
+ return 0;
+
+ sysfs_attr_init(&bcare_ctl->attrs[1].attr);
+ bcare_ctl->attrs[1].attr.name = "battery_care_health";
+ bcare_ctl->attrs[1].attr.mode = S_IRUGO;
+ bcare_ctl->attrs[1].show = sony_nc_battery_care_health_show;
+
+ ret = device_create_file(&pd->dev, &bcare_ctl->attrs[1]);
+ if (ret)
+ goto outlimiter;
+
+ return 0;
+
+outlimiter:
+ device_remove_file(&pd->dev, &bcare_ctl->attrs[0]);
+
+outkzalloc:
+ kfree(bcare_ctl);
+ bcare_ctl = NULL;
+
+ return ret;
+}
+
+static void sony_nc_battery_care_cleanup(struct platform_device *pd)
+{
+ if (bcare_ctl) {
+ device_remove_file(&pd->dev, &bcare_ctl->attrs[0]);
+ if (bcare_ctl->handle != 0x0115)
+ device_remove_file(&pd->dev, &bcare_ctl->attrs[1]);
+
+ kfree(bcare_ctl);
+ bcare_ctl = NULL;
+ }
+}
+
+struct snc_thermal_ctrl {
+ unsigned int mode;
+ unsigned int profiles;
+ struct device_attribute mode_attr;
+ struct device_attribute profiles_attr;
+};
+static struct snc_thermal_ctrl *th_handle;
+
+#define THM_PROFILE_MAX 3
+static const char * const snc_thermal_profiles[] = {
+ "balanced",
+ "silent",
+ "performance"
+};
+
+static int sony_nc_thermal_mode_set(unsigned short mode)
+{
+ unsigned int result;
+
+ /* the thermal profile seems to be a two bit bitmask:
+ * lsb -> silent
+ * msb -> performance
+ * no bit set is the normal operation and is always valid
+ * Some vaio models only have "balanced" and "performance"
+ */
+ if ((mode && !(th_handle->profiles & mode)) || mode >= THM_PROFILE_MAX)
+ return -EINVAL;
+
+ if (sony_call_snc_handle(0x0122, mode << 0x10 | 0x0200, &result))
+ return -EIO;
+
+ th_handle->mode = mode;
+
+ return 0;
+}
+
+static int sony_nc_thermal_mode_get(void)
+{
+ unsigned int result;
+
+ if (sony_call_snc_handle(0x0122, 0x0100, &result))
+ return -EIO;
+
+ return result & 0xff;
+}
+
+static ssize_t sony_nc_thermal_profiles_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ short cnt;
+ size_t idx = 0;
+
+ for (cnt = 0; cnt < THM_PROFILE_MAX; cnt++) {
+ if (!cnt || (th_handle->profiles & cnt))
+ idx += snprintf(buffer + idx, PAGE_SIZE - idx, "%s ",
+ snc_thermal_profiles[cnt]);
+ }
+ idx += snprintf(buffer + idx, PAGE_SIZE - idx, "\n");
+
+ return idx;
+}
+
+static ssize_t sony_nc_thermal_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned short cmd;
+ size_t len = count;
+
+ if (count == 0)
+ return -EINVAL;
+
+ /* skip the newline if present */
+ if (buffer[len - 1] == '\n')
+ len--;
+
+ for (cmd = 0; cmd < THM_PROFILE_MAX; cmd++)
+ if (strncmp(buffer, snc_thermal_profiles[cmd], len) == 0)
+ break;
+
+ if (sony_nc_thermal_mode_set(cmd))
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t sony_nc_thermal_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ ssize_t count = 0;
+ int mode = sony_nc_thermal_mode_get();
+
+ if (mode < 0)
+ return mode;
+
+ count = snprintf(buffer, PAGE_SIZE, "%s\n", snc_thermal_profiles[mode]);
+
+ return count;
+}
+
+static int sony_nc_thermal_setup(struct platform_device *pd)
+{
+ int ret = 0;
+ th_handle = kzalloc(sizeof(struct snc_thermal_ctrl), GFP_KERNEL);
+ if (!th_handle)
+ return -ENOMEM;
+
+ ret = sony_call_snc_handle(0x0122, 0x0000, &th_handle->profiles);
+ if (ret) {
+ pr_warn("couldn't to read the thermal profiles\n");
+ goto outkzalloc;
+ }
+
+ ret = sony_nc_thermal_mode_get();
+ if (ret < 0) {
+ pr_warn("couldn't to read the current thermal profile");
+ goto outkzalloc;
+ }
+ th_handle->mode = ret;
+
+ sysfs_attr_init(&th_handle->profiles_attr.attr);
+ th_handle->profiles_attr.attr.name = "thermal_profiles";
+ th_handle->profiles_attr.attr.mode = S_IRUGO;
+ th_handle->profiles_attr.show = sony_nc_thermal_profiles_show;
+
+ sysfs_attr_init(&th_handle->mode_attr.attr);
+ th_handle->mode_attr.attr.name = "thermal_control";
+ th_handle->mode_attr.attr.mode = S_IRUGO | S_IWUSR;
+ th_handle->mode_attr.show = sony_nc_thermal_mode_show;
+ th_handle->mode_attr.store = sony_nc_thermal_mode_store;
+
+ ret = device_create_file(&pd->dev, &th_handle->profiles_attr);
+ if (ret)
+ goto outkzalloc;
+
+ ret = device_create_file(&pd->dev, &th_handle->mode_attr);
+ if (ret)
+ goto outprofiles;
+
+ return 0;
+
+outprofiles:
+ device_remove_file(&pd->dev, &th_handle->profiles_attr);
+outkzalloc:
+ kfree(th_handle);
+ th_handle = NULL;
+ return ret;
+}
+
+static void sony_nc_thermal_cleanup(struct platform_device *pd)
+{
+ if (th_handle) {
+ device_remove_file(&pd->dev, &th_handle->profiles_attr);
+ device_remove_file(&pd->dev, &th_handle->mode_attr);
+ kfree(th_handle);
+ th_handle = NULL;
+ }
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void sony_nc_thermal_resume(void)
+{
+ unsigned int status = sony_nc_thermal_mode_get();
+
+ if (status != th_handle->mode)
+ sony_nc_thermal_mode_set(th_handle->mode);
+}
+#endif
+
+/* resume on LID open */
+struct snc_lid_resume_control {
+ struct device_attribute attrs[3];
+ unsigned int status;
+};
+static struct snc_lid_resume_control *lid_ctl;
+
+static ssize_t sony_nc_lid_resume_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned int result, pos;
+ unsigned long value;
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value) || value > 1)
+ return -EINVAL;
+
+ /* the value we have to write to SNC is a bitmask:
+ * +--------------+
+ * | S3 | S4 | S5 |
+ * +--------------+
+ * 2 1 0
+ */
+ if (strcmp(attr->attr.name, "lid_resume_S3") == 0)
+ pos = 2;
+ else if (strcmp(attr->attr.name, "lid_resume_S4") == 0)
+ pos = 1;
+ else if (strcmp(attr->attr.name, "lid_resume_S5") == 0)
+ pos = 0;
+ else
+ return -EINVAL;
+
+ if (value)
+ value = lid_ctl->status | (1 << pos);
+ else
+ value = lid_ctl->status & ~(1 << pos);
+
+ if (sony_call_snc_handle(0x0119, value << 0x10 | 0x0100, &result))
+ return -EIO;
+
+ lid_ctl->status = value;
+
+ return count;
+}
+
+static ssize_t sony_nc_lid_resume_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ unsigned int pos;
+
+ if (strcmp(attr->attr.name, "lid_resume_S3") == 0)
+ pos = 2;
+ else if (strcmp(attr->attr.name, "lid_resume_S4") == 0)
+ pos = 1;
+ else if (strcmp(attr->attr.name, "lid_resume_S5") == 0)
+ pos = 0;
+ else
+ return -EINVAL;
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n",
+ (lid_ctl->status >> pos) & 0x01);
+}
+
+static int sony_nc_lid_resume_setup(struct platform_device *pd)
+{
+ unsigned int result;
+ int i;
+
+ if (sony_call_snc_handle(0x0119, 0x0000, &result))
+ return -EIO;
+
+ lid_ctl = kzalloc(sizeof(struct snc_lid_resume_control), GFP_KERNEL);
+ if (!lid_ctl)
+ return -ENOMEM;
+
+ lid_ctl->status = result & 0x7;
+
+ sysfs_attr_init(&lid_ctl->attrs[0].attr);
+ lid_ctl->attrs[0].attr.name = "lid_resume_S3";
+ lid_ctl->attrs[0].attr.mode = S_IRUGO | S_IWUSR;
+ lid_ctl->attrs[0].show = sony_nc_lid_resume_show;
+ lid_ctl->attrs[0].store = sony_nc_lid_resume_store;
+
+ sysfs_attr_init(&lid_ctl->attrs[1].attr);
+ lid_ctl->attrs[1].attr.name = "lid_resume_S4";
+ lid_ctl->attrs[1].attr.mode = S_IRUGO | S_IWUSR;
+ lid_ctl->attrs[1].show = sony_nc_lid_resume_show;
+ lid_ctl->attrs[1].store = sony_nc_lid_resume_store;
+
+ sysfs_attr_init(&lid_ctl->attrs[2].attr);
+ lid_ctl->attrs[2].attr.name = "lid_resume_S5";
+ lid_ctl->attrs[2].attr.mode = S_IRUGO | S_IWUSR;
+ lid_ctl->attrs[2].show = sony_nc_lid_resume_show;
+ lid_ctl->attrs[2].store = sony_nc_lid_resume_store;
+
+ for (i = 0; i < 3; i++) {
+ result = device_create_file(&pd->dev, &lid_ctl->attrs[i]);
+ if (result)
+ goto liderror;
+ }
+
+ return 0;
+
+liderror:
+ for (i--; i >= 0; i--)
+ device_remove_file(&pd->dev, &lid_ctl->attrs[i]);
+
+ kfree(lid_ctl);
+ lid_ctl = NULL;
+
+ return result;
+}
+
+static void sony_nc_lid_resume_cleanup(struct platform_device *pd)
+{
+ int i;
+
+ if (lid_ctl) {
+ for (i = 0; i < 3; i++)
+ device_remove_file(&pd->dev, &lid_ctl->attrs[i]);
+
+ kfree(lid_ctl);
+ lid_ctl = NULL;
+ }
+}
+
+/* GFX Switch position */
+enum gfx_switch {
+ SPEED,
+ STAMINA,
+ AUTO
+};
+struct snc_gfx_switch_control {
+ struct device_attribute attr;
+ unsigned int handle;
+};
+static struct snc_gfx_switch_control *gfxs_ctl;
+
+/* returns 0 for speed, 1 for stamina */
+static int __sony_nc_gfx_switch_status_get(void)
+{
+ unsigned int result;
+
+ if (sony_call_snc_handle(gfxs_ctl->handle,
+ gfxs_ctl->handle == 0x015B ? 0x0000 : 0x0100,
+ &result))
+ return -EIO;
+
+ switch (gfxs_ctl->handle) {
+ case 0x0146:
+ /* 1: discrete GFX (speed)
+ * 0: integrated GFX (stamina)
+ */
+ return result & 0x1 ? SPEED : STAMINA;
+ break;
+ case 0x015B:
+ /* 0: discrete GFX (speed)
+ * 1: integrated GFX (stamina)
+ */
+ return result & 0x1 ? STAMINA : SPEED;
+ break;
+ case 0x0128:
+ /* it's a more elaborated bitmask, for now:
+ * 2: integrated GFX (stamina)
+ * 0: discrete GFX (speed)
+ */
+ dprintk("GFX Status: 0x%x\n", result);
+ return result & 0x80 ? AUTO :
+ result & 0x02 ? STAMINA : SPEED;
+ break;
+ }
+ return -EINVAL;
+}
+
+static ssize_t sony_nc_gfx_switch_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buffer)
+{
+ int pos = __sony_nc_gfx_switch_status_get();
+
+ if (pos < 0)
+ return pos;
+
+ return snprintf(buffer, PAGE_SIZE, "%s\n", pos ? "speed" : "stamina");
+}
+
+static int sony_nc_gfx_switch_setup(struct platform_device *pd,
+ unsigned int handle)
+{
+ unsigned int result;
+
+ gfxs_ctl = kzalloc(sizeof(struct snc_gfx_switch_control), GFP_KERNEL);
+ if (!gfxs_ctl)
+ return -ENOMEM;
+
+ gfxs_ctl->handle = handle;
+
+ sysfs_attr_init(&gfxs_ctl->attr.attr);
+ gfxs_ctl->attr.attr.name = "gfx_switch_status";
+ gfxs_ctl->attr.attr.mode = S_IRUGO;
+ gfxs_ctl->attr.show = sony_nc_gfx_switch_status_show;
+
+ result = device_create_file(&pd->dev, &gfxs_ctl->attr);
+ if (result)
+ goto gfxerror;
+
+ return 0;
+
+gfxerror:
+ kfree(gfxs_ctl);
+ gfxs_ctl = NULL;
+
+ return result;
+}
+
+static void sony_nc_gfx_switch_cleanup(struct platform_device *pd)
+{
+ if (gfxs_ctl) {
+ device_remove_file(&pd->dev, &gfxs_ctl->attr);
+
+ kfree(gfxs_ctl);
+ gfxs_ctl = NULL;
+ }
+}
+
+/* High speed charging function */
+static struct device_attribute *hsc_handle;
+
+static ssize_t sony_nc_highspeed_charging_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned int result;
+ unsigned long value;
+
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value) || value > 1)
+ return -EINVAL;
+
+ if (sony_call_snc_handle(0x0131, value << 0x10 | 0x0200, &result))
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t sony_nc_highspeed_charging_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ unsigned int result;
+
+ if (sony_call_snc_handle(0x0131, 0x0100, &result))
+ return -EIO;
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", result & 0x01);
+}
+
+static int sony_nc_highspeed_charging_setup(struct platform_device *pd)
+{
+ unsigned int result;
+
+ if (sony_call_snc_handle(0x0131, 0x0000, &result) || !(result & 0x01)) {
+ /* some models advertise the handle but have no implementation
+ * for it
+ */
+ pr_info("No High Speed Charging capability found\n");
+ return 0;
+ }
+
+ hsc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ if (!hsc_handle)
+ return -ENOMEM;
+
+ sysfs_attr_init(&hsc_handle->attr);
+ hsc_handle->attr.name = "battery_highspeed_charging";
+ hsc_handle->attr.mode = S_IRUGO | S_IWUSR;
+ hsc_handle->show = sony_nc_highspeed_charging_show;
+ hsc_handle->store = sony_nc_highspeed_charging_store;
+
+ result = device_create_file(&pd->dev, hsc_handle);
+ if (result) {
+ kfree(hsc_handle);
+ hsc_handle = NULL;
+ return result;
+ }
+
+ return 0;
+}
+
+static void sony_nc_highspeed_charging_cleanup(struct platform_device *pd)
+{
+ if (hsc_handle) {
+ device_remove_file(&pd->dev, hsc_handle);
+ kfree(hsc_handle);
+ hsc_handle = NULL;
+ }
+}
+
+/* Touchpad enable/disable */
+struct touchpad_control {
+ struct device_attribute attr;
+ int handle;
+};
+static struct touchpad_control *tp_ctl;
+
+static ssize_t sony_nc_touchpad_store(struct device *dev,
+ struct device_attribute *attr, const char *buffer, size_t count)
+{
+ unsigned int result;
+ unsigned long value;
+
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value) || value > 1)
+ return -EINVAL;
+
+ /* sysfs: 0 disabled, 1 enabled
+ * EC: 0 enabled, 1 disabled
+ */
+ if (sony_call_snc_handle(tp_ctl->handle,
+ (!value << 0x10) | 0x100, &result))
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t sony_nc_touchpad_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ unsigned int result;
+
+ if (sony_call_snc_handle(tp_ctl->handle, 0x000, &result))
+ return -EINVAL;
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", !(result & 0x01));
+}
+
+static int sony_nc_touchpad_setup(struct platform_device *pd,
+ unsigned int handle)
+{
+ int ret = 0;
+
+ tp_ctl = kzalloc(sizeof(struct touchpad_control), GFP_KERNEL);
+ if (!tp_ctl)
+ return -ENOMEM;
+
+ tp_ctl->handle = handle;
+
+ sysfs_attr_init(&tp_ctl->attr.attr);
+ tp_ctl->attr.attr.name = "touchpad";
+ tp_ctl->attr.attr.mode = S_IRUGO | S_IWUSR;
+ tp_ctl->attr.show = sony_nc_touchpad_show;
+ tp_ctl->attr.store = sony_nc_touchpad_store;
+
+ ret = device_create_file(&pd->dev, &tp_ctl->attr);
+ if (ret) {
+ kfree(tp_ctl);
+ tp_ctl = NULL;
+ }
+
+ return ret;
+}
+
+static void sony_nc_touchpad_cleanup(struct platform_device *pd)
+{
+ if (tp_ctl) {
+ device_remove_file(&pd->dev, &tp_ctl->attr);
+ kfree(tp_ctl);
+ tp_ctl = NULL;
+ }
+}
+
+static void sony_nc_backlight_ng_read_limits(int handle,
+ struct sony_backlight_props *props)
+{
+ u64 offset;
+ int i;
+ int lvl_table_len = 0;
+ u8 min = 0xff, max = 0x00;
+ unsigned char buffer[32] = { 0 };
+
+ props->handle = handle;
+ props->offset = 0;
+ props->maxlvl = 0xff;
+
+ offset = sony_find_snc_handle(handle);
+
+ /* try to read the boundaries from ACPI tables, if we fail the above
+ * defaults should be reasonable
+ */
+ i = sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &offset, buffer,
+ 32);
+ if (i < 0)
+ return;
+
+ switch (handle) {
+ case 0x012f:
+ case 0x0137:
+ lvl_table_len = 9;
+ break;
+ case 0x143:
+ case 0x14b:
+ case 0x14c:
+ lvl_table_len = 16;
+ break;
+ }
+
+ /* the buffer lists brightness levels available, brightness levels are
+ * from position 0 to 8 in the array, other values are used by ALS
+ * control.
+ */
+ for (i = 0; i < lvl_table_len && i < ARRAY_SIZE(buffer); i++) {
+
+ dprintk("Brightness level: %d\n", buffer[i]);
+
+ if (!buffer[i])
+ break;
+
+ if (buffer[i] > max)
+ max = buffer[i];
+ if (buffer[i] < min)
+ min = buffer[i];
+ }
+ props->offset = min;
+ props->maxlvl = max;
+ dprintk("Brightness levels: min=%d max=%d\n", props->offset,
+ props->maxlvl);
+}
+
+static void sony_nc_backlight_setup(void)
+{
+ acpi_handle unused;
+ int max_brightness = 0;
+ const struct backlight_ops *ops = NULL;
+ struct backlight_properties props;
+
+ if (sony_find_snc_handle(0x12f) >= 0) {
+ ops = &sony_backlight_ng_ops;
+ sony_bl_props.cmd_base = 0x0100;
+ sony_nc_backlight_ng_read_limits(0x12f, &sony_bl_props);
+ max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+ } else if (sony_find_snc_handle(0x137) >= 0) {
+ ops = &sony_backlight_ng_ops;
+ sony_bl_props.cmd_base = 0x0100;
+ sony_nc_backlight_ng_read_limits(0x137, &sony_bl_props);
+ max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+ } else if (sony_find_snc_handle(0x143) >= 0) {
+ ops = &sony_backlight_ng_ops;
+ sony_bl_props.cmd_base = 0x3000;
+ sony_nc_backlight_ng_read_limits(0x143, &sony_bl_props);
+ max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+ } else if (sony_find_snc_handle(0x14b) >= 0) {
+ ops = &sony_backlight_ng_ops;
+ sony_bl_props.cmd_base = 0x3000;
+ sony_nc_backlight_ng_read_limits(0x14b, &sony_bl_props);
+ max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+ } else if (sony_find_snc_handle(0x14c) >= 0) {
+ ops = &sony_backlight_ng_ops;
+ sony_bl_props.cmd_base = 0x3000;
+ sony_nc_backlight_ng_read_limits(0x14c, &sony_bl_props);
+ max_brightness = sony_bl_props.maxlvl - sony_bl_props.offset;
+
+ } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT",
+ &unused))) {
+ ops = &sony_backlight_ops;
+ max_brightness = SONY_MAX_BRIGHTNESS - 1;
+
+ } else
+ return;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = max_brightness;
+ sony_bl_props.dev = backlight_device_register("sony", NULL,
+ &sony_bl_props,
+ ops, &props);
+
+ if (IS_ERR(sony_bl_props.dev)) {
+ pr_warn("unable to register backlight device\n");
+ sony_bl_props.dev = NULL;
+ } else
+ sony_bl_props.dev->props.brightness =
+ ops->get_brightness(sony_bl_props.dev);
+}
+
+static void sony_nc_backlight_cleanup(void)
+{
+ if (sony_bl_props.dev)
+ backlight_device_unregister(sony_bl_props.dev);
+}
+
+static int sony_nc_add(struct acpi_device *device)
+{
+ acpi_status status;
+ int result = 0;
+ acpi_handle handle;
+ struct sony_nc_value *item;
+
+ pr_info("%s v%s\n", SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION);
+
+ sony_nc_acpi_device = device;
+ strcpy(acpi_device_class(device), "sony/hotkey");
+
+ sony_nc_acpi_handle = device->handle;
+
+ /* read device status */
+ result = acpi_bus_get_status(device);
+ /* bail IFF the above call was successful and the device is not present */
+ if (!result && !device->status.present) {
+ dprintk("Device not present\n");
+ result = -ENODEV;
+ goto outwalk;
+ }
+
+ result = sony_pf_add();
+ if (result)
+ goto outpresent;
+
+ if (debug) {
+ status = acpi_walk_namespace(ACPI_TYPE_METHOD,
+ sony_nc_acpi_handle, 1, sony_walk_callback,
+ NULL, NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_warn("unable to walk acpi resources\n");
+ result = -ENODEV;
+ goto outpresent;
+ }
+ }
+
+ result = sony_laptop_setup_input(device);
+ if (result) {
+ pr_err("Unable to create input devices\n");
+ goto outplatform;
+ }
+
+ if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "ECON",
+ &handle))) {
+ int arg = 1;
+ if (sony_nc_int_call(sony_nc_acpi_handle, "ECON", &arg, NULL))
+ dprintk("ECON Method failed\n");
+ }
+
+ if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00",
+ &handle))) {
+ dprintk("Doing SNC setup\n");
+ /* retrieve the available handles */
+ result = sony_nc_handles_setup(sony_pf_device);
+ if (!result)
+ sony_nc_function_setup(device, sony_pf_device);
+ }
+
+ /* setup input devices and helper fifo */
+ if (acpi_video_backlight_support()) {
+ pr_info("brightness ignored, must be controlled by ACPI video driver\n");
+ } else {
+ sony_nc_backlight_setup();
+ }
+
+ /* create sony_pf sysfs attributes related to the SNC device */
+ for (item = sony_nc_values; item->name; ++item) {
+
+ if (!debug && item->debug)
+ continue;
+
+ /* find the available acpiget as described in the DSDT */
+ for (; item->acpiget && *item->acpiget; ++item->acpiget) {
+ if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle,
+ *item->acpiget,
+ &handle))) {
+ dprintk("Found %s getter: %s\n",
+ item->name, *item->acpiget);
+ item->devattr.attr.mode |= S_IRUGO;
+ break;
+ }
+ }
+
+ /* find the available acpiset as described in the DSDT */
+ for (; item->acpiset && *item->acpiset; ++item->acpiset) {
+ if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle,
+ *item->acpiset,
+ &handle))) {
+ dprintk("Found %s setter: %s\n",
+ item->name, *item->acpiset);
+ item->devattr.attr.mode |= S_IWUSR;
+ break;
+ }
+ }
+
+ if (item->devattr.attr.mode != 0) {
+ result =
+ device_create_file(&sony_pf_device->dev,
+ &item->devattr);
+ if (result)
+ goto out_sysfs;
+ }
+ }
+
+ return 0;
+
+out_sysfs:
+ for (item = sony_nc_values; item->name; ++item) {
+ device_remove_file(&sony_pf_device->dev, &item->devattr);
+ }
+ sony_nc_backlight_cleanup();
+ sony_nc_function_cleanup(sony_pf_device);
+ sony_nc_handles_cleanup(sony_pf_device);
+
+outplatform:
+ sony_laptop_remove_input();
+
+outpresent:
+ sony_pf_remove();
+
+outwalk:
+ sony_nc_rfkill_cleanup();
+ return result;
+}
+
+static int sony_nc_remove(struct acpi_device *device)
+{
+ struct sony_nc_value *item;
+
+ sony_nc_backlight_cleanup();
+
+ sony_nc_acpi_device = NULL;
+
+ for (item = sony_nc_values; item->name; ++item) {
+ device_remove_file(&sony_pf_device->dev, &item->devattr);
+ }
+
+ sony_nc_function_cleanup(sony_pf_device);
+ sony_nc_handles_cleanup(sony_pf_device);
+ sony_pf_remove();
+ sony_laptop_remove_input();
+ dprintk(SONY_NC_DRIVER_NAME " removed.\n");
+
+ return 0;
+}
+
+static const struct acpi_device_id sony_device_ids[] = {
+ {SONY_NC_HID, 0},
+ {SONY_PIC_HID, 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, sony_device_ids);
+
+static const struct acpi_device_id sony_nc_device_ids[] = {
+ {SONY_NC_HID, 0},
+ {"", 0},
+};
+
+static struct acpi_driver sony_nc_driver = {
+ .name = SONY_NC_DRIVER_NAME,
+ .class = SONY_NC_CLASS,
+ .ids = sony_nc_device_ids,
+ .owner = THIS_MODULE,
+ .ops = {
+ .add = sony_nc_add,
+ .remove = sony_nc_remove,
+ .notify = sony_nc_notify,
+ },
+ .drv.pm = &sony_nc_pm,
+};
+
+/*********** SPIC (SNY6001) Device ***********/
+
+#define SONYPI_DEVICE_TYPE1 0x00000001
+#define SONYPI_DEVICE_TYPE2 0x00000002
+#define SONYPI_DEVICE_TYPE3 0x00000004
+
+#define SONYPI_TYPE1_OFFSET 0x04
+#define SONYPI_TYPE2_OFFSET 0x12
+#define SONYPI_TYPE3_OFFSET 0x12
+
+struct sony_pic_ioport {
+ struct acpi_resource_io io1;
+ struct acpi_resource_io io2;
+ struct list_head list;
+};
+
+struct sony_pic_irq {
+ struct acpi_resource_irq irq;
+ struct list_head list;
+};
+
+struct sonypi_eventtypes {
+ u8 data;
+ unsigned long mask;
+ struct sonypi_event *events;
+};
+
+struct sony_pic_dev {
+ struct acpi_device *acpi_dev;
+ struct sony_pic_irq *cur_irq;
+ struct sony_pic_ioport *cur_ioport;
+ struct list_head interrupts;
+ struct list_head ioports;
+ struct mutex lock;
+ struct sonypi_eventtypes *event_types;
+ int (*handle_irq)(const u8, const u8);
+ int model;
+ u16 evport_offset;
+ u8 camera_power;
+ u8 bluetooth_power;
+ u8 wwan_power;
+};
+
+static struct sony_pic_dev spic_dev = {
+ .interrupts = LIST_HEAD_INIT(spic_dev.interrupts),
+ .ioports = LIST_HEAD_INIT(spic_dev.ioports),
+};
+
+static int spic_drv_registered;
+
+/* Event masks */
+#define SONYPI_JOGGER_MASK 0x00000001
+#define SONYPI_CAPTURE_MASK 0x00000002
+#define SONYPI_FNKEY_MASK 0x00000004
+#define SONYPI_BLUETOOTH_MASK 0x00000008
+#define SONYPI_PKEY_MASK 0x00000010
+#define SONYPI_BACK_MASK 0x00000020
+#define SONYPI_HELP_MASK 0x00000040
+#define SONYPI_LID_MASK 0x00000080
+#define SONYPI_ZOOM_MASK 0x00000100
+#define SONYPI_THUMBPHRASE_MASK 0x00000200
+#define SONYPI_MEYE_MASK 0x00000400
+#define SONYPI_MEMORYSTICK_MASK 0x00000800
+#define SONYPI_BATTERY_MASK 0x00001000
+#define SONYPI_WIRELESS_MASK 0x00002000
+
+struct sonypi_event {
+ u8 data;
+ u8 event;
+};
+
+/* The set of possible button release events */
+static struct sonypi_event sonypi_releaseev[] = {
+ { 0x00, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0, 0 }
+};
+
+/* The set of possible jogger events */
+static struct sonypi_event sonypi_joggerev[] = {
+ { 0x1f, SONYPI_EVENT_JOGDIAL_UP },
+ { 0x01, SONYPI_EVENT_JOGDIAL_DOWN },
+ { 0x5f, SONYPI_EVENT_JOGDIAL_UP_PRESSED },
+ { 0x41, SONYPI_EVENT_JOGDIAL_DOWN_PRESSED },
+ { 0x1e, SONYPI_EVENT_JOGDIAL_FAST_UP },
+ { 0x02, SONYPI_EVENT_JOGDIAL_FAST_DOWN },
+ { 0x5e, SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED },
+ { 0x42, SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED },
+ { 0x1d, SONYPI_EVENT_JOGDIAL_VFAST_UP },
+ { 0x03, SONYPI_EVENT_JOGDIAL_VFAST_DOWN },
+ { 0x5d, SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED },
+ { 0x43, SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED },
+ { 0x40, SONYPI_EVENT_JOGDIAL_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible capture button events */
+static struct sonypi_event sonypi_captureev[] = {
+ { 0x05, SONYPI_EVENT_CAPTURE_PARTIALPRESSED },
+ { 0x07, SONYPI_EVENT_CAPTURE_PRESSED },
+ { 0x40, SONYPI_EVENT_CAPTURE_PRESSED },
+ { 0x01, SONYPI_EVENT_CAPTURE_PARTIALRELEASED },
+ { 0, 0 }
+};
+
+/* The set of possible fnkeys events */
+static struct sonypi_event sonypi_fnkeyev[] = {
+ { 0x10, SONYPI_EVENT_FNKEY_ESC },
+ { 0x11, SONYPI_EVENT_FNKEY_F1 },
+ { 0x12, SONYPI_EVENT_FNKEY_F2 },
+ { 0x13, SONYPI_EVENT_FNKEY_F3 },
+ { 0x14, SONYPI_EVENT_FNKEY_F4 },
+ { 0x15, SONYPI_EVENT_FNKEY_F5 },
+ { 0x16, SONYPI_EVENT_FNKEY_F6 },
+ { 0x17, SONYPI_EVENT_FNKEY_F7 },
+ { 0x18, SONYPI_EVENT_FNKEY_F8 },
+ { 0x19, SONYPI_EVENT_FNKEY_F9 },
+ { 0x1a, SONYPI_EVENT_FNKEY_F10 },
+ { 0x1b, SONYPI_EVENT_FNKEY_F11 },
+ { 0x1c, SONYPI_EVENT_FNKEY_F12 },
+ { 0x1f, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x21, SONYPI_EVENT_FNKEY_1 },
+ { 0x22, SONYPI_EVENT_FNKEY_2 },
+ { 0x31, SONYPI_EVENT_FNKEY_D },
+ { 0x32, SONYPI_EVENT_FNKEY_E },
+ { 0x33, SONYPI_EVENT_FNKEY_F },
+ { 0x34, SONYPI_EVENT_FNKEY_S },
+ { 0x35, SONYPI_EVENT_FNKEY_B },
+ { 0x36, SONYPI_EVENT_FNKEY_ONLY },
+ { 0, 0 }
+};
+
+/* The set of possible program key events */
+static struct sonypi_event sonypi_pkeyev[] = {
+ { 0x01, SONYPI_EVENT_PKEY_P1 },
+ { 0x02, SONYPI_EVENT_PKEY_P2 },
+ { 0x04, SONYPI_EVENT_PKEY_P3 },
+ { 0x20, SONYPI_EVENT_PKEY_P1 },
+ { 0, 0 }
+};
+
+/* The set of possible bluetooth events */
+static struct sonypi_event sonypi_blueev[] = {
+ { 0x55, SONYPI_EVENT_BLUETOOTH_PRESSED },
+ { 0x59, SONYPI_EVENT_BLUETOOTH_ON },
+ { 0x5a, SONYPI_EVENT_BLUETOOTH_OFF },
+ { 0, 0 }
+};
+
+/* The set of possible wireless events */
+static struct sonypi_event sonypi_wlessev[] = {
+ { 0x59, SONYPI_EVENT_IGNORE },
+ { 0x5a, SONYPI_EVENT_IGNORE },
+ { 0, 0 }
+};
+
+/* The set of possible back button events */
+static struct sonypi_event sonypi_backev[] = {
+ { 0x20, SONYPI_EVENT_BACK_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible help button events */
+static struct sonypi_event sonypi_helpev[] = {
+ { 0x3b, SONYPI_EVENT_HELP_PRESSED },
+ { 0, 0 }
+};
+
+
+/* The set of possible lid events */
+static struct sonypi_event sonypi_lidev[] = {
+ { 0x51, SONYPI_EVENT_LID_CLOSED },
+ { 0x50, SONYPI_EVENT_LID_OPENED },
+ { 0, 0 }
+};
+
+/* The set of possible zoom events */
+static struct sonypi_event sonypi_zoomev[] = {
+ { 0x39, SONYPI_EVENT_ZOOM_PRESSED },
+ { 0x10, SONYPI_EVENT_ZOOM_IN_PRESSED },
+ { 0x20, SONYPI_EVENT_ZOOM_OUT_PRESSED },
+ { 0x04, SONYPI_EVENT_ZOOM_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible thumbphrase events */
+static struct sonypi_event sonypi_thumbphraseev[] = {
+ { 0x3a, SONYPI_EVENT_THUMBPHRASE_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible motioneye camera events */
+static struct sonypi_event sonypi_meyeev[] = {
+ { 0x00, SONYPI_EVENT_MEYE_FACE },
+ { 0x01, SONYPI_EVENT_MEYE_OPPOSITE },
+ { 0, 0 }
+};
+
+/* The set of possible memorystick events */
+static struct sonypi_event sonypi_memorystickev[] = {
+ { 0x53, SONYPI_EVENT_MEMORYSTICK_INSERT },
+ { 0x54, SONYPI_EVENT_MEMORYSTICK_EJECT },
+ { 0, 0 }
+};
+
+/* The set of possible battery events */
+static struct sonypi_event sonypi_batteryev[] = {
+ { 0x20, SONYPI_EVENT_BATTERY_INSERT },
+ { 0x30, SONYPI_EVENT_BATTERY_REMOVE },
+ { 0, 0 }
+};
+
+/* The set of possible volume events */
+static struct sonypi_event sonypi_volumeev[] = {
+ { 0x01, SONYPI_EVENT_VOLUME_INC_PRESSED },
+ { 0x02, SONYPI_EVENT_VOLUME_DEC_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible brightness events */
+static struct sonypi_event sonypi_brightnessev[] = {
+ { 0x80, SONYPI_EVENT_BRIGHTNESS_PRESSED },
+ { 0, 0 }
+};
+
+static struct sonypi_eventtypes type1_events[] = {
+ { 0, 0xffffffff, sonypi_releaseev },
+ { 0x70, SONYPI_MEYE_MASK, sonypi_meyeev },
+ { 0x30, SONYPI_LID_MASK, sonypi_lidev },
+ { 0x60, SONYPI_CAPTURE_MASK, sonypi_captureev },
+ { 0x10, SONYPI_JOGGER_MASK, sonypi_joggerev },
+ { 0x20, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+ { 0x30, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+ { 0x40, SONYPI_PKEY_MASK, sonypi_pkeyev },
+ { 0x30, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+ { 0x40, SONYPI_BATTERY_MASK, sonypi_batteryev },
+ { 0 },
+};
+static struct sonypi_eventtypes type2_events[] = {
+ { 0, 0xffffffff, sonypi_releaseev },
+ { 0x38, SONYPI_LID_MASK, sonypi_lidev },
+ { 0x11, SONYPI_JOGGER_MASK, sonypi_joggerev },
+ { 0x61, SONYPI_CAPTURE_MASK, sonypi_captureev },
+ { 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+ { 0x31, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+ { 0x08, SONYPI_PKEY_MASK, sonypi_pkeyev },
+ { 0x11, SONYPI_BACK_MASK, sonypi_backev },
+ { 0x21, SONYPI_HELP_MASK, sonypi_helpev },
+ { 0x21, SONYPI_ZOOM_MASK, sonypi_zoomev },
+ { 0x20, SONYPI_THUMBPHRASE_MASK, sonypi_thumbphraseev },
+ { 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+ { 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+ { 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+ { 0 },
+};
+static struct sonypi_eventtypes type3_events[] = {
+ { 0, 0xffffffff, sonypi_releaseev },
+ { 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+ { 0x31, SONYPI_WIRELESS_MASK, sonypi_wlessev },
+ { 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+ { 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+ { 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+ { 0x05, SONYPI_PKEY_MASK, sonypi_pkeyev },
+ { 0x05, SONYPI_ZOOM_MASK, sonypi_zoomev },
+ { 0x05, SONYPI_CAPTURE_MASK, sonypi_captureev },
+ { 0x05, SONYPI_PKEY_MASK, sonypi_volumeev },
+ { 0x05, SONYPI_PKEY_MASK, sonypi_brightnessev },
+ { 0 },
+};
+
+/* low level spic calls */
+#define ITERATIONS_LONG 10000
+#define ITERATIONS_SHORT 10
+#define wait_on_command(command, iterations) { \
+ unsigned int n = iterations; \
+ while (--n && (command)) \
+ udelay(1); \
+ if (!n) \
+ dprintk("command failed at %s : %s (line %d)\n", \
+ __FILE__, __func__, __LINE__); \
+}
+
+static u8 sony_pic_call1(u8 dev)
+{
+ u8 v1, v2;
+
+ wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2,
+ ITERATIONS_LONG);
+ outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
+ v1 = inb_p(spic_dev.cur_ioport->io1.minimum + 4);
+ v2 = inb_p(spic_dev.cur_ioport->io1.minimum);
+ dprintk("sony_pic_call1(0x%.2x): 0x%.4x\n", dev, (v2 << 8) | v1);
+ return v2;
+}
+
+static u8 sony_pic_call2(u8 dev, u8 fn)
+{
+ u8 v1;
+
+ wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2,
+ ITERATIONS_LONG);
+ outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
+ wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2,
+ ITERATIONS_LONG);
+ outb(fn, spic_dev.cur_ioport->io1.minimum);
+ v1 = inb_p(spic_dev.cur_ioport->io1.minimum);
+ dprintk("sony_pic_call2(0x%.2x - 0x%.2x): 0x%.4x\n", dev, fn, v1);
+ return v1;
+}
+
+static u8 sony_pic_call3(u8 dev, u8 fn, u8 v)
+{
+ u8 v1;
+
+ wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
+ outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
+ wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
+ outb(fn, spic_dev.cur_ioport->io1.minimum);
+ wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
+ outb(v, spic_dev.cur_ioport->io1.minimum);
+ v1 = inb_p(spic_dev.cur_ioport->io1.minimum);
+ dprintk("sony_pic_call3(0x%.2x - 0x%.2x - 0x%.2x): 0x%.4x\n",
+ dev, fn, v, v1);
+ return v1;
+}
+
+/*
+ * minidrivers for SPIC models
+ */
+static int type3_handle_irq(const u8 data_mask, const u8 ev)
+{
+ /*
+ * 0x31 could mean we have to take some extra action and wait for
+ * the next irq for some Type3 models, it will generate a new
+ * irq and we can read new data from the device:
+ * - 0x5c and 0x5f requires 0xA0
+ * - 0x61 requires 0xB3
+ */
+ if (data_mask == 0x31) {
+ if (ev == 0x5c || ev == 0x5f)
+ sony_pic_call1(0xA0);
+ else if (ev == 0x61)
+ sony_pic_call1(0xB3);
+ return 0;
+ }
+ return 1;
+}
+
+static void sony_pic_detect_device_type(struct sony_pic_dev *dev)
+{
+ struct pci_dev *pcidev;
+
+ pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_82371AB_3, NULL);
+ if (pcidev) {
+ dev->model = SONYPI_DEVICE_TYPE1;
+ dev->evport_offset = SONYPI_TYPE1_OFFSET;
+ dev->event_types = type1_events;
+ goto out;
+ }
+
+ pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_ICH6_1, NULL);
+ if (pcidev) {
+ dev->model = SONYPI_DEVICE_TYPE2;
+ dev->evport_offset = SONYPI_TYPE2_OFFSET;
+ dev->event_types = type2_events;
+ goto out;
+ }
+
+ pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_ICH7_1, NULL);
+ if (pcidev) {
+ dev->model = SONYPI_DEVICE_TYPE3;
+ dev->handle_irq = type3_handle_irq;
+ dev->evport_offset = SONYPI_TYPE3_OFFSET;
+ dev->event_types = type3_events;
+ goto out;
+ }
+
+ pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_ICH8_4, NULL);
+ if (pcidev) {
+ dev->model = SONYPI_DEVICE_TYPE3;
+ dev->handle_irq = type3_handle_irq;
+ dev->evport_offset = SONYPI_TYPE3_OFFSET;
+ dev->event_types = type3_events;
+ goto out;
+ }
+
+ pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_ICH9_1, NULL);
+ if (pcidev) {
+ dev->model = SONYPI_DEVICE_TYPE3;
+ dev->handle_irq = type3_handle_irq;
+ dev->evport_offset = SONYPI_TYPE3_OFFSET;
+ dev->event_types = type3_events;
+ goto out;
+ }
+
+ /* default */
+ dev->model = SONYPI_DEVICE_TYPE2;
+ dev->evport_offset = SONYPI_TYPE2_OFFSET;
+ dev->event_types = type2_events;
+
+out:
+ if (pcidev)
+ pci_dev_put(pcidev);
+
+ pr_info("detected Type%d model\n",
+ dev->model == SONYPI_DEVICE_TYPE1 ? 1 :
+ dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3);
+}
+
+/* camera tests and poweron/poweroff */
+#define SONYPI_CAMERA_PICTURE 5
+#define SONYPI_CAMERA_CONTROL 0x10
+
+#define SONYPI_CAMERA_BRIGHTNESS 0
+#define SONYPI_CAMERA_CONTRAST 1
+#define SONYPI_CAMERA_HUE 2
+#define SONYPI_CAMERA_COLOR 3
+#define SONYPI_CAMERA_SHARPNESS 4
+
+#define SONYPI_CAMERA_EXPOSURE_MASK 0xC
+#define SONYPI_CAMERA_WHITE_BALANCE_MASK 0x3
+#define SONYPI_CAMERA_PICTURE_MODE_MASK 0x30
+#define SONYPI_CAMERA_MUTE_MASK 0x40
+
+/* the rest don't need a loop until not 0xff */
+#define SONYPI_CAMERA_AGC 6
+#define SONYPI_CAMERA_AGC_MASK 0x30
+#define SONYPI_CAMERA_SHUTTER_MASK 0x7
+
+#define SONYPI_CAMERA_SHUTDOWN_REQUEST 7
+#define SONYPI_CAMERA_CONTROL 0x10
+
+#define SONYPI_CAMERA_STATUS 7
+#define SONYPI_CAMERA_STATUS_READY 0x2
+#define SONYPI_CAMERA_STATUS_POSITION 0x4
+
+#define SONYPI_DIRECTION_BACKWARDS 0x4
+
+#define SONYPI_CAMERA_REVISION 8
+#define SONYPI_CAMERA_ROMVERSION 9
+
+static int __sony_pic_camera_ready(void)
+{
+ u8 v;
+
+ v = sony_pic_call2(0x8f, SONYPI_CAMERA_STATUS);
+ return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY));
+}
+
+static int __sony_pic_camera_off(void)
+{
+ if (!camera) {
+ pr_warn("camera control not enabled\n");
+ return -ENODEV;
+ }
+
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE,
+ SONYPI_CAMERA_MUTE_MASK),
+ ITERATIONS_SHORT);
+
+ if (spic_dev.camera_power) {
+ sony_pic_call2(0x91, 0);
+ spic_dev.camera_power = 0;
+ }
+ return 0;
+}
+
+static int __sony_pic_camera_on(void)
+{
+ int i, j, x;
+
+ if (!camera) {
+ pr_warn("camera control not enabled\n");
+ return -ENODEV;
+ }
+
+ if (spic_dev.camera_power)
+ return 0;
+
+ for (j = 5; j > 0; j--) {
+
+ for (x = 0; x < 100 && sony_pic_call2(0x91, 0x1); x++)
+ msleep(10);
+ sony_pic_call1(0x93);
+
+ for (i = 400; i > 0; i--) {
+ if (__sony_pic_camera_ready())
+ break;
+ msleep(10);
+ }
+ if (i)
+ break;
+ }
+
+ if (j == 0) {
+ pr_warn("failed to power on camera\n");
+ return -ENODEV;
+ }
+
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTROL,
+ 0x5a),
+ ITERATIONS_SHORT);
+
+ spic_dev.camera_power = 1;
+ return 0;
+}
+
+/* External camera command (exported to the motion eye v4l driver) */
+int sony_pic_camera_command(int command, u8 value)
+{
+ if (!camera)
+ return -EIO;
+
+ mutex_lock(&spic_dev.lock);
+
+ switch (command) {
+ case SONY_PIC_COMMAND_SETCAMERA:
+ if (value)
+ __sony_pic_camera_on();
+ else
+ __sony_pic_camera_off();
+ break;
+ case SONY_PIC_COMMAND_SETCAMERABRIGHTNESS:
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_BRIGHTNESS, value),
+ ITERATIONS_SHORT);
+ break;
+ case SONY_PIC_COMMAND_SETCAMERACONTRAST:
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTRAST, value),
+ ITERATIONS_SHORT);
+ break;
+ case SONY_PIC_COMMAND_SETCAMERAHUE:
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_HUE, value),
+ ITERATIONS_SHORT);
+ break;
+ case SONY_PIC_COMMAND_SETCAMERACOLOR:
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_COLOR, value),
+ ITERATIONS_SHORT);
+ break;
+ case SONY_PIC_COMMAND_SETCAMERASHARPNESS:
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_SHARPNESS, value),
+ ITERATIONS_SHORT);
+ break;
+ case SONY_PIC_COMMAND_SETCAMERAPICTURE:
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE, value),
+ ITERATIONS_SHORT);
+ break;
+ case SONY_PIC_COMMAND_SETCAMERAAGC:
+ wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_AGC, value),
+ ITERATIONS_SHORT);
+ break;
+ default:
+ pr_err("sony_pic_camera_command invalid: %d\n", command);
+ break;
+ }
+ mutex_unlock(&spic_dev.lock);
+ return 0;
+}
+EXPORT_SYMBOL(sony_pic_camera_command);
+
+/* gprs/edge modem (SZ460N and SZ210P), thanks to Joshua Wise */
+static void __sony_pic_set_wwanpower(u8 state)
+{
+ state = !!state;
+ if (spic_dev.wwan_power == state)
+ return;
+ sony_pic_call2(0xB0, state);
+ sony_pic_call1(0x82);
+ spic_dev.wwan_power = state;
+}
+
+static ssize_t sony_pic_wwanpower_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long value;
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ mutex_lock(&spic_dev.lock);
+ __sony_pic_set_wwanpower(value);
+ mutex_unlock(&spic_dev.lock);
+
+ return count;
+}
+
+static ssize_t sony_pic_wwanpower_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ ssize_t count;
+ mutex_lock(&spic_dev.lock);
+ count = snprintf(buffer, PAGE_SIZE, "%d\n", spic_dev.wwan_power);
+ mutex_unlock(&spic_dev.lock);
+ return count;
+}
+
+/* bluetooth subsystem power state */
+static void __sony_pic_set_bluetoothpower(u8 state)
+{
+ state = !!state;
+ if (spic_dev.bluetooth_power == state)
+ return;
+ sony_pic_call2(0x96, state);
+ sony_pic_call1(0x82);
+ spic_dev.bluetooth_power = state;
+}
+
+static ssize_t sony_pic_bluetoothpower_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long value;
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ mutex_lock(&spic_dev.lock);
+ __sony_pic_set_bluetoothpower(value);
+ mutex_unlock(&spic_dev.lock);
+
+ return count;
+}
+
+static ssize_t sony_pic_bluetoothpower_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ ssize_t count = 0;
+ mutex_lock(&spic_dev.lock);
+ count = snprintf(buffer, PAGE_SIZE, "%d\n", spic_dev.bluetooth_power);
+ mutex_unlock(&spic_dev.lock);
+ return count;
+}
+
+/* fan speed */
+/* FAN0 information (reverse engineered from ACPI tables) */
+#define SONY_PIC_FAN0_STATUS 0x93
+static int sony_pic_set_fanspeed(unsigned long value)
+{
+ return ec_write(SONY_PIC_FAN0_STATUS, value);
+}
+
+static int sony_pic_get_fanspeed(u8 *value)
+{
+ return ec_read(SONY_PIC_FAN0_STATUS, value);
+}
+
+static ssize_t sony_pic_fanspeed_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buffer, size_t count)
+{
+ unsigned long value;
+ if (count > 31)
+ return -EINVAL;
+
+ if (kstrtoul(buffer, 10, &value))
+ return -EINVAL;
+
+ if (sony_pic_set_fanspeed(value))
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t sony_pic_fanspeed_show(struct device *dev,
+ struct device_attribute *attr, char *buffer)
+{
+ u8 value = 0;
+ if (sony_pic_get_fanspeed(&value))
+ return -EIO;
+
+ return snprintf(buffer, PAGE_SIZE, "%d\n", value);
+}
+
+#define SPIC_ATTR(_name, _mode) \
+struct device_attribute spic_attr_##_name = __ATTR(_name, \
+ _mode, sony_pic_## _name ##_show, \
+ sony_pic_## _name ##_store)
+
+static SPIC_ATTR(bluetoothpower, 0644);
+static SPIC_ATTR(wwanpower, 0644);
+static SPIC_ATTR(fanspeed, 0644);
+
+static struct attribute *spic_attributes[] = {
+ &spic_attr_bluetoothpower.attr,
+ &spic_attr_wwanpower.attr,
+ &spic_attr_fanspeed.attr,
+ NULL
+};
+
+static struct attribute_group spic_attribute_group = {
+ .attrs = spic_attributes
+};
+
+/******** SONYPI compatibility **********/
+#ifdef CONFIG_SONYPI_COMPAT
+
+/* battery / brightness / temperature addresses */
+#define SONYPI_BAT_FLAGS 0x81
+#define SONYPI_LCD_LIGHT 0x96
+#define SONYPI_BAT1_PCTRM 0xa0
+#define SONYPI_BAT1_LEFT 0xa2
+#define SONYPI_BAT1_MAXRT 0xa4
+#define SONYPI_BAT2_PCTRM 0xa8
+#define SONYPI_BAT2_LEFT 0xaa
+#define SONYPI_BAT2_MAXRT 0xac
+#define SONYPI_BAT1_MAXTK 0xb0
+#define SONYPI_BAT1_FULL 0xb2
+#define SONYPI_BAT2_MAXTK 0xb8
+#define SONYPI_BAT2_FULL 0xba
+#define SONYPI_TEMP_STATUS 0xC1
+
+struct sonypi_compat_s {
+ struct fasync_struct *fifo_async;
+ struct kfifo fifo;
+ spinlock_t fifo_lock;
+ wait_queue_head_t fifo_proc_list;
+ atomic_t open_count;
+};
+static struct sonypi_compat_s sonypi_compat = {
+ .open_count = ATOMIC_INIT(0),
+};
+
+static int sonypi_misc_fasync(int fd, struct file *filp, int on)
+{
+ return fasync_helper(fd, filp, on, &sonypi_compat.fifo_async);
+}
+
+static int sonypi_misc_release(struct inode *inode, struct file *file)
+{
+ atomic_dec(&sonypi_compat.open_count);
+ return 0;
+}
+
+static int sonypi_misc_open(struct inode *inode, struct file *file)
+{
+ /* Flush input queue on first open */
+ unsigned long flags;
+
+ spin_lock_irqsave(&sonypi_compat.fifo_lock, flags);
+
+ if (atomic_inc_return(&sonypi_compat.open_count) == 1)
+ kfifo_reset(&sonypi_compat.fifo);
+
+ spin_unlock_irqrestore(&sonypi_compat.fifo_lock, flags);
+
+ return 0;
+}
+
+static ssize_t sonypi_misc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ ssize_t ret;
+ unsigned char c;
+
+ if ((kfifo_len(&sonypi_compat.fifo) == 0) &&
+ (file->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(sonypi_compat.fifo_proc_list,
+ kfifo_len(&sonypi_compat.fifo) != 0);
+ if (ret)
+ return ret;
+
+ while (ret < count &&
+ (kfifo_out_locked(&sonypi_compat.fifo, &c, sizeof(c),
+ &sonypi_compat.fifo_lock) == sizeof(c))) {
+ if (put_user(c, buf++))
+ return -EFAULT;
+ ret++;
+ }
+
+ if (ret > 0) {
+ struct inode *inode = file_inode(file);
+ inode->i_atime = current_fs_time(inode->i_sb);
+ }
+
+ return ret;
+}
+
+static unsigned int sonypi_misc_poll(struct file *file, poll_table *wait)
+{
+ poll_wait(file, &sonypi_compat.fifo_proc_list, wait);
+ if (kfifo_len(&sonypi_compat.fifo))
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+static int ec_read16(u8 addr, u16 *value)
+{
+ u8 val_lb, val_hb;
+ if (ec_read(addr, &val_lb))
+ return -1;
+ if (ec_read(addr + 1, &val_hb))
+ return -1;
+ *value = val_lb | (val_hb << 8);
+ return 0;
+}
+
+static long sonypi_misc_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = 0;
+ void __user *argp = (void __user *)arg;
+ u8 val8;
+ u16 val16;
+ int value;
+
+ mutex_lock(&spic_dev.lock);
+ switch (cmd) {
+ case SONYPI_IOCGBRT:
+ if (sony_bl_props.dev == NULL) {
+ ret = -EIO;
+ break;
+ }
+ if (sony_nc_int_call(sony_nc_acpi_handle, "GBRT", NULL,
+ &value)) {
+ ret = -EIO;
+ break;
+ }
+ val8 = ((value & 0xff) - 1) << 5;
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCSBRT:
+ if (sony_bl_props.dev == NULL) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_from_user(&val8, argp, sizeof(val8))) {
+ ret = -EFAULT;
+ break;
+ }
+ value = (val8 >> 5) + 1;
+ if (sony_nc_int_call(sony_nc_acpi_handle, "SBRT", &value,
+ NULL)) {
+ ret = -EIO;
+ break;
+ }
+ /* sync the backlight device status */
+ sony_bl_props.dev->props.brightness =
+ sony_backlight_get_brightness(sony_bl_props.dev);
+ break;
+ case SONYPI_IOCGBAT1CAP:
+ if (ec_read16(SONYPI_BAT1_FULL, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBAT1REM:
+ if (ec_read16(SONYPI_BAT1_LEFT, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBAT2CAP:
+ if (ec_read16(SONYPI_BAT2_FULL, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBAT2REM:
+ if (ec_read16(SONYPI_BAT2_LEFT, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBATFLAGS:
+ if (ec_read(SONYPI_BAT_FLAGS, &val8)) {
+ ret = -EIO;
+ break;
+ }
+ val8 &= 0x07;
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBLUE:
+ val8 = spic_dev.bluetooth_power;
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCSBLUE:
+ if (copy_from_user(&val8, argp, sizeof(val8))) {
+ ret = -EFAULT;
+ break;
+ }
+ __sony_pic_set_bluetoothpower(val8);
+ break;
+ /* FAN Controls */
+ case SONYPI_IOCGFAN:
+ if (sony_pic_get_fanspeed(&val8)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCSFAN:
+ if (copy_from_user(&val8, argp, sizeof(val8))) {
+ ret = -EFAULT;
+ break;
+ }
+ if (sony_pic_set_fanspeed(val8))
+ ret = -EIO;
+ break;
+ /* GET Temperature (useful under APM) */
+ case SONYPI_IOCGTEMP:
+ if (ec_read(SONYPI_TEMP_STATUS, &val8)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&spic_dev.lock);
+ return ret;
+}
+
+static const struct file_operations sonypi_misc_fops = {
+ .owner = THIS_MODULE,
+ .read = sonypi_misc_read,
+ .poll = sonypi_misc_poll,
+ .open = sonypi_misc_open,
+ .release = sonypi_misc_release,
+ .fasync = sonypi_misc_fasync,
+ .unlocked_ioctl = sonypi_misc_ioctl,
+ .llseek = noop_llseek,
+};
+
+static struct miscdevice sonypi_misc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "sonypi",
+ .fops = &sonypi_misc_fops,
+};
+
+static void sonypi_compat_report_event(u8 event)
+{
+ kfifo_in_locked(&sonypi_compat.fifo, (unsigned char *)&event,
+ sizeof(event), &sonypi_compat.fifo_lock);
+ kill_fasync(&sonypi_compat.fifo_async, SIGIO, POLL_IN);
+ wake_up_interruptible(&sonypi_compat.fifo_proc_list);
+}
+
+static int sonypi_compat_init(void)
+{
+ int error;
+
+ spin_lock_init(&sonypi_compat.fifo_lock);
+ error =
+ kfifo_alloc(&sonypi_compat.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL);
+ if (error) {
+ pr_err("kfifo_alloc failed\n");
+ return error;
+ }
+
+ init_waitqueue_head(&sonypi_compat.fifo_proc_list);
+
+ if (minor != -1)
+ sonypi_misc_device.minor = minor;
+ error = misc_register(&sonypi_misc_device);
+ if (error) {
+ pr_err("misc_register failed\n");
+ goto err_free_kfifo;
+ }
+ if (minor == -1)
+ pr_info("device allocated minor is %d\n",
+ sonypi_misc_device.minor);
+
+ return 0;
+
+err_free_kfifo:
+ kfifo_free(&sonypi_compat.fifo);
+ return error;
+}
+
+static void sonypi_compat_exit(void)
+{
+ misc_deregister(&sonypi_misc_device);
+ kfifo_free(&sonypi_compat.fifo);
+}
+#else
+static int sonypi_compat_init(void) { return 0; }
+static void sonypi_compat_exit(void) { }
+static void sonypi_compat_report_event(u8 event) { }
+#endif /* CONFIG_SONYPI_COMPAT */
+
+/*
+ * ACPI callbacks
+ */
+static acpi_status
+sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
+{
+ u32 i;
+ struct sony_pic_dev *dev = (struct sony_pic_dev *)context;
+
+ switch (resource->type) {
+ case ACPI_RESOURCE_TYPE_START_DEPENDENT:
+ {
+ /* start IO enumeration */
+ struct sony_pic_ioport *ioport = kzalloc(sizeof(*ioport), GFP_KERNEL);
+ if (!ioport)
+ return AE_ERROR;
+
+ list_add(&ioport->list, &dev->ioports);
+ return AE_OK;
+ }
+
+ case ACPI_RESOURCE_TYPE_END_DEPENDENT:
+ /* end IO enumeration */
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_IRQ:
+ {
+ struct acpi_resource_irq *p = &resource->data.irq;
+ struct sony_pic_irq *interrupt = NULL;
+ if (!p || !p->interrupt_count) {
+ /*
+ * IRQ descriptors may have no IRQ# bits set,
+ * particularly those those w/ _STA disabled
+ */
+ dprintk("Blank IRQ resource\n");
+ return AE_OK;
+ }
+ for (i = 0; i < p->interrupt_count; i++) {
+ if (!p->interrupts[i]) {
+ pr_warn("Invalid IRQ %d\n",
+ p->interrupts[i]);
+ continue;
+ }
+ interrupt = kzalloc(sizeof(*interrupt),
+ GFP_KERNEL);
+ if (!interrupt)
+ return AE_ERROR;
+
+ list_add(&interrupt->list, &dev->interrupts);
+ interrupt->irq.triggering = p->triggering;
+ interrupt->irq.polarity = p->polarity;
+ interrupt->irq.sharable = p->sharable;
+ interrupt->irq.interrupt_count = 1;
+ interrupt->irq.interrupts[0] = p->interrupts[i];
+ }
+ return AE_OK;
+ }
+ case ACPI_RESOURCE_TYPE_IO:
+ {
+ struct acpi_resource_io *io = &resource->data.io;
+ struct sony_pic_ioport *ioport =
+ list_first_entry(&dev->ioports, struct sony_pic_ioport, list);
+ if (!io) {
+ dprintk("Blank IO resource\n");
+ return AE_OK;
+ }
+
+ if (!ioport->io1.minimum) {
+ memcpy(&ioport->io1, io, sizeof(*io));
+ dprintk("IO1 at 0x%.4x (0x%.2x)\n", ioport->io1.minimum,
+ ioport->io1.address_length);
+ }
+ else if (!ioport->io2.minimum) {
+ memcpy(&ioport->io2, io, sizeof(*io));
+ dprintk("IO2 at 0x%.4x (0x%.2x)\n", ioport->io2.minimum,
+ ioport->io2.address_length);
+ }
+ else {
+ pr_err("Unknown SPIC Type, more than 2 IO Ports\n");
+ return AE_ERROR;
+ }
+ return AE_OK;
+ }
+ default:
+ dprintk("Resource %d isn't an IRQ nor an IO port\n",
+ resource->type);
+
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ return AE_OK;
+ }
+ return AE_CTRL_TERMINATE;
+}
+
+static int sony_pic_possible_resources(struct acpi_device *device)
+{
+ int result = 0;
+ acpi_status status = AE_OK;
+
+ if (!device)
+ return -EINVAL;
+
+ /* get device status */
+ /* see acpi_pci_link_get_current acpi_pci_link_get_possible */
+ dprintk("Evaluating _STA\n");
+ result = acpi_bus_get_status(device);
+ if (result) {
+ pr_warn("Unable to read status\n");
+ goto end;
+ }
+
+ if (!device->status.enabled)
+ dprintk("Device disabled\n");
+ else
+ dprintk("Device enabled\n");
+
+ /*
+ * Query and parse 'method'
+ */
+ dprintk("Evaluating %s\n", METHOD_NAME__PRS);
+ status = acpi_walk_resources(device->handle, METHOD_NAME__PRS,
+ sony_pic_read_possible_resource, &spic_dev);
+ if (ACPI_FAILURE(status)) {
+ pr_warn("Failure evaluating %s\n", METHOD_NAME__PRS);
+ result = -ENODEV;
+ }
+end:
+ return result;
+}
+
+/*
+ * Disable the spic device by calling its _DIS method
+ */
+static int sony_pic_disable(struct acpi_device *device)
+{
+ acpi_status ret = acpi_evaluate_object(device->handle, "_DIS", NULL,
+ NULL);
+
+ if (ACPI_FAILURE(ret) && ret != AE_NOT_FOUND)
+ return -ENXIO;
+
+ dprintk("Device disabled\n");
+ return 0;
+}
+
+
+/*
+ * Based on drivers/acpi/pci_link.c:acpi_pci_link_set
+ *
+ * Call _SRS to set current resources
+ */
+static int sony_pic_enable(struct acpi_device *device,
+ struct sony_pic_ioport *ioport, struct sony_pic_irq *irq)
+{
+ acpi_status status;
+ int result = 0;
+ /* Type 1 resource layout is:
+ * IO
+ * IO
+ * IRQNoFlags
+ * End
+ *
+ * Type 2 and 3 resource layout is:
+ * IO
+ * IRQNoFlags
+ * End
+ */
+ struct {
+ struct acpi_resource res1;
+ struct acpi_resource res2;
+ struct acpi_resource res3;
+ struct acpi_resource res4;
+ } *resource;
+ struct acpi_buffer buffer = { 0, NULL };
+
+ if (!ioport || !irq)
+ return -EINVAL;
+
+ /* init acpi_buffer */
+ resource = kzalloc(sizeof(*resource) + 1, GFP_KERNEL);
+ if (!resource)
+ return -ENOMEM;
+
+ buffer.length = sizeof(*resource) + 1;
+ buffer.pointer = resource;
+
+ /* setup Type 1 resources */
+ if (spic_dev.model == SONYPI_DEVICE_TYPE1) {
+
+ /* setup io resources */
+ resource->res1.type = ACPI_RESOURCE_TYPE_IO;
+ resource->res1.length = sizeof(struct acpi_resource);
+ memcpy(&resource->res1.data.io, &ioport->io1,
+ sizeof(struct acpi_resource_io));
+
+ resource->res2.type = ACPI_RESOURCE_TYPE_IO;
+ resource->res2.length = sizeof(struct acpi_resource);
+ memcpy(&resource->res2.data.io, &ioport->io2,
+ sizeof(struct acpi_resource_io));
+
+ /* setup irq resource */
+ resource->res3.type = ACPI_RESOURCE_TYPE_IRQ;
+ resource->res3.length = sizeof(struct acpi_resource);
+ memcpy(&resource->res3.data.irq, &irq->irq,
+ sizeof(struct acpi_resource_irq));
+ /* we requested a shared irq */
+ resource->res3.data.irq.sharable = ACPI_SHARED;
+
+ resource->res4.type = ACPI_RESOURCE_TYPE_END_TAG;
+ resource->res4.length = sizeof(struct acpi_resource);
+ }
+ /* setup Type 2/3 resources */
+ else {
+ /* setup io resource */
+ resource->res1.type = ACPI_RESOURCE_TYPE_IO;
+ resource->res1.length = sizeof(struct acpi_resource);
+ memcpy(&resource->res1.data.io, &ioport->io1,
+ sizeof(struct acpi_resource_io));
+
+ /* setup irq resource */
+ resource->res2.type = ACPI_RESOURCE_TYPE_IRQ;
+ resource->res2.length = sizeof(struct acpi_resource);
+ memcpy(&resource->res2.data.irq, &irq->irq,
+ sizeof(struct acpi_resource_irq));
+ /* we requested a shared irq */
+ resource->res2.data.irq.sharable = ACPI_SHARED;
+
+ resource->res3.type = ACPI_RESOURCE_TYPE_END_TAG;
+ resource->res3.length = sizeof(struct acpi_resource);
+ }
+
+ /* Attempt to set the resource */
+ dprintk("Evaluating _SRS\n");
+ status = acpi_set_current_resources(device->handle, &buffer);
+
+ /* check for total failure */
+ if (ACPI_FAILURE(status)) {
+ pr_err("Error evaluating _SRS\n");
+ result = -ENODEV;
+ goto end;
+ }
+
+ /* Necessary device initializations calls (from sonypi) */
+ sony_pic_call1(0x82);
+ sony_pic_call2(0x81, 0xff);
+ sony_pic_call1(compat ? 0x92 : 0x82);
+
+end:
+ kfree(resource);
+ return result;
+}
+
+/*****************
+ *
+ * ISR: some event is available
+ *
+ *****************/
+static irqreturn_t sony_pic_irq(int irq, void *dev_id)
+{
+ int i, j;
+ u8 ev = 0;
+ u8 data_mask = 0;
+ u8 device_event = 0;
+
+ struct sony_pic_dev *dev = (struct sony_pic_dev *) dev_id;
+
+ ev = inb_p(dev->cur_ioport->io1.minimum);
+ if (dev->cur_ioport->io2.minimum)
+ data_mask = inb_p(dev->cur_ioport->io2.minimum);
+ else
+ data_mask = inb_p(dev->cur_ioport->io1.minimum +
+ dev->evport_offset);
+
+ dprintk("event ([%.2x] [%.2x]) at port 0x%.4x(+0x%.2x)\n",
+ ev, data_mask, dev->cur_ioport->io1.minimum,
+ dev->evport_offset);
+
+ if (ev == 0x00 || ev == 0xff)
+ return IRQ_HANDLED;
+
+ for (i = 0; dev->event_types[i].mask; i++) {
+
+ if ((data_mask & dev->event_types[i].data) !=
+ dev->event_types[i].data)
+ continue;
+
+ if (!(mask & dev->event_types[i].mask))
+ continue;
+
+ for (j = 0; dev->event_types[i].events[j].event; j++) {
+ if (ev == dev->event_types[i].events[j].data) {
+ device_event =
+ dev->event_types[i].events[j].event;
+ /* some events may require ignoring */
+ if (!device_event)
+ return IRQ_HANDLED;
+ goto found;
+ }
+ }
+ }
+ /* Still not able to decode the event try to pass
+ * it over to the minidriver
+ */
+ if (dev->handle_irq && dev->handle_irq(data_mask, ev) == 0)
+ return IRQ_HANDLED;
+
+ dprintk("unknown event ([%.2x] [%.2x]) at port 0x%.4x(+0x%.2x)\n",
+ ev, data_mask, dev->cur_ioport->io1.minimum,
+ dev->evport_offset);
+ return IRQ_HANDLED;
+
+found:
+ sony_laptop_report_input_event(device_event);
+ acpi_bus_generate_proc_event(dev->acpi_dev, 1, device_event);
+ sonypi_compat_report_event(device_event);
+ return IRQ_HANDLED;
+}
+
+/*****************
+ *
+ * ACPI driver
+ *
+ *****************/
+static int sony_pic_remove(struct acpi_device *device)
+{
+ struct sony_pic_ioport *io, *tmp_io;
+ struct sony_pic_irq *irq, *tmp_irq;
+
+ if (sony_pic_disable(device)) {
+ pr_err("Couldn't disable device\n");
+ return -ENXIO;
+ }
+
+ free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev);
+ release_region(spic_dev.cur_ioport->io1.minimum,
+ spic_dev.cur_ioport->io1.address_length);
+ if (spic_dev.cur_ioport->io2.minimum)
+ release_region(spic_dev.cur_ioport->io2.minimum,
+ spic_dev.cur_ioport->io2.address_length);
+
+ sonypi_compat_exit();
+
+ sony_laptop_remove_input();
+
+ /* pf attrs */
+ sysfs_remove_group(&sony_pf_device->dev.kobj, &spic_attribute_group);
+ sony_pf_remove();
+
+ list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {
+ list_del(&io->list);
+ kfree(io);
+ }
+ list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) {
+ list_del(&irq->list);
+ kfree(irq);
+ }
+ spic_dev.cur_ioport = NULL;
+ spic_dev.cur_irq = NULL;
+
+ dprintk(SONY_PIC_DRIVER_NAME " removed.\n");
+ return 0;
+}
+
+static int sony_pic_add(struct acpi_device *device)
+{
+ int result;
+ struct sony_pic_ioport *io, *tmp_io;
+ struct sony_pic_irq *irq, *tmp_irq;
+
+ pr_info("%s v%s\n", SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION);
+
+ spic_dev.acpi_dev = device;
+ strcpy(acpi_device_class(device), "sony/hotkey");
+ sony_pic_detect_device_type(&spic_dev);
+ mutex_init(&spic_dev.lock);
+
+ /* read _PRS resources */
+ result = sony_pic_possible_resources(device);
+ if (result) {
+ pr_err("Unable to read possible resources\n");
+ goto err_free_resources;
+ }
+
+ /* setup input devices and helper fifo */
+ result = sony_laptop_setup_input(device);
+ if (result) {
+ pr_err("Unable to create input devices\n");
+ goto err_free_resources;
+ }
+
+ if (sonypi_compat_init())
+ goto err_remove_input;
+
+ /* request io port */
+ list_for_each_entry_reverse(io, &spic_dev.ioports, list) {
+ if (request_region(io->io1.minimum, io->io1.address_length,
+ "Sony Programmable I/O Device")) {
+ dprintk("I/O port1: 0x%.4x (0x%.4x) + 0x%.2x\n",
+ io->io1.minimum, io->io1.maximum,
+ io->io1.address_length);
+ /* Type 1 have 2 ioports */
+ if (io->io2.minimum) {
+ if (request_region(io->io2.minimum,
+ io->io2.address_length,
+ "Sony Programmable I/O Device")) {
+ dprintk("I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n",
+ io->io2.minimum, io->io2.maximum,
+ io->io2.address_length);
+ spic_dev.cur_ioport = io;
+ break;
+ }
+ else {
+ dprintk("Unable to get I/O port2: "
+ "0x%.4x (0x%.4x) + 0x%.2x\n",
+ io->io2.minimum, io->io2.maximum,
+ io->io2.address_length);
+ release_region(io->io1.minimum,
+ io->io1.address_length);
+ }
+ }
+ else {
+ spic_dev.cur_ioport = io;
+ break;
+ }
+ }
+ }
+ if (!spic_dev.cur_ioport) {
+ pr_err("Failed to request_region\n");
+ result = -ENODEV;
+ goto err_remove_compat;
+ }
+
+ /* request IRQ */
+ list_for_each_entry_reverse(irq, &spic_dev.interrupts, list) {
+ if (!request_irq(irq->irq.interrupts[0], sony_pic_irq,
+ 0, "sony-laptop", &spic_dev)) {
+ dprintk("IRQ: %d - triggering: %d - "
+ "polarity: %d - shr: %d\n",
+ irq->irq.interrupts[0],
+ irq->irq.triggering,
+ irq->irq.polarity,
+ irq->irq.sharable);
+ spic_dev.cur_irq = irq;
+ break;
+ }
+ }
+ if (!spic_dev.cur_irq) {
+ pr_err("Failed to request_irq\n");
+ result = -ENODEV;
+ goto err_release_region;
+ }
+
+ /* set resource status _SRS */
+ result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq);
+ if (result) {
+ pr_err("Couldn't enable device\n");
+ goto err_free_irq;
+ }
+
+ spic_dev.bluetooth_power = -1;
+ /* create device attributes */
+ result = sony_pf_add();
+ if (result)
+ goto err_disable_device;
+
+ result = sysfs_create_group(&sony_pf_device->dev.kobj, &spic_attribute_group);
+ if (result)
+ goto err_remove_pf;
+
+ return 0;
+
+err_remove_pf:
+ sony_pf_remove();
+
+err_disable_device:
+ sony_pic_disable(device);
+
+err_free_irq:
+ free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev);
+
+err_release_region:
+ release_region(spic_dev.cur_ioport->io1.minimum,
+ spic_dev.cur_ioport->io1.address_length);
+ if (spic_dev.cur_ioport->io2.minimum)
+ release_region(spic_dev.cur_ioport->io2.minimum,
+ spic_dev.cur_ioport->io2.address_length);
+
+err_remove_compat:
+ sonypi_compat_exit();
+
+err_remove_input:
+ sony_laptop_remove_input();
+
+err_free_resources:
+ list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {
+ list_del(&io->list);
+ kfree(io);
+ }
+ list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) {
+ list_del(&irq->list);
+ kfree(irq);
+ }
+ spic_dev.cur_ioport = NULL;
+ spic_dev.cur_irq = NULL;
+
+ return result;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sony_pic_suspend(struct device *dev)
+{
+ if (sony_pic_disable(to_acpi_device(dev)))
+ return -ENXIO;
+ return 0;
+}
+
+static int sony_pic_resume(struct device *dev)
+{
+ sony_pic_enable(to_acpi_device(dev),
+ spic_dev.cur_ioport, spic_dev.cur_irq);
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(sony_pic_pm, sony_pic_suspend, sony_pic_resume);
+
+static const struct acpi_device_id sony_pic_device_ids[] = {
+ {SONY_PIC_HID, 0},
+ {"", 0},
+};
+
+static struct acpi_driver sony_pic_driver = {
+ .name = SONY_PIC_DRIVER_NAME,
+ .class = SONY_PIC_CLASS,
+ .ids = sony_pic_device_ids,
+ .owner = THIS_MODULE,
+ .ops = {
+ .add = sony_pic_add,
+ .remove = sony_pic_remove,
+ },
+ .drv.pm = &sony_pic_pm,
+};
+
+static struct dmi_system_id __initdata sonypi_dmi_table[] = {
+ {
+ .ident = "Sony Vaio",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "PCG-"),
+ },
+ },
+ {
+ .ident = "Sony Vaio",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-"),
+ },
+ },
+ { }
+};
+
+static int __init sony_laptop_init(void)
+{
+ int result;
+
+ if (!no_spic && dmi_check_system(sonypi_dmi_table)) {
+ result = acpi_bus_register_driver(&sony_pic_driver);
+ if (result) {
+ pr_err("Unable to register SPIC driver\n");
+ goto out;
+ }
+ spic_drv_registered = 1;
+ }
+
+ result = acpi_bus_register_driver(&sony_nc_driver);
+ if (result) {
+ pr_err("Unable to register SNC driver\n");
+ goto out_unregister_pic;
+ }
+
+ return 0;
+
+out_unregister_pic:
+ if (spic_drv_registered)
+ acpi_bus_unregister_driver(&sony_pic_driver);
+out:
+ return result;
+}
+
+static void __exit sony_laptop_exit(void)
+{
+ acpi_bus_unregister_driver(&sony_nc_driver);
+ if (spic_drv_registered)
+ acpi_bus_unregister_driver(&sony_pic_driver);
+}
+
+module_init(sony_laptop_init);
+module_exit(sony_laptop_exit);
diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c
new file mode 100644
index 00000000..9b93fdb6
--- /dev/null
+++ b/drivers/platform/x86/tc1100-wmi.c
@@ -0,0 +1,283 @@
+/*
+ * HP Compaq TC1100 Tablet WMI Extras Driver
+ *
+ * Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ * Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com>
+ * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
+ * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <acpi/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/platform_device.h>
+
+#define GUID "C364AC71-36DB-495A-8494-B439D472A505"
+
+#define TC1100_INSTANCE_WIRELESS 1
+#define TC1100_INSTANCE_JOGDIAL 2
+
+MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho");
+MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("wmi:C364AC71-36DB-495A-8494-B439D472A505");
+
+static struct platform_device *tc1100_device;
+
+struct tc1100_data {
+ u32 wireless;
+ u32 jogdial;
+};
+
+static struct tc1100_data suspend_data;
+
+/* --------------------------------------------------------------------------
+ Device Management
+ -------------------------------------------------------------------------- */
+
+static int get_state(u32 *out, u8 instance)
+{
+ u32 tmp;
+ acpi_status status;
+ struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+
+ if (!out)
+ return -EINVAL;
+
+ if (instance > 2)
+ return -ENODEV;
+
+ status = wmi_query_block(GUID, instance, &result);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ obj = (union acpi_object *) result.pointer;
+ if (obj && obj->type == ACPI_TYPE_INTEGER) {
+ tmp = obj->integer.value;
+ } else {
+ tmp = 0;
+ }
+
+ if (result.length > 0 && result.pointer)
+ kfree(result.pointer);
+
+ switch (instance) {
+ case TC1100_INSTANCE_WIRELESS:
+ *out = (tmp == 3) ? 1 : 0;
+ return 0;
+ case TC1100_INSTANCE_JOGDIAL:
+ *out = (tmp == 1) ? 0 : 1;
+ return 0;
+ default:
+ return -ENODEV;
+ }
+}
+
+static int set_state(u32 *in, u8 instance)
+{
+ u32 value;
+ acpi_status status;
+ struct acpi_buffer input;
+
+ if (!in)
+ return -EINVAL;
+
+ if (instance > 2)
+ return -ENODEV;
+
+ switch (instance) {
+ case TC1100_INSTANCE_WIRELESS:
+ value = (*in) ? 1 : 2;
+ break;
+ case TC1100_INSTANCE_JOGDIAL:
+ value = (*in) ? 0 : 1;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ input.length = sizeof(u32);
+ input.pointer = &value;
+
+ status = wmi_set_block(GUID, instance, &input);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------
+ FS Interface (/sys)
+ -------------------------------------------------------------------------- */
+
+/*
+ * Read/ write bool sysfs macro
+ */
+#define show_set_bool(value, instance) \
+static ssize_t \
+show_bool_##value(struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ u32 result; \
+ acpi_status status = get_state(&result, instance); \
+ if (ACPI_SUCCESS(status)) \
+ return sprintf(buf, "%d\n", result); \
+ return sprintf(buf, "Read error\n"); \
+} \
+\
+static ssize_t \
+set_bool_##value(struct device *dev, struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ u32 tmp = simple_strtoul(buf, NULL, 10); \
+ acpi_status status = set_state(&tmp, instance); \
+ if (ACPI_FAILURE(status)) \
+ return -EINVAL; \
+ return count; \
+} \
+static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, \
+ show_bool_##value, set_bool_##value);
+
+show_set_bool(wireless, TC1100_INSTANCE_WIRELESS);
+show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL);
+
+static struct attribute *tc1100_attributes[] = {
+ &dev_attr_wireless.attr,
+ &dev_attr_jogdial.attr,
+ NULL
+};
+
+static struct attribute_group tc1100_attribute_group = {
+ .attrs = tc1100_attributes,
+};
+
+/* --------------------------------------------------------------------------
+ Driver Model
+ -------------------------------------------------------------------------- */
+
+static int __init tc1100_probe(struct platform_device *device)
+{
+ return sysfs_create_group(&device->dev.kobj, &tc1100_attribute_group);
+}
+
+
+static int tc1100_remove(struct platform_device *device)
+{
+ sysfs_remove_group(&device->dev.kobj, &tc1100_attribute_group);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int tc1100_suspend(struct device *dev)
+{
+ int ret;
+
+ ret = get_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+ if (ret)
+ return ret;
+
+ ret = get_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tc1100_resume(struct device *dev)
+{
+ int ret;
+
+ ret = set_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+ if (ret)
+ return ret;
+
+ ret = set_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct dev_pm_ops tc1100_pm_ops = {
+ .suspend = tc1100_suspend,
+ .resume = tc1100_resume,
+ .freeze = tc1100_suspend,
+ .restore = tc1100_resume,
+};
+#endif
+
+static struct platform_driver tc1100_driver = {
+ .driver = {
+ .name = "tc1100-wmi",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &tc1100_pm_ops,
+#endif
+ },
+ .remove = tc1100_remove,
+};
+
+static int __init tc1100_init(void)
+{
+ int error;
+
+ if (!wmi_has_guid(GUID))
+ return -ENODEV;
+
+ tc1100_device = platform_device_alloc("tc1100-wmi", -1);
+ if (!tc1100_device)
+ return -ENOMEM;
+
+ error = platform_device_add(tc1100_device);
+ if (error)
+ goto err_device_put;
+
+ error = platform_driver_probe(&tc1100_driver, tc1100_probe);
+ if (error)
+ goto err_device_del;
+
+ pr_info("HP Compaq TC1100 Tablet WMI Extras loaded\n");
+ return 0;
+
+ err_device_del:
+ platform_device_del(tc1100_device);
+ err_device_put:
+ platform_device_put(tc1100_device);
+ return error;
+}
+
+static void __exit tc1100_exit(void)
+{
+ platform_device_unregister(tc1100_device);
+ platform_driver_unregister(&tc1100_driver);
+}
+
+module_init(tc1100_init);
+module_exit(tc1100_exit);
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
new file mode 100644
index 00000000..54d31c0a
--- /dev/null
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -0,0 +1,9163 @@
+/*
+ * thinkpad_acpi.c - ThinkPad ACPI Extras
+ *
+ *
+ * Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net>
+ * Copyright (C) 2006-2009 Henrique de Moraes Holschuh <hmh@hmh.eng.br>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define TPACPI_VERSION "0.24"
+#define TPACPI_SYSFS_VERSION 0x020700
+
+/*
+ * Changelog:
+ * 2007-10-20 changelog trimmed down
+ *
+ * 2007-03-27 0.14 renamed to thinkpad_acpi and moved to
+ * drivers/misc.
+ *
+ * 2006-11-22 0.13 new maintainer
+ * changelog now lives in git commit history, and will
+ * not be updated further in-file.
+ *
+ * 2005-03-17 0.11 support for 600e, 770x
+ * thanks to Jamie Lentin <lentinj@dial.pipex.com>
+ *
+ * 2005-01-16 0.9 use MODULE_VERSION
+ * thanks to Henrik Brix Andersen <brix@gentoo.org>
+ * fix parameter passing on module loading
+ * thanks to Rusty Russell <rusty@rustcorp.com.au>
+ * thanks to Jim Radford <radford@blackbean.org>
+ * 2004-11-08 0.8 fix init error case, don't return from a macro
+ * thanks to Chris Wright <chrisw@osdl.org>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <linux/nvram.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/sysfs.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/rfkill.h>
+#include <asm/uaccess.h>
+
+#include <linux/dmi.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+#include <acpi/acpi_drivers.h>
+
+#include <linux/pci_ids.h>
+
+
+/* ThinkPad CMOS commands */
+#define TP_CMOS_VOLUME_DOWN 0
+#define TP_CMOS_VOLUME_UP 1
+#define TP_CMOS_VOLUME_MUTE 2
+#define TP_CMOS_BRIGHTNESS_UP 4
+#define TP_CMOS_BRIGHTNESS_DOWN 5
+#define TP_CMOS_THINKLIGHT_ON 12
+#define TP_CMOS_THINKLIGHT_OFF 13
+
+/* NVRAM Addresses */
+enum tp_nvram_addr {
+ TP_NVRAM_ADDR_HK2 = 0x57,
+ TP_NVRAM_ADDR_THINKLIGHT = 0x58,
+ TP_NVRAM_ADDR_VIDEO = 0x59,
+ TP_NVRAM_ADDR_BRIGHTNESS = 0x5e,
+ TP_NVRAM_ADDR_MIXER = 0x60,
+};
+
+/* NVRAM bit masks */
+enum {
+ TP_NVRAM_MASK_HKT_THINKPAD = 0x08,
+ TP_NVRAM_MASK_HKT_ZOOM = 0x20,
+ TP_NVRAM_MASK_HKT_DISPLAY = 0x40,
+ TP_NVRAM_MASK_HKT_HIBERNATE = 0x80,
+ TP_NVRAM_MASK_THINKLIGHT = 0x10,
+ TP_NVRAM_MASK_HKT_DISPEXPND = 0x30,
+ TP_NVRAM_MASK_HKT_BRIGHTNESS = 0x20,
+ TP_NVRAM_MASK_LEVEL_BRIGHTNESS = 0x0f,
+ TP_NVRAM_POS_LEVEL_BRIGHTNESS = 0,
+ TP_NVRAM_MASK_MUTE = 0x40,
+ TP_NVRAM_MASK_HKT_VOLUME = 0x80,
+ TP_NVRAM_MASK_LEVEL_VOLUME = 0x0f,
+ TP_NVRAM_POS_LEVEL_VOLUME = 0,
+};
+
+/* Misc NVRAM-related */
+enum {
+ TP_NVRAM_LEVEL_VOLUME_MAX = 14,
+};
+
+/* ACPI HIDs */
+#define TPACPI_ACPI_IBM_HKEY_HID "IBM0068"
+#define TPACPI_ACPI_LENOVO_HKEY_HID "LEN0068"
+#define TPACPI_ACPI_EC_HID "PNP0C09"
+
+/* Input IDs */
+#define TPACPI_HKEY_INPUT_PRODUCT 0x5054 /* "TP" */
+#define TPACPI_HKEY_INPUT_VERSION 0x4101
+
+/* ACPI \WGSV commands */
+enum {
+ TP_ACPI_WGSV_GET_STATE = 0x01, /* Get state information */
+ TP_ACPI_WGSV_PWR_ON_ON_RESUME = 0x02, /* Resume WWAN powered on */
+ TP_ACPI_WGSV_PWR_OFF_ON_RESUME = 0x03, /* Resume WWAN powered off */
+ TP_ACPI_WGSV_SAVE_STATE = 0x04, /* Save state for S4/S5 */
+};
+
+/* TP_ACPI_WGSV_GET_STATE bits */
+enum {
+ TP_ACPI_WGSV_STATE_WWANEXIST = 0x0001, /* WWAN hw available */
+ TP_ACPI_WGSV_STATE_WWANPWR = 0x0002, /* WWAN radio enabled */
+ TP_ACPI_WGSV_STATE_WWANPWRRES = 0x0004, /* WWAN state at resume */
+ TP_ACPI_WGSV_STATE_WWANBIOSOFF = 0x0008, /* WWAN disabled in BIOS */
+ TP_ACPI_WGSV_STATE_BLTHEXIST = 0x0001, /* BLTH hw available */
+ TP_ACPI_WGSV_STATE_BLTHPWR = 0x0002, /* BLTH radio enabled */
+ TP_ACPI_WGSV_STATE_BLTHPWRRES = 0x0004, /* BLTH state at resume */
+ TP_ACPI_WGSV_STATE_BLTHBIOSOFF = 0x0008, /* BLTH disabled in BIOS */
+ TP_ACPI_WGSV_STATE_UWBEXIST = 0x0010, /* UWB hw available */
+ TP_ACPI_WGSV_STATE_UWBPWR = 0x0020, /* UWB radio enabled */
+};
+
+/* HKEY events */
+enum tpacpi_hkey_event_t {
+ /* Hotkey-related */
+ TP_HKEY_EV_HOTKEY_BASE = 0x1001, /* first hotkey (FN+F1) */
+ TP_HKEY_EV_BRGHT_UP = 0x1010, /* Brightness up */
+ TP_HKEY_EV_BRGHT_DOWN = 0x1011, /* Brightness down */
+ TP_HKEY_EV_VOL_UP = 0x1015, /* Volume up or unmute */
+ TP_HKEY_EV_VOL_DOWN = 0x1016, /* Volume down or unmute */
+ TP_HKEY_EV_VOL_MUTE = 0x1017, /* Mixer output mute */
+
+ /* Reasons for waking up from S3/S4 */
+ TP_HKEY_EV_WKUP_S3_UNDOCK = 0x2304, /* undock requested, S3 */
+ TP_HKEY_EV_WKUP_S4_UNDOCK = 0x2404, /* undock requested, S4 */
+ TP_HKEY_EV_WKUP_S3_BAYEJ = 0x2305, /* bay ejection req, S3 */
+ TP_HKEY_EV_WKUP_S4_BAYEJ = 0x2405, /* bay ejection req, S4 */
+ TP_HKEY_EV_WKUP_S3_BATLOW = 0x2313, /* battery empty, S3 */
+ TP_HKEY_EV_WKUP_S4_BATLOW = 0x2413, /* battery empty, S4 */
+
+ /* Auto-sleep after eject request */
+ TP_HKEY_EV_BAYEJ_ACK = 0x3003, /* bay ejection complete */
+ TP_HKEY_EV_UNDOCK_ACK = 0x4003, /* undock complete */
+
+ /* Misc bay events */
+ TP_HKEY_EV_OPTDRV_EJ = 0x3006, /* opt. drive tray ejected */
+ TP_HKEY_EV_HOTPLUG_DOCK = 0x4010, /* docked into hotplug dock
+ or port replicator */
+ TP_HKEY_EV_HOTPLUG_UNDOCK = 0x4011, /* undocked from hotplug
+ dock or port replicator */
+
+ /* User-interface events */
+ TP_HKEY_EV_LID_CLOSE = 0x5001, /* laptop lid closed */
+ TP_HKEY_EV_LID_OPEN = 0x5002, /* laptop lid opened */
+ TP_HKEY_EV_TABLET_TABLET = 0x5009, /* tablet swivel up */
+ TP_HKEY_EV_TABLET_NOTEBOOK = 0x500a, /* tablet swivel down */
+ TP_HKEY_EV_PEN_INSERTED = 0x500b, /* tablet pen inserted */
+ TP_HKEY_EV_PEN_REMOVED = 0x500c, /* tablet pen removed */
+ TP_HKEY_EV_BRGHT_CHANGED = 0x5010, /* backlight control event */
+
+ /* Key-related user-interface events */
+ TP_HKEY_EV_KEY_NUMLOCK = 0x6000, /* NumLock key pressed */
+ TP_HKEY_EV_KEY_FN = 0x6005, /* Fn key pressed? E420 */
+
+ /* Thermal events */
+ TP_HKEY_EV_ALARM_BAT_HOT = 0x6011, /* battery too hot */
+ TP_HKEY_EV_ALARM_BAT_XHOT = 0x6012, /* battery critically hot */
+ TP_HKEY_EV_ALARM_SENSOR_HOT = 0x6021, /* sensor too hot */
+ TP_HKEY_EV_ALARM_SENSOR_XHOT = 0x6022, /* sensor critically hot */
+ TP_HKEY_EV_THM_TABLE_CHANGED = 0x6030, /* thermal table changed */
+
+ /* AC-related events */
+ TP_HKEY_EV_AC_CHANGED = 0x6040, /* AC status changed */
+
+ /* Misc */
+ TP_HKEY_EV_RFKILL_CHANGED = 0x7000, /* rfkill switch changed */
+};
+
+/****************************************************************************
+ * Main driver
+ */
+
+#define TPACPI_NAME "thinkpad"
+#define TPACPI_DESC "ThinkPad ACPI Extras"
+#define TPACPI_FILE TPACPI_NAME "_acpi"
+#define TPACPI_URL "http://ibm-acpi.sf.net/"
+#define TPACPI_MAIL "ibm-acpi-devel@lists.sourceforge.net"
+
+#define TPACPI_PROC_DIR "ibm"
+#define TPACPI_ACPI_EVENT_PREFIX "ibm"
+#define TPACPI_DRVR_NAME TPACPI_FILE
+#define TPACPI_DRVR_SHORTNAME "tpacpi"
+#define TPACPI_HWMON_DRVR_NAME TPACPI_NAME "_hwmon"
+
+#define TPACPI_NVRAM_KTHREAD_NAME "ktpacpi_nvramd"
+#define TPACPI_WORKQUEUE_NAME "ktpacpid"
+
+#define TPACPI_MAX_ACPI_ARGS 3
+
+/* Debugging printk groups */
+#define TPACPI_DBG_ALL 0xffff
+#define TPACPI_DBG_DISCLOSETASK 0x8000
+#define TPACPI_DBG_INIT 0x0001
+#define TPACPI_DBG_EXIT 0x0002
+#define TPACPI_DBG_RFKILL 0x0004
+#define TPACPI_DBG_HKEY 0x0008
+#define TPACPI_DBG_FAN 0x0010
+#define TPACPI_DBG_BRGHT 0x0020
+#define TPACPI_DBG_MIXER 0x0040
+
+#define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off")
+#define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
+#define strlencmp(a, b) (strncmp((a), (b), strlen(b)))
+
+
+/****************************************************************************
+ * Driver-wide structs and misc. variables
+ */
+
+struct ibm_struct;
+
+struct tp_acpi_drv_struct {
+ const struct acpi_device_id *hid;
+ struct acpi_driver *driver;
+
+ void (*notify) (struct ibm_struct *, u32);
+ acpi_handle *handle;
+ u32 type;
+ struct acpi_device *device;
+};
+
+struct ibm_struct {
+ char *name;
+
+ int (*read) (struct seq_file *);
+ int (*write) (char *);
+ void (*exit) (void);
+ void (*resume) (void);
+ void (*suspend) (void);
+ void (*shutdown) (void);
+
+ struct list_head all_drivers;
+
+ struct tp_acpi_drv_struct *acpi;
+
+ struct {
+ u8 acpi_driver_registered:1;
+ u8 acpi_notify_installed:1;
+ u8 proc_created:1;
+ u8 init_called:1;
+ u8 experimental:1;
+ } flags;
+};
+
+struct ibm_init_struct {
+ char param[32];
+
+ int (*init) (struct ibm_init_struct *);
+ umode_t base_procfs_mode;
+ struct ibm_struct *data;
+};
+
+static struct {
+ u32 bluetooth:1;
+ u32 hotkey:1;
+ u32 hotkey_mask:1;
+ u32 hotkey_wlsw:1;
+ u32 hotkey_tablet:1;
+ u32 light:1;
+ u32 light_status:1;
+ u32 bright_acpimode:1;
+ u32 bright_unkfw:1;
+ u32 wan:1;
+ u32 uwb:1;
+ u32 fan_ctrl_status_undef:1;
+ u32 second_fan:1;
+ u32 beep_needs_two_args:1;
+ u32 mixer_no_level_control:1;
+ u32 input_device_registered:1;
+ u32 platform_drv_registered:1;
+ u32 platform_drv_attrs_registered:1;
+ u32 sensors_pdrv_registered:1;
+ u32 sensors_pdrv_attrs_registered:1;
+ u32 sensors_pdev_attrs_registered:1;
+ u32 hotkey_poll_active:1;
+} tp_features;
+
+static struct {
+ u16 hotkey_mask_ff:1;
+ u16 volume_ctrl_forbidden:1;
+} tp_warned;
+
+struct thinkpad_id_data {
+ unsigned int vendor; /* ThinkPad vendor:
+ * PCI_VENDOR_ID_IBM/PCI_VENDOR_ID_LENOVO */
+
+ char *bios_version_str; /* Something like 1ZET51WW (1.03z) */
+ char *ec_version_str; /* Something like 1ZHT51WW-1.04a */
+
+ u16 bios_model; /* 1Y = 0x5931, 0 = unknown */
+ u16 ec_model;
+ u16 bios_release; /* 1ZETK1WW = 0x314b, 0 = unknown */
+ u16 ec_release;
+
+ char *model_str; /* ThinkPad T43 */
+ char *nummodel_str; /* 9384A9C for a 9384-A9C model */
+};
+static struct thinkpad_id_data thinkpad_id;
+
+static enum {
+ TPACPI_LIFE_INIT = 0,
+ TPACPI_LIFE_RUNNING,
+ TPACPI_LIFE_EXITING,
+} tpacpi_lifecycle;
+
+static int experimental;
+static u32 dbg_level;
+
+static struct workqueue_struct *tpacpi_wq;
+
+enum led_status_t {
+ TPACPI_LED_OFF = 0,
+ TPACPI_LED_ON,
+ TPACPI_LED_BLINK,
+};
+
+/* Special LED class that can defer work */
+struct tpacpi_led_classdev {
+ struct led_classdev led_classdev;
+ struct work_struct work;
+ enum led_status_t new_state;
+ unsigned int led;
+};
+
+/* brightness level capabilities */
+static unsigned int bright_maxlvl; /* 0 = unknown */
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+static int dbg_wlswemul;
+static bool tpacpi_wlsw_emulstate;
+static int dbg_bluetoothemul;
+static bool tpacpi_bluetooth_emulstate;
+static int dbg_wwanemul;
+static bool tpacpi_wwan_emulstate;
+static int dbg_uwbemul;
+static bool tpacpi_uwb_emulstate;
+#endif
+
+
+/*************************************************************************
+ * Debugging helpers
+ */
+
+#define dbg_printk(a_dbg_level, format, arg...) \
+do { \
+ if (dbg_level & (a_dbg_level)) \
+ printk(KERN_DEBUG pr_fmt("%s: " format), \
+ __func__, ##arg); \
+} while (0)
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUG
+#define vdbg_printk dbg_printk
+static const char *str_supported(int is_supported);
+#else
+static inline const char *str_supported(int is_supported) { return ""; }
+#define vdbg_printk(a_dbg_level, format, arg...) \
+ no_printk(format, ##arg)
+#endif
+
+static void tpacpi_log_usertask(const char * const what)
+{
+ printk(KERN_DEBUG pr_fmt("%s: access by process with PID %d\n"),
+ what, task_tgid_vnr(current));
+}
+
+#define tpacpi_disclose_usertask(what, format, arg...) \
+do { \
+ if (unlikely((dbg_level & TPACPI_DBG_DISCLOSETASK) && \
+ (tpacpi_lifecycle == TPACPI_LIFE_RUNNING))) { \
+ printk(KERN_DEBUG pr_fmt("%s: PID %d: " format), \
+ what, task_tgid_vnr(current), ## arg); \
+ } \
+} while (0)
+
+/*
+ * Quirk handling helpers
+ *
+ * ThinkPad IDs and versions seen in the field so far
+ * are two-characters from the set [0-9A-Z], i.e. base 36.
+ *
+ * We use values well outside that range as specials.
+ */
+
+#define TPACPI_MATCH_ANY 0xffffU
+#define TPACPI_MATCH_UNKNOWN 0U
+
+/* TPID('1', 'Y') == 0x5931 */
+#define TPID(__c1, __c2) (((__c2) << 8) | (__c1))
+
+#define TPACPI_Q_IBM(__id1, __id2, __quirk) \
+ { .vendor = PCI_VENDOR_ID_IBM, \
+ .bios = TPID(__id1, __id2), \
+ .ec = TPACPI_MATCH_ANY, \
+ .quirks = (__quirk) }
+
+#define TPACPI_Q_LNV(__id1, __id2, __quirk) \
+ { .vendor = PCI_VENDOR_ID_LENOVO, \
+ .bios = TPID(__id1, __id2), \
+ .ec = TPACPI_MATCH_ANY, \
+ .quirks = (__quirk) }
+
+#define TPACPI_QEC_LNV(__id1, __id2, __quirk) \
+ { .vendor = PCI_VENDOR_ID_LENOVO, \
+ .bios = TPACPI_MATCH_ANY, \
+ .ec = TPID(__id1, __id2), \
+ .quirks = (__quirk) }
+
+struct tpacpi_quirk {
+ unsigned int vendor;
+ u16 bios;
+ u16 ec;
+ unsigned long quirks;
+};
+
+/**
+ * tpacpi_check_quirks() - search BIOS/EC version on a list
+ * @qlist: array of &struct tpacpi_quirk
+ * @qlist_size: number of elements in @qlist
+ *
+ * Iterates over a quirks list until one is found that matches the
+ * ThinkPad's vendor, BIOS and EC model.
+ *
+ * Returns 0 if nothing matches, otherwise returns the quirks field of
+ * the matching &struct tpacpi_quirk entry.
+ *
+ * The match criteria is: vendor, ec and bios much match.
+ */
+static unsigned long __init tpacpi_check_quirks(
+ const struct tpacpi_quirk *qlist,
+ unsigned int qlist_size)
+{
+ while (qlist_size) {
+ if ((qlist->vendor == thinkpad_id.vendor ||
+ qlist->vendor == TPACPI_MATCH_ANY) &&
+ (qlist->bios == thinkpad_id.bios_model ||
+ qlist->bios == TPACPI_MATCH_ANY) &&
+ (qlist->ec == thinkpad_id.ec_model ||
+ qlist->ec == TPACPI_MATCH_ANY))
+ return qlist->quirks;
+
+ qlist_size--;
+ qlist++;
+ }
+ return 0;
+}
+
+static inline bool __pure __init tpacpi_is_lenovo(void)
+{
+ return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
+}
+
+static inline bool __pure __init tpacpi_is_ibm(void)
+{
+ return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
+}
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * ACPI Helpers and device model
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * ACPI basic handles
+ */
+
+static acpi_handle root_handle;
+static acpi_handle ec_handle;
+
+#define TPACPI_HANDLE(object, parent, paths...) \
+ static acpi_handle object##_handle; \
+ static const acpi_handle * const object##_parent __initconst = \
+ &parent##_handle; \
+ static char *object##_paths[] __initdata = { paths }
+
+TPACPI_HANDLE(ecrd, ec, "ECRD"); /* 570 */
+TPACPI_HANDLE(ecwr, ec, "ECWR"); /* 570 */
+
+TPACPI_HANDLE(cmos, root, "\\UCMS", /* R50, R50e, R50p, R51, */
+ /* T4x, X31, X40 */
+ "\\CMOS", /* A3x, G4x, R32, T23, T30, X22-24, X30 */
+ "\\CMS", /* R40, R40e */
+ ); /* all others */
+
+TPACPI_HANDLE(hkey, ec, "\\_SB.HKEY", /* 600e/x, 770e, 770x */
+ "^HKEY", /* R30, R31 */
+ "HKEY", /* all others */
+ ); /* 570 */
+
+/*************************************************************************
+ * ACPI helpers
+ */
+
+static int acpi_evalf(acpi_handle handle,
+ int *res, char *method, char *fmt, ...)
+{
+ char *fmt0 = fmt;
+ struct acpi_object_list params;
+ union acpi_object in_objs[TPACPI_MAX_ACPI_ARGS];
+ struct acpi_buffer result, *resultp;
+ union acpi_object out_obj;
+ acpi_status status;
+ va_list ap;
+ char res_type;
+ int success;
+ int quiet;
+
+ if (!*fmt) {
+ pr_err("acpi_evalf() called with empty format\n");
+ return 0;
+ }
+
+ if (*fmt == 'q') {
+ quiet = 1;
+ fmt++;
+ } else
+ quiet = 0;
+
+ res_type = *(fmt++);
+
+ params.count = 0;
+ params.pointer = &in_objs[0];
+
+ va_start(ap, fmt);
+ while (*fmt) {
+ char c = *(fmt++);
+ switch (c) {
+ case 'd': /* int */
+ in_objs[params.count].integer.value = va_arg(ap, int);
+ in_objs[params.count++].type = ACPI_TYPE_INTEGER;
+ break;
+ /* add more types as needed */
+ default:
+ pr_err("acpi_evalf() called "
+ "with invalid format character '%c'\n", c);
+ va_end(ap);
+ return 0;
+ }
+ }
+ va_end(ap);
+
+ if (res_type != 'v') {
+ result.length = sizeof(out_obj);
+ result.pointer = &out_obj;
+ resultp = &result;
+ } else
+ resultp = NULL;
+
+ status = acpi_evaluate_object(handle, method, &params, resultp);
+
+ switch (res_type) {
+ case 'd': /* int */
+ success = (status == AE_OK &&
+ out_obj.type == ACPI_TYPE_INTEGER);
+ if (success && res)
+ *res = out_obj.integer.value;
+ break;
+ case 'v': /* void */
+ success = status == AE_OK;
+ break;
+ /* add more types as needed */
+ default:
+ pr_err("acpi_evalf() called "
+ "with invalid format character '%c'\n", res_type);
+ return 0;
+ }
+
+ if (!success && !quiet)
+ pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
+ method, fmt0, acpi_format_exception(status));
+
+ return success;
+}
+
+static int acpi_ec_read(int i, u8 *p)
+{
+ int v;
+
+ if (ecrd_handle) {
+ if (!acpi_evalf(ecrd_handle, &v, NULL, "dd", i))
+ return 0;
+ *p = v;
+ } else {
+ if (ec_read(i, p) < 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int acpi_ec_write(int i, u8 v)
+{
+ if (ecwr_handle) {
+ if (!acpi_evalf(ecwr_handle, NULL, NULL, "vdd", i, v))
+ return 0;
+ } else {
+ if (ec_write(i, v) < 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int issue_thinkpad_cmos_command(int cmos_cmd)
+{
+ if (!cmos_handle)
+ return -ENXIO;
+
+ if (!acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd))
+ return -EIO;
+
+ return 0;
+}
+
+/*************************************************************************
+ * ACPI device model
+ */
+
+#define TPACPI_ACPIHANDLE_INIT(object) \
+ drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
+ object##_paths, ARRAY_SIZE(object##_paths))
+
+static void __init drv_acpi_handle_init(const char *name,
+ acpi_handle *handle, const acpi_handle parent,
+ char **paths, const int num_paths)
+{
+ int i;
+ acpi_status status;
+
+ vdbg_printk(TPACPI_DBG_INIT, "trying to locate ACPI handle for %s\n",
+ name);
+
+ for (i = 0; i < num_paths; i++) {
+ status = acpi_get_handle(parent, paths[i], handle);
+ if (ACPI_SUCCESS(status)) {
+ dbg_printk(TPACPI_DBG_INIT,
+ "Found ACPI handle %s for %s\n",
+ paths[i], name);
+ return;
+ }
+ }
+
+ vdbg_printk(TPACPI_DBG_INIT, "ACPI handle for %s not found\n",
+ name);
+ *handle = NULL;
+}
+
+static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle,
+ u32 level, void *context, void **return_value)
+{
+ *(acpi_handle *)return_value = handle;
+
+ return AE_CTRL_TERMINATE;
+}
+
+static void __init tpacpi_acpi_handle_locate(const char *name,
+ const char *hid,
+ acpi_handle *handle)
+{
+ acpi_status status;
+ acpi_handle device_found;
+
+ BUG_ON(!name || !hid || !handle);
+ vdbg_printk(TPACPI_DBG_INIT,
+ "trying to locate ACPI handle for %s, using HID %s\n",
+ name, hid);
+
+ memset(&device_found, 0, sizeof(device_found));
+ status = acpi_get_devices(hid, tpacpi_acpi_handle_locate_callback,
+ (void *)name, &device_found);
+
+ *handle = NULL;
+
+ if (ACPI_SUCCESS(status)) {
+ *handle = device_found;
+ dbg_printk(TPACPI_DBG_INIT,
+ "Found ACPI handle for %s\n", name);
+ } else {
+ vdbg_printk(TPACPI_DBG_INIT,
+ "Could not locate an ACPI handle for %s: %s\n",
+ name, acpi_format_exception(status));
+ }
+}
+
+static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct ibm_struct *ibm = data;
+
+ if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
+ return;
+
+ if (!ibm || !ibm->acpi || !ibm->acpi->notify)
+ return;
+
+ ibm->acpi->notify(ibm, event);
+}
+
+static int __init setup_acpi_notify(struct ibm_struct *ibm)
+{
+ acpi_status status;
+ int rc;
+
+ BUG_ON(!ibm->acpi);
+
+ if (!*ibm->acpi->handle)
+ return 0;
+
+ vdbg_printk(TPACPI_DBG_INIT,
+ "setting up ACPI notify for %s\n", ibm->name);
+
+ rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device);
+ if (rc < 0) {
+ pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc);
+ return -ENODEV;
+ }
+
+ ibm->acpi->device->driver_data = ibm;
+ sprintf(acpi_device_class(ibm->acpi->device), "%s/%s",
+ TPACPI_ACPI_EVENT_PREFIX,
+ ibm->name);
+
+ status = acpi_install_notify_handler(*ibm->acpi->handle,
+ ibm->acpi->type, dispatch_acpi_notify, ibm);
+ if (ACPI_FAILURE(status)) {
+ if (status == AE_ALREADY_EXISTS) {
+ pr_notice("another device driver is already "
+ "handling %s events\n", ibm->name);
+ } else {
+ pr_err("acpi_install_notify_handler(%s) failed: %s\n",
+ ibm->name, acpi_format_exception(status));
+ }
+ return -ENODEV;
+ }
+ ibm->flags.acpi_notify_installed = 1;
+ return 0;
+}
+
+static int __init tpacpi_device_add(struct acpi_device *device)
+{
+ return 0;
+}
+
+static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
+{
+ int rc;
+
+ dbg_printk(TPACPI_DBG_INIT,
+ "registering %s as an ACPI driver\n", ibm->name);
+
+ BUG_ON(!ibm->acpi);
+
+ ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
+ if (!ibm->acpi->driver) {
+ pr_err("failed to allocate memory for ibm->acpi->driver\n");
+ return -ENOMEM;
+ }
+
+ sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_NAME, ibm->name);
+ ibm->acpi->driver->ids = ibm->acpi->hid;
+
+ ibm->acpi->driver->ops.add = &tpacpi_device_add;
+
+ rc = acpi_bus_register_driver(ibm->acpi->driver);
+ if (rc < 0) {
+ pr_err("acpi_bus_register_driver(%s) failed: %d\n",
+ ibm->name, rc);
+ kfree(ibm->acpi->driver);
+ ibm->acpi->driver = NULL;
+ } else if (!rc)
+ ibm->flags.acpi_driver_registered = 1;
+
+ return rc;
+}
+
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Procfs Helpers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+static int dispatch_proc_show(struct seq_file *m, void *v)
+{
+ struct ibm_struct *ibm = m->private;
+
+ if (!ibm || !ibm->read)
+ return -EINVAL;
+ return ibm->read(m);
+}
+
+static int dispatch_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dispatch_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t dispatch_proc_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *pos)
+{
+ struct ibm_struct *ibm = PDE_DATA(file_inode(file));
+ char *kernbuf;
+ int ret;
+
+ if (!ibm || !ibm->write)
+ return -EINVAL;
+ if (count > PAGE_SIZE - 2)
+ return -EINVAL;
+
+ kernbuf = kmalloc(count + 2, GFP_KERNEL);
+ if (!kernbuf)
+ return -ENOMEM;
+
+ if (copy_from_user(kernbuf, userbuf, count)) {
+ kfree(kernbuf);
+ return -EFAULT;
+ }
+
+ kernbuf[count] = 0;
+ strcat(kernbuf, ",");
+ ret = ibm->write(kernbuf);
+ if (ret == 0)
+ ret = count;
+
+ kfree(kernbuf);
+
+ return ret;
+}
+
+static const struct file_operations dispatch_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = dispatch_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = dispatch_proc_write,
+};
+
+static char *next_cmd(char **cmds)
+{
+ char *start = *cmds;
+ char *end;
+
+ while ((end = strchr(start, ',')) && end == start)
+ start = end + 1;
+
+ if (!end)
+ return NULL;
+
+ *end = 0;
+ *cmds = end + 1;
+ return start;
+}
+
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Device model: input, hwmon and platform
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+static struct platform_device *tpacpi_pdev;
+static struct platform_device *tpacpi_sensors_pdev;
+static struct device *tpacpi_hwmon;
+static struct input_dev *tpacpi_inputdev;
+static struct mutex tpacpi_inputdev_send_mutex;
+static LIST_HEAD(tpacpi_all_drivers);
+
+#ifdef CONFIG_PM_SLEEP
+static int tpacpi_suspend_handler(struct device *dev)
+{
+ struct ibm_struct *ibm, *itmp;
+
+ list_for_each_entry_safe(ibm, itmp,
+ &tpacpi_all_drivers,
+ all_drivers) {
+ if (ibm->suspend)
+ (ibm->suspend)();
+ }
+
+ return 0;
+}
+
+static int tpacpi_resume_handler(struct device *dev)
+{
+ struct ibm_struct *ibm, *itmp;
+
+ list_for_each_entry_safe(ibm, itmp,
+ &tpacpi_all_drivers,
+ all_drivers) {
+ if (ibm->resume)
+ (ibm->resume)();
+ }
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(tpacpi_pm,
+ tpacpi_suspend_handler, tpacpi_resume_handler);
+
+static void tpacpi_shutdown_handler(struct platform_device *pdev)
+{
+ struct ibm_struct *ibm, *itmp;
+
+ list_for_each_entry_safe(ibm, itmp,
+ &tpacpi_all_drivers,
+ all_drivers) {
+ if (ibm->shutdown)
+ (ibm->shutdown)();
+ }
+}
+
+static struct platform_driver tpacpi_pdriver = {
+ .driver = {
+ .name = TPACPI_DRVR_NAME,
+ .owner = THIS_MODULE,
+ .pm = &tpacpi_pm,
+ },
+ .shutdown = tpacpi_shutdown_handler,
+};
+
+static struct platform_driver tpacpi_hwmon_pdriver = {
+ .driver = {
+ .name = TPACPI_HWMON_DRVR_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+/*************************************************************************
+ * sysfs support helpers
+ */
+
+struct attribute_set {
+ unsigned int members, max_members;
+ struct attribute_group group;
+};
+
+struct attribute_set_obj {
+ struct attribute_set s;
+ struct attribute *a;
+} __attribute__((packed));
+
+static struct attribute_set *create_attr_set(unsigned int max_members,
+ const char *name)
+{
+ struct attribute_set_obj *sobj;
+
+ if (max_members == 0)
+ return NULL;
+
+ /* Allocates space for implicit NULL at the end too */
+ sobj = kzalloc(sizeof(struct attribute_set_obj) +
+ max_members * sizeof(struct attribute *),
+ GFP_KERNEL);
+ if (!sobj)
+ return NULL;
+ sobj->s.max_members = max_members;
+ sobj->s.group.attrs = &sobj->a;
+ sobj->s.group.name = name;
+
+ return &sobj->s;
+}
+
+#define destroy_attr_set(_set) \
+ kfree(_set);
+
+/* not multi-threaded safe, use it in a single thread per set */
+static int add_to_attr_set(struct attribute_set *s, struct attribute *attr)
+{
+ if (!s || !attr)
+ return -EINVAL;
+
+ if (s->members >= s->max_members)
+ return -ENOMEM;
+
+ s->group.attrs[s->members] = attr;
+ s->members++;
+
+ return 0;
+}
+
+static int add_many_to_attr_set(struct attribute_set *s,
+ struct attribute **attr,
+ unsigned int count)
+{
+ int i, res;
+
+ for (i = 0; i < count; i++) {
+ res = add_to_attr_set(s, attr[i]);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static void delete_attr_set(struct attribute_set *s, struct kobject *kobj)
+{
+ sysfs_remove_group(kobj, &s->group);
+ destroy_attr_set(s);
+}
+
+#define register_attr_set_with_sysfs(_attr_set, _kobj) \
+ sysfs_create_group(_kobj, &_attr_set->group)
+
+static int parse_strtoul(const char *buf,
+ unsigned long max, unsigned long *value)
+{
+ char *endp;
+
+ *value = simple_strtoul(skip_spaces(buf), &endp, 0);
+ endp = skip_spaces(endp);
+ if (*endp || *value > max)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void tpacpi_disable_brightness_delay(void)
+{
+ if (acpi_evalf(hkey_handle, NULL, "PWMS", "qvd", 0))
+ pr_notice("ACPI backlight control delay disabled\n");
+}
+
+static void printk_deprecated_attribute(const char * const what,
+ const char * const details)
+{
+ tpacpi_log_usertask("deprecated sysfs attribute");
+ pr_warn("WARNING: sysfs attribute %s is deprecated and "
+ "will be removed. %s\n",
+ what, details);
+}
+
+/*************************************************************************
+ * rfkill and radio control support helpers
+ */
+
+/*
+ * ThinkPad-ACPI firmware handling model:
+ *
+ * WLSW (master wireless switch) is event-driven, and is common to all
+ * firmware-controlled radios. It cannot be controlled, just monitored,
+ * as expected. It overrides all radio state in firmware
+ *
+ * The kernel, a masked-off hotkey, and WLSW can change the radio state
+ * (TODO: verify how WLSW interacts with the returned radio state).
+ *
+ * The only time there are shadow radio state changes, is when
+ * masked-off hotkeys are used.
+ */
+
+/*
+ * Internal driver API for radio state:
+ *
+ * int: < 0 = error, otherwise enum tpacpi_rfkill_state
+ * bool: true means radio blocked (off)
+ */
+enum tpacpi_rfkill_state {
+ TPACPI_RFK_RADIO_OFF = 0,
+ TPACPI_RFK_RADIO_ON
+};
+
+/* rfkill switches */
+enum tpacpi_rfk_id {
+ TPACPI_RFK_BLUETOOTH_SW_ID = 0,
+ TPACPI_RFK_WWAN_SW_ID,
+ TPACPI_RFK_UWB_SW_ID,
+ TPACPI_RFK_SW_MAX
+};
+
+static const char *tpacpi_rfkill_names[] = {
+ [TPACPI_RFK_BLUETOOTH_SW_ID] = "bluetooth",
+ [TPACPI_RFK_WWAN_SW_ID] = "wwan",
+ [TPACPI_RFK_UWB_SW_ID] = "uwb",
+ [TPACPI_RFK_SW_MAX] = NULL
+};
+
+/* ThinkPad-ACPI rfkill subdriver */
+struct tpacpi_rfk {
+ struct rfkill *rfkill;
+ enum tpacpi_rfk_id id;
+ const struct tpacpi_rfk_ops *ops;
+};
+
+struct tpacpi_rfk_ops {
+ /* firmware interface */
+ int (*get_status)(void);
+ int (*set_status)(const enum tpacpi_rfkill_state);
+};
+
+static struct tpacpi_rfk *tpacpi_rfkill_switches[TPACPI_RFK_SW_MAX];
+
+/* Query FW and update rfkill sw state for a given rfkill switch */
+static int tpacpi_rfk_update_swstate(const struct tpacpi_rfk *tp_rfk)
+{
+ int status;
+
+ if (!tp_rfk)
+ return -ENODEV;
+
+ status = (tp_rfk->ops->get_status)();
+ if (status < 0)
+ return status;
+
+ rfkill_set_sw_state(tp_rfk->rfkill,
+ (status == TPACPI_RFK_RADIO_OFF));
+
+ return status;
+}
+
+/* Query FW and update rfkill sw state for all rfkill switches */
+static void tpacpi_rfk_update_swstate_all(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < TPACPI_RFK_SW_MAX; i++)
+ tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[i]);
+}
+
+/*
+ * Sync the HW-blocking state of all rfkill switches,
+ * do notice it causes the rfkill core to schedule uevents
+ */
+static void tpacpi_rfk_update_hwblock_state(bool blocked)
+{
+ unsigned int i;
+ struct tpacpi_rfk *tp_rfk;
+
+ for (i = 0; i < TPACPI_RFK_SW_MAX; i++) {
+ tp_rfk = tpacpi_rfkill_switches[i];
+ if (tp_rfk) {
+ if (rfkill_set_hw_state(tp_rfk->rfkill,
+ blocked)) {
+ /* ignore -- we track sw block */
+ }
+ }
+ }
+}
+
+/* Call to get the WLSW state from the firmware */
+static int hotkey_get_wlsw(void);
+
+/* Call to query WLSW state and update all rfkill switches */
+static bool tpacpi_rfk_check_hwblock_state(void)
+{
+ int res = hotkey_get_wlsw();
+ int hw_blocked;
+
+ /* When unknown or unsupported, we have to assume it is unblocked */
+ if (res < 0)
+ return false;
+
+ hw_blocked = (res == TPACPI_RFK_RADIO_OFF);
+ tpacpi_rfk_update_hwblock_state(hw_blocked);
+
+ return hw_blocked;
+}
+
+static int tpacpi_rfk_hook_set_block(void *data, bool blocked)
+{
+ struct tpacpi_rfk *tp_rfk = data;
+ int res;
+
+ dbg_printk(TPACPI_DBG_RFKILL,
+ "request to change radio state to %s\n",
+ blocked ? "blocked" : "unblocked");
+
+ /* try to set radio state */
+ res = (tp_rfk->ops->set_status)(blocked ?
+ TPACPI_RFK_RADIO_OFF : TPACPI_RFK_RADIO_ON);
+
+ /* and update the rfkill core with whatever the FW really did */
+ tpacpi_rfk_update_swstate(tp_rfk);
+
+ return (res < 0) ? res : 0;
+}
+
+static const struct rfkill_ops tpacpi_rfk_rfkill_ops = {
+ .set_block = tpacpi_rfk_hook_set_block,
+};
+
+static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
+ const struct tpacpi_rfk_ops *tp_rfkops,
+ const enum rfkill_type rfktype,
+ const char *name,
+ const bool set_default)
+{
+ struct tpacpi_rfk *atp_rfk;
+ int res;
+ bool sw_state = false;
+ bool hw_state;
+ int sw_status;
+
+ BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
+
+ atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
+ if (atp_rfk)
+ atp_rfk->rfkill = rfkill_alloc(name,
+ &tpacpi_pdev->dev,
+ rfktype,
+ &tpacpi_rfk_rfkill_ops,
+ atp_rfk);
+ if (!atp_rfk || !atp_rfk->rfkill) {
+ pr_err("failed to allocate memory for rfkill class\n");
+ kfree(atp_rfk);
+ return -ENOMEM;
+ }
+
+ atp_rfk->id = id;
+ atp_rfk->ops = tp_rfkops;
+
+ sw_status = (tp_rfkops->get_status)();
+ if (sw_status < 0) {
+ pr_err("failed to read initial state for %s, error %d\n",
+ name, sw_status);
+ } else {
+ sw_state = (sw_status == TPACPI_RFK_RADIO_OFF);
+ if (set_default) {
+ /* try to keep the initial state, since we ask the
+ * firmware to preserve it across S5 in NVRAM */
+ rfkill_init_sw_state(atp_rfk->rfkill, sw_state);
+ }
+ }
+ hw_state = tpacpi_rfk_check_hwblock_state();
+ rfkill_set_hw_state(atp_rfk->rfkill, hw_state);
+
+ res = rfkill_register(atp_rfk->rfkill);
+ if (res < 0) {
+ pr_err("failed to register %s rfkill switch: %d\n", name, res);
+ rfkill_destroy(atp_rfk->rfkill);
+ kfree(atp_rfk);
+ return res;
+ }
+
+ tpacpi_rfkill_switches[id] = atp_rfk;
+
+ pr_info("rfkill switch %s: radio is %sblocked\n",
+ name, (sw_state || hw_state) ? "" : "un");
+ return 0;
+}
+
+static void tpacpi_destroy_rfkill(const enum tpacpi_rfk_id id)
+{
+ struct tpacpi_rfk *tp_rfk;
+
+ BUG_ON(id >= TPACPI_RFK_SW_MAX);
+
+ tp_rfk = tpacpi_rfkill_switches[id];
+ if (tp_rfk) {
+ rfkill_unregister(tp_rfk->rfkill);
+ rfkill_destroy(tp_rfk->rfkill);
+ tpacpi_rfkill_switches[id] = NULL;
+ kfree(tp_rfk);
+ }
+}
+
+static void printk_deprecated_rfkill_attribute(const char * const what)
+{
+ printk_deprecated_attribute(what,
+ "Please switch to generic rfkill before year 2010");
+}
+
+/* sysfs <radio> enable ------------------------------------------------ */
+static ssize_t tpacpi_rfk_sysfs_enable_show(const enum tpacpi_rfk_id id,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int status;
+
+ printk_deprecated_rfkill_attribute(attr->attr.name);
+
+ /* This is in the ABI... */
+ if (tpacpi_rfk_check_hwblock_state()) {
+ status = TPACPI_RFK_RADIO_OFF;
+ } else {
+ status = tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
+ if (status < 0)
+ return status;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ (status == TPACPI_RFK_RADIO_ON) ? 1 : 0);
+}
+
+static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ int res;
+
+ printk_deprecated_rfkill_attribute(attr->attr.name);
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ tpacpi_disclose_usertask(attr->attr.name, "set to %ld\n", t);
+
+ /* This is in the ABI... */
+ if (tpacpi_rfk_check_hwblock_state() && !!t)
+ return -EPERM;
+
+ res = tpacpi_rfkill_switches[id]->ops->set_status((!!t) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF);
+ tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
+
+ return (res < 0) ? res : count;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
+{
+ if (id >= TPACPI_RFK_SW_MAX)
+ seq_printf(m, "status:\t\tnot supported\n");
+ else {
+ int status;
+
+ /* This is in the ABI... */
+ if (tpacpi_rfk_check_hwblock_state()) {
+ status = TPACPI_RFK_RADIO_OFF;
+ } else {
+ status = tpacpi_rfk_update_swstate(
+ tpacpi_rfkill_switches[id]);
+ if (status < 0)
+ return status;
+ }
+
+ seq_printf(m, "status:\t\t%s\n",
+ (status == TPACPI_RFK_RADIO_ON) ?
+ "enabled" : "disabled");
+ seq_printf(m, "commands:\tenable, disable\n");
+ }
+
+ return 0;
+}
+
+static int tpacpi_rfk_procfs_write(const enum tpacpi_rfk_id id, char *buf)
+{
+ char *cmd;
+ int status = -1;
+ int res = 0;
+
+ if (id >= TPACPI_RFK_SW_MAX)
+ return -ENODEV;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (strlencmp(cmd, "enable") == 0)
+ status = TPACPI_RFK_RADIO_ON;
+ else if (strlencmp(cmd, "disable") == 0)
+ status = TPACPI_RFK_RADIO_OFF;
+ else
+ return -EINVAL;
+ }
+
+ if (status != -1) {
+ tpacpi_disclose_usertask("procfs", "attempt to %s %s\n",
+ (status == TPACPI_RFK_RADIO_ON) ?
+ "enable" : "disable",
+ tpacpi_rfkill_names[id]);
+ res = (tpacpi_rfkill_switches[id]->ops->set_status)(status);
+ tpacpi_rfk_update_swstate(tpacpi_rfkill_switches[id]);
+ }
+
+ return res;
+}
+
+/*************************************************************************
+ * thinkpad-acpi driver attributes
+ */
+
+/* interface_version --------------------------------------------------- */
+static ssize_t tpacpi_driver_interface_version_show(
+ struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%08x\n", TPACPI_SYSFS_VERSION);
+}
+
+static DRIVER_ATTR(interface_version, S_IRUGO,
+ tpacpi_driver_interface_version_show, NULL);
+
+/* debug_level --------------------------------------------------------- */
+static ssize_t tpacpi_driver_debug_show(struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%04x\n", dbg_level);
+}
+
+static ssize_t tpacpi_driver_debug_store(struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 0xffff, &t))
+ return -EINVAL;
+
+ dbg_level = t;
+
+ return count;
+}
+
+static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO,
+ tpacpi_driver_debug_show, tpacpi_driver_debug_store);
+
+/* version ------------------------------------------------------------- */
+static ssize_t tpacpi_driver_version_show(struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s v%s\n",
+ TPACPI_DESC, TPACPI_VERSION);
+}
+
+static DRIVER_ATTR(version, S_IRUGO,
+ tpacpi_driver_version_show, NULL);
+
+/* --------------------------------------------------------------------- */
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+
+/* wlsw_emulstate ------------------------------------------------------ */
+static ssize_t tpacpi_driver_wlsw_emulstate_show(struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wlsw_emulstate);
+}
+
+static ssize_t tpacpi_driver_wlsw_emulstate_store(struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ if (tpacpi_wlsw_emulstate != !!t) {
+ tpacpi_wlsw_emulstate = !!t;
+ tpacpi_rfk_update_hwblock_state(!t); /* negative logic */
+ }
+
+ return count;
+}
+
+static DRIVER_ATTR(wlsw_emulstate, S_IWUSR | S_IRUGO,
+ tpacpi_driver_wlsw_emulstate_show,
+ tpacpi_driver_wlsw_emulstate_store);
+
+/* bluetooth_emulstate ------------------------------------------------- */
+static ssize_t tpacpi_driver_bluetooth_emulstate_show(
+ struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_bluetooth_emulstate);
+}
+
+static ssize_t tpacpi_driver_bluetooth_emulstate_store(
+ struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ tpacpi_bluetooth_emulstate = !!t;
+
+ return count;
+}
+
+static DRIVER_ATTR(bluetooth_emulstate, S_IWUSR | S_IRUGO,
+ tpacpi_driver_bluetooth_emulstate_show,
+ tpacpi_driver_bluetooth_emulstate_store);
+
+/* wwan_emulstate ------------------------------------------------- */
+static ssize_t tpacpi_driver_wwan_emulstate_show(
+ struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_wwan_emulstate);
+}
+
+static ssize_t tpacpi_driver_wwan_emulstate_store(
+ struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ tpacpi_wwan_emulstate = !!t;
+
+ return count;
+}
+
+static DRIVER_ATTR(wwan_emulstate, S_IWUSR | S_IRUGO,
+ tpacpi_driver_wwan_emulstate_show,
+ tpacpi_driver_wwan_emulstate_store);
+
+/* uwb_emulstate ------------------------------------------------- */
+static ssize_t tpacpi_driver_uwb_emulstate_show(
+ struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", !!tpacpi_uwb_emulstate);
+}
+
+static ssize_t tpacpi_driver_uwb_emulstate_store(
+ struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ tpacpi_uwb_emulstate = !!t;
+
+ return count;
+}
+
+static DRIVER_ATTR(uwb_emulstate, S_IWUSR | S_IRUGO,
+ tpacpi_driver_uwb_emulstate_show,
+ tpacpi_driver_uwb_emulstate_store);
+#endif
+
+/* --------------------------------------------------------------------- */
+
+static struct driver_attribute *tpacpi_driver_attributes[] = {
+ &driver_attr_debug_level, &driver_attr_version,
+ &driver_attr_interface_version,
+};
+
+static int __init tpacpi_create_driver_attributes(struct device_driver *drv)
+{
+ int i, res;
+
+ i = 0;
+ res = 0;
+ while (!res && i < ARRAY_SIZE(tpacpi_driver_attributes)) {
+ res = driver_create_file(drv, tpacpi_driver_attributes[i]);
+ i++;
+ }
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (!res && dbg_wlswemul)
+ res = driver_create_file(drv, &driver_attr_wlsw_emulstate);
+ if (!res && dbg_bluetoothemul)
+ res = driver_create_file(drv, &driver_attr_bluetooth_emulstate);
+ if (!res && dbg_wwanemul)
+ res = driver_create_file(drv, &driver_attr_wwan_emulstate);
+ if (!res && dbg_uwbemul)
+ res = driver_create_file(drv, &driver_attr_uwb_emulstate);
+#endif
+
+ return res;
+}
+
+static void tpacpi_remove_driver_attributes(struct device_driver *drv)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tpacpi_driver_attributes); i++)
+ driver_remove_file(drv, tpacpi_driver_attributes[i]);
+
+#ifdef THINKPAD_ACPI_DEBUGFACILITIES
+ driver_remove_file(drv, &driver_attr_wlsw_emulstate);
+ driver_remove_file(drv, &driver_attr_bluetooth_emulstate);
+ driver_remove_file(drv, &driver_attr_wwan_emulstate);
+ driver_remove_file(drv, &driver_attr_uwb_emulstate);
+#endif
+}
+
+/*************************************************************************
+ * Firmware Data
+ */
+
+/*
+ * Table of recommended minimum BIOS versions
+ *
+ * Reasons for listing:
+ * 1. Stable BIOS, listed because the unknown amount of
+ * bugs and bad ACPI behaviour on older versions
+ *
+ * 2. BIOS or EC fw with known bugs that trigger on Linux
+ *
+ * 3. BIOS with known reduced functionality in older versions
+ *
+ * We recommend the latest BIOS and EC version.
+ * We only support the latest BIOS and EC fw version as a rule.
+ *
+ * Sources: IBM ThinkPad Public Web Documents (update changelogs),
+ * Information from users in ThinkWiki
+ *
+ * WARNING: we use this table also to detect that the machine is
+ * a ThinkPad in some cases, so don't remove entries lightly.
+ */
+
+#define TPV_Q(__v, __id1, __id2, __bv1, __bv2) \
+ { .vendor = (__v), \
+ .bios = TPID(__id1, __id2), \
+ .ec = TPACPI_MATCH_ANY, \
+ .quirks = TPACPI_MATCH_ANY << 16 \
+ | (__bv1) << 8 | (__bv2) }
+
+#define TPV_Q_X(__v, __bid1, __bid2, __bv1, __bv2, \
+ __eid, __ev1, __ev2) \
+ { .vendor = (__v), \
+ .bios = TPID(__bid1, __bid2), \
+ .ec = __eid, \
+ .quirks = (__ev1) << 24 | (__ev2) << 16 \
+ | (__bv1) << 8 | (__bv2) }
+
+#define TPV_QI0(__id1, __id2, __bv1, __bv2) \
+ TPV_Q(PCI_VENDOR_ID_IBM, __id1, __id2, __bv1, __bv2)
+
+/* Outdated IBM BIOSes often lack the EC id string */
+#define TPV_QI1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
+ TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2, \
+ __bv1, __bv2, TPID(__id1, __id2), \
+ __ev1, __ev2), \
+ TPV_Q_X(PCI_VENDOR_ID_IBM, __id1, __id2, \
+ __bv1, __bv2, TPACPI_MATCH_UNKNOWN, \
+ __ev1, __ev2)
+
+/* Outdated IBM BIOSes often lack the EC id string */
+#define TPV_QI2(__bid1, __bid2, __bv1, __bv2, \
+ __eid1, __eid2, __ev1, __ev2) \
+ TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2, \
+ __bv1, __bv2, TPID(__eid1, __eid2), \
+ __ev1, __ev2), \
+ TPV_Q_X(PCI_VENDOR_ID_IBM, __bid1, __bid2, \
+ __bv1, __bv2, TPACPI_MATCH_UNKNOWN, \
+ __ev1, __ev2)
+
+#define TPV_QL0(__id1, __id2, __bv1, __bv2) \
+ TPV_Q(PCI_VENDOR_ID_LENOVO, __id1, __id2, __bv1, __bv2)
+
+#define TPV_QL1(__id1, __id2, __bv1, __bv2, __ev1, __ev2) \
+ TPV_Q_X(PCI_VENDOR_ID_LENOVO, __id1, __id2, \
+ __bv1, __bv2, TPID(__id1, __id2), \
+ __ev1, __ev2)
+
+#define TPV_QL2(__bid1, __bid2, __bv1, __bv2, \
+ __eid1, __eid2, __ev1, __ev2) \
+ TPV_Q_X(PCI_VENDOR_ID_LENOVO, __bid1, __bid2, \
+ __bv1, __bv2, TPID(__eid1, __eid2), \
+ __ev1, __ev2)
+
+static const struct tpacpi_quirk tpacpi_bios_version_qtable[] __initconst = {
+ /* Numeric models ------------------ */
+ /* FW MODEL BIOS VERS */
+ TPV_QI0('I', 'M', '6', '5'), /* 570 */
+ TPV_QI0('I', 'U', '2', '6'), /* 570E */
+ TPV_QI0('I', 'B', '5', '4'), /* 600 */
+ TPV_QI0('I', 'H', '4', '7'), /* 600E */
+ TPV_QI0('I', 'N', '3', '6'), /* 600E */
+ TPV_QI0('I', 'T', '5', '5'), /* 600X */
+ TPV_QI0('I', 'D', '4', '8'), /* 770, 770E, 770ED */
+ TPV_QI0('I', 'I', '4', '2'), /* 770X */
+ TPV_QI0('I', 'O', '2', '3'), /* 770Z */
+
+ /* A-series ------------------------- */
+ /* FW MODEL BIOS VERS EC VERS */
+ TPV_QI0('I', 'W', '5', '9'), /* A20m */
+ TPV_QI0('I', 'V', '6', '9'), /* A20p */
+ TPV_QI0('1', '0', '2', '6'), /* A21e, A22e */
+ TPV_QI0('K', 'U', '3', '6'), /* A21e */
+ TPV_QI0('K', 'X', '3', '6'), /* A21m, A22m */
+ TPV_QI0('K', 'Y', '3', '8'), /* A21p, A22p */
+ TPV_QI0('1', 'B', '1', '7'), /* A22e */
+ TPV_QI0('1', '3', '2', '0'), /* A22m */
+ TPV_QI0('1', 'E', '7', '3'), /* A30/p (0) */
+ TPV_QI1('1', 'G', '4', '1', '1', '7'), /* A31/p (0) */
+ TPV_QI1('1', 'N', '1', '6', '0', '7'), /* A31/p (0) */
+
+ /* G-series ------------------------- */
+ /* FW MODEL BIOS VERS */
+ TPV_QI0('1', 'T', 'A', '6'), /* G40 */
+ TPV_QI0('1', 'X', '5', '7'), /* G41 */
+
+ /* R-series, T-series --------------- */
+ /* FW MODEL BIOS VERS EC VERS */
+ TPV_QI0('1', 'C', 'F', '0'), /* R30 */
+ TPV_QI0('1', 'F', 'F', '1'), /* R31 */
+ TPV_QI0('1', 'M', '9', '7'), /* R32 */
+ TPV_QI0('1', 'O', '6', '1'), /* R40 */
+ TPV_QI0('1', 'P', '6', '5'), /* R40 */
+ TPV_QI0('1', 'S', '7', '0'), /* R40e */
+ TPV_QI1('1', 'R', 'D', 'R', '7', '1'), /* R50/p, R51,
+ T40/p, T41/p, T42/p (1) */
+ TPV_QI1('1', 'V', '7', '1', '2', '8'), /* R50e, R51 (1) */
+ TPV_QI1('7', '8', '7', '1', '0', '6'), /* R51e (1) */
+ TPV_QI1('7', '6', '6', '9', '1', '6'), /* R52 (1) */
+ TPV_QI1('7', '0', '6', '9', '2', '8'), /* R52, T43 (1) */
+
+ TPV_QI0('I', 'Y', '6', '1'), /* T20 */
+ TPV_QI0('K', 'Z', '3', '4'), /* T21 */
+ TPV_QI0('1', '6', '3', '2'), /* T22 */
+ TPV_QI1('1', 'A', '6', '4', '2', '3'), /* T23 (0) */
+ TPV_QI1('1', 'I', '7', '1', '2', '0'), /* T30 (0) */
+ TPV_QI1('1', 'Y', '6', '5', '2', '9'), /* T43/p (1) */
+
+ TPV_QL1('7', '9', 'E', '3', '5', '0'), /* T60/p */
+ TPV_QL1('7', 'C', 'D', '2', '2', '2'), /* R60, R60i */
+ TPV_QL1('7', 'E', 'D', '0', '1', '5'), /* R60e, R60i */
+
+ /* BIOS FW BIOS VERS EC FW EC VERS */
+ TPV_QI2('1', 'W', '9', '0', '1', 'V', '2', '8'), /* R50e (1) */
+ TPV_QL2('7', 'I', '3', '4', '7', '9', '5', '0'), /* T60/p wide */
+
+ /* X-series ------------------------- */
+ /* FW MODEL BIOS VERS EC VERS */
+ TPV_QI0('I', 'Z', '9', 'D'), /* X20, X21 */
+ TPV_QI0('1', 'D', '7', '0'), /* X22, X23, X24 */
+ TPV_QI1('1', 'K', '4', '8', '1', '8'), /* X30 (0) */
+ TPV_QI1('1', 'Q', '9', '7', '2', '3'), /* X31, X32 (0) */
+ TPV_QI1('1', 'U', 'D', '3', 'B', '2'), /* X40 (0) */
+ TPV_QI1('7', '4', '6', '4', '2', '7'), /* X41 (0) */
+ TPV_QI1('7', '5', '6', '0', '2', '0'), /* X41t (0) */
+
+ TPV_QL1('7', 'B', 'D', '7', '4', '0'), /* X60/s */
+ TPV_QL1('7', 'J', '3', '0', '1', '3'), /* X60t */
+
+ /* (0) - older versions lack DMI EC fw string and functionality */
+ /* (1) - older versions known to lack functionality */
+};
+
+#undef TPV_QL1
+#undef TPV_QL0
+#undef TPV_QI2
+#undef TPV_QI1
+#undef TPV_QI0
+#undef TPV_Q_X
+#undef TPV_Q
+
+static void __init tpacpi_check_outdated_fw(void)
+{
+ unsigned long fwvers;
+ u16 ec_version, bios_version;
+
+ fwvers = tpacpi_check_quirks(tpacpi_bios_version_qtable,
+ ARRAY_SIZE(tpacpi_bios_version_qtable));
+
+ if (!fwvers)
+ return;
+
+ bios_version = fwvers & 0xffffU;
+ ec_version = (fwvers >> 16) & 0xffffU;
+
+ /* note that unknown versions are set to 0x0000 and we use that */
+ if ((bios_version > thinkpad_id.bios_release) ||
+ (ec_version > thinkpad_id.ec_release &&
+ ec_version != TPACPI_MATCH_ANY)) {
+ /*
+ * The changelogs would let us track down the exact
+ * reason, but it is just too much of a pain to track
+ * it. We only list BIOSes that are either really
+ * broken, or really stable to begin with, so it is
+ * best if the user upgrades the firmware anyway.
+ */
+ pr_warn("WARNING: Outdated ThinkPad BIOS/EC firmware\n");
+ pr_warn("WARNING: This firmware may be missing critical bug "
+ "fixes and/or important features\n");
+ }
+}
+
+static bool __init tpacpi_is_fw_known(void)
+{
+ return tpacpi_check_quirks(tpacpi_bios_version_qtable,
+ ARRAY_SIZE(tpacpi_bios_version_qtable)) != 0;
+}
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Subdrivers
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*************************************************************************
+ * thinkpad-acpi metadata subdriver
+ */
+
+static int thinkpad_acpi_driver_read(struct seq_file *m)
+{
+ seq_printf(m, "driver:\t\t%s\n", TPACPI_DESC);
+ seq_printf(m, "version:\t%s\n", TPACPI_VERSION);
+ return 0;
+}
+
+static struct ibm_struct thinkpad_acpi_driver_data = {
+ .name = "driver",
+ .read = thinkpad_acpi_driver_read,
+};
+
+/*************************************************************************
+ * Hotkey subdriver
+ */
+
+/*
+ * ThinkPad firmware event model
+ *
+ * The ThinkPad firmware has two main event interfaces: normal ACPI
+ * notifications (which follow the ACPI standard), and a private event
+ * interface.
+ *
+ * The private event interface also issues events for the hotkeys. As
+ * the driver gained features, the event handling code ended up being
+ * built around the hotkey subdriver. This will need to be refactored
+ * to a more formal event API eventually.
+ *
+ * Some "hotkeys" are actually supposed to be used as event reports,
+ * such as "brightness has changed", "volume has changed", depending on
+ * the ThinkPad model and how the firmware is operating.
+ *
+ * Unlike other classes, hotkey-class events have mask/unmask control on
+ * non-ancient firmware. However, how it behaves changes a lot with the
+ * firmware model and version.
+ */
+
+enum { /* hot key scan codes (derived from ACPI DSDT) */
+ TP_ACPI_HOTKEYSCAN_FNF1 = 0,
+ TP_ACPI_HOTKEYSCAN_FNF2,
+ TP_ACPI_HOTKEYSCAN_FNF3,
+ TP_ACPI_HOTKEYSCAN_FNF4,
+ TP_ACPI_HOTKEYSCAN_FNF5,
+ TP_ACPI_HOTKEYSCAN_FNF6,
+ TP_ACPI_HOTKEYSCAN_FNF7,
+ TP_ACPI_HOTKEYSCAN_FNF8,
+ TP_ACPI_HOTKEYSCAN_FNF9,
+ TP_ACPI_HOTKEYSCAN_FNF10,
+ TP_ACPI_HOTKEYSCAN_FNF11,
+ TP_ACPI_HOTKEYSCAN_FNF12,
+ TP_ACPI_HOTKEYSCAN_FNBACKSPACE,
+ TP_ACPI_HOTKEYSCAN_FNINSERT,
+ TP_ACPI_HOTKEYSCAN_FNDELETE,
+ TP_ACPI_HOTKEYSCAN_FNHOME,
+ TP_ACPI_HOTKEYSCAN_FNEND,
+ TP_ACPI_HOTKEYSCAN_FNPAGEUP,
+ TP_ACPI_HOTKEYSCAN_FNPAGEDOWN,
+ TP_ACPI_HOTKEYSCAN_FNSPACE,
+ TP_ACPI_HOTKEYSCAN_VOLUMEUP,
+ TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
+ TP_ACPI_HOTKEYSCAN_MUTE,
+ TP_ACPI_HOTKEYSCAN_THINKPAD,
+ TP_ACPI_HOTKEYSCAN_UNK1,
+ TP_ACPI_HOTKEYSCAN_UNK2,
+ TP_ACPI_HOTKEYSCAN_UNK3,
+ TP_ACPI_HOTKEYSCAN_UNK4,
+ TP_ACPI_HOTKEYSCAN_UNK5,
+ TP_ACPI_HOTKEYSCAN_UNK6,
+ TP_ACPI_HOTKEYSCAN_UNK7,
+ TP_ACPI_HOTKEYSCAN_UNK8,
+
+ /* Hotkey keymap size */
+ TPACPI_HOTKEY_MAP_LEN
+};
+
+enum { /* Keys/events available through NVRAM polling */
+ TPACPI_HKEY_NVRAM_KNOWN_MASK = 0x00fb88c0U,
+ TPACPI_HKEY_NVRAM_GOOD_MASK = 0x00fb8000U,
+};
+
+enum { /* Positions of some of the keys in hotkey masks */
+ TP_ACPI_HKEY_DISPSWTCH_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF7,
+ TP_ACPI_HKEY_DISPXPAND_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF8,
+ TP_ACPI_HKEY_HIBERNATE_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNF12,
+ TP_ACPI_HKEY_BRGHTUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNHOME,
+ TP_ACPI_HKEY_BRGHTDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNEND,
+ TP_ACPI_HKEY_THNKLGHT_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNPAGEUP,
+ TP_ACPI_HKEY_ZOOM_MASK = 1 << TP_ACPI_HOTKEYSCAN_FNSPACE,
+ TP_ACPI_HKEY_VOLUP_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEUP,
+ TP_ACPI_HKEY_VOLDWN_MASK = 1 << TP_ACPI_HOTKEYSCAN_VOLUMEDOWN,
+ TP_ACPI_HKEY_MUTE_MASK = 1 << TP_ACPI_HOTKEYSCAN_MUTE,
+ TP_ACPI_HKEY_THINKPAD_MASK = 1 << TP_ACPI_HOTKEYSCAN_THINKPAD,
+};
+
+enum { /* NVRAM to ACPI HKEY group map */
+ TP_NVRAM_HKEY_GROUP_HK2 = TP_ACPI_HKEY_THINKPAD_MASK |
+ TP_ACPI_HKEY_ZOOM_MASK |
+ TP_ACPI_HKEY_DISPSWTCH_MASK |
+ TP_ACPI_HKEY_HIBERNATE_MASK,
+ TP_NVRAM_HKEY_GROUP_BRIGHTNESS = TP_ACPI_HKEY_BRGHTUP_MASK |
+ TP_ACPI_HKEY_BRGHTDWN_MASK,
+ TP_NVRAM_HKEY_GROUP_VOLUME = TP_ACPI_HKEY_VOLUP_MASK |
+ TP_ACPI_HKEY_VOLDWN_MASK |
+ TP_ACPI_HKEY_MUTE_MASK,
+};
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+struct tp_nvram_state {
+ u16 thinkpad_toggle:1;
+ u16 zoom_toggle:1;
+ u16 display_toggle:1;
+ u16 thinklight_toggle:1;
+ u16 hibernate_toggle:1;
+ u16 displayexp_toggle:1;
+ u16 display_state:1;
+ u16 brightness_toggle:1;
+ u16 volume_toggle:1;
+ u16 mute:1;
+
+ u8 brightness_level;
+ u8 volume_level;
+};
+
+/* kthread for the hotkey poller */
+static struct task_struct *tpacpi_hotkey_task;
+
+/*
+ * Acquire mutex to write poller control variables as an
+ * atomic block.
+ *
+ * Increment hotkey_config_change when changing them if you
+ * want the kthread to forget old state.
+ *
+ * See HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
+ */
+static struct mutex hotkey_thread_data_mutex;
+static unsigned int hotkey_config_change;
+
+/*
+ * hotkey poller control variables
+ *
+ * Must be atomic or readers will also need to acquire mutex
+ *
+ * HOTKEY_CONFIG_CRITICAL_START/HOTKEY_CONFIG_CRITICAL_END
+ * should be used only when the changes need to be taken as
+ * a block, OR when one needs to force the kthread to forget
+ * old state.
+ */
+static u32 hotkey_source_mask; /* bit mask 0=ACPI,1=NVRAM */
+static unsigned int hotkey_poll_freq = 10; /* Hz */
+
+#define HOTKEY_CONFIG_CRITICAL_START \
+ do { \
+ mutex_lock(&hotkey_thread_data_mutex); \
+ hotkey_config_change++; \
+ } while (0);
+#define HOTKEY_CONFIG_CRITICAL_END \
+ mutex_unlock(&hotkey_thread_data_mutex);
+
+#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+#define hotkey_source_mask 0U
+#define HOTKEY_CONFIG_CRITICAL_START
+#define HOTKEY_CONFIG_CRITICAL_END
+
+#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+static struct mutex hotkey_mutex;
+
+static enum { /* Reasons for waking up */
+ TP_ACPI_WAKEUP_NONE = 0, /* None or unknown */
+ TP_ACPI_WAKEUP_BAYEJ, /* Bay ejection request */
+ TP_ACPI_WAKEUP_UNDOCK, /* Undock request */
+} hotkey_wakeup_reason;
+
+static int hotkey_autosleep_ack;
+
+static u32 hotkey_orig_mask; /* events the BIOS had enabled */
+static u32 hotkey_all_mask; /* all events supported in fw */
+static u32 hotkey_reserved_mask; /* events better left disabled */
+static u32 hotkey_driver_mask; /* events needed by the driver */
+static u32 hotkey_user_mask; /* events visible to userspace */
+static u32 hotkey_acpi_mask; /* events enabled in firmware */
+
+static unsigned int hotkey_report_mode;
+
+static u16 *hotkey_keycode_map;
+
+static struct attribute_set *hotkey_dev_attributes;
+
+static void tpacpi_driver_event(const unsigned int hkey_event);
+static void hotkey_driver_event(const unsigned int scancode);
+static void hotkey_poll_setup(const bool may_warn);
+
+/* HKEY.MHKG() return bits */
+#define TP_HOTKEY_TABLET_MASK (1 << 3)
+
+static int hotkey_get_wlsw(void)
+{
+ int status;
+
+ if (!tp_features.hotkey_wlsw)
+ return -ENODEV;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_wlswemul)
+ return (tpacpi_wlsw_emulstate) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+ if (!acpi_evalf(hkey_handle, &status, "WLSW", "d"))
+ return -EIO;
+
+ return (status) ? TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+static int hotkey_get_tablet_mode(int *status)
+{
+ int s;
+
+ if (!acpi_evalf(hkey_handle, &s, "MHKG", "d"))
+ return -EIO;
+
+ *status = ((s & TP_HOTKEY_TABLET_MASK) != 0);
+ return 0;
+}
+
+/*
+ * Reads current event mask from firmware, and updates
+ * hotkey_acpi_mask accordingly. Also resets any bits
+ * from hotkey_user_mask that are unavailable to be
+ * delivered (shadow requirement of the userspace ABI).
+ *
+ * Call with hotkey_mutex held
+ */
+static int hotkey_mask_get(void)
+{
+ if (tp_features.hotkey_mask) {
+ u32 m = 0;
+
+ if (!acpi_evalf(hkey_handle, &m, "DHKN", "d"))
+ return -EIO;
+
+ hotkey_acpi_mask = m;
+ } else {
+ /* no mask support doesn't mean no event support... */
+ hotkey_acpi_mask = hotkey_all_mask;
+ }
+
+ /* sync userspace-visible mask */
+ hotkey_user_mask &= (hotkey_acpi_mask | hotkey_source_mask);
+
+ return 0;
+}
+
+void static hotkey_mask_warn_incomplete_mask(void)
+{
+ /* log only what the user can fix... */
+ const u32 wantedmask = hotkey_driver_mask &
+ ~(hotkey_acpi_mask | hotkey_source_mask) &
+ (hotkey_all_mask | TPACPI_HKEY_NVRAM_KNOWN_MASK);
+
+ if (wantedmask)
+ pr_notice("required events 0x%08x not enabled!\n", wantedmask);
+}
+
+/*
+ * Set the firmware mask when supported
+ *
+ * Also calls hotkey_mask_get to update hotkey_acpi_mask.
+ *
+ * NOTE: does not set bits in hotkey_user_mask, but may reset them.
+ *
+ * Call with hotkey_mutex held
+ */
+static int hotkey_mask_set(u32 mask)
+{
+ int i;
+ int rc = 0;
+
+ const u32 fwmask = mask & ~hotkey_source_mask;
+
+ if (tp_features.hotkey_mask) {
+ for (i = 0; i < 32; i++) {
+ if (!acpi_evalf(hkey_handle,
+ NULL, "MHKM", "vdd", i + 1,
+ !!(mask & (1 << i)))) {
+ rc = -EIO;
+ break;
+ }
+ }
+ }
+
+ /*
+ * We *must* make an inconditional call to hotkey_mask_get to
+ * refresh hotkey_acpi_mask and update hotkey_user_mask
+ *
+ * Take the opportunity to also log when we cannot _enable_
+ * a given event.
+ */
+ if (!hotkey_mask_get() && !rc && (fwmask & ~hotkey_acpi_mask)) {
+ pr_notice("asked for hotkey mask 0x%08x, but "
+ "firmware forced it to 0x%08x\n",
+ fwmask, hotkey_acpi_mask);
+ }
+
+ if (tpacpi_lifecycle != TPACPI_LIFE_EXITING)
+ hotkey_mask_warn_incomplete_mask();
+
+ return rc;
+}
+
+/*
+ * Sets hotkey_user_mask and tries to set the firmware mask
+ *
+ * Call with hotkey_mutex held
+ */
+static int hotkey_user_mask_set(const u32 mask)
+{
+ int rc;
+
+ /* Give people a chance to notice they are doing something that
+ * is bound to go boom on their users sooner or later */
+ if (!tp_warned.hotkey_mask_ff &&
+ (mask == 0xffff || mask == 0xffffff ||
+ mask == 0xffffffff)) {
+ tp_warned.hotkey_mask_ff = 1;
+ pr_notice("setting the hotkey mask to 0x%08x is likely "
+ "not the best way to go about it\n", mask);
+ pr_notice("please consider using the driver defaults, "
+ "and refer to up-to-date thinkpad-acpi "
+ "documentation\n");
+ }
+
+ /* Try to enable what the user asked for, plus whatever we need.
+ * this syncs everything but won't enable bits in hotkey_user_mask */
+ rc = hotkey_mask_set((mask | hotkey_driver_mask) & ~hotkey_source_mask);
+
+ /* Enable the available bits in hotkey_user_mask */
+ hotkey_user_mask = mask & (hotkey_acpi_mask | hotkey_source_mask);
+
+ return rc;
+}
+
+/*
+ * Sets the driver hotkey mask.
+ *
+ * Can be called even if the hotkey subdriver is inactive
+ */
+static int tpacpi_hotkey_driver_mask_set(const u32 mask)
+{
+ int rc;
+
+ /* Do the right thing if hotkey_init has not been called yet */
+ if (!tp_features.hotkey) {
+ hotkey_driver_mask = mask;
+ return 0;
+ }
+
+ mutex_lock(&hotkey_mutex);
+
+ HOTKEY_CONFIG_CRITICAL_START
+ hotkey_driver_mask = mask;
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ hotkey_source_mask |= (mask & ~hotkey_all_mask);
+#endif
+ HOTKEY_CONFIG_CRITICAL_END
+
+ rc = hotkey_mask_set((hotkey_acpi_mask | hotkey_driver_mask) &
+ ~hotkey_source_mask);
+ hotkey_poll_setup(true);
+
+ mutex_unlock(&hotkey_mutex);
+
+ return rc;
+}
+
+static int hotkey_status_get(int *status)
+{
+ if (!acpi_evalf(hkey_handle, status, "DHKC", "d"))
+ return -EIO;
+
+ return 0;
+}
+
+static int hotkey_status_set(bool enable)
+{
+ if (!acpi_evalf(hkey_handle, NULL, "MHKC", "vd", enable ? 1 : 0))
+ return -EIO;
+
+ return 0;
+}
+
+static void tpacpi_input_send_tabletsw(void)
+{
+ int state;
+
+ if (tp_features.hotkey_tablet &&
+ !hotkey_get_tablet_mode(&state)) {
+ mutex_lock(&tpacpi_inputdev_send_mutex);
+
+ input_report_switch(tpacpi_inputdev,
+ SW_TABLET_MODE, !!state);
+ input_sync(tpacpi_inputdev);
+
+ mutex_unlock(&tpacpi_inputdev_send_mutex);
+ }
+}
+
+/* Do NOT call without validating scancode first */
+static void tpacpi_input_send_key(const unsigned int scancode)
+{
+ const unsigned int keycode = hotkey_keycode_map[scancode];
+
+ if (keycode != KEY_RESERVED) {
+ mutex_lock(&tpacpi_inputdev_send_mutex);
+
+ input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
+ input_report_key(tpacpi_inputdev, keycode, 1);
+ input_sync(tpacpi_inputdev);
+
+ input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode);
+ input_report_key(tpacpi_inputdev, keycode, 0);
+ input_sync(tpacpi_inputdev);
+
+ mutex_unlock(&tpacpi_inputdev_send_mutex);
+ }
+}
+
+/* Do NOT call without validating scancode first */
+static void tpacpi_input_send_key_masked(const unsigned int scancode)
+{
+ hotkey_driver_event(scancode);
+ if (hotkey_user_mask & (1 << scancode))
+ tpacpi_input_send_key(scancode);
+}
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+static struct tp_acpi_drv_struct ibm_hotkey_acpidriver;
+
+/* Do NOT call without validating scancode first */
+static void tpacpi_hotkey_send_key(unsigned int scancode)
+{
+ tpacpi_input_send_key_masked(scancode);
+ if (hotkey_report_mode < 2) {
+ acpi_bus_generate_proc_event(ibm_hotkey_acpidriver.device,
+ 0x80, TP_HKEY_EV_HOTKEY_BASE + scancode);
+ }
+}
+
+static void hotkey_read_nvram(struct tp_nvram_state *n, const u32 m)
+{
+ u8 d;
+
+ if (m & TP_NVRAM_HKEY_GROUP_HK2) {
+ d = nvram_read_byte(TP_NVRAM_ADDR_HK2);
+ n->thinkpad_toggle = !!(d & TP_NVRAM_MASK_HKT_THINKPAD);
+ n->zoom_toggle = !!(d & TP_NVRAM_MASK_HKT_ZOOM);
+ n->display_toggle = !!(d & TP_NVRAM_MASK_HKT_DISPLAY);
+ n->hibernate_toggle = !!(d & TP_NVRAM_MASK_HKT_HIBERNATE);
+ }
+ if (m & TP_ACPI_HKEY_THNKLGHT_MASK) {
+ d = nvram_read_byte(TP_NVRAM_ADDR_THINKLIGHT);
+ n->thinklight_toggle = !!(d & TP_NVRAM_MASK_THINKLIGHT);
+ }
+ if (m & TP_ACPI_HKEY_DISPXPAND_MASK) {
+ d = nvram_read_byte(TP_NVRAM_ADDR_VIDEO);
+ n->displayexp_toggle =
+ !!(d & TP_NVRAM_MASK_HKT_DISPEXPND);
+ }
+ if (m & TP_NVRAM_HKEY_GROUP_BRIGHTNESS) {
+ d = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
+ n->brightness_level = (d & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+ >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+ n->brightness_toggle =
+ !!(d & TP_NVRAM_MASK_HKT_BRIGHTNESS);
+ }
+ if (m & TP_NVRAM_HKEY_GROUP_VOLUME) {
+ d = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+ n->volume_level = (d & TP_NVRAM_MASK_LEVEL_VOLUME)
+ >> TP_NVRAM_POS_LEVEL_VOLUME;
+ n->mute = !!(d & TP_NVRAM_MASK_MUTE);
+ n->volume_toggle = !!(d & TP_NVRAM_MASK_HKT_VOLUME);
+ }
+}
+
+static void hotkey_compare_and_issue_event(struct tp_nvram_state *oldn,
+ struct tp_nvram_state *newn,
+ const u32 event_mask)
+{
+
+#define TPACPI_COMPARE_KEY(__scancode, __member) \
+ do { \
+ if ((event_mask & (1 << __scancode)) && \
+ oldn->__member != newn->__member) \
+ tpacpi_hotkey_send_key(__scancode); \
+ } while (0)
+
+#define TPACPI_MAY_SEND_KEY(__scancode) \
+ do { \
+ if (event_mask & (1 << __scancode)) \
+ tpacpi_hotkey_send_key(__scancode); \
+ } while (0)
+
+ void issue_volchange(const unsigned int oldvol,
+ const unsigned int newvol)
+ {
+ unsigned int i = oldvol;
+
+ while (i > newvol) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+ i--;
+ }
+ while (i < newvol) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+ i++;
+ }
+ }
+
+ void issue_brightnesschange(const unsigned int oldbrt,
+ const unsigned int newbrt)
+ {
+ unsigned int i = oldbrt;
+
+ while (i > newbrt) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+ i--;
+ }
+ while (i < newbrt) {
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+ i++;
+ }
+ }
+
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_THINKPAD, thinkpad_toggle);
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNSPACE, zoom_toggle);
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF7, display_toggle);
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF12, hibernate_toggle);
+
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNPAGEUP, thinklight_toggle);
+
+ TPACPI_COMPARE_KEY(TP_ACPI_HOTKEYSCAN_FNF8, displayexp_toggle);
+
+ /*
+ * Handle volume
+ *
+ * This code is supposed to duplicate the IBM firmware behaviour:
+ * - Pressing MUTE issues mute hotkey message, even when already mute
+ * - Pressing Volume up/down issues volume up/down hotkey messages,
+ * even when already at maximum or minimum volume
+ * - The act of unmuting issues volume up/down notification,
+ * depending which key was used to unmute
+ *
+ * We are constrained to what the NVRAM can tell us, which is not much
+ * and certainly not enough if more than one volume hotkey was pressed
+ * since the last poll cycle.
+ *
+ * Just to make our life interesting, some newer Lenovo ThinkPads have
+ * bugs in the BIOS and may fail to update volume_toggle properly.
+ */
+ if (newn->mute) {
+ /* muted */
+ if (!oldn->mute ||
+ oldn->volume_toggle != newn->volume_toggle ||
+ oldn->volume_level != newn->volume_level) {
+ /* recently muted, or repeated mute keypress, or
+ * multiple presses ending in mute */
+ issue_volchange(oldn->volume_level, newn->volume_level);
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_MUTE);
+ }
+ } else {
+ /* unmute */
+ if (oldn->mute) {
+ /* recently unmuted, issue 'unmute' keypress */
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+ }
+ if (oldn->volume_level != newn->volume_level) {
+ issue_volchange(oldn->volume_level, newn->volume_level);
+ } else if (oldn->volume_toggle != newn->volume_toggle) {
+ /* repeated vol up/down keypress at end of scale ? */
+ if (newn->volume_level == 0)
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEDOWN);
+ else if (newn->volume_level >= TP_NVRAM_LEVEL_VOLUME_MAX)
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_VOLUMEUP);
+ }
+ }
+
+ /* handle brightness */
+ if (oldn->brightness_level != newn->brightness_level) {
+ issue_brightnesschange(oldn->brightness_level,
+ newn->brightness_level);
+ } else if (oldn->brightness_toggle != newn->brightness_toggle) {
+ /* repeated key presses that didn't change state */
+ if (newn->brightness_level == 0)
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNEND);
+ else if (newn->brightness_level >= bright_maxlvl
+ && !tp_features.bright_unkfw)
+ TPACPI_MAY_SEND_KEY(TP_ACPI_HOTKEYSCAN_FNHOME);
+ }
+
+#undef TPACPI_COMPARE_KEY
+#undef TPACPI_MAY_SEND_KEY
+}
+
+/*
+ * Polling driver
+ *
+ * We track all events in hotkey_source_mask all the time, since
+ * most of them are edge-based. We only issue those requested by
+ * hotkey_user_mask or hotkey_driver_mask, though.
+ */
+static int hotkey_kthread(void *data)
+{
+ struct tp_nvram_state s[2];
+ u32 poll_mask, event_mask;
+ unsigned int si, so;
+ unsigned long t;
+ unsigned int change_detector;
+ unsigned int poll_freq;
+ bool was_frozen;
+
+ if (tpacpi_lifecycle == TPACPI_LIFE_EXITING)
+ goto exit;
+
+ set_freezable();
+
+ so = 0;
+ si = 1;
+ t = 0;
+
+ /* Initial state for compares */
+ mutex_lock(&hotkey_thread_data_mutex);
+ change_detector = hotkey_config_change;
+ poll_mask = hotkey_source_mask;
+ event_mask = hotkey_source_mask &
+ (hotkey_driver_mask | hotkey_user_mask);
+ poll_freq = hotkey_poll_freq;
+ mutex_unlock(&hotkey_thread_data_mutex);
+ hotkey_read_nvram(&s[so], poll_mask);
+
+ while (!kthread_should_stop()) {
+ if (t == 0) {
+ if (likely(poll_freq))
+ t = 1000/poll_freq;
+ else
+ t = 100; /* should never happen... */
+ }
+ t = msleep_interruptible(t);
+ if (unlikely(kthread_freezable_should_stop(&was_frozen)))
+ break;
+
+ if (t > 0 && !was_frozen)
+ continue;
+
+ mutex_lock(&hotkey_thread_data_mutex);
+ if (was_frozen || hotkey_config_change != change_detector) {
+ /* forget old state on thaw or config change */
+ si = so;
+ t = 0;
+ change_detector = hotkey_config_change;
+ }
+ poll_mask = hotkey_source_mask;
+ event_mask = hotkey_source_mask &
+ (hotkey_driver_mask | hotkey_user_mask);
+ poll_freq = hotkey_poll_freq;
+ mutex_unlock(&hotkey_thread_data_mutex);
+
+ if (likely(poll_mask)) {
+ hotkey_read_nvram(&s[si], poll_mask);
+ if (likely(si != so)) {
+ hotkey_compare_and_issue_event(&s[so], &s[si],
+ event_mask);
+ }
+ }
+
+ so = si;
+ si ^= 1;
+ }
+
+exit:
+ return 0;
+}
+
+/* call with hotkey_mutex held */
+static void hotkey_poll_stop_sync(void)
+{
+ if (tpacpi_hotkey_task) {
+ kthread_stop(tpacpi_hotkey_task);
+ tpacpi_hotkey_task = NULL;
+ }
+}
+
+/* call with hotkey_mutex held */
+static void hotkey_poll_setup(const bool may_warn)
+{
+ const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask;
+ const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask;
+
+ if (hotkey_poll_freq > 0 &&
+ (poll_driver_mask ||
+ (poll_user_mask && tpacpi_inputdev->users > 0))) {
+ if (!tpacpi_hotkey_task) {
+ tpacpi_hotkey_task = kthread_run(hotkey_kthread,
+ NULL, TPACPI_NVRAM_KTHREAD_NAME);
+ if (IS_ERR(tpacpi_hotkey_task)) {
+ tpacpi_hotkey_task = NULL;
+ pr_err("could not create kernel thread "
+ "for hotkey polling\n");
+ }
+ }
+ } else {
+ hotkey_poll_stop_sync();
+ if (may_warn && (poll_driver_mask || poll_user_mask) &&
+ hotkey_poll_freq == 0) {
+ pr_notice("hot keys 0x%08x and/or events 0x%08x "
+ "require polling, which is currently "
+ "disabled\n",
+ poll_user_mask, poll_driver_mask);
+ }
+ }
+}
+
+static void hotkey_poll_setup_safe(const bool may_warn)
+{
+ mutex_lock(&hotkey_mutex);
+ hotkey_poll_setup(may_warn);
+ mutex_unlock(&hotkey_mutex);
+}
+
+/* call with hotkey_mutex held */
+static void hotkey_poll_set_freq(unsigned int freq)
+{
+ if (!freq)
+ hotkey_poll_stop_sync();
+
+ hotkey_poll_freq = freq;
+}
+
+#else /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+static void hotkey_poll_setup(const bool __unused)
+{
+}
+
+static void hotkey_poll_setup_safe(const bool __unused)
+{
+}
+
+#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+static int hotkey_inputdev_open(struct input_dev *dev)
+{
+ switch (tpacpi_lifecycle) {
+ case TPACPI_LIFE_INIT:
+ case TPACPI_LIFE_RUNNING:
+ hotkey_poll_setup_safe(false);
+ return 0;
+ case TPACPI_LIFE_EXITING:
+ return -EBUSY;
+ }
+
+ /* Should only happen if tpacpi_lifecycle is corrupt */
+ BUG();
+ return -EBUSY;
+}
+
+static void hotkey_inputdev_close(struct input_dev *dev)
+{
+ /* disable hotkey polling when possible */
+ if (tpacpi_lifecycle != TPACPI_LIFE_EXITING &&
+ !(hotkey_source_mask & hotkey_driver_mask))
+ hotkey_poll_setup_safe(false);
+}
+
+/* sysfs hotkey enable ------------------------------------------------- */
+static ssize_t hotkey_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res, status;
+
+ printk_deprecated_attribute("hotkey_enable",
+ "Hotkey reporting is always enabled");
+
+ res = hotkey_status_get(&status);
+ if (res)
+ return res;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", status);
+}
+
+static ssize_t hotkey_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ printk_deprecated_attribute("hotkey_enable",
+ "Hotkeys can be disabled through hotkey_mask");
+
+ if (parse_strtoul(buf, 1, &t))
+ return -EINVAL;
+
+ if (t == 0)
+ return -EPERM;
+
+ return count;
+}
+
+static struct device_attribute dev_attr_hotkey_enable =
+ __ATTR(hotkey_enable, S_IWUSR | S_IRUGO,
+ hotkey_enable_show, hotkey_enable_store);
+
+/* sysfs hotkey mask --------------------------------------------------- */
+static ssize_t hotkey_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_user_mask);
+}
+
+static ssize_t hotkey_mask_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ int res;
+
+ if (parse_strtoul(buf, 0xffffffffUL, &t))
+ return -EINVAL;
+
+ if (mutex_lock_killable(&hotkey_mutex))
+ return -ERESTARTSYS;
+
+ res = hotkey_user_mask_set(t);
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ hotkey_poll_setup(true);
+#endif
+
+ mutex_unlock(&hotkey_mutex);
+
+ tpacpi_disclose_usertask("hotkey_mask", "set to 0x%08lx\n", t);
+
+ return (res) ? res : count;
+}
+
+static struct device_attribute dev_attr_hotkey_mask =
+ __ATTR(hotkey_mask, S_IWUSR | S_IRUGO,
+ hotkey_mask_show, hotkey_mask_store);
+
+/* sysfs hotkey bios_enabled ------------------------------------------- */
+static ssize_t hotkey_bios_enabled_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "0\n");
+}
+
+static struct device_attribute dev_attr_hotkey_bios_enabled =
+ __ATTR(hotkey_bios_enabled, S_IRUGO, hotkey_bios_enabled_show, NULL);
+
+/* sysfs hotkey bios_mask ---------------------------------------------- */
+static ssize_t hotkey_bios_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ printk_deprecated_attribute("hotkey_bios_mask",
+ "This attribute is useless.");
+ return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_orig_mask);
+}
+
+static struct device_attribute dev_attr_hotkey_bios_mask =
+ __ATTR(hotkey_bios_mask, S_IRUGO, hotkey_bios_mask_show, NULL);
+
+/* sysfs hotkey all_mask ----------------------------------------------- */
+static ssize_t hotkey_all_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%08x\n",
+ hotkey_all_mask | hotkey_source_mask);
+}
+
+static struct device_attribute dev_attr_hotkey_all_mask =
+ __ATTR(hotkey_all_mask, S_IRUGO, hotkey_all_mask_show, NULL);
+
+/* sysfs hotkey recommended_mask --------------------------------------- */
+static ssize_t hotkey_recommended_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%08x\n",
+ (hotkey_all_mask | hotkey_source_mask)
+ & ~hotkey_reserved_mask);
+}
+
+static struct device_attribute dev_attr_hotkey_recommended_mask =
+ __ATTR(hotkey_recommended_mask, S_IRUGO,
+ hotkey_recommended_mask_show, NULL);
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+
+/* sysfs hotkey hotkey_source_mask ------------------------------------- */
+static ssize_t hotkey_source_mask_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%08x\n", hotkey_source_mask);
+}
+
+static ssize_t hotkey_source_mask_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ u32 r_ev;
+ int rc;
+
+ if (parse_strtoul(buf, 0xffffffffUL, &t) ||
+ ((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0))
+ return -EINVAL;
+
+ if (mutex_lock_killable(&hotkey_mutex))
+ return -ERESTARTSYS;
+
+ HOTKEY_CONFIG_CRITICAL_START
+ hotkey_source_mask = t;
+ HOTKEY_CONFIG_CRITICAL_END
+
+ rc = hotkey_mask_set((hotkey_user_mask | hotkey_driver_mask) &
+ ~hotkey_source_mask);
+ hotkey_poll_setup(true);
+
+ /* check if events needed by the driver got disabled */
+ r_ev = hotkey_driver_mask & ~(hotkey_acpi_mask & hotkey_all_mask)
+ & ~hotkey_source_mask & TPACPI_HKEY_NVRAM_KNOWN_MASK;
+
+ mutex_unlock(&hotkey_mutex);
+
+ if (rc < 0)
+ pr_err("hotkey_source_mask: "
+ "failed to update the firmware event mask!\n");
+
+ if (r_ev)
+ pr_notice("hotkey_source_mask: "
+ "some important events were disabled: 0x%04x\n",
+ r_ev);
+
+ tpacpi_disclose_usertask("hotkey_source_mask", "set to 0x%08lx\n", t);
+
+ return (rc < 0) ? rc : count;
+}
+
+static struct device_attribute dev_attr_hotkey_source_mask =
+ __ATTR(hotkey_source_mask, S_IWUSR | S_IRUGO,
+ hotkey_source_mask_show, hotkey_source_mask_store);
+
+/* sysfs hotkey hotkey_poll_freq --------------------------------------- */
+static ssize_t hotkey_poll_freq_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_poll_freq);
+}
+
+static ssize_t hotkey_poll_freq_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 25, &t))
+ return -EINVAL;
+
+ if (mutex_lock_killable(&hotkey_mutex))
+ return -ERESTARTSYS;
+
+ hotkey_poll_set_freq(t);
+ hotkey_poll_setup(true);
+
+ mutex_unlock(&hotkey_mutex);
+
+ tpacpi_disclose_usertask("hotkey_poll_freq", "set to %lu\n", t);
+
+ return count;
+}
+
+static struct device_attribute dev_attr_hotkey_poll_freq =
+ __ATTR(hotkey_poll_freq, S_IWUSR | S_IRUGO,
+ hotkey_poll_freq_show, hotkey_poll_freq_store);
+
+#endif /* CONFIG_THINKPAD_ACPI_HOTKEY_POLL */
+
+/* sysfs hotkey radio_sw (pollable) ------------------------------------ */
+static ssize_t hotkey_radio_sw_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res;
+ res = hotkey_get_wlsw();
+ if (res < 0)
+ return res;
+
+ /* Opportunistic update */
+ tpacpi_rfk_update_hwblock_state((res == TPACPI_RFK_RADIO_OFF));
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ (res == TPACPI_RFK_RADIO_OFF) ? 0 : 1);
+}
+
+static struct device_attribute dev_attr_hotkey_radio_sw =
+ __ATTR(hotkey_radio_sw, S_IRUGO, hotkey_radio_sw_show, NULL);
+
+static void hotkey_radio_sw_notify_change(void)
+{
+ if (tp_features.hotkey_wlsw)
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+ "hotkey_radio_sw");
+}
+
+/* sysfs hotkey tablet mode (pollable) --------------------------------- */
+static ssize_t hotkey_tablet_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res, s;
+ res = hotkey_get_tablet_mode(&s);
+ if (res < 0)
+ return res;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", !!s);
+}
+
+static struct device_attribute dev_attr_hotkey_tablet_mode =
+ __ATTR(hotkey_tablet_mode, S_IRUGO, hotkey_tablet_mode_show, NULL);
+
+static void hotkey_tablet_mode_notify_change(void)
+{
+ if (tp_features.hotkey_tablet)
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+ "hotkey_tablet_mode");
+}
+
+/* sysfs hotkey report_mode -------------------------------------------- */
+static ssize_t hotkey_report_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ (hotkey_report_mode != 0) ? hotkey_report_mode : 1);
+}
+
+static struct device_attribute dev_attr_hotkey_report_mode =
+ __ATTR(hotkey_report_mode, S_IRUGO, hotkey_report_mode_show, NULL);
+
+/* sysfs wakeup reason (pollable) -------------------------------------- */
+static ssize_t hotkey_wakeup_reason_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_wakeup_reason);
+}
+
+static struct device_attribute dev_attr_hotkey_wakeup_reason =
+ __ATTR(wakeup_reason, S_IRUGO, hotkey_wakeup_reason_show, NULL);
+
+static void hotkey_wakeup_reason_notify_change(void)
+{
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+ "wakeup_reason");
+}
+
+/* sysfs wakeup hotunplug_complete (pollable) -------------------------- */
+static ssize_t hotkey_wakeup_hotunplug_complete_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", hotkey_autosleep_ack);
+}
+
+static struct device_attribute dev_attr_hotkey_wakeup_hotunplug_complete =
+ __ATTR(wakeup_hotunplug_complete, S_IRUGO,
+ hotkey_wakeup_hotunplug_complete_show, NULL);
+
+static void hotkey_wakeup_hotunplug_complete_notify_change(void)
+{
+ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL,
+ "wakeup_hotunplug_complete");
+}
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *hotkey_attributes[] __initdata = {
+ &dev_attr_hotkey_enable.attr,
+ &dev_attr_hotkey_bios_enabled.attr,
+ &dev_attr_hotkey_bios_mask.attr,
+ &dev_attr_hotkey_report_mode.attr,
+ &dev_attr_hotkey_wakeup_reason.attr,
+ &dev_attr_hotkey_wakeup_hotunplug_complete.attr,
+ &dev_attr_hotkey_mask.attr,
+ &dev_attr_hotkey_all_mask.attr,
+ &dev_attr_hotkey_recommended_mask.attr,
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ &dev_attr_hotkey_source_mask.attr,
+ &dev_attr_hotkey_poll_freq.attr,
+#endif
+};
+
+/*
+ * Sync both the hw and sw blocking state of all switches
+ */
+static void tpacpi_send_radiosw_update(void)
+{
+ int wlsw;
+
+ /*
+ * We must sync all rfkill controllers *before* issuing any
+ * rfkill input events, or we will race the rfkill core input
+ * handler.
+ *
+ * tpacpi_inputdev_send_mutex works as a synchronization point
+ * for the above.
+ *
+ * We optimize to avoid numerous calls to hotkey_get_wlsw.
+ */
+
+ wlsw = hotkey_get_wlsw();
+
+ /* Sync hw blocking state first if it is hw-blocked */
+ if (wlsw == TPACPI_RFK_RADIO_OFF)
+ tpacpi_rfk_update_hwblock_state(true);
+
+ /* Sync sw blocking state */
+ tpacpi_rfk_update_swstate_all();
+
+ /* Sync hw blocking state last if it is hw-unblocked */
+ if (wlsw == TPACPI_RFK_RADIO_ON)
+ tpacpi_rfk_update_hwblock_state(false);
+
+ /* Issue rfkill input event for WLSW switch */
+ if (!(wlsw < 0)) {
+ mutex_lock(&tpacpi_inputdev_send_mutex);
+
+ input_report_switch(tpacpi_inputdev,
+ SW_RFKILL_ALL, (wlsw > 0));
+ input_sync(tpacpi_inputdev);
+
+ mutex_unlock(&tpacpi_inputdev_send_mutex);
+ }
+
+ /*
+ * this can be unconditional, as we will poll state again
+ * if userspace uses the notify to read data
+ */
+ hotkey_radio_sw_notify_change();
+}
+
+static void hotkey_exit(void)
+{
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ mutex_lock(&hotkey_mutex);
+ hotkey_poll_stop_sync();
+ mutex_unlock(&hotkey_mutex);
+#endif
+
+ if (hotkey_dev_attributes)
+ delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+
+ dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY,
+ "restoring original HKEY status and mask\n");
+ /* yes, there is a bitwise or below, we want the
+ * functions to be called even if one of them fail */
+ if (((tp_features.hotkey_mask &&
+ hotkey_mask_set(hotkey_orig_mask)) |
+ hotkey_status_set(false)) != 0)
+ pr_err("failed to restore hot key mask "
+ "to BIOS defaults\n");
+}
+
+static void __init hotkey_unmap(const unsigned int scancode)
+{
+ if (hotkey_keycode_map[scancode] != KEY_RESERVED) {
+ clear_bit(hotkey_keycode_map[scancode],
+ tpacpi_inputdev->keybit);
+ hotkey_keycode_map[scancode] = KEY_RESERVED;
+ }
+}
+
+/*
+ * HKEY quirks:
+ * TPACPI_HK_Q_INIMASK: Supports FN+F3,FN+F4,FN+F12
+ */
+
+#define TPACPI_HK_Q_INIMASK 0x0001
+
+static const struct tpacpi_quirk tpacpi_hotkey_qtable[] __initconst = {
+ TPACPI_Q_IBM('I', 'H', TPACPI_HK_Q_INIMASK), /* 600E */
+ TPACPI_Q_IBM('I', 'N', TPACPI_HK_Q_INIMASK), /* 600E */
+ TPACPI_Q_IBM('I', 'D', TPACPI_HK_Q_INIMASK), /* 770, 770E, 770ED */
+ TPACPI_Q_IBM('I', 'W', TPACPI_HK_Q_INIMASK), /* A20m */
+ TPACPI_Q_IBM('I', 'V', TPACPI_HK_Q_INIMASK), /* A20p */
+ TPACPI_Q_IBM('1', '0', TPACPI_HK_Q_INIMASK), /* A21e, A22e */
+ TPACPI_Q_IBM('K', 'U', TPACPI_HK_Q_INIMASK), /* A21e */
+ TPACPI_Q_IBM('K', 'X', TPACPI_HK_Q_INIMASK), /* A21m, A22m */
+ TPACPI_Q_IBM('K', 'Y', TPACPI_HK_Q_INIMASK), /* A21p, A22p */
+ TPACPI_Q_IBM('1', 'B', TPACPI_HK_Q_INIMASK), /* A22e */
+ TPACPI_Q_IBM('1', '3', TPACPI_HK_Q_INIMASK), /* A22m */
+ TPACPI_Q_IBM('1', 'E', TPACPI_HK_Q_INIMASK), /* A30/p (0) */
+ TPACPI_Q_IBM('1', 'C', TPACPI_HK_Q_INIMASK), /* R30 */
+ TPACPI_Q_IBM('1', 'F', TPACPI_HK_Q_INIMASK), /* R31 */
+ TPACPI_Q_IBM('I', 'Y', TPACPI_HK_Q_INIMASK), /* T20 */
+ TPACPI_Q_IBM('K', 'Z', TPACPI_HK_Q_INIMASK), /* T21 */
+ TPACPI_Q_IBM('1', '6', TPACPI_HK_Q_INIMASK), /* T22 */
+ TPACPI_Q_IBM('I', 'Z', TPACPI_HK_Q_INIMASK), /* X20, X21 */
+ TPACPI_Q_IBM('1', 'D', TPACPI_HK_Q_INIMASK), /* X22, X23, X24 */
+};
+
+typedef u16 tpacpi_keymap_entry_t;
+typedef tpacpi_keymap_entry_t tpacpi_keymap_t[TPACPI_HOTKEY_MAP_LEN];
+
+static int __init hotkey_init(struct ibm_init_struct *iibm)
+{
+ /* Requirements for changing the default keymaps:
+ *
+ * 1. Many of the keys are mapped to KEY_RESERVED for very
+ * good reasons. Do not change them unless you have deep
+ * knowledge on the IBM and Lenovo ThinkPad firmware for
+ * the various ThinkPad models. The driver behaves
+ * differently for KEY_RESERVED: such keys have their
+ * hot key mask *unset* in mask_recommended, and also
+ * in the initial hot key mask programmed into the
+ * firmware at driver load time, which means the firm-
+ * ware may react very differently if you change them to
+ * something else;
+ *
+ * 2. You must be subscribed to the linux-thinkpad and
+ * ibm-acpi-devel mailing lists, and you should read the
+ * list archives since 2007 if you want to change the
+ * keymaps. This requirement exists so that you will
+ * know the past history of problems with the thinkpad-
+ * acpi driver keymaps, and also that you will be
+ * listening to any bug reports;
+ *
+ * 3. Do not send thinkpad-acpi specific patches directly to
+ * for merging, *ever*. Send them to the linux-acpi
+ * mailinglist for comments. Merging is to be done only
+ * through acpi-test and the ACPI maintainer.
+ *
+ * If the above is too much to ask, don't change the keymap.
+ * Ask the thinkpad-acpi maintainer to do it, instead.
+ */
+
+ enum keymap_index {
+ TPACPI_KEYMAP_IBM_GENERIC = 0,
+ TPACPI_KEYMAP_LENOVO_GENERIC,
+ };
+
+ static const tpacpi_keymap_t tpacpi_keymaps[] __initconst = {
+ /* Generic keymap for IBM ThinkPads */
+ [TPACPI_KEYMAP_IBM_GENERIC] = {
+ /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
+ KEY_FN_F1, KEY_BATTERY, KEY_COFFEE, KEY_SLEEP,
+ KEY_WLAN, KEY_FN_F6, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
+ KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
+
+ /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
+ KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
+ KEY_UNKNOWN, /* 0x0D: FN+INSERT */
+ KEY_UNKNOWN, /* 0x0E: FN+DELETE */
+
+ /* brightness: firmware always reacts to them */
+ KEY_RESERVED, /* 0x0F: FN+HOME (brightness up) */
+ KEY_RESERVED, /* 0x10: FN+END (brightness down) */
+
+ /* Thinklight: firmware always react to it */
+ KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
+
+ KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
+ KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
+
+ /* Volume: firmware always react to it and reprograms
+ * the built-in *extra* mixer. Never map it to control
+ * another mixer by default. */
+ KEY_RESERVED, /* 0x14: VOLUME UP */
+ KEY_RESERVED, /* 0x15: VOLUME DOWN */
+ KEY_RESERVED, /* 0x16: MUTE */
+
+ KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
+
+ /* (assignments unknown, please report if found) */
+ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+ },
+
+ /* Generic keymap for Lenovo ThinkPads */
+ [TPACPI_KEYMAP_LENOVO_GENERIC] = {
+ /* Scan Codes 0x00 to 0x0B: ACPI HKEY FN+F1..F12 */
+ KEY_FN_F1, KEY_COFFEE, KEY_BATTERY, KEY_SLEEP,
+ KEY_WLAN, KEY_CAMERA, KEY_SWITCHVIDEOMODE, KEY_FN_F8,
+ KEY_FN_F9, KEY_FN_F10, KEY_FN_F11, KEY_SUSPEND,
+
+ /* Scan codes 0x0C to 0x1F: Other ACPI HKEY hot keys */
+ KEY_UNKNOWN, /* 0x0C: FN+BACKSPACE */
+ KEY_UNKNOWN, /* 0x0D: FN+INSERT */
+ KEY_UNKNOWN, /* 0x0E: FN+DELETE */
+
+ /* These should be enabled --only-- when ACPI video
+ * is disabled (i.e. in "vendor" mode), and are handled
+ * in a special way by the init code */
+ KEY_BRIGHTNESSUP, /* 0x0F: FN+HOME (brightness up) */
+ KEY_BRIGHTNESSDOWN, /* 0x10: FN+END (brightness down) */
+
+ KEY_RESERVED, /* 0x11: FN+PGUP (thinklight toggle) */
+
+ KEY_UNKNOWN, /* 0x12: FN+PGDOWN */
+ KEY_ZOOM, /* 0x13: FN+SPACE (zoom) */
+
+ /* Volume: z60/z61, T60 (BIOS version?): firmware always
+ * react to it and reprograms the built-in *extra* mixer.
+ * Never map it to control another mixer by default.
+ *
+ * T60?, T61, R60?, R61: firmware and EC tries to send
+ * these over the regular keyboard, so these are no-ops,
+ * but there are still weird bugs re. MUTE, so do not
+ * change unless you get test reports from all Lenovo
+ * models. May cause the BIOS to interfere with the
+ * HDA mixer.
+ */
+ KEY_RESERVED, /* 0x14: VOLUME UP */
+ KEY_RESERVED, /* 0x15: VOLUME DOWN */
+ KEY_RESERVED, /* 0x16: MUTE */
+
+ KEY_VENDOR, /* 0x17: Thinkpad/AccessIBM/Lenovo */
+
+ /* (assignments unknown, please report if found) */
+ KEY_UNKNOWN, KEY_UNKNOWN,
+
+ /*
+ * The mic mute button only sends 0x1a. It does not
+ * automatically mute the mic or change the mute light.
+ */
+ KEY_MICMUTE, /* 0x1a: Mic mute (since ?400 or so) */
+
+ /* (assignments unknown, please report if found) */
+ KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN,
+ KEY_UNKNOWN,
+ },
+ };
+
+ static const struct tpacpi_quirk tpacpi_keymap_qtable[] __initconst = {
+ /* Generic maps (fallback) */
+ {
+ .vendor = PCI_VENDOR_ID_IBM,
+ .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+ .quirks = TPACPI_KEYMAP_IBM_GENERIC,
+ },
+ {
+ .vendor = PCI_VENDOR_ID_LENOVO,
+ .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+ .quirks = TPACPI_KEYMAP_LENOVO_GENERIC,
+ },
+ };
+
+#define TPACPI_HOTKEY_MAP_SIZE sizeof(tpacpi_keymap_t)
+#define TPACPI_HOTKEY_MAP_TYPESIZE sizeof(tpacpi_keymap_entry_t)
+
+ int res, i;
+ int status;
+ int hkeyv;
+ bool radiosw_state = false;
+ bool tabletsw_state = false;
+
+ unsigned long quirks;
+ unsigned long keymap_id;
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "initializing hotkey subdriver\n");
+
+ BUG_ON(!tpacpi_inputdev);
+ BUG_ON(tpacpi_inputdev->open != NULL ||
+ tpacpi_inputdev->close != NULL);
+
+ TPACPI_ACPIHANDLE_INIT(hkey);
+ mutex_init(&hotkey_mutex);
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ mutex_init(&hotkey_thread_data_mutex);
+#endif
+
+ /* hotkey not supported on 570 */
+ tp_features.hotkey = hkey_handle != NULL;
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "hotkeys are %s\n",
+ str_supported(tp_features.hotkey));
+
+ if (!tp_features.hotkey)
+ return 1;
+
+ quirks = tpacpi_check_quirks(tpacpi_hotkey_qtable,
+ ARRAY_SIZE(tpacpi_hotkey_qtable));
+
+ tpacpi_disable_brightness_delay();
+
+ /* MUST have enough space for all attributes to be added to
+ * hotkey_dev_attributes */
+ hotkey_dev_attributes = create_attr_set(
+ ARRAY_SIZE(hotkey_attributes) + 2,
+ NULL);
+ if (!hotkey_dev_attributes)
+ return -ENOMEM;
+ res = add_many_to_attr_set(hotkey_dev_attributes,
+ hotkey_attributes,
+ ARRAY_SIZE(hotkey_attributes));
+ if (res)
+ goto err_exit;
+
+ /* mask not supported on 600e/x, 770e, 770x, A21e, A2xm/p,
+ A30, R30, R31, T20-22, X20-21, X22-24. Detected by checking
+ for HKEY interface version 0x100 */
+ if (acpi_evalf(hkey_handle, &hkeyv, "MHKV", "qd")) {
+ if ((hkeyv >> 8) != 1) {
+ pr_err("unknown version of the HKEY interface: 0x%x\n",
+ hkeyv);
+ pr_err("please report this to %s\n", TPACPI_MAIL);
+ } else {
+ /*
+ * MHKV 0x100 in A31, R40, R40e,
+ * T4x, X31, and later
+ */
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "firmware HKEY interface version: 0x%x\n",
+ hkeyv);
+
+ /* Paranoia check AND init hotkey_all_mask */
+ if (!acpi_evalf(hkey_handle, &hotkey_all_mask,
+ "MHKA", "qd")) {
+ pr_err("missing MHKA handler, "
+ "please report this to %s\n",
+ TPACPI_MAIL);
+ /* Fallback: pre-init for FN+F3,F4,F12 */
+ hotkey_all_mask = 0x080cU;
+ } else {
+ tp_features.hotkey_mask = 1;
+ }
+ }
+ }
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "hotkey masks are %s\n",
+ str_supported(tp_features.hotkey_mask));
+
+ /* Init hotkey_all_mask if not initialized yet */
+ if (!tp_features.hotkey_mask && !hotkey_all_mask &&
+ (quirks & TPACPI_HK_Q_INIMASK))
+ hotkey_all_mask = 0x080cU; /* FN+F12, FN+F4, FN+F3 */
+
+ /* Init hotkey_acpi_mask and hotkey_orig_mask */
+ if (tp_features.hotkey_mask) {
+ /* hotkey_source_mask *must* be zero for
+ * the first hotkey_mask_get to return hotkey_orig_mask */
+ res = hotkey_mask_get();
+ if (res)
+ goto err_exit;
+
+ hotkey_orig_mask = hotkey_acpi_mask;
+ } else {
+ hotkey_orig_mask = hotkey_all_mask;
+ hotkey_acpi_mask = hotkey_all_mask;
+ }
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_wlswemul) {
+ tp_features.hotkey_wlsw = 1;
+ radiosw_state = !!tpacpi_wlsw_emulstate;
+ pr_info("radio switch emulation enabled\n");
+ } else
+#endif
+ /* Not all thinkpads have a hardware radio switch */
+ if (acpi_evalf(hkey_handle, &status, "WLSW", "qd")) {
+ tp_features.hotkey_wlsw = 1;
+ radiosw_state = !!status;
+ pr_info("radio switch found; radios are %s\n",
+ enabled(status, 0));
+ }
+ if (tp_features.hotkey_wlsw)
+ res = add_to_attr_set(hotkey_dev_attributes,
+ &dev_attr_hotkey_radio_sw.attr);
+
+ /* For X41t, X60t, X61t Tablets... */
+ if (!res && acpi_evalf(hkey_handle, &status, "MHKG", "qd")) {
+ tp_features.hotkey_tablet = 1;
+ tabletsw_state = !!(status & TP_HOTKEY_TABLET_MASK);
+ pr_info("possible tablet mode switch found; "
+ "ThinkPad in %s mode\n",
+ (tabletsw_state) ? "tablet" : "laptop");
+ res = add_to_attr_set(hotkey_dev_attributes,
+ &dev_attr_hotkey_tablet_mode.attr);
+ }
+
+ if (!res)
+ res = register_attr_set_with_sysfs(
+ hotkey_dev_attributes,
+ &tpacpi_pdev->dev.kobj);
+ if (res)
+ goto err_exit;
+
+ /* Set up key map */
+ hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE,
+ GFP_KERNEL);
+ if (!hotkey_keycode_map) {
+ pr_err("failed to allocate memory for key map\n");
+ res = -ENOMEM;
+ goto err_exit;
+ }
+
+ keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable,
+ ARRAY_SIZE(tpacpi_keymap_qtable));
+ BUG_ON(keymap_id >= ARRAY_SIZE(tpacpi_keymaps));
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "using keymap number %lu\n", keymap_id);
+
+ memcpy(hotkey_keycode_map, &tpacpi_keymaps[keymap_id],
+ TPACPI_HOTKEY_MAP_SIZE);
+
+ input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN);
+ tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE;
+ tpacpi_inputdev->keycodemax = TPACPI_HOTKEY_MAP_LEN;
+ tpacpi_inputdev->keycode = hotkey_keycode_map;
+ for (i = 0; i < TPACPI_HOTKEY_MAP_LEN; i++) {
+ if (hotkey_keycode_map[i] != KEY_RESERVED) {
+ input_set_capability(tpacpi_inputdev, EV_KEY,
+ hotkey_keycode_map[i]);
+ } else {
+ if (i < sizeof(hotkey_reserved_mask)*8)
+ hotkey_reserved_mask |= 1 << i;
+ }
+ }
+
+ if (tp_features.hotkey_wlsw) {
+ input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
+ input_report_switch(tpacpi_inputdev,
+ SW_RFKILL_ALL, radiosw_state);
+ }
+ if (tp_features.hotkey_tablet) {
+ input_set_capability(tpacpi_inputdev, EV_SW, SW_TABLET_MODE);
+ input_report_switch(tpacpi_inputdev,
+ SW_TABLET_MODE, tabletsw_state);
+ }
+
+ /* Do not issue duplicate brightness change events to
+ * userspace. tpacpi_detect_brightness_capabilities() must have
+ * been called before this point */
+ if (acpi_video_backlight_support()) {
+ pr_info("This ThinkPad has standard ACPI backlight "
+ "brightness control, supported by the ACPI "
+ "video driver\n");
+ pr_notice("Disabling thinkpad-acpi brightness events "
+ "by default...\n");
+
+ /* Disable brightness up/down on Lenovo thinkpads when
+ * ACPI is handling them, otherwise it is plain impossible
+ * for userspace to do something even remotely sane */
+ hotkey_reserved_mask |=
+ (1 << TP_ACPI_HOTKEYSCAN_FNHOME)
+ | (1 << TP_ACPI_HOTKEYSCAN_FNEND);
+ hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNHOME);
+ hotkey_unmap(TP_ACPI_HOTKEYSCAN_FNEND);
+ }
+
+#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
+ hotkey_source_mask = TPACPI_HKEY_NVRAM_GOOD_MASK
+ & ~hotkey_all_mask
+ & ~hotkey_reserved_mask;
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "hotkey source mask 0x%08x, polling freq %u\n",
+ hotkey_source_mask, hotkey_poll_freq);
+#endif
+
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "enabling firmware HKEY event interface...\n");
+ res = hotkey_status_set(true);
+ if (res) {
+ hotkey_exit();
+ return res;
+ }
+ res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask)
+ | hotkey_driver_mask)
+ & ~hotkey_source_mask);
+ if (res < 0 && res != -ENXIO) {
+ hotkey_exit();
+ return res;
+ }
+ hotkey_user_mask = (hotkey_acpi_mask | hotkey_source_mask)
+ & ~hotkey_reserved_mask;
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "initial masks: user=0x%08x, fw=0x%08x, poll=0x%08x\n",
+ hotkey_user_mask, hotkey_acpi_mask, hotkey_source_mask);
+
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
+ "legacy ibm/hotkey event reporting over procfs %s\n",
+ (hotkey_report_mode < 2) ?
+ "enabled" : "disabled");
+
+ tpacpi_inputdev->open = &hotkey_inputdev_open;
+ tpacpi_inputdev->close = &hotkey_inputdev_close;
+
+ hotkey_poll_setup_safe(true);
+
+ return 0;
+
+err_exit:
+ delete_attr_set(hotkey_dev_attributes, &tpacpi_pdev->dev.kobj);
+ hotkey_dev_attributes = NULL;
+
+ return (res < 0)? res : 1;
+}
+
+static bool hotkey_notify_hotkey(const u32 hkey,
+ bool *send_acpi_ev,
+ bool *ignore_acpi_ev)
+{
+ /* 0x1000-0x1FFF: key presses */
+ unsigned int scancode = hkey & 0xfff;
+ *send_acpi_ev = true;
+ *ignore_acpi_ev = false;
+
+ /* HKEY event 0x1001 is scancode 0x00 */
+ if (scancode > 0 && scancode <= TPACPI_HOTKEY_MAP_LEN) {
+ scancode--;
+ if (!(hotkey_source_mask & (1 << scancode))) {
+ tpacpi_input_send_key_masked(scancode);
+ *send_acpi_ev = false;
+ } else {
+ *ignore_acpi_ev = true;
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool hotkey_notify_wakeup(const u32 hkey,
+ bool *send_acpi_ev,
+ bool *ignore_acpi_ev)
+{
+ /* 0x2000-0x2FFF: Wakeup reason */
+ *send_acpi_ev = true;
+ *ignore_acpi_ev = false;
+
+ switch (hkey) {
+ case TP_HKEY_EV_WKUP_S3_UNDOCK: /* suspend, undock */
+ case TP_HKEY_EV_WKUP_S4_UNDOCK: /* hibernation, undock */
+ hotkey_wakeup_reason = TP_ACPI_WAKEUP_UNDOCK;
+ *ignore_acpi_ev = true;
+ break;
+
+ case TP_HKEY_EV_WKUP_S3_BAYEJ: /* suspend, bay eject */
+ case TP_HKEY_EV_WKUP_S4_BAYEJ: /* hibernation, bay eject */
+ hotkey_wakeup_reason = TP_ACPI_WAKEUP_BAYEJ;
+ *ignore_acpi_ev = true;
+ break;
+
+ case TP_HKEY_EV_WKUP_S3_BATLOW: /* Battery on critical low level/S3 */
+ case TP_HKEY_EV_WKUP_S4_BATLOW: /* Battery on critical low level/S4 */
+ pr_alert("EMERGENCY WAKEUP: battery almost empty\n");
+ /* how to auto-heal: */
+ /* 2313: woke up from S3, go to S4/S5 */
+ /* 2413: woke up from S4, go to S5 */
+ break;
+
+ default:
+ return false;
+ }
+
+ if (hotkey_wakeup_reason != TP_ACPI_WAKEUP_NONE) {
+ pr_info("woke up due to a hot-unplug request...\n");
+ hotkey_wakeup_reason_notify_change();
+ }
+ return true;
+}
+
+static bool hotkey_notify_dockevent(const u32 hkey,
+ bool *send_acpi_ev,
+ bool *ignore_acpi_ev)
+{
+ /* 0x4000-0x4FFF: dock-related events */
+ *send_acpi_ev = true;
+ *ignore_acpi_ev = false;
+
+ switch (hkey) {
+ case TP_HKEY_EV_UNDOCK_ACK:
+ /* ACPI undock operation completed after wakeup */
+ hotkey_autosleep_ack = 1;
+ pr_info("undocked\n");
+ hotkey_wakeup_hotunplug_complete_notify_change();
+ return true;
+
+ case TP_HKEY_EV_HOTPLUG_DOCK: /* docked to port replicator */
+ pr_info("docked into hotplug port replicator\n");
+ return true;
+ case TP_HKEY_EV_HOTPLUG_UNDOCK: /* undocked from port replicator */
+ pr_info("undocked from hotplug port replicator\n");
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static bool hotkey_notify_usrevent(const u32 hkey,
+ bool *send_acpi_ev,
+ bool *ignore_acpi_ev)
+{
+ /* 0x5000-0x5FFF: human interface helpers */
+ *send_acpi_ev = true;
+ *ignore_acpi_ev = false;
+
+ switch (hkey) {
+ case TP_HKEY_EV_PEN_INSERTED: /* X61t: tablet pen inserted into bay */
+ case TP_HKEY_EV_PEN_REMOVED: /* X61t: tablet pen removed from bay */
+ return true;
+
+ case TP_HKEY_EV_TABLET_TABLET: /* X41t-X61t: tablet mode */
+ case TP_HKEY_EV_TABLET_NOTEBOOK: /* X41t-X61t: normal mode */
+ tpacpi_input_send_tabletsw();
+ hotkey_tablet_mode_notify_change();
+ *send_acpi_ev = false;
+ return true;
+
+ case TP_HKEY_EV_LID_CLOSE: /* Lid closed */
+ case TP_HKEY_EV_LID_OPEN: /* Lid opened */
+ case TP_HKEY_EV_BRGHT_CHANGED: /* brightness changed */
+ /* do not propagate these events */
+ *ignore_acpi_ev = true;
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static void thermal_dump_all_sensors(void);
+
+static bool hotkey_notify_6xxx(const u32 hkey,
+ bool *send_acpi_ev,
+ bool *ignore_acpi_ev)
+{
+ bool known = true;
+
+ /* 0x6000-0x6FFF: thermal alarms/notices and keyboard events */
+ *send_acpi_ev = true;
+ *ignore_acpi_ev = false;
+
+ switch (hkey) {
+ case TP_HKEY_EV_THM_TABLE_CHANGED:
+ pr_info("EC reports that Thermal Table has changed\n");
+ /* recommended action: do nothing, we don't have
+ * Lenovo ATM information */
+ return true;
+ case TP_HKEY_EV_ALARM_BAT_HOT:
+ pr_crit("THERMAL ALARM: battery is too hot!\n");
+ /* recommended action: warn user through gui */
+ break;
+ case TP_HKEY_EV_ALARM_BAT_XHOT:
+ pr_alert("THERMAL EMERGENCY: battery is extremely hot!\n");
+ /* recommended action: immediate sleep/hibernate */
+ break;
+ case TP_HKEY_EV_ALARM_SENSOR_HOT:
+ pr_crit("THERMAL ALARM: "
+ "a sensor reports something is too hot!\n");
+ /* recommended action: warn user through gui, that */
+ /* some internal component is too hot */
+ break;
+ case TP_HKEY_EV_ALARM_SENSOR_XHOT:
+ pr_alert("THERMAL EMERGENCY: "
+ "a sensor reports something is extremely hot!\n");
+ /* recommended action: immediate sleep/hibernate */
+ break;
+ case TP_HKEY_EV_AC_CHANGED:
+ /* X120e, X121e, X220, X220i, X220t, X230, T420, T420s, W520:
+ * AC status changed; can be triggered by plugging or
+ * unplugging AC adapter, docking or undocking. */
+
+ /* fallthrough */
+
+ case TP_HKEY_EV_KEY_NUMLOCK:
+ case TP_HKEY_EV_KEY_FN:
+ /* key press events, we just ignore them as long as the EC
+ * is still reporting them in the normal keyboard stream */
+ *send_acpi_ev = false;
+ *ignore_acpi_ev = true;
+ return true;
+
+ default:
+ pr_warn("unknown possible thermal alarm or keyboard event received\n");
+ known = false;
+ }
+
+ thermal_dump_all_sensors();
+
+ return known;
+}
+
+static void hotkey_notify(struct ibm_struct *ibm, u32 event)
+{
+ u32 hkey;
+ bool send_acpi_ev;
+ bool ignore_acpi_ev;
+ bool known_ev;
+
+ if (event != 0x80) {
+ pr_err("unknown HKEY notification event %d\n", event);
+ /* forward it to userspace, maybe it knows how to handle it */
+ acpi_bus_generate_netlink_event(
+ ibm->acpi->device->pnp.device_class,
+ dev_name(&ibm->acpi->device->dev),
+ event, 0);
+ return;
+ }
+
+ while (1) {
+ if (!acpi_evalf(hkey_handle, &hkey, "MHKP", "d")) {
+ pr_err("failed to retrieve HKEY event\n");
+ return;
+ }
+
+ if (hkey == 0) {
+ /* queue empty */
+ return;
+ }
+
+ send_acpi_ev = true;
+ ignore_acpi_ev = false;
+
+ switch (hkey >> 12) {
+ case 1:
+ /* 0x1000-0x1FFF: key presses */
+ known_ev = hotkey_notify_hotkey(hkey, &send_acpi_ev,
+ &ignore_acpi_ev);
+ break;
+ case 2:
+ /* 0x2000-0x2FFF: Wakeup reason */
+ known_ev = hotkey_notify_wakeup(hkey, &send_acpi_ev,
+ &ignore_acpi_ev);
+ break;
+ case 3:
+ /* 0x3000-0x3FFF: bay-related wakeups */
+ switch (hkey) {
+ case TP_HKEY_EV_BAYEJ_ACK:
+ hotkey_autosleep_ack = 1;
+ pr_info("bay ejected\n");
+ hotkey_wakeup_hotunplug_complete_notify_change();
+ known_ev = true;
+ break;
+ case TP_HKEY_EV_OPTDRV_EJ:
+ /* FIXME: kick libata if SATA link offline */
+ known_ev = true;
+ break;
+ default:
+ known_ev = false;
+ }
+ break;
+ case 4:
+ /* 0x4000-0x4FFF: dock-related events */
+ known_ev = hotkey_notify_dockevent(hkey, &send_acpi_ev,
+ &ignore_acpi_ev);
+ break;
+ case 5:
+ /* 0x5000-0x5FFF: human interface helpers */
+ known_ev = hotkey_notify_usrevent(hkey, &send_acpi_ev,
+ &ignore_acpi_ev);
+ break;
+ case 6:
+ /* 0x6000-0x6FFF: thermal alarms/notices and
+ * keyboard events */
+ known_ev = hotkey_notify_6xxx(hkey, &send_acpi_ev,
+ &ignore_acpi_ev);
+ break;
+ case 7:
+ /* 0x7000-0x7FFF: misc */
+ if (tp_features.hotkey_wlsw &&
+ hkey == TP_HKEY_EV_RFKILL_CHANGED) {
+ tpacpi_send_radiosw_update();
+ send_acpi_ev = 0;
+ known_ev = true;
+ break;
+ }
+ /* fallthrough to default */
+ default:
+ known_ev = false;
+ }
+ if (!known_ev) {
+ pr_notice("unhandled HKEY event 0x%04x\n", hkey);
+ pr_notice("please report the conditions when this "
+ "event happened to %s\n", TPACPI_MAIL);
+ }
+
+ /* Legacy events */
+ if (!ignore_acpi_ev &&
+ (send_acpi_ev || hotkey_report_mode < 2)) {
+ acpi_bus_generate_proc_event(ibm->acpi->device,
+ event, hkey);
+ }
+
+ /* netlink events */
+ if (!ignore_acpi_ev && send_acpi_ev) {
+ acpi_bus_generate_netlink_event(
+ ibm->acpi->device->pnp.device_class,
+ dev_name(&ibm->acpi->device->dev),
+ event, hkey);
+ }
+ }
+}
+
+static void hotkey_suspend(void)
+{
+ /* Do these on suspend, we get the events on early resume! */
+ hotkey_wakeup_reason = TP_ACPI_WAKEUP_NONE;
+ hotkey_autosleep_ack = 0;
+}
+
+static void hotkey_resume(void)
+{
+ tpacpi_disable_brightness_delay();
+
+ if (hotkey_status_set(true) < 0 ||
+ hotkey_mask_set(hotkey_acpi_mask) < 0)
+ pr_err("error while attempting to reset the event "
+ "firmware interface\n");
+
+ tpacpi_send_radiosw_update();
+ hotkey_tablet_mode_notify_change();
+ hotkey_wakeup_reason_notify_change();
+ hotkey_wakeup_hotunplug_complete_notify_change();
+ hotkey_poll_setup_safe(false);
+}
+
+/* procfs -------------------------------------------------------------- */
+static int hotkey_read(struct seq_file *m)
+{
+ int res, status;
+
+ if (!tp_features.hotkey) {
+ seq_printf(m, "status:\t\tnot supported\n");
+ return 0;
+ }
+
+ if (mutex_lock_killable(&hotkey_mutex))
+ return -ERESTARTSYS;
+ res = hotkey_status_get(&status);
+ if (!res)
+ res = hotkey_mask_get();
+ mutex_unlock(&hotkey_mutex);
+ if (res)
+ return res;
+
+ seq_printf(m, "status:\t\t%s\n", enabled(status, 0));
+ if (hotkey_all_mask) {
+ seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask);
+ seq_printf(m, "commands:\tenable, disable, reset, <mask>\n");
+ } else {
+ seq_printf(m, "mask:\t\tnot supported\n");
+ seq_printf(m, "commands:\tenable, disable, reset\n");
+ }
+
+ return 0;
+}
+
+static void hotkey_enabledisable_warn(bool enable)
+{
+ tpacpi_log_usertask("procfs hotkey enable/disable");
+ if (!WARN((tpacpi_lifecycle == TPACPI_LIFE_RUNNING || !enable),
+ pr_fmt("hotkey enable/disable functionality has been "
+ "removed from the driver. "
+ "Hotkeys are always enabled.\n")))
+ pr_err("Please remove the hotkey=enable module "
+ "parameter, it is deprecated. "
+ "Hotkeys are always enabled.\n");
+}
+
+static int hotkey_write(char *buf)
+{
+ int res;
+ u32 mask;
+ char *cmd;
+
+ if (!tp_features.hotkey)
+ return -ENODEV;
+
+ if (mutex_lock_killable(&hotkey_mutex))
+ return -ERESTARTSYS;
+
+ mask = hotkey_user_mask;
+
+ res = 0;
+ while ((cmd = next_cmd(&buf))) {
+ if (strlencmp(cmd, "enable") == 0) {
+ hotkey_enabledisable_warn(1);
+ } else if (strlencmp(cmd, "disable") == 0) {
+ hotkey_enabledisable_warn(0);
+ res = -EPERM;
+ } else if (strlencmp(cmd, "reset") == 0) {
+ mask = (hotkey_all_mask | hotkey_source_mask)
+ & ~hotkey_reserved_mask;
+ } else if (sscanf(cmd, "0x%x", &mask) == 1) {
+ /* mask set */
+ } else if (sscanf(cmd, "%x", &mask) == 1) {
+ /* mask set */
+ } else {
+ res = -EINVAL;
+ goto errexit;
+ }
+ }
+
+ if (!res) {
+ tpacpi_disclose_usertask("procfs hotkey",
+ "set mask to 0x%08x\n", mask);
+ res = hotkey_user_mask_set(mask);
+ }
+
+errexit:
+ mutex_unlock(&hotkey_mutex);
+ return res;
+}
+
+static const struct acpi_device_id ibm_htk_device_ids[] = {
+ {TPACPI_ACPI_IBM_HKEY_HID, 0},
+ {TPACPI_ACPI_LENOVO_HKEY_HID, 0},
+ {"", 0},
+};
+
+static struct tp_acpi_drv_struct ibm_hotkey_acpidriver = {
+ .hid = ibm_htk_device_ids,
+ .notify = hotkey_notify,
+ .handle = &hkey_handle,
+ .type = ACPI_DEVICE_NOTIFY,
+};
+
+static struct ibm_struct hotkey_driver_data = {
+ .name = "hotkey",
+ .read = hotkey_read,
+ .write = hotkey_write,
+ .exit = hotkey_exit,
+ .resume = hotkey_resume,
+ .suspend = hotkey_suspend,
+ .acpi = &ibm_hotkey_acpidriver,
+};
+
+/*************************************************************************
+ * Bluetooth subdriver
+ */
+
+enum {
+ /* ACPI GBDC/SBDC bits */
+ TP_ACPI_BLUETOOTH_HWPRESENT = 0x01, /* Bluetooth hw available */
+ TP_ACPI_BLUETOOTH_RADIOSSW = 0x02, /* Bluetooth radio enabled */
+ TP_ACPI_BLUETOOTH_RESUMECTRL = 0x04, /* Bluetooth state at resume:
+ 0 = disable, 1 = enable */
+};
+
+enum {
+ /* ACPI \BLTH commands */
+ TP_ACPI_BLTH_GET_ULTRAPORT_ID = 0x00, /* Get Ultraport BT ID */
+ TP_ACPI_BLTH_GET_PWR_ON_RESUME = 0x01, /* Get power-on-resume state */
+ TP_ACPI_BLTH_PWR_ON_ON_RESUME = 0x02, /* Resume powered on */
+ TP_ACPI_BLTH_PWR_OFF_ON_RESUME = 0x03, /* Resume powered off */
+ TP_ACPI_BLTH_SAVE_STATE = 0x05, /* Save state for S4/S5 */
+};
+
+#define TPACPI_RFK_BLUETOOTH_SW_NAME "tpacpi_bluetooth_sw"
+
+static int bluetooth_get_status(void)
+{
+ int status;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_bluetoothemul)
+ return (tpacpi_bluetooth_emulstate) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+ if (!acpi_evalf(hkey_handle, &status, "GBDC", "d"))
+ return -EIO;
+
+ return ((status & TP_ACPI_BLUETOOTH_RADIOSSW) != 0) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+static int bluetooth_set_status(enum tpacpi_rfkill_state state)
+{
+ int status;
+
+ vdbg_printk(TPACPI_DBG_RFKILL,
+ "will attempt to %s bluetooth\n",
+ (state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_bluetoothemul) {
+ tpacpi_bluetooth_emulstate = (state == TPACPI_RFK_RADIO_ON);
+ return 0;
+ }
+#endif
+
+ if (state == TPACPI_RFK_RADIO_ON)
+ status = TP_ACPI_BLUETOOTH_RADIOSSW
+ | TP_ACPI_BLUETOOTH_RESUMECTRL;
+ else
+ status = 0;
+
+ if (!acpi_evalf(hkey_handle, NULL, "SBDC", "vd", status))
+ return -EIO;
+
+ return 0;
+}
+
+/* sysfs bluetooth enable ---------------------------------------------- */
+static ssize_t bluetooth_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_BLUETOOTH_SW_ID,
+ attr, buf);
+}
+
+static ssize_t bluetooth_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_BLUETOOTH_SW_ID,
+ attr, buf, count);
+}
+
+static struct device_attribute dev_attr_bluetooth_enable =
+ __ATTR(bluetooth_enable, S_IWUSR | S_IRUGO,
+ bluetooth_enable_show, bluetooth_enable_store);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *bluetooth_attributes[] = {
+ &dev_attr_bluetooth_enable.attr,
+ NULL
+};
+
+static const struct attribute_group bluetooth_attr_group = {
+ .attrs = bluetooth_attributes,
+};
+
+static const struct tpacpi_rfk_ops bluetooth_tprfk_ops = {
+ .get_status = bluetooth_get_status,
+ .set_status = bluetooth_set_status,
+};
+
+static void bluetooth_shutdown(void)
+{
+ /* Order firmware to save current state to NVRAM */
+ if (!acpi_evalf(NULL, NULL, "\\BLTH", "vd",
+ TP_ACPI_BLTH_SAVE_STATE))
+ pr_notice("failed to save bluetooth state to NVRAM\n");
+ else
+ vdbg_printk(TPACPI_DBG_RFKILL,
+ "bluetooth state saved to NVRAM\n");
+}
+
+static void bluetooth_exit(void)
+{
+ sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+ &bluetooth_attr_group);
+
+ tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
+
+ bluetooth_shutdown();
+}
+
+static int __init bluetooth_init(struct ibm_init_struct *iibm)
+{
+ int res;
+ int status = 0;
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "initializing bluetooth subdriver\n");
+
+ TPACPI_ACPIHANDLE_INIT(hkey);
+
+ /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
+ G4x, R30, R31, R40e, R50e, T20-22, X20-21 */
+ tp_features.bluetooth = hkey_handle &&
+ acpi_evalf(hkey_handle, &status, "GBDC", "qd");
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "bluetooth is %s, status 0x%02x\n",
+ str_supported(tp_features.bluetooth),
+ status);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_bluetoothemul) {
+ tp_features.bluetooth = 1;
+ pr_info("bluetooth switch emulation enabled\n");
+ } else
+#endif
+ if (tp_features.bluetooth &&
+ !(status & TP_ACPI_BLUETOOTH_HWPRESENT)) {
+ /* no bluetooth hardware present in system */
+ tp_features.bluetooth = 0;
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "bluetooth hardware not installed\n");
+ }
+
+ if (!tp_features.bluetooth)
+ return 1;
+
+ res = tpacpi_new_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID,
+ &bluetooth_tprfk_ops,
+ RFKILL_TYPE_BLUETOOTH,
+ TPACPI_RFK_BLUETOOTH_SW_NAME,
+ true);
+ if (res)
+ return res;
+
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ &bluetooth_attr_group);
+ if (res) {
+ tpacpi_destroy_rfkill(TPACPI_RFK_BLUETOOTH_SW_ID);
+ return res;
+ }
+
+ return 0;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int bluetooth_read(struct seq_file *m)
+{
+ return tpacpi_rfk_procfs_read(TPACPI_RFK_BLUETOOTH_SW_ID, m);
+}
+
+static int bluetooth_write(char *buf)
+{
+ return tpacpi_rfk_procfs_write(TPACPI_RFK_BLUETOOTH_SW_ID, buf);
+}
+
+static struct ibm_struct bluetooth_driver_data = {
+ .name = "bluetooth",
+ .read = bluetooth_read,
+ .write = bluetooth_write,
+ .exit = bluetooth_exit,
+ .shutdown = bluetooth_shutdown,
+};
+
+/*************************************************************************
+ * Wan subdriver
+ */
+
+enum {
+ /* ACPI GWAN/SWAN bits */
+ TP_ACPI_WANCARD_HWPRESENT = 0x01, /* Wan hw available */
+ TP_ACPI_WANCARD_RADIOSSW = 0x02, /* Wan radio enabled */
+ TP_ACPI_WANCARD_RESUMECTRL = 0x04, /* Wan state at resume:
+ 0 = disable, 1 = enable */
+};
+
+#define TPACPI_RFK_WWAN_SW_NAME "tpacpi_wwan_sw"
+
+static int wan_get_status(void)
+{
+ int status;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_wwanemul)
+ return (tpacpi_wwan_emulstate) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+ if (!acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+ return -EIO;
+
+ return ((status & TP_ACPI_WANCARD_RADIOSSW) != 0) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+static int wan_set_status(enum tpacpi_rfkill_state state)
+{
+ int status;
+
+ vdbg_printk(TPACPI_DBG_RFKILL,
+ "will attempt to %s wwan\n",
+ (state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_wwanemul) {
+ tpacpi_wwan_emulstate = (state == TPACPI_RFK_RADIO_ON);
+ return 0;
+ }
+#endif
+
+ if (state == TPACPI_RFK_RADIO_ON)
+ status = TP_ACPI_WANCARD_RADIOSSW
+ | TP_ACPI_WANCARD_RESUMECTRL;
+ else
+ status = 0;
+
+ if (!acpi_evalf(hkey_handle, NULL, "SWAN", "vd", status))
+ return -EIO;
+
+ return 0;
+}
+
+/* sysfs wan enable ---------------------------------------------------- */
+static ssize_t wan_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return tpacpi_rfk_sysfs_enable_show(TPACPI_RFK_WWAN_SW_ID,
+ attr, buf);
+}
+
+static ssize_t wan_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return tpacpi_rfk_sysfs_enable_store(TPACPI_RFK_WWAN_SW_ID,
+ attr, buf, count);
+}
+
+static struct device_attribute dev_attr_wan_enable =
+ __ATTR(wwan_enable, S_IWUSR | S_IRUGO,
+ wan_enable_show, wan_enable_store);
+
+/* --------------------------------------------------------------------- */
+
+static struct attribute *wan_attributes[] = {
+ &dev_attr_wan_enable.attr,
+ NULL
+};
+
+static const struct attribute_group wan_attr_group = {
+ .attrs = wan_attributes,
+};
+
+static const struct tpacpi_rfk_ops wan_tprfk_ops = {
+ .get_status = wan_get_status,
+ .set_status = wan_set_status,
+};
+
+static void wan_shutdown(void)
+{
+ /* Order firmware to save current state to NVRAM */
+ if (!acpi_evalf(NULL, NULL, "\\WGSV", "vd",
+ TP_ACPI_WGSV_SAVE_STATE))
+ pr_notice("failed to save WWAN state to NVRAM\n");
+ else
+ vdbg_printk(TPACPI_DBG_RFKILL,
+ "WWAN state saved to NVRAM\n");
+}
+
+static void wan_exit(void)
+{
+ sysfs_remove_group(&tpacpi_pdev->dev.kobj,
+ &wan_attr_group);
+
+ tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
+
+ wan_shutdown();
+}
+
+static int __init wan_init(struct ibm_init_struct *iibm)
+{
+ int res;
+ int status = 0;
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "initializing wan subdriver\n");
+
+ TPACPI_ACPIHANDLE_INIT(hkey);
+
+ tp_features.wan = hkey_handle &&
+ acpi_evalf(hkey_handle, &status, "GWAN", "qd");
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "wan is %s, status 0x%02x\n",
+ str_supported(tp_features.wan),
+ status);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_wwanemul) {
+ tp_features.wan = 1;
+ pr_info("wwan switch emulation enabled\n");
+ } else
+#endif
+ if (tp_features.wan &&
+ !(status & TP_ACPI_WANCARD_HWPRESENT)) {
+ /* no wan hardware present in system */
+ tp_features.wan = 0;
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "wan hardware not installed\n");
+ }
+
+ if (!tp_features.wan)
+ return 1;
+
+ res = tpacpi_new_rfkill(TPACPI_RFK_WWAN_SW_ID,
+ &wan_tprfk_ops,
+ RFKILL_TYPE_WWAN,
+ TPACPI_RFK_WWAN_SW_NAME,
+ true);
+ if (res)
+ return res;
+
+ res = sysfs_create_group(&tpacpi_pdev->dev.kobj,
+ &wan_attr_group);
+
+ if (res) {
+ tpacpi_destroy_rfkill(TPACPI_RFK_WWAN_SW_ID);
+ return res;
+ }
+
+ return 0;
+}
+
+/* procfs -------------------------------------------------------------- */
+static int wan_read(struct seq_file *m)
+{
+ return tpacpi_rfk_procfs_read(TPACPI_RFK_WWAN_SW_ID, m);
+}
+
+static int wan_write(char *buf)
+{
+ return tpacpi_rfk_procfs_write(TPACPI_RFK_WWAN_SW_ID, buf);
+}
+
+static struct ibm_struct wan_driver_data = {
+ .name = "wan",
+ .read = wan_read,
+ .write = wan_write,
+ .exit = wan_exit,
+ .shutdown = wan_shutdown,
+};
+
+/*************************************************************************
+ * UWB subdriver
+ */
+
+enum {
+ /* ACPI GUWB/SUWB bits */
+ TP_ACPI_UWB_HWPRESENT = 0x01, /* UWB hw available */
+ TP_ACPI_UWB_RADIOSSW = 0x02, /* UWB radio enabled */
+};
+
+#define TPACPI_RFK_UWB_SW_NAME "tpacpi_uwb_sw"
+
+static int uwb_get_status(void)
+{
+ int status;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_uwbemul)
+ return (tpacpi_uwb_emulstate) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+#endif
+
+ if (!acpi_evalf(hkey_handle, &status, "GUWB", "d"))
+ return -EIO;
+
+ return ((status & TP_ACPI_UWB_RADIOSSW) != 0) ?
+ TPACPI_RFK_RADIO_ON : TPACPI_RFK_RADIO_OFF;
+}
+
+static int uwb_set_status(enum tpacpi_rfkill_state state)
+{
+ int status;
+
+ vdbg_printk(TPACPI_DBG_RFKILL,
+ "will attempt to %s UWB\n",
+ (state == TPACPI_RFK_RADIO_ON) ? "enable" : "disable");
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_uwbemul) {
+ tpacpi_uwb_emulstate = (state == TPACPI_RFK_RADIO_ON);
+ return 0;
+ }
+#endif
+
+ if (state == TPACPI_RFK_RADIO_ON)
+ status = TP_ACPI_UWB_RADIOSSW;
+ else
+ status = 0;
+
+ if (!acpi_evalf(hkey_handle, NULL, "SUWB", "vd", status))
+ return -EIO;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static const struct tpacpi_rfk_ops uwb_tprfk_ops = {
+ .get_status = uwb_get_status,
+ .set_status = uwb_set_status,
+};
+
+static void uwb_exit(void)
+{
+ tpacpi_destroy_rfkill(TPACPI_RFK_UWB_SW_ID);
+}
+
+static int __init uwb_init(struct ibm_init_struct *iibm)
+{
+ int res;
+ int status = 0;
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "initializing uwb subdriver\n");
+
+ TPACPI_ACPIHANDLE_INIT(hkey);
+
+ tp_features.uwb = hkey_handle &&
+ acpi_evalf(hkey_handle, &status, "GUWB", "qd");
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL,
+ "uwb is %s, status 0x%02x\n",
+ str_supported(tp_features.uwb),
+ status);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+ if (dbg_uwbemul) {
+ tp_features.uwb = 1;
+ pr_info("uwb switch emulation enabled\n");
+ } else
+#endif
+ if (tp_features.uwb &&
+ !(status & TP_ACPI_UWB_HWPRESENT)) {
+ /* no uwb hardware present in system */
+ tp_features.uwb = 0;
+ dbg_printk(TPACPI_DBG_INIT,
+ "uwb hardware not installed\n");
+ }
+
+ if (!tp_features.uwb)
+ return 1;
+
+ res = tpacpi_new_rfkill(TPACPI_RFK_UWB_SW_ID,
+ &uwb_tprfk_ops,
+ RFKILL_TYPE_UWB,
+ TPACPI_RFK_UWB_SW_NAME,
+ false);
+ return res;
+}
+
+static struct ibm_struct uwb_driver_data = {
+ .name = "uwb",
+ .exit = uwb_exit,
+ .flags.experimental = 1,
+};
+
+/*************************************************************************
+ * Video subdriver
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_VIDEO
+
+enum video_access_mode {
+ TPACPI_VIDEO_NONE = 0,
+ TPACPI_VIDEO_570, /* 570 */
+ TPACPI_VIDEO_770, /* 600e/x, 770e, 770x */
+ TPACPI_VIDEO_NEW, /* all others */
+};
+
+enum { /* video status flags, based on VIDEO_570 */
+ TP_ACPI_VIDEO_S_LCD = 0x01, /* LCD output enabled */
+ TP_ACPI_VIDEO_S_CRT = 0x02, /* CRT output enabled */
+ TP_ACPI_VIDEO_S_DVI = 0x08, /* DVI output enabled */
+};
+
+enum { /* TPACPI_VIDEO_570 constants */
+ TP_ACPI_VIDEO_570_PHSCMD = 0x87, /* unknown magic constant :( */
+ TP_ACPI_VIDEO_570_PHSMASK = 0x03, /* PHS bits that map to
+ * video_status_flags */
+ TP_ACPI_VIDEO_570_PHS2CMD = 0x8b, /* unknown magic constant :( */
+ TP_ACPI_VIDEO_570_PHS2SET = 0x80, /* unknown magic constant :( */
+};
+
+static enum video_access_mode video_supported;
+static int video_orig_autosw;
+
+static int video_autosw_get(void);
+static int video_autosw_set(int enable);
+
+TPACPI_HANDLE(vid, root,
+ "\\_SB.PCI.AGP.VGA", /* 570 */
+ "\\_SB.PCI0.AGP0.VID0", /* 600e/x, 770x */
+ "\\_SB.PCI0.VID0", /* 770e */
+ "\\_SB.PCI0.VID", /* A21e, G4x, R50e, X30, X40 */
+ "\\_SB.PCI0.AGP.VGA", /* X100e and a few others */
+ "\\_SB.PCI0.AGP.VID", /* all others */
+ ); /* R30, R31 */
+
+TPACPI_HANDLE(vid2, root, "\\_SB.PCI0.AGPB.VID"); /* G41 */
+
+static int __init video_init(struct ibm_init_struct *iibm)
+{
+ int ivga;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing video subdriver\n");
+
+ TPACPI_ACPIHANDLE_INIT(vid);
+ if (tpacpi_is_ibm())
+ TPACPI_ACPIHANDLE_INIT(vid2);
+
+ if (vid2_handle && acpi_evalf(NULL, &ivga, "\\IVGA", "d") && ivga)
+ /* G41, assume IVGA doesn't change */
+ vid_handle = vid2_handle;
+
+ if (!vid_handle)
+ /* video switching not supported on R30, R31 */
+ video_supported = TPACPI_VIDEO_NONE;
+ else if (tpacpi_is_ibm() &&
+ acpi_evalf(vid_handle, &video_orig_autosw, "SWIT", "qd"))
+ /* 570 */
+ video_supported = TPACPI_VIDEO_570;
+ else if (tpacpi_is_ibm() &&
+ acpi_evalf(vid_handle, &video_orig_autosw, "^VADL", "qd"))
+ /* 600e/x, 770e, 770x */
+ video_supported = TPACPI_VIDEO_770;
+ else
+ /* all others */
+ video_supported = TPACPI_VIDEO_NEW;
+
+ vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n",
+ str_supported(video_supported != TPACPI_VIDEO_NONE),
+ video_supported);
+
+ return (video_supported != TPACPI_VIDEO_NONE)? 0 : 1;
+}
+
+static void video_exit(void)
+{
+ dbg_printk(TPACPI_DBG_EXIT,
+ "restoring original video autoswitch mode\n");
+ if (video_autosw_set(video_orig_autosw))
+ pr_err("error while trying to restore original "
+ "video autoswitch mode\n");
+}
+
+static int video_outputsw_get(void)
+{
+ int status = 0;
+ int i;
+
+ switch (video_supported) {
+ case TPACPI_VIDEO_570:
+ if (!acpi_evalf(NULL, &i, "\\_SB.PHS", "dd",
+ TP_ACPI_VIDEO_570_PHSCMD))
+ return -EIO;
+ status = i & TP_ACPI_VIDEO_570_PHSMASK;
+ break;
+ case TPACPI_VIDEO_770:
+ if (!acpi_evalf(NULL, &i, "\\VCDL", "d"))
+ return -EIO;
+ if (i)
+ status |= TP_ACPI_VIDEO_S_LCD;
+ if (!acpi_evalf(NULL, &i, "\\VCDC", "d"))
+ return -EIO;
+ if (i)
+ status |= TP_ACPI_VIDEO_S_CRT;
+ break;
+ case TPACPI_VIDEO_NEW:
+ if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 1) ||
+ !acpi_evalf(NULL, &i, "\\VCDC", "d"))
+ return -EIO;
+ if (i)
+ status |= TP_ACPI_VIDEO_S_CRT;
+
+ if (!acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0) ||
+ !acpi_evalf(NULL, &i, "\\VCDL", "d"))
+ return -EIO;
+ if (i)
+ status |= TP_ACPI_VIDEO_S_LCD;
+ if (!acpi_evalf(NULL, &i, "\\VCDD", "d"))
+ return -EIO;
+ if (i)
+ status |= TP_ACPI_VIDEO_S_DVI;
+ break;
+ default:
+ return -ENOSYS;
+ }
+
+ return status;
+}
+
+static int video_outputsw_set(int status)
+{
+ int autosw;
+ int res = 0;
+
+ switch (video_supported) {
+ case TPACPI_VIDEO_570:
+ res = acpi_evalf(NULL, NULL,
+ "\\_SB.PHS2", "vdd",
+ TP_ACPI_VIDEO_570_PHS2CMD,
+ status | TP_ACPI_VIDEO_570_PHS2SET);
+ break;
+ case TPACPI_VIDEO_770:
+ autosw = video_autosw_get();
+ if (autosw < 0)
+ return autosw;
+
+ res = video_autosw_set(1);
+ if (res)
+ return res;
+ res = acpi_evalf(vid_handle, NULL,
+ "ASWT", "vdd", status * 0x100, 0);
+ if (!autosw && video_autosw_set(autosw)) {
+ pr_err("video auto-switch left enabled due to error\n");
+ return -EIO;
+ }
+ break;
+ case TPACPI_VIDEO_NEW:
+ res = acpi_evalf(NULL, NULL, "\\VUPS", "vd", 0x80) &&
+ acpi_evalf(NULL, NULL, "\\VSDS", "vdd", status, 1);
+ break;
+ default:
+ return -ENOSYS;
+ }
+
+ return (res)? 0 : -EIO;
+}
+
+static int video_autosw_get(void)
+{
+ int autosw = 0;
+
+ switch (video_supported) {
+ case TPACPI_VIDEO_570:
+ if (!acpi_evalf(vid_handle, &autosw, "SWIT", "d"))
+ return -EIO;
+ break;
+ case TPACPI_VIDEO_770:
+ case TPACPI_VIDEO_NEW:
+ if (!acpi_evalf(vid_handle, &autosw, "^VDEE", "d"))
+ return -EIO;
+ break;
+ default:
+ return -ENOSYS;
+ }
+
+ return autosw & 1;
+}
+
+static int video_autosw_set(int enable)
+{
+ if (!acpi_evalf(vid_handle, NULL, "_DOS", "vd", (enable)? 1 : 0))
+ return -EIO;
+ return 0;
+}
+
+static int video_outputsw_cycle(void)
+{
+ int autosw = video_autosw_get();
+ int res;
+
+ if (autosw < 0)
+ return autosw;
+
+ switch (video_supported) {
+ case TPACPI_VIDEO_570:
+ res = video_autosw_set(1);
+ if (res)
+ return res;
+ res = acpi_evalf(ec_handle, NULL, "_Q16", "v");
+ break;
+ case TPACPI_VIDEO_770:
+ case TPACPI_VIDEO_NEW:
+ res = video_autosw_set(1);
+ if (res)
+ return res;
+ res = acpi_evalf(vid_handle, NULL, "VSWT", "v");
+ break;
+ default:
+ return -ENOSYS;
+ }
+ if (!autosw && video_autosw_set(autosw)) {
+ pr_err("video auto-switch left enabled due to error\n");
+ return -EIO;
+ }
+
+ return (res)? 0 : -EIO;
+}
+
+static int video_expand_toggle(void)
+{
+ switch (video_supported) {
+ case TPACPI_VIDEO_570:
+ return acpi_evalf(ec_handle, NULL, "_Q17", "v")?
+ 0 : -EIO;
+ case TPACPI_VIDEO_770:
+ return acpi_evalf(vid_handle, NULL, "VEXP", "v")?
+ 0 : -EIO;
+ case TPACPI_VIDEO_NEW:
+ return acpi_evalf(NULL, NULL, "\\VEXP", "v")?
+ 0 : -EIO;
+ default:
+ return -ENOSYS;
+ }
+ /* not reached */
+}
+
+static int video_read(struct seq_file *m)
+{
+ int status, autosw;
+
+ if (video_supported == TPACPI_VIDEO_NONE) {
+ seq_printf(m, "status:\t\tnot supported\n");
+ return 0;
+ }
+
+ /* Even reads can crash X.org, so... */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ status = video_outputsw_get();
+ if (status < 0)
+ return status;
+
+ autosw = video_autosw_get();
+ if (autosw < 0)
+ return autosw;
+
+ seq_printf(m, "status:\t\tsupported\n");
+ seq_printf(m, "lcd:\t\t%s\n", enabled(status, 0));
+ seq_printf(m, "crt:\t\t%s\n", enabled(status, 1));
+ if (video_supported == TPACPI_VIDEO_NEW)
+ seq_printf(m, "dvi:\t\t%s\n", enabled(status, 3));
+ seq_printf(m, "auto:\t\t%s\n", enabled(autosw, 0));
+ seq_printf(m, "commands:\tlcd_enable, lcd_disable\n");
+ seq_printf(m, "commands:\tcrt_enable, crt_disable\n");
+ if (video_supported == TPACPI_VIDEO_NEW)
+ seq_printf(m, "commands:\tdvi_enable, dvi_disable\n");
+ seq_printf(m, "commands:\tauto_enable, auto_disable\n");
+ seq_printf(m, "commands:\tvideo_switch, expand_toggle\n");
+
+ return 0;
+}
+
+static int video_write(char *buf)
+{
+ char *cmd;
+ int enable, disable, status;
+ int res;
+
+ if (video_supported == TPACPI_VIDEO_NONE)
+ return -ENODEV;
+
+ /* Even reads can crash X.org, let alone writes... */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ enable = 0;
+ disable = 0;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (strlencmp(cmd, "lcd_enable") == 0) {
+ enable |= TP_ACPI_VIDEO_S_LCD;
+ } else if (strlencmp(cmd, "lcd_disable") == 0) {
+ disable |= TP_ACPI_VIDEO_S_LCD;
+ } else if (strlencmp(cmd, "crt_enable") == 0) {
+ enable |= TP_ACPI_VIDEO_S_CRT;
+ } else if (strlencmp(cmd, "crt_disable") == 0) {
+ disable |= TP_ACPI_VIDEO_S_CRT;
+ } else if (video_supported == TPACPI_VIDEO_NEW &&
+ strlencmp(cmd, "dvi_enable") == 0) {
+ enable |= TP_ACPI_VIDEO_S_DVI;
+ } else if (video_supported == TPACPI_VIDEO_NEW &&
+ strlencmp(cmd, "dvi_disable") == 0) {
+ disable |= TP_ACPI_VIDEO_S_DVI;
+ } else if (strlencmp(cmd, "auto_enable") == 0) {
+ res = video_autosw_set(1);
+ if (res)
+ return res;
+ } else if (strlencmp(cmd, "auto_disable") == 0) {
+ res = video_autosw_set(0);
+ if (res)
+ return res;
+ } else if (strlencmp(cmd, "video_switch") == 0) {
+ res = video_outputsw_cycle();
+ if (res)
+ return res;
+ } else if (strlencmp(cmd, "expand_toggle") == 0) {
+ res = video_expand_toggle();
+ if (res)
+ return res;
+ } else
+ return -EINVAL;
+ }
+
+ if (enable || disable) {
+ status = video_outputsw_get();
+ if (status < 0)
+ return status;
+ res = video_outputsw_set((status & ~disable) | enable);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static struct ibm_struct video_driver_data = {
+ .name = "video",
+ .read = video_read,
+ .write = video_write,
+ .exit = video_exit,
+};
+
+#endif /* CONFIG_THINKPAD_ACPI_VIDEO */
+
+/*************************************************************************
+ * Light (thinklight) subdriver
+ */
+
+TPACPI_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */
+TPACPI_HANDLE(ledb, ec, "LEDB"); /* G4x */
+
+static int light_get_status(void)
+{
+ int status = 0;
+
+ if (tp_features.light_status) {
+ if (!acpi_evalf(ec_handle, &status, "KBLT", "d"))
+ return -EIO;
+ return (!!status);
+ }
+
+ return -ENXIO;
+}
+
+static int light_set_status(int status)
+{
+ int rc;
+
+ if (tp_features.light) {
+ if (cmos_handle) {
+ rc = acpi_evalf(cmos_handle, NULL, NULL, "vd",
+ (status)?
+ TP_CMOS_THINKLIGHT_ON :
+ TP_CMOS_THINKLIGHT_OFF);
+ } else {
+ rc = acpi_evalf(lght_handle, NULL, NULL, "vd",
+ (status)? 1 : 0);
+ }
+ return (rc)? 0 : -EIO;
+ }
+
+ return -ENXIO;
+}
+
+static void light_set_status_worker(struct work_struct *work)
+{
+ struct tpacpi_led_classdev *data =
+ container_of(work, struct tpacpi_led_classdev, work);
+
+ if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
+ light_set_status((data->new_state != TPACPI_LED_OFF));
+}
+
+static void light_sysfs_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct tpacpi_led_classdev *data =
+ container_of(led_cdev,
+ struct tpacpi_led_classdev,
+ led_classdev);
+ data->new_state = (brightness != LED_OFF) ?
+ TPACPI_LED_ON : TPACPI_LED_OFF;
+ queue_work(tpacpi_wq, &data->work);
+}
+
+static enum led_brightness light_sysfs_get(struct led_classdev *led_cdev)
+{
+ return (light_get_status() == 1)? LED_FULL : LED_OFF;
+}
+
+static struct tpacpi_led_classdev tpacpi_led_thinklight = {
+ .led_classdev = {
+ .name = "tpacpi::thinklight",
+ .brightness_set = &light_sysfs_set,
+ .brightness_get = &light_sysfs_get,
+ }
+};
+
+static int __init light_init(struct ibm_init_struct *iibm)
+{
+ int rc;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n");
+
+ if (tpacpi_is_ibm()) {
+ TPACPI_ACPIHANDLE_INIT(ledb);
+ TPACPI_ACPIHANDLE_INIT(lght);
+ }
+ TPACPI_ACPIHANDLE_INIT(cmos);
+ INIT_WORK(&tpacpi_led_thinklight.work, light_set_status_worker);
+
+ /* light not supported on 570, 600e/x, 770e, 770x, G4x, R30, R31 */
+ tp_features.light = (cmos_handle || lght_handle) && !ledb_handle;
+
+ if (tp_features.light)
+ /* light status not supported on
+ 570, 600e/x, 770e, 770x, G4x, R30, R31, R32, X20 */
+ tp_features.light_status =
+ acpi_evalf(ec_handle, NULL, "KBLT", "qv");
+
+ vdbg_printk(TPACPI_DBG_INIT, "light is %s, light status is %s\n",
+ str_supported(tp_features.light),
+ str_supported(tp_features.light_status));
+
+ if (!tp_features.light)
+ return 1;
+
+ rc = led_classdev_register(&tpacpi_pdev->dev,
+ &tpacpi_led_thinklight.led_classdev);
+
+ if (rc < 0) {
+ tp_features.light = 0;
+ tp_features.light_status = 0;
+ } else {
+ rc = 0;
+ }
+
+ return rc;
+}
+
+static void light_exit(void)
+{
+ led_classdev_unregister(&tpacpi_led_thinklight.led_classdev);
+ flush_workqueue(tpacpi_wq);
+}
+
+static int light_read(struct seq_file *m)
+{
+ int status;
+
+ if (!tp_features.light) {
+ seq_printf(m, "status:\t\tnot supported\n");
+ } else if (!tp_features.light_status) {
+ seq_printf(m, "status:\t\tunknown\n");
+ seq_printf(m, "commands:\ton, off\n");
+ } else {
+ status = light_get_status();
+ if (status < 0)
+ return status;
+ seq_printf(m, "status:\t\t%s\n", onoff(status, 0));
+ seq_printf(m, "commands:\ton, off\n");
+ }
+
+ return 0;
+}
+
+static int light_write(char *buf)
+{
+ char *cmd;
+ int newstatus = 0;
+
+ if (!tp_features.light)
+ return -ENODEV;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (strlencmp(cmd, "on") == 0) {
+ newstatus = 1;
+ } else if (strlencmp(cmd, "off") == 0) {
+ newstatus = 0;
+ } else
+ return -EINVAL;
+ }
+
+ return light_set_status(newstatus);
+}
+
+static struct ibm_struct light_driver_data = {
+ .name = "light",
+ .read = light_read,
+ .write = light_write,
+ .exit = light_exit,
+};
+
+/*************************************************************************
+ * CMOS subdriver
+ */
+
+/* sysfs cmos_command -------------------------------------------------- */
+static ssize_t cmos_command_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long cmos_cmd;
+ int res;
+
+ if (parse_strtoul(buf, 21, &cmos_cmd))
+ return -EINVAL;
+
+ res = issue_thinkpad_cmos_command(cmos_cmd);
+ return (res)? res : count;
+}
+
+static struct device_attribute dev_attr_cmos_command =
+ __ATTR(cmos_command, S_IWUSR, NULL, cmos_command_store);
+
+/* --------------------------------------------------------------------- */
+
+static int __init cmos_init(struct ibm_init_struct *iibm)
+{
+ int res;
+
+ vdbg_printk(TPACPI_DBG_INIT,
+ "initializing cmos commands subdriver\n");
+
+ TPACPI_ACPIHANDLE_INIT(cmos);
+
+ vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n",
+ str_supported(cmos_handle != NULL));
+
+ res = device_create_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
+ if (res)
+ return res;
+
+ return (cmos_handle)? 0 : 1;
+}
+
+static void cmos_exit(void)
+{
+ device_remove_file(&tpacpi_pdev->dev, &dev_attr_cmos_command);
+}
+
+static int cmos_read(struct seq_file *m)
+{
+ /* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
+ R30, R31, T20-22, X20-21 */
+ if (!cmos_handle)
+ seq_printf(m, "status:\t\tnot supported\n");
+ else {
+ seq_printf(m, "status:\t\tsupported\n");
+ seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
+ }
+
+ return 0;
+}
+
+static int cmos_write(char *buf)
+{
+ char *cmd;
+ int cmos_cmd, res;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (sscanf(cmd, "%u", &cmos_cmd) == 1 &&
+ cmos_cmd >= 0 && cmos_cmd <= 21) {
+ /* cmos_cmd set */
+ } else
+ return -EINVAL;
+
+ res = issue_thinkpad_cmos_command(cmos_cmd);
+ if (res)
+ return res;
+ }
+
+ return 0;
+}
+
+static struct ibm_struct cmos_driver_data = {
+ .name = "cmos",
+ .read = cmos_read,
+ .write = cmos_write,
+ .exit = cmos_exit,
+};
+
+/*************************************************************************
+ * LED subdriver
+ */
+
+enum led_access_mode {
+ TPACPI_LED_NONE = 0,
+ TPACPI_LED_570, /* 570 */
+ TPACPI_LED_OLD, /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
+ TPACPI_LED_NEW, /* all others */
+};
+
+enum { /* For TPACPI_LED_OLD */
+ TPACPI_LED_EC_HLCL = 0x0c, /* EC reg to get led to power on */
+ TPACPI_LED_EC_HLBL = 0x0d, /* EC reg to blink a lit led */
+ TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */
+};
+
+static enum led_access_mode led_supported;
+
+static acpi_handle led_handle;
+
+#define TPACPI_LED_NUMLEDS 16
+static struct tpacpi_led_classdev *tpacpi_leds;
+static enum led_status_t tpacpi_led_state_cache[TPACPI_LED_NUMLEDS];
+static const char * const tpacpi_led_names[TPACPI_LED_NUMLEDS] = {
+ /* there's a limit of 19 chars + NULL before 2.6.26 */
+ "tpacpi::power",
+ "tpacpi:orange:batt",
+ "tpacpi:green:batt",
+ "tpacpi::dock_active",
+ "tpacpi::bay_active",
+ "tpacpi::dock_batt",
+ "tpacpi::unknown_led",
+ "tpacpi::standby",
+ "tpacpi::dock_status1",
+ "tpacpi::dock_status2",
+ "tpacpi::unknown_led2",
+ "tpacpi::unknown_led3",
+ "tpacpi::thinkvantage",
+};
+#define TPACPI_SAFE_LEDS 0x1081U
+
+static inline bool tpacpi_is_led_restricted(const unsigned int led)
+{
+#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
+ return false;
+#else
+ return (1U & (TPACPI_SAFE_LEDS >> led)) == 0;
+#endif
+}
+
+static int led_get_status(const unsigned int led)
+{
+ int status;
+ enum led_status_t led_s;
+
+ switch (led_supported) {
+ case TPACPI_LED_570:
+ if (!acpi_evalf(ec_handle,
+ &status, "GLED", "dd", 1 << led))
+ return -EIO;
+ led_s = (status == 0)?
+ TPACPI_LED_OFF :
+ ((status == 1)?
+ TPACPI_LED_ON :
+ TPACPI_LED_BLINK);
+ tpacpi_led_state_cache[led] = led_s;
+ return led_s;
+ default:
+ return -ENXIO;
+ }
+
+ /* not reached */
+}
+
+static int led_set_status(const unsigned int led,
+ const enum led_status_t ledstatus)
+{
+ /* off, on, blink. Index is led_status_t */
+ static const unsigned int led_sled_arg1[] = { 0, 1, 3 };
+ static const unsigned int led_led_arg1[] = { 0, 0x80, 0xc0 };
+
+ int rc = 0;
+
+ switch (led_supported) {
+ case TPACPI_LED_570:
+ /* 570 */
+ if (unlikely(led > 7))
+ return -EINVAL;
+ if (unlikely(tpacpi_is_led_restricted(led)))
+ return -EPERM;
+ if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
+ (1 << led), led_sled_arg1[ledstatus]))
+ rc = -EIO;
+ break;
+ case TPACPI_LED_OLD:
+ /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */
+ if (unlikely(led > 7))
+ return -EINVAL;
+ if (unlikely(tpacpi_is_led_restricted(led)))
+ return -EPERM;
+ rc = ec_write(TPACPI_LED_EC_HLMS, (1 << led));
+ if (rc >= 0)
+ rc = ec_write(TPACPI_LED_EC_HLBL,
+ (ledstatus == TPACPI_LED_BLINK) << led);
+ if (rc >= 0)
+ rc = ec_write(TPACPI_LED_EC_HLCL,
+ (ledstatus != TPACPI_LED_OFF) << led);
+ break;
+ case TPACPI_LED_NEW:
+ /* all others */
+ if (unlikely(led >= TPACPI_LED_NUMLEDS))
+ return -EINVAL;
+ if (unlikely(tpacpi_is_led_restricted(led)))
+ return -EPERM;
+ if (!acpi_evalf(led_handle, NULL, NULL, "vdd",
+ led, led_led_arg1[ledstatus]))
+ rc = -EIO;
+ break;
+ default:
+ rc = -ENXIO;
+ }
+
+ if (!rc)
+ tpacpi_led_state_cache[led] = ledstatus;
+
+ return rc;
+}
+
+static void led_set_status_worker(struct work_struct *work)
+{
+ struct tpacpi_led_classdev *data =
+ container_of(work, struct tpacpi_led_classdev, work);
+
+ if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
+ led_set_status(data->led, data->new_state);
+}
+
+static void led_sysfs_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct tpacpi_led_classdev *data = container_of(led_cdev,
+ struct tpacpi_led_classdev, led_classdev);
+
+ if (brightness == LED_OFF)
+ data->new_state = TPACPI_LED_OFF;
+ else if (tpacpi_led_state_cache[data->led] != TPACPI_LED_BLINK)
+ data->new_state = TPACPI_LED_ON;
+ else
+ data->new_state = TPACPI_LED_BLINK;
+
+ queue_work(tpacpi_wq, &data->work);
+}
+
+static int led_sysfs_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct tpacpi_led_classdev *data = container_of(led_cdev,
+ struct tpacpi_led_classdev, led_classdev);
+
+ /* Can we choose the flash rate? */
+ if (*delay_on == 0 && *delay_off == 0) {
+ /* yes. set them to the hardware blink rate (1 Hz) */
+ *delay_on = 500; /* ms */
+ *delay_off = 500; /* ms */
+ } else if ((*delay_on != 500) || (*delay_off != 500))
+ return -EINVAL;
+
+ data->new_state = TPACPI_LED_BLINK;
+ queue_work(tpacpi_wq, &data->work);
+
+ return 0;
+}
+
+static enum led_brightness led_sysfs_get(struct led_classdev *led_cdev)
+{
+ int rc;
+
+ struct tpacpi_led_classdev *data = container_of(led_cdev,
+ struct tpacpi_led_classdev, led_classdev);
+
+ rc = led_get_status(data->led);
+
+ if (rc == TPACPI_LED_OFF || rc < 0)
+ rc = LED_OFF; /* no error handling in led class :( */
+ else
+ rc = LED_FULL;
+
+ return rc;
+}
+
+static void led_exit(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
+ if (tpacpi_leds[i].led_classdev.name)
+ led_classdev_unregister(&tpacpi_leds[i].led_classdev);
+ }
+
+ flush_workqueue(tpacpi_wq);
+ kfree(tpacpi_leds);
+}
+
+static int __init tpacpi_init_led(unsigned int led)
+{
+ int rc;
+
+ tpacpi_leds[led].led = led;
+
+ /* LEDs with no name don't get registered */
+ if (!tpacpi_led_names[led])
+ return 0;
+
+ tpacpi_leds[led].led_classdev.brightness_set = &led_sysfs_set;
+ tpacpi_leds[led].led_classdev.blink_set = &led_sysfs_blink_set;
+ if (led_supported == TPACPI_LED_570)
+ tpacpi_leds[led].led_classdev.brightness_get =
+ &led_sysfs_get;
+
+ tpacpi_leds[led].led_classdev.name = tpacpi_led_names[led];
+
+ INIT_WORK(&tpacpi_leds[led].work, led_set_status_worker);
+
+ rc = led_classdev_register(&tpacpi_pdev->dev,
+ &tpacpi_leds[led].led_classdev);
+ if (rc < 0)
+ tpacpi_leds[led].led_classdev.name = NULL;
+
+ return rc;
+}
+
+static const struct tpacpi_quirk led_useful_qtable[] __initconst = {
+ TPACPI_Q_IBM('1', 'E', 0x009f), /* A30 */
+ TPACPI_Q_IBM('1', 'N', 0x009f), /* A31 */
+ TPACPI_Q_IBM('1', 'G', 0x009f), /* A31 */
+
+ TPACPI_Q_IBM('1', 'I', 0x0097), /* T30 */
+ TPACPI_Q_IBM('1', 'R', 0x0097), /* T40, T41, T42, R50, R51 */
+ TPACPI_Q_IBM('7', '0', 0x0097), /* T43, R52 */
+ TPACPI_Q_IBM('1', 'Y', 0x0097), /* T43 */
+ TPACPI_Q_IBM('1', 'W', 0x0097), /* R50e */
+ TPACPI_Q_IBM('1', 'V', 0x0097), /* R51 */
+ TPACPI_Q_IBM('7', '8', 0x0097), /* R51e */
+ TPACPI_Q_IBM('7', '6', 0x0097), /* R52 */
+
+ TPACPI_Q_IBM('1', 'K', 0x00bf), /* X30 */
+ TPACPI_Q_IBM('1', 'Q', 0x00bf), /* X31, X32 */
+ TPACPI_Q_IBM('1', 'U', 0x00bf), /* X40 */
+ TPACPI_Q_IBM('7', '4', 0x00bf), /* X41 */
+ TPACPI_Q_IBM('7', '5', 0x00bf), /* X41t */
+
+ TPACPI_Q_IBM('7', '9', 0x1f97), /* T60 (1) */
+ TPACPI_Q_IBM('7', '7', 0x1f97), /* Z60* (1) */
+ TPACPI_Q_IBM('7', 'F', 0x1f97), /* Z61* (1) */
+ TPACPI_Q_IBM('7', 'B', 0x1fb7), /* X60 (1) */
+
+ /* (1) - may have excess leds enabled on MSB */
+
+ /* Defaults (order matters, keep last, don't reorder!) */
+ { /* Lenovo */
+ .vendor = PCI_VENDOR_ID_LENOVO,
+ .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+ .quirks = 0x1fffU,
+ },
+ { /* IBM ThinkPads with no EC version string */
+ .vendor = PCI_VENDOR_ID_IBM,
+ .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_UNKNOWN,
+ .quirks = 0x00ffU,
+ },
+ { /* IBM ThinkPads with EC version string */
+ .vendor = PCI_VENDOR_ID_IBM,
+ .bios = TPACPI_MATCH_ANY, .ec = TPACPI_MATCH_ANY,
+ .quirks = 0x00bfU,
+ },
+};
+
+#undef TPACPI_LEDQ_IBM
+#undef TPACPI_LEDQ_LNV
+
+static enum led_access_mode __init led_init_detect_mode(void)
+{
+ acpi_status status;
+
+ if (tpacpi_is_ibm()) {
+ /* 570 */
+ status = acpi_get_handle(ec_handle, "SLED", &led_handle);
+ if (ACPI_SUCCESS(status))
+ return TPACPI_LED_570;
+
+ /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20-21 */
+ status = acpi_get_handle(ec_handle, "SYSL", &led_handle);
+ if (ACPI_SUCCESS(status))
+ return TPACPI_LED_OLD;
+ }
+
+ /* most others */
+ status = acpi_get_handle(ec_handle, "LED", &led_handle);
+ if (ACPI_SUCCESS(status))
+ return TPACPI_LED_NEW;
+
+ /* R30, R31, and unknown firmwares */
+ led_handle = NULL;
+ return TPACPI_LED_NONE;
+}
+
+static int __init led_init(struct ibm_init_struct *iibm)
+{
+ unsigned int i;
+ int rc;
+ unsigned long useful_leds;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n");
+
+ led_supported = led_init_detect_mode();
+
+ vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n",
+ str_supported(led_supported), led_supported);
+
+ if (led_supported == TPACPI_LED_NONE)
+ return 1;
+
+ tpacpi_leds = kzalloc(sizeof(*tpacpi_leds) * TPACPI_LED_NUMLEDS,
+ GFP_KERNEL);
+ if (!tpacpi_leds) {
+ pr_err("Out of memory for LED data\n");
+ return -ENOMEM;
+ }
+
+ useful_leds = tpacpi_check_quirks(led_useful_qtable,
+ ARRAY_SIZE(led_useful_qtable));
+
+ for (i = 0; i < TPACPI_LED_NUMLEDS; i++) {
+ if (!tpacpi_is_led_restricted(i) &&
+ test_bit(i, &useful_leds)) {
+ rc = tpacpi_init_led(i);
+ if (rc < 0) {
+ led_exit();
+ return rc;
+ }
+ }
+ }
+
+#ifdef CONFIG_THINKPAD_ACPI_UNSAFE_LEDS
+ pr_notice("warning: userspace override of important "
+ "firmware LEDs is enabled\n");
+#endif
+ return 0;
+}
+
+#define str_led_status(s) \
+ ((s) == TPACPI_LED_OFF ? "off" : \
+ ((s) == TPACPI_LED_ON ? "on" : "blinking"))
+
+static int led_read(struct seq_file *m)
+{
+ if (!led_supported) {
+ seq_printf(m, "status:\t\tnot supported\n");
+ return 0;
+ }
+ seq_printf(m, "status:\t\tsupported\n");
+
+ if (led_supported == TPACPI_LED_570) {
+ /* 570 */
+ int i, status;
+ for (i = 0; i < 8; i++) {
+ status = led_get_status(i);
+ if (status < 0)
+ return -EIO;
+ seq_printf(m, "%d:\t\t%s\n",
+ i, str_led_status(status));
+ }
+ }
+
+ seq_printf(m, "commands:\t"
+ "<led> on, <led> off, <led> blink (<led> is 0-15)\n");
+
+ return 0;
+}
+
+static int led_write(char *buf)
+{
+ char *cmd;
+ int led, rc;
+ enum led_status_t s;
+
+ if (!led_supported)
+ return -ENODEV;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (sscanf(cmd, "%d", &led) != 1 || led < 0 || led > 15)
+ return -EINVAL;
+
+ if (strstr(cmd, "off")) {
+ s = TPACPI_LED_OFF;
+ } else if (strstr(cmd, "on")) {
+ s = TPACPI_LED_ON;
+ } else if (strstr(cmd, "blink")) {
+ s = TPACPI_LED_BLINK;
+ } else {
+ return -EINVAL;
+ }
+
+ rc = led_set_status(led, s);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static struct ibm_struct led_driver_data = {
+ .name = "led",
+ .read = led_read,
+ .write = led_write,
+ .exit = led_exit,
+};
+
+/*************************************************************************
+ * Beep subdriver
+ */
+
+TPACPI_HANDLE(beep, ec, "BEEP"); /* all except R30, R31 */
+
+#define TPACPI_BEEP_Q1 0x0001
+
+static const struct tpacpi_quirk beep_quirk_table[] __initconst = {
+ TPACPI_Q_IBM('I', 'M', TPACPI_BEEP_Q1), /* 570 */
+ TPACPI_Q_IBM('I', 'U', TPACPI_BEEP_Q1), /* 570E - unverified */
+};
+
+static int __init beep_init(struct ibm_init_struct *iibm)
+{
+ unsigned long quirks;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing beep subdriver\n");
+
+ TPACPI_ACPIHANDLE_INIT(beep);
+
+ vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n",
+ str_supported(beep_handle != NULL));
+
+ quirks = tpacpi_check_quirks(beep_quirk_table,
+ ARRAY_SIZE(beep_quirk_table));
+
+ tp_features.beep_needs_two_args = !!(quirks & TPACPI_BEEP_Q1);
+
+ return (beep_handle)? 0 : 1;
+}
+
+static int beep_read(struct seq_file *m)
+{
+ if (!beep_handle)
+ seq_printf(m, "status:\t\tnot supported\n");
+ else {
+ seq_printf(m, "status:\t\tsupported\n");
+ seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
+ }
+
+ return 0;
+}
+
+static int beep_write(char *buf)
+{
+ char *cmd;
+ int beep_cmd;
+
+ if (!beep_handle)
+ return -ENODEV;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (sscanf(cmd, "%u", &beep_cmd) == 1 &&
+ beep_cmd >= 0 && beep_cmd <= 17) {
+ /* beep_cmd set */
+ } else
+ return -EINVAL;
+ if (tp_features.beep_needs_two_args) {
+ if (!acpi_evalf(beep_handle, NULL, NULL, "vdd",
+ beep_cmd, 0))
+ return -EIO;
+ } else {
+ if (!acpi_evalf(beep_handle, NULL, NULL, "vd",
+ beep_cmd))
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static struct ibm_struct beep_driver_data = {
+ .name = "beep",
+ .read = beep_read,
+ .write = beep_write,
+};
+
+/*************************************************************************
+ * Thermal subdriver
+ */
+
+enum thermal_access_mode {
+ TPACPI_THERMAL_NONE = 0, /* No thermal support */
+ TPACPI_THERMAL_ACPI_TMP07, /* Use ACPI TMP0-7 */
+ TPACPI_THERMAL_ACPI_UPDT, /* Use ACPI TMP0-7 with UPDT */
+ TPACPI_THERMAL_TPEC_8, /* Use ACPI EC regs, 8 sensors */
+ TPACPI_THERMAL_TPEC_16, /* Use ACPI EC regs, 16 sensors */
+};
+
+enum { /* TPACPI_THERMAL_TPEC_* */
+ TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */
+ TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */
+ TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */
+
+ TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */
+};
+
+
+#define TPACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */
+struct ibm_thermal_sensors_struct {
+ s32 temp[TPACPI_MAX_THERMAL_SENSORS];
+};
+
+static enum thermal_access_mode thermal_read_mode;
+
+/* idx is zero-based */
+static int thermal_get_sensor(int idx, s32 *value)
+{
+ int t;
+ s8 tmp;
+ char tmpi[5];
+
+ t = TP_EC_THERMAL_TMP0;
+
+ switch (thermal_read_mode) {
+#if TPACPI_MAX_THERMAL_SENSORS >= 16
+ case TPACPI_THERMAL_TPEC_16:
+ if (idx >= 8 && idx <= 15) {
+ t = TP_EC_THERMAL_TMP8;
+ idx -= 8;
+ }
+ /* fallthrough */
+#endif
+ case TPACPI_THERMAL_TPEC_8:
+ if (idx <= 7) {
+ if (!acpi_ec_read(t + idx, &tmp))
+ return -EIO;
+ *value = tmp * 1000;
+ return 0;
+ }
+ break;
+
+ case TPACPI_THERMAL_ACPI_UPDT:
+ if (idx <= 7) {
+ snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+ if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
+ return -EIO;
+ if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+ return -EIO;
+ *value = (t - 2732) * 100;
+ return 0;
+ }
+ break;
+
+ case TPACPI_THERMAL_ACPI_TMP07:
+ if (idx <= 7) {
+ snprintf(tmpi, sizeof(tmpi), "TMP%c", '0' + idx);
+ if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+ return -EIO;
+ if (t > 127 || t < -127)
+ t = TP_EC_THERMAL_TMP_NA;
+ *value = t * 1000;
+ return 0;
+ }
+ break;
+
+ case TPACPI_THERMAL_NONE:
+ default:
+ return -ENOSYS;
+ }
+
+ return -EINVAL;
+}
+
+static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
+{
+ int res, i;
+ int n;
+
+ n = 8;
+ i = 0;
+
+ if (!s)
+ return -EINVAL;
+
+ if (thermal_read_mode == TPACPI_THERMAL_TPEC_16)
+ n = 16;
+
+ for (i = 0 ; i < n; i++) {
+ res = thermal_get_sensor(i, &s->temp[i]);
+ if (res)
+ return res;
+ }
+
+ return n;
+}
+
+static void thermal_dump_all_sensors(void)
+{
+ int n, i;
+ struct ibm_thermal_sensors_struct t;
+
+ n = thermal_get_sensors(&t);
+ if (n <= 0)
+ return;
+
+ pr_notice("temperatures (Celsius):");
+
+ for (i = 0; i < n; i++) {
+ if (t.temp[i] != TPACPI_THERMAL_SENSOR_NA)
+ pr_cont(" %d", (int)(t.temp[i] / 1000));
+ else
+ pr_cont(" N/A");
+ }
+
+ pr_cont("\n");
+}
+
+/* sysfs temp##_input -------------------------------------------------- */
+
+static ssize_t thermal_temp_input_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute *sensor_attr =
+ to_sensor_dev_attr(attr);
+ int idx = sensor_attr->index;
+ s32 value;
+ int res;
+
+ res = thermal_get_sensor(idx, &value);
+ if (res)
+ return res;
+ if (value == TPACPI_THERMAL_SENSOR_NA)
+ return -ENXIO;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", value);
+}
+
+#define THERMAL_SENSOR_ATTR_TEMP(_idxA, _idxB) \
+ SENSOR_ATTR(temp##_idxA##_input, S_IRUGO, \
+ thermal_temp_input_show, NULL, _idxB)
+
+static struct sensor_device_attribute sensor_dev_attr_thermal_temp_input[] = {
+ THERMAL_SENSOR_ATTR_TEMP(1, 0),
+ THERMAL_SENSOR_ATTR_TEMP(2, 1),
+ THERMAL_SENSOR_ATTR_TEMP(3, 2),
+ THERMAL_SENSOR_ATTR_TEMP(4, 3),
+ THERMAL_SENSOR_ATTR_TEMP(5, 4),
+ THERMAL_SENSOR_ATTR_TEMP(6, 5),
+ THERMAL_SENSOR_ATTR_TEMP(7, 6),
+ THERMAL_SENSOR_ATTR_TEMP(8, 7),
+ THERMAL_SENSOR_ATTR_TEMP(9, 8),
+ THERMAL_SENSOR_ATTR_TEMP(10, 9),
+ THERMAL_SENSOR_ATTR_TEMP(11, 10),
+ THERMAL_SENSOR_ATTR_TEMP(12, 11),
+ THERMAL_SENSOR_ATTR_TEMP(13, 12),
+ THERMAL_SENSOR_ATTR_TEMP(14, 13),
+ THERMAL_SENSOR_ATTR_TEMP(15, 14),
+ THERMAL_SENSOR_ATTR_TEMP(16, 15),
+};
+
+#define THERMAL_ATTRS(X) \
+ &sensor_dev_attr_thermal_temp_input[X].dev_attr.attr
+
+static struct attribute *thermal_temp_input_attr[] = {
+ THERMAL_ATTRS(8),
+ THERMAL_ATTRS(9),
+ THERMAL_ATTRS(10),
+ THERMAL_ATTRS(11),
+ THERMAL_ATTRS(12),
+ THERMAL_ATTRS(13),
+ THERMAL_ATTRS(14),
+ THERMAL_ATTRS(15),
+ THERMAL_ATTRS(0),
+ THERMAL_ATTRS(1),
+ THERMAL_ATTRS(2),
+ THERMAL_ATTRS(3),
+ THERMAL_ATTRS(4),
+ THERMAL_ATTRS(5),
+ THERMAL_ATTRS(6),
+ THERMAL_ATTRS(7),
+ NULL
+};
+
+static const struct attribute_group thermal_temp_input16_group = {
+ .attrs = thermal_temp_input_attr
+};
+
+static const struct attribute_group thermal_temp_input8_group = {
+ .attrs = &thermal_temp_input_attr[8]
+};
+
+#undef THERMAL_SENSOR_ATTR_TEMP
+#undef THERMAL_ATTRS
+
+/* --------------------------------------------------------------------- */
+
+static int __init thermal_init(struct ibm_init_struct *iibm)
+{
+ u8 t, ta1, ta2;
+ int i;
+ int acpi_tmp7;
+ int res;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing thermal subdriver\n");
+
+ acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
+
+ if (thinkpad_id.ec_model) {
+ /*
+ * Direct EC access mode: sensors at registers
+ * 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for
+ * non-implemented, thermal sensors return 0x80 when
+ * not available
+ */
+
+ ta1 = ta2 = 0;
+ for (i = 0; i < 8; i++) {
+ if (acpi_ec_read(TP_EC_THERMAL_TMP0 + i, &t)) {
+ ta1 |= t;
+ } else {
+ ta1 = 0;
+ break;
+ }
+ if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
+ ta2 |= t;
+ } else {
+ ta1 = 0;
+ break;
+ }
+ }
+ if (ta1 == 0) {
+ /* This is sheer paranoia, but we handle it anyway */
+ if (acpi_tmp7) {
+ pr_err("ThinkPad ACPI EC access misbehaving, "
+ "falling back to ACPI TMPx access "
+ "mode\n");
+ thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
+ } else {
+ pr_err("ThinkPad ACPI EC access misbehaving, "
+ "disabling thermal sensors access\n");
+ thermal_read_mode = TPACPI_THERMAL_NONE;
+ }
+ } else {
+ thermal_read_mode =
+ (ta2 != 0) ?
+ TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
+ }
+ } else if (acpi_tmp7) {
+ if (tpacpi_is_ibm() &&
+ acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
+ /* 600e/x, 770e, 770x */
+ thermal_read_mode = TPACPI_THERMAL_ACPI_UPDT;
+ } else {
+ /* IBM/LENOVO DSDT EC.TMPx access, max 8 sensors */
+ thermal_read_mode = TPACPI_THERMAL_ACPI_TMP07;
+ }
+ } else {
+ /* temperatures not supported on 570, G4x, R30, R31, R32 */
+ thermal_read_mode = TPACPI_THERMAL_NONE;
+ }
+
+ vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n",
+ str_supported(thermal_read_mode != TPACPI_THERMAL_NONE),
+ thermal_read_mode);
+
+ switch (thermal_read_mode) {
+ case TPACPI_THERMAL_TPEC_16:
+ res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
+ &thermal_temp_input16_group);
+ if (res)
+ return res;
+ break;
+ case TPACPI_THERMAL_TPEC_8:
+ case TPACPI_THERMAL_ACPI_TMP07:
+ case TPACPI_THERMAL_ACPI_UPDT:
+ res = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
+ &thermal_temp_input8_group);
+ if (res)
+ return res;
+ break;
+ case TPACPI_THERMAL_NONE:
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static void thermal_exit(void)
+{
+ switch (thermal_read_mode) {
+ case TPACPI_THERMAL_TPEC_16:
+ sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
+ &thermal_temp_input16_group);
+ break;
+ case TPACPI_THERMAL_TPEC_8:
+ case TPACPI_THERMAL_ACPI_TMP07:
+ case TPACPI_THERMAL_ACPI_UPDT:
+ sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
+ &thermal_temp_input8_group);
+ break;
+ case TPACPI_THERMAL_NONE:
+ default:
+ break;
+ }
+}
+
+static int thermal_read(struct seq_file *m)
+{
+ int n, i;
+ struct ibm_thermal_sensors_struct t;
+
+ n = thermal_get_sensors(&t);
+ if (unlikely(n < 0))
+ return n;
+
+ seq_printf(m, "temperatures:\t");
+
+ if (n > 0) {
+ for (i = 0; i < (n - 1); i++)
+ seq_printf(m, "%d ", t.temp[i] / 1000);
+ seq_printf(m, "%d\n", t.temp[i] / 1000);
+ } else
+ seq_printf(m, "not supported\n");
+
+ return 0;
+}
+
+static struct ibm_struct thermal_driver_data = {
+ .name = "thermal",
+ .read = thermal_read,
+ .exit = thermal_exit,
+};
+
+/*************************************************************************
+ * Backlight/brightness subdriver
+ */
+
+#define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
+
+/*
+ * ThinkPads can read brightness from two places: EC HBRV (0x31), or
+ * CMOS NVRAM byte 0x5E, bits 0-3.
+ *
+ * EC HBRV (0x31) has the following layout
+ * Bit 7: unknown function
+ * Bit 6: unknown function
+ * Bit 5: Z: honour scale changes, NZ: ignore scale changes
+ * Bit 4: must be set to zero to avoid problems
+ * Bit 3-0: backlight brightness level
+ *
+ * brightness_get_raw returns status data in the HBRV layout
+ *
+ * WARNING: The X61 has been verified to use HBRV for something else, so
+ * this should be used _only_ on IBM ThinkPads, and maybe with some careful
+ * testing on the very early *60 Lenovo models...
+ */
+
+enum {
+ TP_EC_BACKLIGHT = 0x31,
+
+ /* TP_EC_BACKLIGHT bitmasks */
+ TP_EC_BACKLIGHT_LVLMSK = 0x1F,
+ TP_EC_BACKLIGHT_CMDMSK = 0xE0,
+ TP_EC_BACKLIGHT_MAPSW = 0x20,
+};
+
+enum tpacpi_brightness_access_mode {
+ TPACPI_BRGHT_MODE_AUTO = 0, /* Not implemented yet */
+ TPACPI_BRGHT_MODE_EC, /* EC control */
+ TPACPI_BRGHT_MODE_UCMS_STEP, /* UCMS step-based control */
+ TPACPI_BRGHT_MODE_ECNVRAM, /* EC control w/ NVRAM store */
+ TPACPI_BRGHT_MODE_MAX
+};
+
+static struct backlight_device *ibm_backlight_device;
+
+static enum tpacpi_brightness_access_mode brightness_mode =
+ TPACPI_BRGHT_MODE_MAX;
+
+static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
+
+static struct mutex brightness_mutex;
+
+/* NVRAM brightness access,
+ * call with brightness_mutex held! */
+static unsigned int tpacpi_brightness_nvram_get(void)
+{
+ u8 lnvram;
+
+ lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
+ & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+ >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+ lnvram &= bright_maxlvl;
+
+ return lnvram;
+}
+
+static void tpacpi_brightness_checkpoint_nvram(void)
+{
+ u8 lec = 0;
+ u8 b_nvram;
+
+ if (brightness_mode != TPACPI_BRGHT_MODE_ECNVRAM)
+ return;
+
+ vdbg_printk(TPACPI_DBG_BRGHT,
+ "trying to checkpoint backlight level to NVRAM...\n");
+
+ if (mutex_lock_killable(&brightness_mutex) < 0)
+ return;
+
+ if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+ goto unlock;
+ lec &= TP_EC_BACKLIGHT_LVLMSK;
+ b_nvram = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
+
+ if (lec != ((b_nvram & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+ >> TP_NVRAM_POS_LEVEL_BRIGHTNESS)) {
+ /* NVRAM needs update */
+ b_nvram &= ~(TP_NVRAM_MASK_LEVEL_BRIGHTNESS <<
+ TP_NVRAM_POS_LEVEL_BRIGHTNESS);
+ b_nvram |= lec;
+ nvram_write_byte(b_nvram, TP_NVRAM_ADDR_BRIGHTNESS);
+ dbg_printk(TPACPI_DBG_BRGHT,
+ "updated NVRAM backlight level to %u (0x%02x)\n",
+ (unsigned int) lec, (unsigned int) b_nvram);
+ } else
+ vdbg_printk(TPACPI_DBG_BRGHT,
+ "NVRAM backlight level already is %u (0x%02x)\n",
+ (unsigned int) lec, (unsigned int) b_nvram);
+
+unlock:
+ mutex_unlock(&brightness_mutex);
+}
+
+
+/* call with brightness_mutex held! */
+static int tpacpi_brightness_get_raw(int *status)
+{
+ u8 lec = 0;
+
+ switch (brightness_mode) {
+ case TPACPI_BRGHT_MODE_UCMS_STEP:
+ *status = tpacpi_brightness_nvram_get();
+ return 0;
+ case TPACPI_BRGHT_MODE_EC:
+ case TPACPI_BRGHT_MODE_ECNVRAM:
+ if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+ return -EIO;
+ *status = lec;
+ return 0;
+ default:
+ return -ENXIO;
+ }
+}
+
+/* call with brightness_mutex held! */
+/* do NOT call with illegal backlight level value */
+static int tpacpi_brightness_set_ec(unsigned int value)
+{
+ u8 lec = 0;
+
+ if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+ return -EIO;
+
+ if (unlikely(!acpi_ec_write(TP_EC_BACKLIGHT,
+ (lec & TP_EC_BACKLIGHT_CMDMSK) |
+ (value & TP_EC_BACKLIGHT_LVLMSK))))
+ return -EIO;
+
+ return 0;
+}
+
+/* call with brightness_mutex held! */
+static int tpacpi_brightness_set_ucmsstep(unsigned int value)
+{
+ int cmos_cmd, inc;
+ unsigned int current_value, i;
+
+ current_value = tpacpi_brightness_nvram_get();
+
+ if (value == current_value)
+ return 0;
+
+ cmos_cmd = (value > current_value) ?
+ TP_CMOS_BRIGHTNESS_UP :
+ TP_CMOS_BRIGHTNESS_DOWN;
+ inc = (value > current_value) ? 1 : -1;
+
+ for (i = current_value; i != value; i += inc)
+ if (issue_thinkpad_cmos_command(cmos_cmd))
+ return -EIO;
+
+ return 0;
+}
+
+/* May return EINTR which can always be mapped to ERESTARTSYS */
+static int brightness_set(unsigned int value)
+{
+ int res;
+
+ if (value > bright_maxlvl || value < 0)
+ return -EINVAL;
+
+ vdbg_printk(TPACPI_DBG_BRGHT,
+ "set backlight level to %d\n", value);
+
+ res = mutex_lock_killable(&brightness_mutex);
+ if (res < 0)
+ return res;
+
+ switch (brightness_mode) {
+ case TPACPI_BRGHT_MODE_EC:
+ case TPACPI_BRGHT_MODE_ECNVRAM:
+ res = tpacpi_brightness_set_ec(value);
+ break;
+ case TPACPI_BRGHT_MODE_UCMS_STEP:
+ res = tpacpi_brightness_set_ucmsstep(value);
+ break;
+ default:
+ res = -ENXIO;
+ }
+
+ mutex_unlock(&brightness_mutex);
+ return res;
+}
+
+/* sysfs backlight class ----------------------------------------------- */
+
+static int brightness_update_status(struct backlight_device *bd)
+{
+ unsigned int level =
+ (bd->props.fb_blank == FB_BLANK_UNBLANK &&
+ bd->props.power == FB_BLANK_UNBLANK) ?
+ bd->props.brightness : 0;
+
+ dbg_printk(TPACPI_DBG_BRGHT,
+ "backlight: attempt to set level to %d\n",
+ level);
+
+ /* it is the backlight class's job (caller) to handle
+ * EINTR and other errors properly */
+ return brightness_set(level);
+}
+
+static int brightness_get(struct backlight_device *bd)
+{
+ int status, res;
+
+ res = mutex_lock_killable(&brightness_mutex);
+ if (res < 0)
+ return 0;
+
+ res = tpacpi_brightness_get_raw(&status);
+
+ mutex_unlock(&brightness_mutex);
+
+ if (res < 0)
+ return 0;
+
+ return status & TP_EC_BACKLIGHT_LVLMSK;
+}
+
+static void tpacpi_brightness_notify_change(void)
+{
+ backlight_force_update(ibm_backlight_device,
+ BACKLIGHT_UPDATE_HOTKEY);
+}
+
+static const struct backlight_ops ibm_backlight_data = {
+ .get_brightness = brightness_get,
+ .update_status = brightness_update_status,
+};
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Call _BCL method of video device. On some ThinkPads this will
+ * switch the firmware to the ACPI brightness control mode.
+ */
+
+static int __init tpacpi_query_bcl_levels(acpi_handle handle)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ int rc;
+
+ if (ACPI_SUCCESS(acpi_evaluate_object(handle, "_BCL", NULL, &buffer))) {
+ obj = (union acpi_object *)buffer.pointer;
+ if (!obj || (obj->type != ACPI_TYPE_PACKAGE)) {
+ pr_err("Unknown _BCL data, please report this to %s\n",
+ TPACPI_MAIL);
+ rc = 0;
+ } else {
+ rc = obj->package.count;
+ }
+ } else {
+ return 0;
+ }
+
+ kfree(buffer.pointer);
+ return rc;
+}
+
+
+/*
+ * Returns 0 (no ACPI _BCL or _BCL invalid), or size of brightness map
+ */
+static unsigned int __init tpacpi_check_std_acpi_brightness_support(void)
+{
+ acpi_handle video_device;
+ int bcl_levels = 0;
+
+ tpacpi_acpi_handle_locate("video", ACPI_VIDEO_HID, &video_device);
+ if (video_device)
+ bcl_levels = tpacpi_query_bcl_levels(video_device);
+
+ tp_features.bright_acpimode = (bcl_levels > 0);
+
+ return (bcl_levels > 2) ? (bcl_levels - 2) : 0;
+}
+
+/*
+ * These are only useful for models that have only one possibility
+ * of GPU. If the BIOS model handles both ATI and Intel, don't use
+ * these quirks.
+ */
+#define TPACPI_BRGHT_Q_NOEC 0x0001 /* Must NOT use EC HBRV */
+#define TPACPI_BRGHT_Q_EC 0x0002 /* Should or must use EC HBRV */
+#define TPACPI_BRGHT_Q_ASK 0x8000 /* Ask for user report */
+
+static const struct tpacpi_quirk brightness_quirk_table[] __initconst = {
+ /* Models with ATI GPUs known to require ECNVRAM mode */
+ TPACPI_Q_IBM('1', 'Y', TPACPI_BRGHT_Q_EC), /* T43/p ATI */
+
+ /* Models with ATI GPUs that can use ECNVRAM */
+ TPACPI_Q_IBM('1', 'R', TPACPI_BRGHT_Q_EC), /* R50,51 T40-42 */
+ TPACPI_Q_IBM('1', 'Q', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+ TPACPI_Q_IBM('7', '6', TPACPI_BRGHT_Q_EC), /* R52 */
+ TPACPI_Q_IBM('7', '8', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+
+ /* Models with Intel Extreme Graphics 2 */
+ TPACPI_Q_IBM('1', 'U', TPACPI_BRGHT_Q_NOEC), /* X40 */
+ TPACPI_Q_IBM('1', 'V', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+ TPACPI_Q_IBM('1', 'W', TPACPI_BRGHT_Q_ASK|TPACPI_BRGHT_Q_EC),
+
+ /* Models with Intel GMA900 */
+ TPACPI_Q_IBM('7', '0', TPACPI_BRGHT_Q_NOEC), /* T43, R52 */
+ TPACPI_Q_IBM('7', '4', TPACPI_BRGHT_Q_NOEC), /* X41 */
+ TPACPI_Q_IBM('7', '5', TPACPI_BRGHT_Q_NOEC), /* X41 Tablet */
+};
+
+/*
+ * Returns < 0 for error, otherwise sets tp_features.bright_*
+ * and bright_maxlvl.
+ */
+static void __init tpacpi_detect_brightness_capabilities(void)
+{
+ unsigned int b;
+
+ vdbg_printk(TPACPI_DBG_INIT,
+ "detecting firmware brightness interface capabilities\n");
+
+ /* we could run a quirks check here (same table used by
+ * brightness_init) if needed */
+
+ /*
+ * We always attempt to detect acpi support, so as to switch
+ * Lenovo Vista BIOS to ACPI brightness mode even if we are not
+ * going to publish a backlight interface
+ */
+ b = tpacpi_check_std_acpi_brightness_support();
+ switch (b) {
+ case 16:
+ bright_maxlvl = 15;
+ pr_info("detected a 16-level brightness capable ThinkPad\n");
+ break;
+ case 8:
+ case 0:
+ bright_maxlvl = 7;
+ pr_info("detected a 8-level brightness capable ThinkPad\n");
+ break;
+ default:
+ pr_err("Unsupported brightness interface, "
+ "please contact %s\n", TPACPI_MAIL);
+ tp_features.bright_unkfw = 1;
+ bright_maxlvl = b - 1;
+ }
+}
+
+static int __init brightness_init(struct ibm_init_struct *iibm)
+{
+ struct backlight_properties props;
+ int b;
+ unsigned long quirks;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing brightness subdriver\n");
+
+ mutex_init(&brightness_mutex);
+
+ quirks = tpacpi_check_quirks(brightness_quirk_table,
+ ARRAY_SIZE(brightness_quirk_table));
+
+ /* tpacpi_detect_brightness_capabilities() must have run already */
+
+ /* if it is unknown, we don't handle it: it wouldn't be safe */
+ if (tp_features.bright_unkfw)
+ return 1;
+
+ if (!brightness_enable) {
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+ "brightness support disabled by "
+ "module parameter\n");
+ return 1;
+ }
+
+ if (acpi_video_backlight_support()) {
+ if (brightness_enable > 1) {
+ pr_info("Standard ACPI backlight interface "
+ "available, not loading native one\n");
+ return 1;
+ } else if (brightness_enable == 1) {
+ pr_warn("Cannot enable backlight brightness support, "
+ "ACPI is already handling it. Refer to the "
+ "acpi_backlight kernel parameter.\n");
+ return 1;
+ }
+ } else if (tp_features.bright_acpimode && brightness_enable > 1) {
+ pr_notice("Standard ACPI backlight interface not "
+ "available, thinkpad_acpi native "
+ "brightness control enabled\n");
+ }
+
+ /*
+ * Check for module parameter bogosity, note that we
+ * init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
+ * able to detect "unspecified"
+ */
+ if (brightness_mode > TPACPI_BRGHT_MODE_MAX)
+ return -EINVAL;
+
+ /* TPACPI_BRGHT_MODE_AUTO not implemented yet, just use default */
+ if (brightness_mode == TPACPI_BRGHT_MODE_AUTO ||
+ brightness_mode == TPACPI_BRGHT_MODE_MAX) {
+ if (quirks & TPACPI_BRGHT_Q_EC)
+ brightness_mode = TPACPI_BRGHT_MODE_ECNVRAM;
+ else
+ brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
+
+ dbg_printk(TPACPI_DBG_BRGHT,
+ "driver auto-selected brightness_mode=%d\n",
+ brightness_mode);
+ }
+
+ /* Safety */
+ if (!tpacpi_is_ibm() &&
+ (brightness_mode == TPACPI_BRGHT_MODE_ECNVRAM ||
+ brightness_mode == TPACPI_BRGHT_MODE_EC))
+ return -EINVAL;
+
+ if (tpacpi_brightness_get_raw(&b) < 0)
+ return 1;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = bright_maxlvl;
+ props.brightness = b & TP_EC_BACKLIGHT_LVLMSK;
+ ibm_backlight_device = backlight_device_register(TPACPI_BACKLIGHT_DEV_NAME,
+ NULL, NULL,
+ &ibm_backlight_data,
+ &props);
+ if (IS_ERR(ibm_backlight_device)) {
+ int rc = PTR_ERR(ibm_backlight_device);
+ ibm_backlight_device = NULL;
+ pr_err("Could not register backlight device\n");
+ return rc;
+ }
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+ "brightness is supported\n");
+
+ if (quirks & TPACPI_BRGHT_Q_ASK) {
+ pr_notice("brightness: will use unverified default: "
+ "brightness_mode=%d\n", brightness_mode);
+ pr_notice("brightness: please report to %s whether it works well "
+ "or not on your ThinkPad\n", TPACPI_MAIL);
+ }
+
+ /* Added by mistake in early 2007. Probably useless, but it could
+ * be working around some unknown firmware problem where the value
+ * read at startup doesn't match the real hardware state... so leave
+ * it in place just in case */
+ backlight_update_status(ibm_backlight_device);
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+ "brightness: registering brightness hotkeys "
+ "as change notification\n");
+ tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+ | TP_ACPI_HKEY_BRGHTUP_MASK
+ | TP_ACPI_HKEY_BRGHTDWN_MASK);
+ return 0;
+}
+
+static void brightness_suspend(void)
+{
+ tpacpi_brightness_checkpoint_nvram();
+}
+
+static void brightness_shutdown(void)
+{
+ tpacpi_brightness_checkpoint_nvram();
+}
+
+static void brightness_exit(void)
+{
+ if (ibm_backlight_device) {
+ vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_BRGHT,
+ "calling backlight_device_unregister()\n");
+ backlight_device_unregister(ibm_backlight_device);
+ }
+
+ tpacpi_brightness_checkpoint_nvram();
+}
+
+static int brightness_read(struct seq_file *m)
+{
+ int level;
+
+ level = brightness_get(NULL);
+ if (level < 0) {
+ seq_printf(m, "level:\t\tunreadable\n");
+ } else {
+ seq_printf(m, "level:\t\t%d\n", level);
+ seq_printf(m, "commands:\tup, down\n");
+ seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
+ bright_maxlvl);
+ }
+
+ return 0;
+}
+
+static int brightness_write(char *buf)
+{
+ int level;
+ int rc;
+ char *cmd;
+
+ level = brightness_get(NULL);
+ if (level < 0)
+ return level;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (strlencmp(cmd, "up") == 0) {
+ if (level < bright_maxlvl)
+ level++;
+ } else if (strlencmp(cmd, "down") == 0) {
+ if (level > 0)
+ level--;
+ } else if (sscanf(cmd, "level %d", &level) == 1 &&
+ level >= 0 && level <= bright_maxlvl) {
+ /* new level set */
+ } else
+ return -EINVAL;
+ }
+
+ tpacpi_disclose_usertask("procfs brightness",
+ "set level to %d\n", level);
+
+ /*
+ * Now we know what the final level should be, so we try to set it.
+ * Doing it this way makes the syscall restartable in case of EINTR
+ */
+ rc = brightness_set(level);
+ if (!rc && ibm_backlight_device)
+ backlight_force_update(ibm_backlight_device,
+ BACKLIGHT_UPDATE_SYSFS);
+ return (rc == -EINTR)? -ERESTARTSYS : rc;
+}
+
+static struct ibm_struct brightness_driver_data = {
+ .name = "brightness",
+ .read = brightness_read,
+ .write = brightness_write,
+ .exit = brightness_exit,
+ .suspend = brightness_suspend,
+ .shutdown = brightness_shutdown,
+};
+
+/*************************************************************************
+ * Volume subdriver
+ */
+
+/*
+ * IBM ThinkPads have a simple volume controller with MUTE gating.
+ * Very early Lenovo ThinkPads follow the IBM ThinkPad spec.
+ *
+ * Since the *61 series (and probably also the later *60 series), Lenovo
+ * ThinkPads only implement the MUTE gate.
+ *
+ * EC register 0x30
+ * Bit 6: MUTE (1 mutes sound)
+ * Bit 3-0: Volume
+ * Other bits should be zero as far as we know.
+ *
+ * This is also stored in CMOS NVRAM, byte 0x60, bit 6 (MUTE), and
+ * bits 3-0 (volume). Other bits in NVRAM may have other functions,
+ * such as bit 7 which is used to detect repeated presses of MUTE,
+ * and we leave them unchanged.
+ */
+
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
+
+#define TPACPI_ALSA_DRVNAME "ThinkPad EC"
+#define TPACPI_ALSA_SHRTNAME "ThinkPad Console Audio Control"
+#define TPACPI_ALSA_MIXERNAME TPACPI_ALSA_SHRTNAME
+
+static int alsa_index = ~((1 << (SNDRV_CARDS - 3)) - 1); /* last three slots */
+static char *alsa_id = "ThinkPadEC";
+static bool alsa_enable = SNDRV_DEFAULT_ENABLE1;
+
+struct tpacpi_alsa_data {
+ struct snd_card *card;
+ struct snd_ctl_elem_id *ctl_mute_id;
+ struct snd_ctl_elem_id *ctl_vol_id;
+};
+
+static struct snd_card *alsa_card;
+
+enum {
+ TP_EC_AUDIO = 0x30,
+
+ /* TP_EC_AUDIO bits */
+ TP_EC_AUDIO_MUTESW = 6,
+
+ /* TP_EC_AUDIO bitmasks */
+ TP_EC_AUDIO_LVL_MSK = 0x0F,
+ TP_EC_AUDIO_MUTESW_MSK = (1 << TP_EC_AUDIO_MUTESW),
+
+ /* Maximum volume */
+ TP_EC_VOLUME_MAX = 14,
+};
+
+enum tpacpi_volume_access_mode {
+ TPACPI_VOL_MODE_AUTO = 0, /* Not implemented yet */
+ TPACPI_VOL_MODE_EC, /* Pure EC control */
+ TPACPI_VOL_MODE_UCMS_STEP, /* UCMS step-based control: N/A */
+ TPACPI_VOL_MODE_ECNVRAM, /* EC control w/ NVRAM store */
+ TPACPI_VOL_MODE_MAX
+};
+
+enum tpacpi_volume_capabilities {
+ TPACPI_VOL_CAP_AUTO = 0, /* Use white/blacklist */
+ TPACPI_VOL_CAP_VOLMUTE, /* Output vol and mute */
+ TPACPI_VOL_CAP_MUTEONLY, /* Output mute only */
+ TPACPI_VOL_CAP_MAX
+};
+
+static enum tpacpi_volume_access_mode volume_mode =
+ TPACPI_VOL_MODE_MAX;
+
+static enum tpacpi_volume_capabilities volume_capabilities;
+static bool volume_control_allowed;
+
+/*
+ * Used to syncronize writers to TP_EC_AUDIO and
+ * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write
+ */
+static struct mutex volume_mutex;
+
+static void tpacpi_volume_checkpoint_nvram(void)
+{
+ u8 lec = 0;
+ u8 b_nvram;
+ u8 ec_mask;
+
+ if (volume_mode != TPACPI_VOL_MODE_ECNVRAM)
+ return;
+ if (!volume_control_allowed)
+ return;
+
+ vdbg_printk(TPACPI_DBG_MIXER,
+ "trying to checkpoint mixer state to NVRAM...\n");
+
+ if (tp_features.mixer_no_level_control)
+ ec_mask = TP_EC_AUDIO_MUTESW_MSK;
+ else
+ ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK;
+
+ if (mutex_lock_killable(&volume_mutex) < 0)
+ return;
+
+ if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec)))
+ goto unlock;
+ lec &= ec_mask;
+ b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER);
+
+ if (lec != (b_nvram & ec_mask)) {
+ /* NVRAM needs update */
+ b_nvram &= ~ec_mask;
+ b_nvram |= lec;
+ nvram_write_byte(b_nvram, TP_NVRAM_ADDR_MIXER);
+ dbg_printk(TPACPI_DBG_MIXER,
+ "updated NVRAM mixer status to 0x%02x (0x%02x)\n",
+ (unsigned int) lec, (unsigned int) b_nvram);
+ } else {
+ vdbg_printk(TPACPI_DBG_MIXER,
+ "NVRAM mixer status already is 0x%02x (0x%02x)\n",
+ (unsigned int) lec, (unsigned int) b_nvram);
+ }
+
+unlock:
+ mutex_unlock(&volume_mutex);
+}
+
+static int volume_get_status_ec(u8 *status)
+{
+ u8 s;
+
+ if (!acpi_ec_read(TP_EC_AUDIO, &s))
+ return -EIO;
+
+ *status = s;
+
+ dbg_printk(TPACPI_DBG_MIXER, "status 0x%02x\n", s);
+
+ return 0;
+}
+
+static int volume_get_status(u8 *status)
+{
+ return volume_get_status_ec(status);
+}
+
+static int volume_set_status_ec(const u8 status)
+{
+ if (!acpi_ec_write(TP_EC_AUDIO, status))
+ return -EIO;
+
+ dbg_printk(TPACPI_DBG_MIXER, "set EC mixer to 0x%02x\n", status);
+
+ return 0;
+}
+
+static int volume_set_status(const u8 status)
+{
+ return volume_set_status_ec(status);
+}
+
+/* returns < 0 on error, 0 on no change, 1 on change */
+static int __volume_set_mute_ec(const bool mute)
+{
+ int rc;
+ u8 s, n;
+
+ if (mutex_lock_killable(&volume_mutex) < 0)
+ return -EINTR;
+
+ rc = volume_get_status_ec(&s);
+ if (rc)
+ goto unlock;
+
+ n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK :
+ s & ~TP_EC_AUDIO_MUTESW_MSK;
+
+ if (n != s) {
+ rc = volume_set_status_ec(n);
+ if (!rc)
+ rc = 1;
+ }
+
+unlock:
+ mutex_unlock(&volume_mutex);
+ return rc;
+}
+
+static int volume_alsa_set_mute(const bool mute)
+{
+ dbg_printk(TPACPI_DBG_MIXER, "ALSA: trying to %smute\n",
+ (mute) ? "" : "un");
+ return __volume_set_mute_ec(mute);
+}
+
+static int volume_set_mute(const bool mute)
+{
+ int rc;
+
+ dbg_printk(TPACPI_DBG_MIXER, "trying to %smute\n",
+ (mute) ? "" : "un");
+
+ rc = __volume_set_mute_ec(mute);
+ return (rc < 0) ? rc : 0;
+}
+
+/* returns < 0 on error, 0 on no change, 1 on change */
+static int __volume_set_volume_ec(const u8 vol)
+{
+ int rc;
+ u8 s, n;
+
+ if (vol > TP_EC_VOLUME_MAX)
+ return -EINVAL;
+
+ if (mutex_lock_killable(&volume_mutex) < 0)
+ return -EINTR;
+
+ rc = volume_get_status_ec(&s);
+ if (rc)
+ goto unlock;
+
+ n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol;
+
+ if (n != s) {
+ rc = volume_set_status_ec(n);
+ if (!rc)
+ rc = 1;
+ }
+
+unlock:
+ mutex_unlock(&volume_mutex);
+ return rc;
+}
+
+static int volume_alsa_set_volume(const u8 vol)
+{
+ dbg_printk(TPACPI_DBG_MIXER,
+ "ALSA: trying to set volume level to %hu\n", vol);
+ return __volume_set_volume_ec(vol);
+}
+
+static void volume_alsa_notify_change(void)
+{
+ struct tpacpi_alsa_data *d;
+
+ if (alsa_card && alsa_card->private_data) {
+ d = alsa_card->private_data;
+ if (d->ctl_mute_id)
+ snd_ctl_notify(alsa_card,
+ SNDRV_CTL_EVENT_MASK_VALUE,
+ d->ctl_mute_id);
+ if (d->ctl_vol_id)
+ snd_ctl_notify(alsa_card,
+ SNDRV_CTL_EVENT_MASK_VALUE,
+ d->ctl_vol_id);
+ }
+}
+
+static int volume_alsa_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = TP_EC_VOLUME_MAX;
+ return 0;
+}
+
+static int volume_alsa_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 s;
+ int rc;
+
+ rc = volume_get_status(&s);
+ if (rc < 0)
+ return rc;
+
+ ucontrol->value.integer.value[0] = s & TP_EC_AUDIO_LVL_MSK;
+ return 0;
+}
+
+static int volume_alsa_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ tpacpi_disclose_usertask("ALSA", "set volume to %ld\n",
+ ucontrol->value.integer.value[0]);
+ return volume_alsa_set_volume(ucontrol->value.integer.value[0]);
+}
+
+#define volume_alsa_mute_info snd_ctl_boolean_mono_info
+
+static int volume_alsa_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 s;
+ int rc;
+
+ rc = volume_get_status(&s);
+ if (rc < 0)
+ return rc;
+
+ ucontrol->value.integer.value[0] =
+ (s & TP_EC_AUDIO_MUTESW_MSK) ? 0 : 1;
+ return 0;
+}
+
+static int volume_alsa_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ tpacpi_disclose_usertask("ALSA", "%smute\n",
+ ucontrol->value.integer.value[0] ?
+ "un" : "");
+ return volume_alsa_set_mute(!ucontrol->value.integer.value[0]);
+}
+
+static struct snd_kcontrol_new volume_alsa_control_vol = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Console Playback Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = volume_alsa_vol_info,
+ .get = volume_alsa_vol_get,
+};
+
+static struct snd_kcontrol_new volume_alsa_control_mute = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Console Playback Switch",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = volume_alsa_mute_info,
+ .get = volume_alsa_mute_get,
+};
+
+static void volume_suspend(void)
+{
+ tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_resume(void)
+{
+ volume_alsa_notify_change();
+}
+
+static void volume_shutdown(void)
+{
+ tpacpi_volume_checkpoint_nvram();
+}
+
+static void volume_exit(void)
+{
+ if (alsa_card) {
+ snd_card_free(alsa_card);
+ alsa_card = NULL;
+ }
+
+ tpacpi_volume_checkpoint_nvram();
+}
+
+static int __init volume_create_alsa_mixer(void)
+{
+ struct snd_card *card;
+ struct tpacpi_alsa_data *data;
+ struct snd_kcontrol *ctl_vol;
+ struct snd_kcontrol *ctl_mute;
+ int rc;
+
+ rc = snd_card_create(alsa_index, alsa_id, THIS_MODULE,
+ sizeof(struct tpacpi_alsa_data), &card);
+ if (rc < 0 || !card) {
+ pr_err("Failed to create ALSA card structures: %d\n", rc);
+ return 1;
+ }
+
+ BUG_ON(!card->private_data);
+ data = card->private_data;
+ data->card = card;
+
+ strlcpy(card->driver, TPACPI_ALSA_DRVNAME,
+ sizeof(card->driver));
+ strlcpy(card->shortname, TPACPI_ALSA_SHRTNAME,
+ sizeof(card->shortname));
+ snprintf(card->mixername, sizeof(card->mixername), "ThinkPad EC %s",
+ (thinkpad_id.ec_version_str) ?
+ thinkpad_id.ec_version_str : "(unknown)");
+ snprintf(card->longname, sizeof(card->longname),
+ "%s at EC reg 0x%02x, fw %s", card->shortname, TP_EC_AUDIO,
+ (thinkpad_id.ec_version_str) ?
+ thinkpad_id.ec_version_str : "unknown");
+
+ if (volume_control_allowed) {
+ volume_alsa_control_vol.put = volume_alsa_vol_put;
+ volume_alsa_control_vol.access =
+ SNDRV_CTL_ELEM_ACCESS_READWRITE;
+
+ volume_alsa_control_mute.put = volume_alsa_mute_put;
+ volume_alsa_control_mute.access =
+ SNDRV_CTL_ELEM_ACCESS_READWRITE;
+ }
+
+ if (!tp_features.mixer_no_level_control) {
+ ctl_vol = snd_ctl_new1(&volume_alsa_control_vol, NULL);
+ rc = snd_ctl_add(card, ctl_vol);
+ if (rc < 0) {
+ pr_err("Failed to create ALSA volume control: %d\n",
+ rc);
+ goto err_exit;
+ }
+ data->ctl_vol_id = &ctl_vol->id;
+ }
+
+ ctl_mute = snd_ctl_new1(&volume_alsa_control_mute, NULL);
+ rc = snd_ctl_add(card, ctl_mute);
+ if (rc < 0) {
+ pr_err("Failed to create ALSA mute control: %d\n", rc);
+ goto err_exit;
+ }
+ data->ctl_mute_id = &ctl_mute->id;
+
+ snd_card_set_dev(card, &tpacpi_pdev->dev);
+ rc = snd_card_register(card);
+ if (rc < 0) {
+ pr_err("Failed to register ALSA card: %d\n", rc);
+ goto err_exit;
+ }
+
+ alsa_card = card;
+ return 0;
+
+err_exit:
+ snd_card_free(card);
+ return 1;
+}
+
+#define TPACPI_VOL_Q_MUTEONLY 0x0001 /* Mute-only control available */
+#define TPACPI_VOL_Q_LEVEL 0x0002 /* Volume control available */
+
+static const struct tpacpi_quirk volume_quirk_table[] __initconst = {
+ /* Whitelist volume level on all IBM by default */
+ { .vendor = PCI_VENDOR_ID_IBM,
+ .bios = TPACPI_MATCH_ANY,
+ .ec = TPACPI_MATCH_ANY,
+ .quirks = TPACPI_VOL_Q_LEVEL },
+
+ /* Lenovo models with volume control (needs confirmation) */
+ TPACPI_QEC_LNV('7', 'C', TPACPI_VOL_Q_LEVEL), /* R60/i */
+ TPACPI_QEC_LNV('7', 'E', TPACPI_VOL_Q_LEVEL), /* R60e/i */
+ TPACPI_QEC_LNV('7', '9', TPACPI_VOL_Q_LEVEL), /* T60/p */
+ TPACPI_QEC_LNV('7', 'B', TPACPI_VOL_Q_LEVEL), /* X60/s */
+ TPACPI_QEC_LNV('7', 'J', TPACPI_VOL_Q_LEVEL), /* X60t */
+ TPACPI_QEC_LNV('7', '7', TPACPI_VOL_Q_LEVEL), /* Z60 */
+ TPACPI_QEC_LNV('7', 'F', TPACPI_VOL_Q_LEVEL), /* Z61 */
+
+ /* Whitelist mute-only on all Lenovo by default */
+ { .vendor = PCI_VENDOR_ID_LENOVO,
+ .bios = TPACPI_MATCH_ANY,
+ .ec = TPACPI_MATCH_ANY,
+ .quirks = TPACPI_VOL_Q_MUTEONLY }
+};
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+ unsigned long quirks;
+ int rc;
+
+ vdbg_printk(TPACPI_DBG_INIT, "initializing volume subdriver\n");
+
+ mutex_init(&volume_mutex);
+
+ /*
+ * Check for module parameter bogosity, note that we
+ * init volume_mode to TPACPI_VOL_MODE_MAX in order to be
+ * able to detect "unspecified"
+ */
+ if (volume_mode > TPACPI_VOL_MODE_MAX)
+ return -EINVAL;
+
+ if (volume_mode == TPACPI_VOL_MODE_UCMS_STEP) {
+ pr_err("UCMS step volume mode not implemented, "
+ "please contact %s\n", TPACPI_MAIL);
+ return 1;
+ }
+
+ if (volume_capabilities >= TPACPI_VOL_CAP_MAX)
+ return -EINVAL;
+
+ /*
+ * The ALSA mixer is our primary interface.
+ * When disabled, don't install the subdriver at all
+ */
+ if (!alsa_enable) {
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "ALSA mixer disabled by parameter, "
+ "not loading volume subdriver...\n");
+ return 1;
+ }
+
+ quirks = tpacpi_check_quirks(volume_quirk_table,
+ ARRAY_SIZE(volume_quirk_table));
+
+ switch (volume_capabilities) {
+ case TPACPI_VOL_CAP_AUTO:
+ if (quirks & TPACPI_VOL_Q_MUTEONLY)
+ tp_features.mixer_no_level_control = 1;
+ else if (quirks & TPACPI_VOL_Q_LEVEL)
+ tp_features.mixer_no_level_control = 0;
+ else
+ return 1; /* no mixer */
+ break;
+ case TPACPI_VOL_CAP_VOLMUTE:
+ tp_features.mixer_no_level_control = 0;
+ break;
+ case TPACPI_VOL_CAP_MUTEONLY:
+ tp_features.mixer_no_level_control = 1;
+ break;
+ default:
+ return 1;
+ }
+
+ if (volume_capabilities != TPACPI_VOL_CAP_AUTO)
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "using user-supplied volume_capabilities=%d\n",
+ volume_capabilities);
+
+ if (volume_mode == TPACPI_VOL_MODE_AUTO ||
+ volume_mode == TPACPI_VOL_MODE_MAX) {
+ volume_mode = TPACPI_VOL_MODE_ECNVRAM;
+
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "driver auto-selected volume_mode=%d\n",
+ volume_mode);
+ } else {
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "using user-supplied volume_mode=%d\n",
+ volume_mode);
+ }
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "mute is supported, volume control is %s\n",
+ str_supported(!tp_features.mixer_no_level_control));
+
+ rc = volume_create_alsa_mixer();
+ if (rc) {
+ pr_err("Could not create the ALSA mixer interface\n");
+ return rc;
+ }
+
+ pr_info("Console audio control enabled, mode: %s\n",
+ (volume_control_allowed) ?
+ "override (read/write)" :
+ "monitor (read only)");
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER,
+ "registering volume hotkeys as change notification\n");
+ tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+ | TP_ACPI_HKEY_VOLUP_MASK
+ | TP_ACPI_HKEY_VOLDWN_MASK
+ | TP_ACPI_HKEY_MUTE_MASK);
+
+ return 0;
+}
+
+static int volume_read(struct seq_file *m)
+{
+ u8 status;
+
+ if (volume_get_status(&status) < 0) {
+ seq_printf(m, "level:\t\tunreadable\n");
+ } else {
+ if (tp_features.mixer_no_level_control)
+ seq_printf(m, "level:\t\tunsupported\n");
+ else
+ seq_printf(m, "level:\t\t%d\n",
+ status & TP_EC_AUDIO_LVL_MSK);
+
+ seq_printf(m, "mute:\t\t%s\n",
+ onoff(status, TP_EC_AUDIO_MUTESW));
+
+ if (volume_control_allowed) {
+ seq_printf(m, "commands:\tunmute, mute\n");
+ if (!tp_features.mixer_no_level_control) {
+ seq_printf(m,
+ "commands:\tup, down\n");
+ seq_printf(m,
+ "commands:\tlevel <level>"
+ " (<level> is 0-%d)\n",
+ TP_EC_VOLUME_MAX);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int volume_write(char *buf)
+{
+ u8 s;
+ u8 new_level, new_mute;
+ int l;
+ char *cmd;
+ int rc;
+
+ /*
+ * We do allow volume control at driver startup, so that the
+ * user can set initial state through the volume=... parameter hack.
+ */
+ if (!volume_control_allowed && tpacpi_lifecycle != TPACPI_LIFE_INIT) {
+ if (unlikely(!tp_warned.volume_ctrl_forbidden)) {
+ tp_warned.volume_ctrl_forbidden = 1;
+ pr_notice("Console audio control in monitor mode, "
+ "changes are not allowed\n");
+ pr_notice("Use the volume_control=1 module parameter "
+ "to enable volume control\n");
+ }
+ return -EPERM;
+ }
+
+ rc = volume_get_status(&s);
+ if (rc < 0)
+ return rc;
+
+ new_level = s & TP_EC_AUDIO_LVL_MSK;
+ new_mute = s & TP_EC_AUDIO_MUTESW_MSK;
+
+ while ((cmd = next_cmd(&buf))) {
+ if (!tp_features.mixer_no_level_control) {
+ if (strlencmp(cmd, "up") == 0) {
+ if (new_mute)
+ new_mute = 0;
+ else if (new_level < TP_EC_VOLUME_MAX)
+ new_level++;
+ continue;
+ } else if (strlencmp(cmd, "down") == 0) {
+ if (new_mute)
+ new_mute = 0;
+ else if (new_level > 0)
+ new_level--;
+ continue;
+ } else if (sscanf(cmd, "level %u", &l) == 1 &&
+ l >= 0 && l <= TP_EC_VOLUME_MAX) {
+ new_level = l;
+ continue;
+ }
+ }
+ if (strlencmp(cmd, "mute") == 0)
+ new_mute = TP_EC_AUDIO_MUTESW_MSK;
+ else if (strlencmp(cmd, "unmute") == 0)
+ new_mute = 0;
+ else
+ return -EINVAL;
+ }
+
+ if (tp_features.mixer_no_level_control) {
+ tpacpi_disclose_usertask("procfs volume", "%smute\n",
+ new_mute ? "" : "un");
+ rc = volume_set_mute(!!new_mute);
+ } else {
+ tpacpi_disclose_usertask("procfs volume",
+ "%smute and set level to %d\n",
+ new_mute ? "" : "un", new_level);
+ rc = volume_set_status(new_mute | new_level);
+ }
+ volume_alsa_notify_change();
+
+ return (rc == -EINTR) ? -ERESTARTSYS : rc;
+}
+
+static struct ibm_struct volume_driver_data = {
+ .name = "volume",
+ .read = volume_read,
+ .write = volume_write,
+ .exit = volume_exit,
+ .suspend = volume_suspend,
+ .resume = volume_resume,
+ .shutdown = volume_shutdown,
+};
+
+#else /* !CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
+#define alsa_card NULL
+
+static void inline volume_alsa_notify_change(void)
+{
+}
+
+static int __init volume_init(struct ibm_init_struct *iibm)
+{
+ pr_info("volume: disabled as there is no ALSA support in this kernel\n");
+
+ return 1;
+}
+
+static struct ibm_struct volume_driver_data = {
+ .name = "volume",
+};
+
+#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
+/*************************************************************************
+ * Fan subdriver
+ */
+
+/*
+ * FAN ACCESS MODES
+ *
+ * TPACPI_FAN_RD_ACPI_GFAN:
+ * ACPI GFAN method: returns fan level
+ *
+ * see TPACPI_FAN_WR_ACPI_SFAN
+ * EC 0x2f (HFSP) not available if GFAN exists
+ *
+ * TPACPI_FAN_WR_ACPI_SFAN:
+ * ACPI SFAN method: sets fan level, 0 (stop) to 7 (max)
+ *
+ * EC 0x2f (HFSP) might be available *for reading*, but do not use
+ * it for writing.
+ *
+ * TPACPI_FAN_WR_TPEC:
+ * ThinkPad EC register 0x2f (HFSP): fan control loop mode
+ * Supported on almost all ThinkPads
+ *
+ * Fan speed changes of any sort (including those caused by the
+ * disengaged mode) are usually done slowly by the firmware as the
+ * maximum amount of fan duty cycle change per second seems to be
+ * limited.
+ *
+ * Reading is not available if GFAN exists.
+ * Writing is not available if SFAN exists.
+ *
+ * Bits
+ * 7 automatic mode engaged;
+ * (default operation mode of the ThinkPad)
+ * fan level is ignored in this mode.
+ * 6 full speed mode (takes precedence over bit 7);
+ * not available on all thinkpads. May disable
+ * the tachometer while the fan controller ramps up
+ * the speed (which can take up to a few *minutes*).
+ * Speeds up fan to 100% duty-cycle, which is far above
+ * the standard RPM levels. It is not impossible that
+ * it could cause hardware damage.
+ * 5-3 unused in some models. Extra bits for fan level
+ * in others, but still useless as all values above
+ * 7 map to the same speed as level 7 in these models.
+ * 2-0 fan level (0..7 usually)
+ * 0x00 = stop
+ * 0x07 = max (set when temperatures critical)
+ * Some ThinkPads may have other levels, see
+ * TPACPI_FAN_WR_ACPI_FANS (X31/X40/X41)
+ *
+ * FIRMWARE BUG: on some models, EC 0x2f might not be initialized at
+ * boot. Apparently the EC does not initialize it, so unless ACPI DSDT
+ * does so, its initial value is meaningless (0x07).
+ *
+ * For firmware bugs, refer to:
+ * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
+ *
+ * ----
+ *
+ * ThinkPad EC register 0x84 (LSB), 0x85 (MSB):
+ * Main fan tachometer reading (in RPM)
+ *
+ * This register is present on all ThinkPads with a new-style EC, and
+ * it is known not to be present on the A21m/e, and T22, as there is
+ * something else in offset 0x84 according to the ACPI DSDT. Other
+ * ThinkPads from this same time period (and earlier) probably lack the
+ * tachometer as well.
+ *
+ * Unfortunately a lot of ThinkPads with new-style ECs but whose firmware
+ * was never fixed by IBM to report the EC firmware version string
+ * probably support the tachometer (like the early X models), so
+ * detecting it is quite hard. We need more data to know for sure.
+ *
+ * FIRMWARE BUG: always read 0x84 first, otherwise incorrect readings
+ * might result.
+ *
+ * FIRMWARE BUG: may go stale while the EC is switching to full speed
+ * mode.
+ *
+ * For firmware bugs, refer to:
+ * http://thinkwiki.org/wiki/Embedded_Controller_Firmware#Firmware_Issues
+ *
+ * ----
+ *
+ * ThinkPad EC register 0x31 bit 0 (only on select models)
+ *
+ * When bit 0 of EC register 0x31 is zero, the tachometer registers
+ * show the speed of the main fan. When bit 0 of EC register 0x31
+ * is one, the tachometer registers show the speed of the auxiliary
+ * fan.
+ *
+ * Fan control seems to affect both fans, regardless of the state
+ * of this bit.
+ *
+ * So far, only the firmware for the X60/X61 non-tablet versions
+ * seem to support this (firmware TP-7M).
+ *
+ * TPACPI_FAN_WR_ACPI_FANS:
+ * ThinkPad X31, X40, X41. Not available in the X60.
+ *
+ * FANS ACPI handle: takes three arguments: low speed, medium speed,
+ * high speed. ACPI DSDT seems to map these three speeds to levels
+ * as follows: STOP LOW LOW MED MED HIGH HIGH HIGH HIGH
+ * (this map is stored on FAN0..FAN8 as "0,1,1,2,2,3,3,3,3")
+ *
+ * The speeds are stored on handles
+ * (FANA:FAN9), (FANC:FANB), (FANE:FAND).
+ *
+ * There are three default speed sets, accessible as handles:
+ * FS1L,FS1M,FS1H; FS2L,FS2M,FS2H; FS3L,FS3M,FS3H
+ *
+ * ACPI DSDT switches which set is in use depending on various
+ * factors.
+ *
+ * TPACPI_FAN_WR_TPEC is also available and should be used to
+ * command the fan. The X31/X40/X41 seems to have 8 fan levels,
+ * but the ACPI tables just mention level 7.
+ */
+
+enum { /* Fan control constants */
+ fan_status_offset = 0x2f, /* EC register 0x2f */
+ fan_rpm_offset = 0x84, /* EC register 0x84: LSB, 0x85 MSB (RPM)
+ * 0x84 must be read before 0x85 */
+ fan_select_offset = 0x31, /* EC register 0x31 (Firmware 7M)
+ bit 0 selects which fan is active */
+
+ TP_EC_FAN_FULLSPEED = 0x40, /* EC fan mode: full speed */
+ TP_EC_FAN_AUTO = 0x80, /* EC fan mode: auto fan control */
+
+ TPACPI_FAN_LAST_LEVEL = 0x100, /* Use cached last-seen fan level */
+};
+
+enum fan_status_access_mode {
+ TPACPI_FAN_NONE = 0, /* No fan status or control */
+ TPACPI_FAN_RD_ACPI_GFAN, /* Use ACPI GFAN */
+ TPACPI_FAN_RD_TPEC, /* Use ACPI EC regs 0x2f, 0x84-0x85 */
+};
+
+enum fan_control_access_mode {
+ TPACPI_FAN_WR_NONE = 0, /* No fan control */
+ TPACPI_FAN_WR_ACPI_SFAN, /* Use ACPI SFAN */
+ TPACPI_FAN_WR_TPEC, /* Use ACPI EC reg 0x2f */
+ TPACPI_FAN_WR_ACPI_FANS, /* Use ACPI FANS and EC reg 0x2f */
+};
+
+enum fan_control_commands {
+ TPACPI_FAN_CMD_SPEED = 0x0001, /* speed command */
+ TPACPI_FAN_CMD_LEVEL = 0x0002, /* level command */
+ TPACPI_FAN_CMD_ENABLE = 0x0004, /* enable/disable cmd,
+ * and also watchdog cmd */
+};
+
+static bool fan_control_allowed;
+
+static enum fan_status_access_mode fan_status_access_mode;
+static enum fan_control_access_mode fan_control_access_mode;
+static enum fan_control_commands fan_control_commands;
+
+static u8 fan_control_initial_status;
+static u8 fan_control_desired_level;
+static u8 fan_control_resume_level;
+static int fan_watchdog_maxinterval;
+
+static struct mutex fan_mutex;
+
+static void fan_watchdog_fire(struct work_struct *ignored);
+static DECLARE_DELAYED_WORK(fan_watchdog_task, fan_watchdog_fire);
+
+TPACPI_HANDLE(fans, ec, "FANS"); /* X31, X40, X41 */
+TPACPI_HANDLE(gfan, ec, "GFAN", /* 570 */
+ "\\FSPD", /* 600e/x, 770e, 770x */
+ ); /* all others */
+TPACPI_HANDLE(sfan, ec, "SFAN", /* 570 */
+ "JFNS", /* 770x-JL */
+ ); /* all others */
+
+/*
+ * Unitialized HFSP quirk: ACPI DSDT and EC fail to initialize the
+ * HFSP register at boot, so it contains 0x07 but the Thinkpad could
+ * be in auto mode (0x80).
+ *
+ * This is corrected by any write to HFSP either by the driver, or
+ * by the firmware.
+ *
+ * We assume 0x07 really means auto mode while this quirk is active,
+ * as this is far more likely than the ThinkPad being in level 7,
+ * which is only used by the firmware during thermal emergencies.
+ *
+ * Enable for TP-1Y (T43), TP-78 (R51e), TP-76 (R52),
+ * TP-70 (T43, R52), which are known to be buggy.
+ */
+
+static void fan_quirk1_setup(void)
+{
+ if (fan_control_initial_status == 0x07) {
+ pr_notice("fan_init: initial fan status is unknown, "
+ "assuming it is in auto mode\n");
+ tp_features.fan_ctrl_status_undef = 1;
+ }
+}
+
+static void fan_quirk1_handle(u8 *fan_status)
+{
+ if (unlikely(tp_features.fan_ctrl_status_undef)) {
+ if (*fan_status != fan_control_initial_status) {
+ /* something changed the HFSP regisnter since
+ * driver init time, so it is not undefined
+ * anymore */
+ tp_features.fan_ctrl_status_undef = 0;
+ } else {
+ /* Return most likely status. In fact, it
+ * might be the only possible status */
+ *fan_status = TP_EC_FAN_AUTO;
+ }
+ }
+}
+
+/* Select main fan on X60/X61, NOOP on others */
+static bool fan_select_fan1(void)
+{
+ if (tp_features.second_fan) {
+ u8 val;
+
+ if (ec_read(fan_select_offset, &val) < 0)
+ return false;
+ val &= 0xFEU;
+ if (ec_write(fan_select_offset, val) < 0)
+ return false;
+ }
+ return true;
+}
+
+/* Select secondary fan on X60/X61 */
+static bool fan_select_fan2(void)
+{
+ u8 val;
+
+ if (!tp_features.second_fan)
+ return false;
+
+ if (ec_read(fan_select_offset, &val) < 0)
+ return false;
+ val |= 0x01U;
+ if (ec_write(fan_select_offset, val) < 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Call with fan_mutex held
+ */
+static void fan_update_desired_level(u8 status)
+{
+ if ((status &
+ (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
+ if (status > 7)
+ fan_control_desired_level = 7;
+ else
+ fan_control_desired_level = status;
+ }
+}
+
+static int fan_get_status(u8 *status)
+{
+ u8 s;
+
+ /* TODO:
+ * Add TPACPI_FAN_RD_ACPI_FANS ? */
+
+ switch (fan_status_access_mode) {
+ case TPACPI_FAN_RD_ACPI_GFAN: {
+ /* 570, 600e/x, 770e, 770x */
+ int res;
+
+ if (unlikely(!acpi_evalf(gfan_handle, &res, NULL, "d")))
+ return -EIO;
+
+ if (likely(status))
+ *status = res & 0x07;
+
+ break;
+ }
+ case TPACPI_FAN_RD_TPEC:
+ /* all except 570, 600e/x, 770e, 770x */
+ if (unlikely(!acpi_ec_read(fan_status_offset, &s)))
+ return -EIO;
+
+ if (likely(status)) {
+ *status = s;
+ fan_quirk1_handle(status);
+ }
+
+ break;
+
+ default:
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int fan_get_status_safe(u8 *status)
+{
+ int rc;
+ u8 s;
+
+ if (mutex_lock_killable(&fan_mutex))
+ return -ERESTARTSYS;
+ rc = fan_get_status(&s);
+ if (!rc)
+ fan_update_desired_level(s);
+ mutex_unlock(&fan_mutex);
+
+ if (status)
+ *status = s;
+
+ return rc;
+}
+
+static int fan_get_speed(unsigned int *speed)
+{
+ u8 hi, lo;
+
+ switch (fan_status_access_mode) {
+ case TPACPI_FAN_RD_TPEC:
+ /* all except 570, 600e/x, 770e, 770x */
+ if (unlikely(!fan_select_fan1()))
+ return -EIO;
+ if (unlikely(!acpi_ec_read(fan_rpm_offset, &lo) ||
+ !acpi_ec_read(fan_rpm_offset + 1, &hi)))
+ return -EIO;
+
+ if (likely(speed))
+ *speed = (hi << 8) | lo;
+
+ break;
+
+ default:
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int fan2_get_speed(unsigned int *speed)
+{
+ u8 hi, lo;
+ bool rc;
+
+ switch (fan_status_access_mode) {
+ case TPACPI_FAN_RD_TPEC:
+ /* all except 570, 600e/x, 770e, 770x */
+ if (unlikely(!fan_select_fan2()))
+ return -EIO;
+ rc = !acpi_ec_read(fan_rpm_offset, &lo) ||
+ !acpi_ec_read(fan_rpm_offset + 1, &hi);
+ fan_select_fan1(); /* play it safe */
+ if (rc)
+ return -EIO;
+
+ if (likely(speed))
+ *speed = (hi << 8) | lo;
+
+ break;
+
+ default:
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int fan_set_level(int level)
+{
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_SFAN:
+ if (level >= 0 && level <= 7) {
+ if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", level))
+ return -EIO;
+ } else
+ return -EINVAL;
+ break;
+
+ case TPACPI_FAN_WR_ACPI_FANS:
+ case TPACPI_FAN_WR_TPEC:
+ if (!(level & TP_EC_FAN_AUTO) &&
+ !(level & TP_EC_FAN_FULLSPEED) &&
+ ((level < 0) || (level > 7)))
+ return -EINVAL;
+
+ /* safety net should the EC not support AUTO
+ * or FULLSPEED mode bits and just ignore them */
+ if (level & TP_EC_FAN_FULLSPEED)
+ level |= 7; /* safety min speed 7 */
+ else if (level & TP_EC_FAN_AUTO)
+ level |= 4; /* safety min speed 4 */
+
+ if (!acpi_ec_write(fan_status_offset, level))
+ return -EIO;
+ else
+ tp_features.fan_ctrl_status_undef = 0;
+ break;
+
+ default:
+ return -ENXIO;
+ }
+
+ vdbg_printk(TPACPI_DBG_FAN,
+ "fan control: set fan control register to 0x%02x\n", level);
+ return 0;
+}
+
+static int fan_set_level_safe(int level)
+{
+ int rc;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ if (mutex_lock_killable(&fan_mutex))
+ return -ERESTARTSYS;
+
+ if (level == TPACPI_FAN_LAST_LEVEL)
+ level = fan_control_desired_level;
+
+ rc = fan_set_level(level);
+ if (!rc)
+ fan_update_desired_level(level);
+
+ mutex_unlock(&fan_mutex);
+ return rc;
+}
+
+static int fan_set_enable(void)
+{
+ u8 s;
+ int rc;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ if (mutex_lock_killable(&fan_mutex))
+ return -ERESTARTSYS;
+
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_FANS:
+ case TPACPI_FAN_WR_TPEC:
+ rc = fan_get_status(&s);
+ if (rc < 0)
+ break;
+
+ /* Don't go out of emergency fan mode */
+ if (s != 7) {
+ s &= 0x07;
+ s |= TP_EC_FAN_AUTO | 4; /* min fan speed 4 */
+ }
+
+ if (!acpi_ec_write(fan_status_offset, s))
+ rc = -EIO;
+ else {
+ tp_features.fan_ctrl_status_undef = 0;
+ rc = 0;
+ }
+ break;
+
+ case TPACPI_FAN_WR_ACPI_SFAN:
+ rc = fan_get_status(&s);
+ if (rc < 0)
+ break;
+
+ s &= 0x07;
+
+ /* Set fan to at least level 4 */
+ s |= 4;
+
+ if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", s))
+ rc = -EIO;
+ else
+ rc = 0;
+ break;
+
+ default:
+ rc = -ENXIO;
+ }
+
+ mutex_unlock(&fan_mutex);
+
+ if (!rc)
+ vdbg_printk(TPACPI_DBG_FAN,
+ "fan control: set fan control register to 0x%02x\n",
+ s);
+ return rc;
+}
+
+static int fan_set_disable(void)
+{
+ int rc;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ if (mutex_lock_killable(&fan_mutex))
+ return -ERESTARTSYS;
+
+ rc = 0;
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_FANS:
+ case TPACPI_FAN_WR_TPEC:
+ if (!acpi_ec_write(fan_status_offset, 0x00))
+ rc = -EIO;
+ else {
+ fan_control_desired_level = 0;
+ tp_features.fan_ctrl_status_undef = 0;
+ }
+ break;
+
+ case TPACPI_FAN_WR_ACPI_SFAN:
+ if (!acpi_evalf(sfan_handle, NULL, NULL, "vd", 0x00))
+ rc = -EIO;
+ else
+ fan_control_desired_level = 0;
+ break;
+
+ default:
+ rc = -ENXIO;
+ }
+
+ if (!rc)
+ vdbg_printk(TPACPI_DBG_FAN,
+ "fan control: set fan control register to 0\n");
+
+ mutex_unlock(&fan_mutex);
+ return rc;
+}
+
+static int fan_set_speed(int speed)
+{
+ int rc;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ if (mutex_lock_killable(&fan_mutex))
+ return -ERESTARTSYS;
+
+ rc = 0;
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_FANS:
+ if (speed >= 0 && speed <= 65535) {
+ if (!acpi_evalf(fans_handle, NULL, NULL, "vddd",
+ speed, speed, speed))
+ rc = -EIO;
+ } else
+ rc = -EINVAL;
+ break;
+
+ default:
+ rc = -ENXIO;
+ }
+
+ mutex_unlock(&fan_mutex);
+ return rc;
+}
+
+static void fan_watchdog_reset(void)
+{
+ if (fan_control_access_mode == TPACPI_FAN_WR_NONE)
+ return;
+
+ if (fan_watchdog_maxinterval > 0 &&
+ tpacpi_lifecycle != TPACPI_LIFE_EXITING)
+ mod_delayed_work(tpacpi_wq, &fan_watchdog_task,
+ msecs_to_jiffies(fan_watchdog_maxinterval * 1000));
+ else
+ cancel_delayed_work(&fan_watchdog_task);
+}
+
+static void fan_watchdog_fire(struct work_struct *ignored)
+{
+ int rc;
+
+ if (tpacpi_lifecycle != TPACPI_LIFE_RUNNING)
+ return;
+
+ pr_notice("fan watchdog: enabling fan\n");
+ rc = fan_set_enable();
+ if (rc < 0) {
+ pr_err("fan watchdog: error %d while enabling fan, "
+ "will try again later...\n", -rc);
+ /* reschedule for later */
+ fan_watchdog_reset();
+ }
+}
+
+/*
+ * SYSFS fan layout: hwmon compatible (device)
+ *
+ * pwm*_enable:
+ * 0: "disengaged" mode
+ * 1: manual mode
+ * 2: native EC "auto" mode (recommended, hardware default)
+ *
+ * pwm*: set speed in manual mode, ignored otherwise.
+ * 0 is level 0; 255 is level 7. Intermediate points done with linear
+ * interpolation.
+ *
+ * fan*_input: tachometer reading, RPM
+ *
+ *
+ * SYSFS fan layout: extensions
+ *
+ * fan_watchdog (driver):
+ * fan watchdog interval in seconds, 0 disables (default), max 120
+ */
+
+/* sysfs fan pwm1_enable ----------------------------------------------- */
+static ssize_t fan_pwm1_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res, mode;
+ u8 status;
+
+ res = fan_get_status_safe(&status);
+ if (res)
+ return res;
+
+ if (status & TP_EC_FAN_FULLSPEED) {
+ mode = 0;
+ } else if (status & TP_EC_FAN_AUTO) {
+ mode = 2;
+ } else
+ mode = 1;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", mode);
+}
+
+static ssize_t fan_pwm1_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+ int res, level;
+
+ if (parse_strtoul(buf, 2, &t))
+ return -EINVAL;
+
+ tpacpi_disclose_usertask("hwmon pwm1_enable",
+ "set fan mode to %lu\n", t);
+
+ switch (t) {
+ case 0:
+ level = TP_EC_FAN_FULLSPEED;
+ break;
+ case 1:
+ level = TPACPI_FAN_LAST_LEVEL;
+ break;
+ case 2:
+ level = TP_EC_FAN_AUTO;
+ break;
+ case 3:
+ /* reserved for software-controlled auto mode */
+ return -ENOSYS;
+ default:
+ return -EINVAL;
+ }
+
+ res = fan_set_level_safe(level);
+ if (res == -ENXIO)
+ return -EINVAL;
+ else if (res < 0)
+ return res;
+
+ fan_watchdog_reset();
+
+ return count;
+}
+
+static struct device_attribute dev_attr_fan_pwm1_enable =
+ __ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+ fan_pwm1_enable_show, fan_pwm1_enable_store);
+
+/* sysfs fan pwm1 ------------------------------------------------------ */
+static ssize_t fan_pwm1_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res;
+ u8 status;
+
+ res = fan_get_status_safe(&status);
+ if (res)
+ return res;
+
+ if ((status &
+ (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) != 0)
+ status = fan_control_desired_level;
+
+ if (status > 7)
+ status = 7;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", (status * 255) / 7);
+}
+
+static ssize_t fan_pwm1_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long s;
+ int rc;
+ u8 status, newlevel;
+
+ if (parse_strtoul(buf, 255, &s))
+ return -EINVAL;
+
+ tpacpi_disclose_usertask("hwmon pwm1",
+ "set fan speed to %lu\n", s);
+
+ /* scale down from 0-255 to 0-7 */
+ newlevel = (s >> 5) & 0x07;
+
+ if (mutex_lock_killable(&fan_mutex))
+ return -ERESTARTSYS;
+
+ rc = fan_get_status(&status);
+ if (!rc && (status &
+ (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
+ rc = fan_set_level(newlevel);
+ if (rc == -ENXIO)
+ rc = -EINVAL;
+ else if (!rc) {
+ fan_update_desired_level(newlevel);
+ fan_watchdog_reset();
+ }
+ }
+
+ mutex_unlock(&fan_mutex);
+ return (rc)? rc : count;
+}
+
+static struct device_attribute dev_attr_fan_pwm1 =
+ __ATTR(pwm1, S_IWUSR | S_IRUGO,
+ fan_pwm1_show, fan_pwm1_store);
+
+/* sysfs fan fan1_input ------------------------------------------------ */
+static ssize_t fan_fan1_input_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res;
+ unsigned int speed;
+
+ res = fan_get_speed(&speed);
+ if (res < 0)
+ return res;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", speed);
+}
+
+static struct device_attribute dev_attr_fan_fan1_input =
+ __ATTR(fan1_input, S_IRUGO,
+ fan_fan1_input_show, NULL);
+
+/* sysfs fan fan2_input ------------------------------------------------ */
+static ssize_t fan_fan2_input_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int res;
+ unsigned int speed;
+
+ res = fan2_get_speed(&speed);
+ if (res < 0)
+ return res;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", speed);
+}
+
+static struct device_attribute dev_attr_fan_fan2_input =
+ __ATTR(fan2_input, S_IRUGO,
+ fan_fan2_input_show, NULL);
+
+/* sysfs fan fan_watchdog (hwmon driver) ------------------------------- */
+static ssize_t fan_fan_watchdog_show(struct device_driver *drv,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%u\n", fan_watchdog_maxinterval);
+}
+
+static ssize_t fan_fan_watchdog_store(struct device_driver *drv,
+ const char *buf, size_t count)
+{
+ unsigned long t;
+
+ if (parse_strtoul(buf, 120, &t))
+ return -EINVAL;
+
+ if (!fan_control_allowed)
+ return -EPERM;
+
+ fan_watchdog_maxinterval = t;
+ fan_watchdog_reset();
+
+ tpacpi_disclose_usertask("fan_watchdog", "set to %lu\n", t);
+
+ return count;
+}
+
+static DRIVER_ATTR(fan_watchdog, S_IWUSR | S_IRUGO,
+ fan_fan_watchdog_show, fan_fan_watchdog_store);
+
+/* --------------------------------------------------------------------- */
+static struct attribute *fan_attributes[] = {
+ &dev_attr_fan_pwm1_enable.attr, &dev_attr_fan_pwm1.attr,
+ &dev_attr_fan_fan1_input.attr,
+ NULL, /* for fan2_input */
+ NULL
+};
+
+static const struct attribute_group fan_attr_group = {
+ .attrs = fan_attributes,
+};
+
+#define TPACPI_FAN_Q1 0x0001 /* Unitialized HFSP */
+#define TPACPI_FAN_2FAN 0x0002 /* EC 0x31 bit 0 selects fan2 */
+
+#define TPACPI_FAN_QI(__id1, __id2, __quirks) \
+ { .vendor = PCI_VENDOR_ID_IBM, \
+ .bios = TPACPI_MATCH_ANY, \
+ .ec = TPID(__id1, __id2), \
+ .quirks = __quirks }
+
+#define TPACPI_FAN_QL(__id1, __id2, __quirks) \
+ { .vendor = PCI_VENDOR_ID_LENOVO, \
+ .bios = TPACPI_MATCH_ANY, \
+ .ec = TPID(__id1, __id2), \
+ .quirks = __quirks }
+
+static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
+ TPACPI_FAN_QI('1', 'Y', TPACPI_FAN_Q1),
+ TPACPI_FAN_QI('7', '8', TPACPI_FAN_Q1),
+ TPACPI_FAN_QI('7', '6', TPACPI_FAN_Q1),
+ TPACPI_FAN_QI('7', '0', TPACPI_FAN_Q1),
+ TPACPI_FAN_QL('7', 'M', TPACPI_FAN_2FAN),
+};
+
+#undef TPACPI_FAN_QL
+#undef TPACPI_FAN_QI
+
+static int __init fan_init(struct ibm_init_struct *iibm)
+{
+ int rc;
+ unsigned long quirks;
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
+ "initializing fan subdriver\n");
+
+ mutex_init(&fan_mutex);
+ fan_status_access_mode = TPACPI_FAN_NONE;
+ fan_control_access_mode = TPACPI_FAN_WR_NONE;
+ fan_control_commands = 0;
+ fan_watchdog_maxinterval = 0;
+ tp_features.fan_ctrl_status_undef = 0;
+ tp_features.second_fan = 0;
+ fan_control_desired_level = 7;
+
+ if (tpacpi_is_ibm()) {
+ TPACPI_ACPIHANDLE_INIT(fans);
+ TPACPI_ACPIHANDLE_INIT(gfan);
+ TPACPI_ACPIHANDLE_INIT(sfan);
+ }
+
+ quirks = tpacpi_check_quirks(fan_quirk_table,
+ ARRAY_SIZE(fan_quirk_table));
+
+ if (gfan_handle) {
+ /* 570, 600e/x, 770e, 770x */
+ fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
+ } else {
+ /* all other ThinkPads: note that even old-style
+ * ThinkPad ECs supports the fan control register */
+ if (likely(acpi_ec_read(fan_status_offset,
+ &fan_control_initial_status))) {
+ fan_status_access_mode = TPACPI_FAN_RD_TPEC;
+ if (quirks & TPACPI_FAN_Q1)
+ fan_quirk1_setup();
+ if (quirks & TPACPI_FAN_2FAN) {
+ tp_features.second_fan = 1;
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
+ "secondary fan support enabled\n");
+ }
+ } else {
+ pr_err("ThinkPad ACPI EC access misbehaving, "
+ "fan status and control unavailable\n");
+ return 1;
+ }
+ }
+
+ if (sfan_handle) {
+ /* 570, 770x-JL */
+ fan_control_access_mode = TPACPI_FAN_WR_ACPI_SFAN;
+ fan_control_commands |=
+ TPACPI_FAN_CMD_LEVEL | TPACPI_FAN_CMD_ENABLE;
+ } else {
+ if (!gfan_handle) {
+ /* gfan without sfan means no fan control */
+ /* all other models implement TP EC 0x2f control */
+
+ if (fans_handle) {
+ /* X31, X40, X41 */
+ fan_control_access_mode =
+ TPACPI_FAN_WR_ACPI_FANS;
+ fan_control_commands |=
+ TPACPI_FAN_CMD_SPEED |
+ TPACPI_FAN_CMD_LEVEL |
+ TPACPI_FAN_CMD_ENABLE;
+ } else {
+ fan_control_access_mode = TPACPI_FAN_WR_TPEC;
+ fan_control_commands |=
+ TPACPI_FAN_CMD_LEVEL |
+ TPACPI_FAN_CMD_ENABLE;
+ }
+ }
+ }
+
+ vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
+ "fan is %s, modes %d, %d\n",
+ str_supported(fan_status_access_mode != TPACPI_FAN_NONE ||
+ fan_control_access_mode != TPACPI_FAN_WR_NONE),
+ fan_status_access_mode, fan_control_access_mode);
+
+ /* fan control master switch */
+ if (!fan_control_allowed) {
+ fan_control_access_mode = TPACPI_FAN_WR_NONE;
+ fan_control_commands = 0;
+ dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN,
+ "fan control features disabled by parameter\n");
+ }
+
+ /* update fan_control_desired_level */
+ if (fan_status_access_mode != TPACPI_FAN_NONE)
+ fan_get_status_safe(NULL);
+
+ if (fan_status_access_mode != TPACPI_FAN_NONE ||
+ fan_control_access_mode != TPACPI_FAN_WR_NONE) {
+ if (tp_features.second_fan) {
+ /* attach second fan tachometer */
+ fan_attributes[ARRAY_SIZE(fan_attributes)-2] =
+ &dev_attr_fan_fan2_input.attr;
+ }
+ rc = sysfs_create_group(&tpacpi_sensors_pdev->dev.kobj,
+ &fan_attr_group);
+ if (rc < 0)
+ return rc;
+
+ rc = driver_create_file(&tpacpi_hwmon_pdriver.driver,
+ &driver_attr_fan_watchdog);
+ if (rc < 0) {
+ sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj,
+ &fan_attr_group);
+ return rc;
+ }
+ return 0;
+ } else
+ return 1;
+}
+
+static void fan_exit(void)
+{
+ vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_FAN,
+ "cancelling any pending fan watchdog tasks\n");
+
+ /* FIXME: can we really do this unconditionally? */
+ sysfs_remove_group(&tpacpi_sensors_pdev->dev.kobj, &fan_attr_group);
+ driver_remove_file(&tpacpi_hwmon_pdriver.driver,
+ &driver_attr_fan_watchdog);
+
+ cancel_delayed_work(&fan_watchdog_task);
+ flush_workqueue(tpacpi_wq);
+}
+
+static void fan_suspend(void)
+{
+ int rc;
+
+ if (!fan_control_allowed)
+ return;
+
+ /* Store fan status in cache */
+ fan_control_resume_level = 0;
+ rc = fan_get_status_safe(&fan_control_resume_level);
+ if (rc < 0)
+ pr_notice("failed to read fan level for later "
+ "restore during resume: %d\n", rc);
+
+ /* if it is undefined, don't attempt to restore it.
+ * KEEP THIS LAST */
+ if (tp_features.fan_ctrl_status_undef)
+ fan_control_resume_level = 0;
+}
+
+static void fan_resume(void)
+{
+ u8 current_level = 7;
+ bool do_set = false;
+ int rc;
+
+ /* DSDT *always* updates status on resume */
+ tp_features.fan_ctrl_status_undef = 0;
+
+ if (!fan_control_allowed ||
+ !fan_control_resume_level ||
+ (fan_get_status_safe(&current_level) < 0))
+ return;
+
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_SFAN:
+ /* never decrease fan level */
+ do_set = (fan_control_resume_level > current_level);
+ break;
+ case TPACPI_FAN_WR_ACPI_FANS:
+ case TPACPI_FAN_WR_TPEC:
+ /* never decrease fan level, scale is:
+ * TP_EC_FAN_FULLSPEED > 7 >= TP_EC_FAN_AUTO
+ *
+ * We expect the firmware to set either 7 or AUTO, but we
+ * handle FULLSPEED out of paranoia.
+ *
+ * So, we can safely only restore FULLSPEED or 7, anything
+ * else could slow the fan. Restoring AUTO is useless, at
+ * best that's exactly what the DSDT already set (it is the
+ * slower it uses).
+ *
+ * Always keep in mind that the DSDT *will* have set the
+ * fans to what the vendor supposes is the best level. We
+ * muck with it only to speed the fan up.
+ */
+ if (fan_control_resume_level != 7 &&
+ !(fan_control_resume_level & TP_EC_FAN_FULLSPEED))
+ return;
+ else
+ do_set = !(current_level & TP_EC_FAN_FULLSPEED) &&
+ (current_level != fan_control_resume_level);
+ break;
+ default:
+ return;
+ }
+ if (do_set) {
+ pr_notice("restoring fan level to 0x%02x\n",
+ fan_control_resume_level);
+ rc = fan_set_level_safe(fan_control_resume_level);
+ if (rc < 0)
+ pr_notice("failed to restore fan level: %d\n", rc);
+ }
+}
+
+static int fan_read(struct seq_file *m)
+{
+ int rc;
+ u8 status;
+ unsigned int speed = 0;
+
+ switch (fan_status_access_mode) {
+ case TPACPI_FAN_RD_ACPI_GFAN:
+ /* 570, 600e/x, 770e, 770x */
+ rc = fan_get_status_safe(&status);
+ if (rc < 0)
+ return rc;
+
+ seq_printf(m, "status:\t\t%s\n"
+ "level:\t\t%d\n",
+ (status != 0) ? "enabled" : "disabled", status);
+ break;
+
+ case TPACPI_FAN_RD_TPEC:
+ /* all except 570, 600e/x, 770e, 770x */
+ rc = fan_get_status_safe(&status);
+ if (rc < 0)
+ return rc;
+
+ seq_printf(m, "status:\t\t%s\n",
+ (status != 0) ? "enabled" : "disabled");
+
+ rc = fan_get_speed(&speed);
+ if (rc < 0)
+ return rc;
+
+ seq_printf(m, "speed:\t\t%d\n", speed);
+
+ if (status & TP_EC_FAN_FULLSPEED)
+ /* Disengaged mode takes precedence */
+ seq_printf(m, "level:\t\tdisengaged\n");
+ else if (status & TP_EC_FAN_AUTO)
+ seq_printf(m, "level:\t\tauto\n");
+ else
+ seq_printf(m, "level:\t\t%d\n", status);
+ break;
+
+ case TPACPI_FAN_NONE:
+ default:
+ seq_printf(m, "status:\t\tnot supported\n");
+ }
+
+ if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
+ seq_printf(m, "commands:\tlevel <level>");
+
+ switch (fan_control_access_mode) {
+ case TPACPI_FAN_WR_ACPI_SFAN:
+ seq_printf(m, " (<level> is 0-7)\n");
+ break;
+
+ default:
+ seq_printf(m, " (<level> is 0-7, "
+ "auto, disengaged, full-speed)\n");
+ break;
+ }
+ }
+
+ if (fan_control_commands & TPACPI_FAN_CMD_ENABLE)
+ seq_printf(m, "commands:\tenable, disable\n"
+ "commands:\twatchdog <timeout> (<timeout> "
+ "is 0 (off), 1-120 (seconds))\n");
+
+ if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
+ seq_printf(m, "commands:\tspeed <speed>"
+ " (<speed> is 0-65535)\n");
+
+ return 0;
+}
+
+static int fan_write_cmd_level(const char *cmd, int *rc)
+{
+ int level;
+
+ if (strlencmp(cmd, "level auto") == 0)
+ level = TP_EC_FAN_AUTO;
+ else if ((strlencmp(cmd, "level disengaged") == 0) |
+ (strlencmp(cmd, "level full-speed") == 0))
+ level = TP_EC_FAN_FULLSPEED;
+ else if (sscanf(cmd, "level %d", &level) != 1)
+ return 0;
+
+ *rc = fan_set_level_safe(level);
+ if (*rc == -ENXIO)
+ pr_err("level command accepted for unsupported access mode %d\n",
+ fan_control_access_mode);
+ else if (!*rc)
+ tpacpi_disclose_usertask("procfs fan",
+ "set level to %d\n", level);
+
+ return 1;
+}
+
+static int fan_write_cmd_enable(const char *cmd, int *rc)
+{
+ if (strlencmp(cmd, "enable") != 0)
+ return 0;
+
+ *rc = fan_set_enable();
+ if (*rc == -ENXIO)
+ pr_err("enable command accepted for unsupported access mode %d\n",
+ fan_control_access_mode);
+ else if (!*rc)
+ tpacpi_disclose_usertask("procfs fan", "enable\n");
+
+ return 1;
+}
+
+static int fan_write_cmd_disable(const char *cmd, int *rc)
+{
+ if (strlencmp(cmd, "disable") != 0)
+ return 0;
+
+ *rc = fan_set_disable();
+ if (*rc == -ENXIO)
+ pr_err("disable command accepted for unsupported access mode %d\n",
+ fan_control_access_mode);
+ else if (!*rc)
+ tpacpi_disclose_usertask("procfs fan", "disable\n");
+
+ return 1;
+}
+
+static int fan_write_cmd_speed(const char *cmd, int *rc)
+{
+ int speed;
+
+ /* TODO:
+ * Support speed <low> <medium> <high> ? */
+
+ if (sscanf(cmd, "speed %d", &speed) != 1)
+ return 0;
+
+ *rc = fan_set_speed(speed);
+ if (*rc == -ENXIO)
+ pr_err("speed command accepted for unsupported access mode %d\n",
+ fan_control_access_mode);
+ else if (!*rc)
+ tpacpi_disclose_usertask("procfs fan",
+ "set speed to %d\n", speed);
+
+ return 1;
+}
+
+static int fan_write_cmd_watchdog(const char *cmd, int *rc)
+{
+ int interval;
+
+ if (sscanf(cmd, "watchdog %d", &interval) != 1)
+ return 0;
+
+ if (interval < 0 || interval > 120)
+ *rc = -EINVAL;
+ else {
+ fan_watchdog_maxinterval = interval;
+ tpacpi_disclose_usertask("procfs fan",
+ "set watchdog timer to %d\n",
+ interval);
+ }
+
+ return 1;
+}
+
+static int fan_write(char *buf)
+{
+ char *cmd;
+ int rc = 0;
+
+ while (!rc && (cmd = next_cmd(&buf))) {
+ if (!((fan_control_commands & TPACPI_FAN_CMD_LEVEL) &&
+ fan_write_cmd_level(cmd, &rc)) &&
+ !((fan_control_commands & TPACPI_FAN_CMD_ENABLE) &&
+ (fan_write_cmd_enable(cmd, &rc) ||
+ fan_write_cmd_disable(cmd, &rc) ||
+ fan_write_cmd_watchdog(cmd, &rc))) &&
+ !((fan_control_commands & TPACPI_FAN_CMD_SPEED) &&
+ fan_write_cmd_speed(cmd, &rc))
+ )
+ rc = -EINVAL;
+ else if (!rc)
+ fan_watchdog_reset();
+ }
+
+ return rc;
+}
+
+static struct ibm_struct fan_driver_data = {
+ .name = "fan",
+ .read = fan_read,
+ .write = fan_write,
+ .exit = fan_exit,
+ .suspend = fan_suspend,
+ .resume = fan_resume,
+};
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Infrastructure
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/*
+ * HKEY event callout for other subdrivers go here
+ * (yes, it is ugly, but it is quick, safe, and gets the job done
+ */
+static void tpacpi_driver_event(const unsigned int hkey_event)
+{
+ if (ibm_backlight_device) {
+ switch (hkey_event) {
+ case TP_HKEY_EV_BRGHT_UP:
+ case TP_HKEY_EV_BRGHT_DOWN:
+ tpacpi_brightness_notify_change();
+ }
+ }
+ if (alsa_card) {
+ switch (hkey_event) {
+ case TP_HKEY_EV_VOL_UP:
+ case TP_HKEY_EV_VOL_DOWN:
+ case TP_HKEY_EV_VOL_MUTE:
+ volume_alsa_notify_change();
+ }
+ }
+}
+
+static void hotkey_driver_event(const unsigned int scancode)
+{
+ tpacpi_driver_event(TP_HKEY_EV_HOTKEY_BASE + scancode);
+}
+
+/* sysfs name ---------------------------------------------------------- */
+static ssize_t thinkpad_acpi_pdev_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s\n", TPACPI_NAME);
+}
+
+static struct device_attribute dev_attr_thinkpad_acpi_pdev_name =
+ __ATTR(name, S_IRUGO, thinkpad_acpi_pdev_name_show, NULL);
+
+/* --------------------------------------------------------------------- */
+
+/* /proc support */
+static struct proc_dir_entry *proc_dir;
+
+/*
+ * Module and infrastructure proble, init and exit handling
+ */
+
+static bool force_load;
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUG
+static const char * __init str_supported(int is_supported)
+{
+ static char text_unsupported[] __initdata = "not supported";
+
+ return (is_supported)? &text_unsupported[4] : &text_unsupported[0];
+}
+#endif /* CONFIG_THINKPAD_ACPI_DEBUG */
+
+static void ibm_exit(struct ibm_struct *ibm)
+{
+ dbg_printk(TPACPI_DBG_EXIT, "removing %s\n", ibm->name);
+
+ list_del_init(&ibm->all_drivers);
+
+ if (ibm->flags.acpi_notify_installed) {
+ dbg_printk(TPACPI_DBG_EXIT,
+ "%s: acpi_remove_notify_handler\n", ibm->name);
+ BUG_ON(!ibm->acpi);
+ acpi_remove_notify_handler(*ibm->acpi->handle,
+ ibm->acpi->type,
+ dispatch_acpi_notify);
+ ibm->flags.acpi_notify_installed = 0;
+ }
+
+ if (ibm->flags.proc_created) {
+ dbg_printk(TPACPI_DBG_EXIT,
+ "%s: remove_proc_entry\n", ibm->name);
+ remove_proc_entry(ibm->name, proc_dir);
+ ibm->flags.proc_created = 0;
+ }
+
+ if (ibm->flags.acpi_driver_registered) {
+ dbg_printk(TPACPI_DBG_EXIT,
+ "%s: acpi_bus_unregister_driver\n", ibm->name);
+ BUG_ON(!ibm->acpi);
+ acpi_bus_unregister_driver(ibm->acpi->driver);
+ kfree(ibm->acpi->driver);
+ ibm->acpi->driver = NULL;
+ ibm->flags.acpi_driver_registered = 0;
+ }
+
+ if (ibm->flags.init_called && ibm->exit) {
+ ibm->exit();
+ ibm->flags.init_called = 0;
+ }
+
+ dbg_printk(TPACPI_DBG_INIT, "finished removing %s\n", ibm->name);
+}
+
+static int __init ibm_init(struct ibm_init_struct *iibm)
+{
+ int ret;
+ struct ibm_struct *ibm = iibm->data;
+ struct proc_dir_entry *entry;
+
+ BUG_ON(ibm == NULL);
+
+ INIT_LIST_HEAD(&ibm->all_drivers);
+
+ if (ibm->flags.experimental && !experimental)
+ return 0;
+
+ dbg_printk(TPACPI_DBG_INIT,
+ "probing for %s\n", ibm->name);
+
+ if (iibm->init) {
+ ret = iibm->init(iibm);
+ if (ret > 0)
+ return 0; /* probe failed */
+ if (ret)
+ return ret;
+
+ ibm->flags.init_called = 1;
+ }
+
+ if (ibm->acpi) {
+ if (ibm->acpi->hid) {
+ ret = register_tpacpi_subdriver(ibm);
+ if (ret)
+ goto err_out;
+ }
+
+ if (ibm->acpi->notify) {
+ ret = setup_acpi_notify(ibm);
+ if (ret == -ENODEV) {
+ pr_notice("disabling subdriver %s\n",
+ ibm->name);
+ ret = 0;
+ goto err_out;
+ }
+ if (ret < 0)
+ goto err_out;
+ }
+ }
+
+ dbg_printk(TPACPI_DBG_INIT,
+ "%s installed\n", ibm->name);
+
+ if (ibm->read) {
+ umode_t mode = iibm->base_procfs_mode;
+
+ if (!mode)
+ mode = S_IRUGO;
+ if (ibm->write)
+ mode |= S_IWUSR;
+ entry = proc_create_data(ibm->name, mode, proc_dir,
+ &dispatch_proc_fops, ibm);
+ if (!entry) {
+ pr_err("unable to create proc entry %s\n", ibm->name);
+ ret = -ENODEV;
+ goto err_out;
+ }
+ ibm->flags.proc_created = 1;
+ }
+
+ list_add_tail(&ibm->all_drivers, &tpacpi_all_drivers);
+
+ return 0;
+
+err_out:
+ dbg_printk(TPACPI_DBG_INIT,
+ "%s: at error exit path with result %d\n",
+ ibm->name, ret);
+
+ ibm_exit(ibm);
+ return (ret < 0)? ret : 0;
+}
+
+/* Probing */
+
+static bool __pure __init tpacpi_is_fw_digit(const char c)
+{
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z');
+}
+
+/* Most models: xxyTkkWW (#.##c); Ancient 570/600 and -SL lacks (#.##c) */
+static bool __pure __init tpacpi_is_valid_fw_id(const char* const s,
+ const char t)
+{
+ return s && strlen(s) >= 8 &&
+ tpacpi_is_fw_digit(s[0]) &&
+ tpacpi_is_fw_digit(s[1]) &&
+ s[2] == t &&
+ (s[3] == 'T' || s[3] == 'N') &&
+ tpacpi_is_fw_digit(s[4]) &&
+ tpacpi_is_fw_digit(s[5]);
+}
+
+/* returns 0 - probe ok, or < 0 - probe error.
+ * Probe ok doesn't mean thinkpad found.
+ * On error, kfree() cleanup on tp->* is not performed, caller must do it */
+static int __must_check __init get_thinkpad_model_data(
+ struct thinkpad_id_data *tp)
+{
+ const struct dmi_device *dev = NULL;
+ char ec_fw_string[18];
+ char const *s;
+
+ if (!tp)
+ return -EINVAL;
+
+ memset(tp, 0, sizeof(*tp));
+
+ if (dmi_name_in_vendors("IBM"))
+ tp->vendor = PCI_VENDOR_ID_IBM;
+ else if (dmi_name_in_vendors("LENOVO"))
+ tp->vendor = PCI_VENDOR_ID_LENOVO;
+ else
+ return 0;
+
+ s = dmi_get_system_info(DMI_BIOS_VERSION);
+ tp->bios_version_str = kstrdup(s, GFP_KERNEL);
+ if (s && !tp->bios_version_str)
+ return -ENOMEM;
+
+ /* Really ancient ThinkPad 240X will fail this, which is fine */
+ if (!(tpacpi_is_valid_fw_id(tp->bios_version_str, 'E') ||
+ tpacpi_is_valid_fw_id(tp->bios_version_str, 'C')))
+ return 0;
+
+ tp->bios_model = tp->bios_version_str[0]
+ | (tp->bios_version_str[1] << 8);
+ tp->bios_release = (tp->bios_version_str[4] << 8)
+ | tp->bios_version_str[5];
+
+ /*
+ * ThinkPad T23 or newer, A31 or newer, R50e or newer,
+ * X32 or newer, all Z series; Some models must have an
+ * up-to-date BIOS or they will not be detected.
+ *
+ * See http://thinkwiki.org/wiki/List_of_DMI_IDs
+ */
+ while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
+ if (sscanf(dev->name,
+ "IBM ThinkPad Embedded Controller -[%17c",
+ ec_fw_string) == 1) {
+ ec_fw_string[sizeof(ec_fw_string) - 1] = 0;
+ ec_fw_string[strcspn(ec_fw_string, " ]")] = 0;
+
+ tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL);
+ if (!tp->ec_version_str)
+ return -ENOMEM;
+
+ if (tpacpi_is_valid_fw_id(ec_fw_string, 'H')) {
+ tp->ec_model = ec_fw_string[0]
+ | (ec_fw_string[1] << 8);
+ tp->ec_release = (ec_fw_string[4] << 8)
+ | ec_fw_string[5];
+ } else {
+ pr_notice("ThinkPad firmware release %s "
+ "doesn't match the known patterns\n",
+ ec_fw_string);
+ pr_notice("please report this to %s\n",
+ TPACPI_MAIL);
+ }
+ break;
+ }
+ }
+
+ s = dmi_get_system_info(DMI_PRODUCT_VERSION);
+ if (s && !(strnicmp(s, "ThinkPad", 8) && strnicmp(s, "Lenovo", 6))) {
+ tp->model_str = kstrdup(s, GFP_KERNEL);
+ if (!tp->model_str)
+ return -ENOMEM;
+ } else {
+ s = dmi_get_system_info(DMI_BIOS_VENDOR);
+ if (s && !(strnicmp(s, "Lenovo", 6))) {
+ tp->model_str = kstrdup(s, GFP_KERNEL);
+ if (!tp->model_str)
+ return -ENOMEM;
+ }
+ }
+
+ s = dmi_get_system_info(DMI_PRODUCT_NAME);
+ tp->nummodel_str = kstrdup(s, GFP_KERNEL);
+ if (s && !tp->nummodel_str)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int __init probe_for_thinkpad(void)
+{
+ int is_thinkpad;
+
+ if (acpi_disabled)
+ return -ENODEV;
+
+ /* It would be dangerous to run the driver in this case */
+ if (!tpacpi_is_ibm() && !tpacpi_is_lenovo())
+ return -ENODEV;
+
+ /*
+ * Non-ancient models have better DMI tagging, but very old models
+ * don't. tpacpi_is_fw_known() is a cheat to help in that case.
+ */
+ is_thinkpad = (thinkpad_id.model_str != NULL) ||
+ (thinkpad_id.ec_model != 0) ||
+ tpacpi_is_fw_known();
+
+ /* The EC handler is required */
+ tpacpi_acpi_handle_locate("ec", TPACPI_ACPI_EC_HID, &ec_handle);
+ if (!ec_handle) {
+ if (is_thinkpad)
+ pr_err("Not yet supported ThinkPad detected!\n");
+ return -ENODEV;
+ }
+
+ if (!is_thinkpad && !force_load)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void __init thinkpad_acpi_init_banner(void)
+{
+ pr_info("%s v%s\n", TPACPI_DESC, TPACPI_VERSION);
+ pr_info("%s\n", TPACPI_URL);
+
+ pr_info("ThinkPad BIOS %s, EC %s\n",
+ (thinkpad_id.bios_version_str) ?
+ thinkpad_id.bios_version_str : "unknown",
+ (thinkpad_id.ec_version_str) ?
+ thinkpad_id.ec_version_str : "unknown");
+
+ BUG_ON(!thinkpad_id.vendor);
+
+ if (thinkpad_id.model_str)
+ pr_info("%s %s, model %s\n",
+ (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) ?
+ "IBM" : ((thinkpad_id.vendor ==
+ PCI_VENDOR_ID_LENOVO) ?
+ "Lenovo" : "Unknown vendor"),
+ thinkpad_id.model_str,
+ (thinkpad_id.nummodel_str) ?
+ thinkpad_id.nummodel_str : "unknown");
+}
+
+/* Module init, exit, parameters */
+
+static struct ibm_init_struct ibms_init[] __initdata = {
+ {
+ .data = &thinkpad_acpi_driver_data,
+ },
+ {
+ .init = hotkey_init,
+ .data = &hotkey_driver_data,
+ },
+ {
+ .init = bluetooth_init,
+ .data = &bluetooth_driver_data,
+ },
+ {
+ .init = wan_init,
+ .data = &wan_driver_data,
+ },
+ {
+ .init = uwb_init,
+ .data = &uwb_driver_data,
+ },
+#ifdef CONFIG_THINKPAD_ACPI_VIDEO
+ {
+ .init = video_init,
+ .base_procfs_mode = S_IRUSR,
+ .data = &video_driver_data,
+ },
+#endif
+ {
+ .init = light_init,
+ .data = &light_driver_data,
+ },
+ {
+ .init = cmos_init,
+ .data = &cmos_driver_data,
+ },
+ {
+ .init = led_init,
+ .data = &led_driver_data,
+ },
+ {
+ .init = beep_init,
+ .data = &beep_driver_data,
+ },
+ {
+ .init = thermal_init,
+ .data = &thermal_driver_data,
+ },
+ {
+ .init = brightness_init,
+ .data = &brightness_driver_data,
+ },
+ {
+ .init = volume_init,
+ .data = &volume_driver_data,
+ },
+ {
+ .init = fan_init,
+ .data = &fan_driver_data,
+ },
+};
+
+static int __init set_ibm_param(const char *val, struct kernel_param *kp)
+{
+ unsigned int i;
+ struct ibm_struct *ibm;
+
+ if (!kp || !kp->name || !val)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
+ ibm = ibms_init[i].data;
+ WARN_ON(ibm == NULL);
+
+ if (!ibm || !ibm->name)
+ continue;
+
+ if (strcmp(ibm->name, kp->name) == 0 && ibm->write) {
+ if (strlen(val) > sizeof(ibms_init[i].param) - 2)
+ return -ENOSPC;
+ strcpy(ibms_init[i].param, val);
+ strcat(ibms_init[i].param, ",");
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+module_param(experimental, int, 0444);
+MODULE_PARM_DESC(experimental,
+ "Enables experimental features when non-zero");
+
+module_param_named(debug, dbg_level, uint, 0);
+MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
+
+module_param(force_load, bool, 0444);
+MODULE_PARM_DESC(force_load,
+ "Attempts to load the driver even on a "
+ "mis-identified ThinkPad when true");
+
+module_param_named(fan_control, fan_control_allowed, bool, 0444);
+MODULE_PARM_DESC(fan_control,
+ "Enables setting fan parameters features when true");
+
+module_param_named(brightness_mode, brightness_mode, uint, 0444);
+MODULE_PARM_DESC(brightness_mode,
+ "Selects brightness control strategy: "
+ "0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM");
+
+module_param(brightness_enable, uint, 0444);
+MODULE_PARM_DESC(brightness_enable,
+ "Enables backlight control when 1, disables when 0");
+
+module_param(hotkey_report_mode, uint, 0444);
+MODULE_PARM_DESC(hotkey_report_mode,
+ "used for backwards compatibility with userspace, "
+ "see documentation");
+
+#ifdef CONFIG_THINKPAD_ACPI_ALSA_SUPPORT
+module_param_named(volume_mode, volume_mode, uint, 0444);
+MODULE_PARM_DESC(volume_mode,
+ "Selects volume control strategy: "
+ "0=auto, 1=EC, 2=N/A, 3=EC+NVRAM");
+
+module_param_named(volume_capabilities, volume_capabilities, uint, 0444);
+MODULE_PARM_DESC(volume_capabilities,
+ "Selects the mixer capabilites: "
+ "0=auto, 1=volume and mute, 2=mute only");
+
+module_param_named(volume_control, volume_control_allowed, bool, 0444);
+MODULE_PARM_DESC(volume_control,
+ "Enables software override for the console audio "
+ "control when true");
+
+/* ALSA module API parameters */
+module_param_named(index, alsa_index, int, 0444);
+MODULE_PARM_DESC(index, "ALSA index for the ACPI EC Mixer");
+module_param_named(id, alsa_id, charp, 0444);
+MODULE_PARM_DESC(id, "ALSA id for the ACPI EC Mixer");
+module_param_named(enable, alsa_enable, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable the ALSA interface for the ACPI EC Mixer");
+#endif /* CONFIG_THINKPAD_ACPI_ALSA_SUPPORT */
+
+#define TPACPI_PARAM(feature) \
+ module_param_call(feature, set_ibm_param, NULL, NULL, 0); \
+ MODULE_PARM_DESC(feature, "Simulates thinkpad-acpi procfs command " \
+ "at module load, see documentation")
+
+TPACPI_PARAM(hotkey);
+TPACPI_PARAM(bluetooth);
+TPACPI_PARAM(video);
+TPACPI_PARAM(light);
+TPACPI_PARAM(cmos);
+TPACPI_PARAM(led);
+TPACPI_PARAM(beep);
+TPACPI_PARAM(brightness);
+TPACPI_PARAM(volume);
+TPACPI_PARAM(fan);
+
+#ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES
+module_param(dbg_wlswemul, uint, 0444);
+MODULE_PARM_DESC(dbg_wlswemul, "Enables WLSW emulation");
+module_param_named(wlsw_state, tpacpi_wlsw_emulstate, bool, 0);
+MODULE_PARM_DESC(wlsw_state,
+ "Initial state of the emulated WLSW switch");
+
+module_param(dbg_bluetoothemul, uint, 0444);
+MODULE_PARM_DESC(dbg_bluetoothemul, "Enables bluetooth switch emulation");
+module_param_named(bluetooth_state, tpacpi_bluetooth_emulstate, bool, 0);
+MODULE_PARM_DESC(bluetooth_state,
+ "Initial state of the emulated bluetooth switch");
+
+module_param(dbg_wwanemul, uint, 0444);
+MODULE_PARM_DESC(dbg_wwanemul, "Enables WWAN switch emulation");
+module_param_named(wwan_state, tpacpi_wwan_emulstate, bool, 0);
+MODULE_PARM_DESC(wwan_state,
+ "Initial state of the emulated WWAN switch");
+
+module_param(dbg_uwbemul, uint, 0444);
+MODULE_PARM_DESC(dbg_uwbemul, "Enables UWB switch emulation");
+module_param_named(uwb_state, tpacpi_uwb_emulstate, bool, 0);
+MODULE_PARM_DESC(uwb_state,
+ "Initial state of the emulated UWB switch");
+#endif
+
+static void thinkpad_acpi_module_exit(void)
+{
+ struct ibm_struct *ibm, *itmp;
+
+ tpacpi_lifecycle = TPACPI_LIFE_EXITING;
+
+ list_for_each_entry_safe_reverse(ibm, itmp,
+ &tpacpi_all_drivers,
+ all_drivers) {
+ ibm_exit(ibm);
+ }
+
+ dbg_printk(TPACPI_DBG_INIT, "finished subdriver exit path...\n");
+
+ if (tpacpi_inputdev) {
+ if (tp_features.input_device_registered)
+ input_unregister_device(tpacpi_inputdev);
+ else
+ input_free_device(tpacpi_inputdev);
+ kfree(hotkey_keycode_map);
+ }
+
+ if (tpacpi_hwmon)
+ hwmon_device_unregister(tpacpi_hwmon);
+
+ if (tp_features.sensors_pdev_attrs_registered)
+ device_remove_file(&tpacpi_sensors_pdev->dev,
+ &dev_attr_thinkpad_acpi_pdev_name);
+ if (tpacpi_sensors_pdev)
+ platform_device_unregister(tpacpi_sensors_pdev);
+ if (tpacpi_pdev)
+ platform_device_unregister(tpacpi_pdev);
+
+ if (tp_features.sensors_pdrv_attrs_registered)
+ tpacpi_remove_driver_attributes(&tpacpi_hwmon_pdriver.driver);
+ if (tp_features.platform_drv_attrs_registered)
+ tpacpi_remove_driver_attributes(&tpacpi_pdriver.driver);
+
+ if (tp_features.sensors_pdrv_registered)
+ platform_driver_unregister(&tpacpi_hwmon_pdriver);
+
+ if (tp_features.platform_drv_registered)
+ platform_driver_unregister(&tpacpi_pdriver);
+
+ if (proc_dir)
+ remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir);
+
+ if (tpacpi_wq)
+ destroy_workqueue(tpacpi_wq);
+
+ kfree(thinkpad_id.bios_version_str);
+ kfree(thinkpad_id.ec_version_str);
+ kfree(thinkpad_id.model_str);
+ kfree(thinkpad_id.nummodel_str);
+}
+
+
+static int __init thinkpad_acpi_module_init(void)
+{
+ int ret, i;
+
+ tpacpi_lifecycle = TPACPI_LIFE_INIT;
+
+ /* Parameter checking */
+ if (hotkey_report_mode > 2)
+ return -EINVAL;
+
+ /* Driver-level probe */
+
+ ret = get_thinkpad_model_data(&thinkpad_id);
+ if (ret) {
+ pr_err("unable to get DMI data: %d\n", ret);
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ ret = probe_for_thinkpad();
+ if (ret) {
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+
+ /* Driver initialization */
+
+ thinkpad_acpi_init_banner();
+ tpacpi_check_outdated_fw();
+
+ TPACPI_ACPIHANDLE_INIT(ecrd);
+ TPACPI_ACPIHANDLE_INIT(ecwr);
+
+ tpacpi_wq = create_singlethread_workqueue(TPACPI_WORKQUEUE_NAME);
+ if (!tpacpi_wq) {
+ thinkpad_acpi_module_exit();
+ return -ENOMEM;
+ }
+
+ proc_dir = proc_mkdir(TPACPI_PROC_DIR, acpi_root_dir);
+ if (!proc_dir) {
+ pr_err("unable to create proc dir " TPACPI_PROC_DIR "\n");
+ thinkpad_acpi_module_exit();
+ return -ENODEV;
+ }
+
+ ret = platform_driver_register(&tpacpi_pdriver);
+ if (ret) {
+ pr_err("unable to register main platform driver\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tp_features.platform_drv_registered = 1;
+
+ ret = platform_driver_register(&tpacpi_hwmon_pdriver);
+ if (ret) {
+ pr_err("unable to register hwmon platform driver\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tp_features.sensors_pdrv_registered = 1;
+
+ ret = tpacpi_create_driver_attributes(&tpacpi_pdriver.driver);
+ if (!ret) {
+ tp_features.platform_drv_attrs_registered = 1;
+ ret = tpacpi_create_driver_attributes(
+ &tpacpi_hwmon_pdriver.driver);
+ }
+ if (ret) {
+ pr_err("unable to create sysfs driver attributes\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tp_features.sensors_pdrv_attrs_registered = 1;
+
+
+ /* Device initialization */
+ tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, -1,
+ NULL, 0);
+ if (IS_ERR(tpacpi_pdev)) {
+ ret = PTR_ERR(tpacpi_pdev);
+ tpacpi_pdev = NULL;
+ pr_err("unable to register platform device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tpacpi_sensors_pdev = platform_device_register_simple(
+ TPACPI_HWMON_DRVR_NAME,
+ -1, NULL, 0);
+ if (IS_ERR(tpacpi_sensors_pdev)) {
+ ret = PTR_ERR(tpacpi_sensors_pdev);
+ tpacpi_sensors_pdev = NULL;
+ pr_err("unable to register hwmon platform device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ ret = device_create_file(&tpacpi_sensors_pdev->dev,
+ &dev_attr_thinkpad_acpi_pdev_name);
+ if (ret) {
+ pr_err("unable to create sysfs hwmon device attributes\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ tp_features.sensors_pdev_attrs_registered = 1;
+ tpacpi_hwmon = hwmon_device_register(&tpacpi_sensors_pdev->dev);
+ if (IS_ERR(tpacpi_hwmon)) {
+ ret = PTR_ERR(tpacpi_hwmon);
+ tpacpi_hwmon = NULL;
+ pr_err("unable to register hwmon device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ mutex_init(&tpacpi_inputdev_send_mutex);
+ tpacpi_inputdev = input_allocate_device();
+ if (!tpacpi_inputdev) {
+ pr_err("unable to allocate input device\n");
+ thinkpad_acpi_module_exit();
+ return -ENOMEM;
+ } else {
+ /* Prepare input device, but don't register */
+ tpacpi_inputdev->name = "ThinkPad Extra Buttons";
+ tpacpi_inputdev->phys = TPACPI_DRVR_NAME "/input0";
+ tpacpi_inputdev->id.bustype = BUS_HOST;
+ tpacpi_inputdev->id.vendor = thinkpad_id.vendor;
+ tpacpi_inputdev->id.product = TPACPI_HKEY_INPUT_PRODUCT;
+ tpacpi_inputdev->id.version = TPACPI_HKEY_INPUT_VERSION;
+ tpacpi_inputdev->dev.parent = &tpacpi_pdev->dev;
+ }
+
+ /* Init subdriver dependencies */
+ tpacpi_detect_brightness_capabilities();
+
+ /* Init subdrivers */
+ for (i = 0; i < ARRAY_SIZE(ibms_init); i++) {
+ ret = ibm_init(&ibms_init[i]);
+ if (ret >= 0 && *ibms_init[i].param)
+ ret = ibms_init[i].data->write(ibms_init[i].param);
+ if (ret < 0) {
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+ }
+
+ tpacpi_lifecycle = TPACPI_LIFE_RUNNING;
+
+ ret = input_register_device(tpacpi_inputdev);
+ if (ret < 0) {
+ pr_err("unable to register input device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ } else {
+ tp_features.input_device_registered = 1;
+ }
+
+ return 0;
+}
+
+MODULE_ALIAS(TPACPI_DRVR_SHORTNAME);
+
+/*
+ * This will autoload the driver in almost every ThinkPad
+ * in widespread use.
+ *
+ * Only _VERY_ old models, like the 240, 240x and 570 lack
+ * the HKEY event interface.
+ */
+MODULE_DEVICE_TABLE(acpi, ibm_htk_device_ids);
+
+/*
+ * DMI matching for module autoloading
+ *
+ * See http://thinkwiki.org/wiki/List_of_DMI_IDs
+ * See http://thinkwiki.org/wiki/BIOS_Upgrade_Downloads
+ *
+ * Only models listed in thinkwiki will be supported, so add yours
+ * if it is not there yet.
+ */
+#define IBM_BIOS_MODULE_ALIAS(__type) \
+ MODULE_ALIAS("dmi:bvnIBM:bvr" __type "ET??WW*")
+
+/* Ancient thinkpad BIOSes have to be identified by
+ * BIOS type or model number, and there are far less
+ * BIOS types than model numbers... */
+IBM_BIOS_MODULE_ALIAS("I[MU]"); /* 570, 570e */
+
+MODULE_AUTHOR("Borislav Deianov <borislav@users.sf.net>");
+MODULE_AUTHOR("Henrique de Moraes Holschuh <hmh@hmh.eng.br>");
+MODULE_DESCRIPTION(TPACPI_DESC);
+MODULE_VERSION(TPACPI_VERSION);
+MODULE_LICENSE("GPL");
+
+module_init(thinkpad_acpi_module_init);
+module_exit(thinkpad_acpi_module_exit);
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c
new file mode 100644
index 00000000..4ab618c6
--- /dev/null
+++ b/drivers/platform/x86/topstar-laptop.c
@@ -0,0 +1,193 @@
+/*
+ * ACPI driver for Topstar notebooks (hotkeys support only)
+ *
+ * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br>
+ *
+ * Implementation inspired by existing x86 platform drivers, in special
+ * asus/eepc/fujitsu-laptop, thanks to their authors
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+
+#define ACPI_TOPSTAR_CLASS "topstar"
+
+struct topstar_hkey {
+ struct input_dev *inputdev;
+};
+
+static const struct key_entry topstar_keymap[] = {
+ { KE_KEY, 0x80, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, 0x83, { KEY_VOLUMEUP } },
+ { KE_KEY, 0x84, { KEY_VOLUMEDOWN } },
+ { KE_KEY, 0x85, { KEY_MUTE } },
+ { KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */
+ { KE_KEY, 0x88, { KEY_WLAN } },
+ { KE_KEY, 0x8a, { KEY_WWW } },
+ { KE_KEY, 0x8b, { KEY_MAIL } },
+ { KE_KEY, 0x8c, { KEY_MEDIA } },
+
+ /* Known non hotkey events don't handled or that we don't care yet */
+ { KE_IGNORE, 0x82, }, /* backlight event */
+ { KE_IGNORE, 0x8e, },
+ { KE_IGNORE, 0x8f, },
+ { KE_IGNORE, 0x90, },
+
+ /*
+ * 'G key' generate two event codes, convert to only
+ * one event/key code for now, consider replacing by
+ * a switch (3G switch - SW_3G?)
+ */
+ { KE_KEY, 0x96, { KEY_F14 } },
+ { KE_KEY, 0x97, { KEY_F14 } },
+
+ { KE_END, 0 }
+};
+
+static void acpi_topstar_notify(struct acpi_device *device, u32 event)
+{
+ static bool dup_evnt[2];
+ bool *dup;
+ struct topstar_hkey *hkey = acpi_driver_data(device);
+
+ /* 0x83 and 0x84 key events comes duplicated... */
+ if (event == 0x83 || event == 0x84) {
+ dup = &dup_evnt[event - 0x83];
+ if (*dup) {
+ *dup = false;
+ return;
+ }
+ *dup = true;
+ }
+
+ if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true))
+ pr_info("unknown event = 0x%02x\n", event);
+}
+
+static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state)
+{
+ acpi_status status;
+ union acpi_object fncx_params[1] = {
+ { .type = ACPI_TYPE_INTEGER }
+ };
+ struct acpi_object_list fncx_arg_list = { 1, &fncx_params[0] };
+
+ fncx_params[0].integer.value = state ? 0x86 : 0x87;
+ status = acpi_evaluate_object(device->handle, "FNCX", &fncx_arg_list, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_err("Unable to switch FNCX notifications\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int acpi_topstar_init_hkey(struct topstar_hkey *hkey)
+{
+ struct input_dev *input;
+ int error;
+
+ input = input_allocate_device();
+ if (!input) {
+ pr_err("Unable to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ input->name = "Topstar Laptop extra buttons";
+ input->phys = "topstar/input0";
+ input->id.bustype = BUS_HOST;
+
+ error = sparse_keymap_setup(input, topstar_keymap, NULL);
+ if (error) {
+ pr_err("Unable to setup input device keymap\n");
+ goto err_free_dev;
+ }
+
+ error = input_register_device(input);
+ if (error) {
+ pr_err("Unable to register input device\n");
+ goto err_free_keymap;
+ }
+
+ hkey->inputdev = input;
+ return 0;
+
+ err_free_keymap:
+ sparse_keymap_free(input);
+ err_free_dev:
+ input_free_device(input);
+ return error;
+}
+
+static int acpi_topstar_add(struct acpi_device *device)
+{
+ struct topstar_hkey *tps_hkey;
+
+ tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL);
+ if (!tps_hkey)
+ return -ENOMEM;
+
+ strcpy(acpi_device_name(device), "Topstar TPSACPI");
+ strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS);
+
+ if (acpi_topstar_fncx_switch(device, true))
+ goto add_err;
+
+ if (acpi_topstar_init_hkey(tps_hkey))
+ goto add_err;
+
+ device->driver_data = tps_hkey;
+ return 0;
+
+add_err:
+ kfree(tps_hkey);
+ return -ENODEV;
+}
+
+static int acpi_topstar_remove(struct acpi_device *device)
+{
+ struct topstar_hkey *tps_hkey = acpi_driver_data(device);
+
+ acpi_topstar_fncx_switch(device, false);
+
+ sparse_keymap_free(tps_hkey->inputdev);
+ input_unregister_device(tps_hkey->inputdev);
+ kfree(tps_hkey);
+
+ return 0;
+}
+
+static const struct acpi_device_id topstar_device_ids[] = {
+ { "TPSACPI01", 0 },
+ { "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, topstar_device_ids);
+
+static struct acpi_driver acpi_topstar_driver = {
+ .name = "Topstar laptop ACPI driver",
+ .class = ACPI_TOPSTAR_CLASS,
+ .ids = topstar_device_ids,
+ .ops = {
+ .add = acpi_topstar_add,
+ .remove = acpi_topstar_remove,
+ .notify = acpi_topstar_notify,
+ },
+};
+module_acpi_driver(acpi_topstar_driver);
+
+MODULE_AUTHOR("Herton Ronaldo Krzesinski");
+MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
new file mode 100644
index 00000000..eb3467ea
--- /dev/null
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -0,0 +1,1373 @@
+/*
+ * toshiba_acpi.c - Toshiba Laptop ACPI Extras
+ *
+ *
+ * Copyright (C) 2002-2004 John Belmonte
+ * Copyright (C) 2008 Philip Langdale
+ * Copyright (C) 2010 Pierre Ducroquet
+ *
+ * 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
+ *
+ *
+ * The devolpment page for this driver is located at
+ * http://memebeam.org/toys/ToshibaAcpiDriver.
+ *
+ * Credits:
+ * Jonathan A. Buzzard - Toshiba HCI info, and critical tips on reverse
+ * engineering the Windows drivers
+ * Yasushi Nagato - changes for linux kernel 2.4 -> 2.5
+ * Rob Miller - TV out and hotkeys help
+ *
+ *
+ * TODO
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define TOSHIBA_ACPI_VERSION "0.19"
+#define PROC_INTERFACE_VERSION 1
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/backlight.h>
+#include <linux/rfkill.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/i8042.h>
+
+#include <asm/uaccess.h>
+
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("John Belmonte");
+MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver");
+MODULE_LICENSE("GPL");
+
+#define TOSHIBA_WMI_EVENT_GUID "59142400-C6A3-40FA-BADB-8A2652834100"
+
+/* Scan code for Fn key on TOS1900 models */
+#define TOS1900_FN_SCAN 0x6e
+
+/* Toshiba ACPI method paths */
+#define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX"
+
+/* Toshiba HCI interface definitions
+ *
+ * HCI is Toshiba's "Hardware Control Interface" which is supposed to
+ * be uniform across all their models. Ideally we would just call
+ * dedicated ACPI methods instead of using this primitive interface.
+ * However the ACPI methods seem to be incomplete in some areas (for
+ * example they allow setting, but not reading, the LCD brightness value),
+ * so this is still useful.
+ */
+
+#define HCI_WORDS 6
+
+/* operations */
+#define HCI_SET 0xff00
+#define HCI_GET 0xfe00
+
+/* return codes */
+#define HCI_SUCCESS 0x0000
+#define HCI_FAILURE 0x1000
+#define HCI_NOT_SUPPORTED 0x8000
+#define HCI_EMPTY 0x8c00
+
+/* registers */
+#define HCI_FAN 0x0004
+#define HCI_TR_BACKLIGHT 0x0005
+#define HCI_SYSTEM_EVENT 0x0016
+#define HCI_VIDEO_OUT 0x001c
+#define HCI_HOTKEY_EVENT 0x001e
+#define HCI_LCD_BRIGHTNESS 0x002a
+#define HCI_WIRELESS 0x0056
+
+/* field definitions */
+#define HCI_HOTKEY_DISABLE 0x0b
+#define HCI_HOTKEY_ENABLE 0x09
+#define HCI_LCD_BRIGHTNESS_BITS 3
+#define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS)
+#define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS)
+#define HCI_VIDEO_OUT_LCD 0x1
+#define HCI_VIDEO_OUT_CRT 0x2
+#define HCI_VIDEO_OUT_TV 0x4
+#define HCI_WIRELESS_KILL_SWITCH 0x01
+#define HCI_WIRELESS_BT_PRESENT 0x0f
+#define HCI_WIRELESS_BT_ATTACH 0x40
+#define HCI_WIRELESS_BT_POWER 0x80
+
+struct toshiba_acpi_dev {
+ struct acpi_device *acpi_dev;
+ const char *method_hci;
+ struct rfkill *bt_rfk;
+ struct input_dev *hotkey_dev;
+ struct work_struct hotkey_work;
+ struct backlight_device *backlight_dev;
+ struct led_classdev led_dev;
+
+ int force_fan;
+ int last_key_event;
+ int key_event_valid;
+
+ unsigned int illumination_supported:1;
+ unsigned int video_supported:1;
+ unsigned int fan_supported:1;
+ unsigned int system_event_supported:1;
+ unsigned int ntfy_supported:1;
+ unsigned int info_supported:1;
+ unsigned int tr_backlight_supported:1;
+
+ struct mutex mutex;
+};
+
+static struct toshiba_acpi_dev *toshiba_acpi;
+
+static const struct acpi_device_id toshiba_device_ids[] = {
+ {"TOS6200", 0},
+ {"TOS6208", 0},
+ {"TOS1900", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, toshiba_device_ids);
+
+static const struct key_entry toshiba_acpi_keymap[] = {
+ { KE_KEY, 0x101, { KEY_MUTE } },
+ { KE_KEY, 0x102, { KEY_ZOOMOUT } },
+ { KE_KEY, 0x103, { KEY_ZOOMIN } },
+ { KE_KEY, 0x12c, { KEY_KBDILLUMTOGGLE } },
+ { KE_KEY, 0x139, { KEY_ZOOMRESET } },
+ { KE_KEY, 0x13b, { KEY_COFFEE } },
+ { KE_KEY, 0x13c, { KEY_BATTERY } },
+ { KE_KEY, 0x13d, { KEY_SLEEP } },
+ { KE_KEY, 0x13e, { KEY_SUSPEND } },
+ { KE_KEY, 0x13f, { KEY_SWITCHVIDEOMODE } },
+ { KE_KEY, 0x140, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, 0x141, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, 0x142, { KEY_WLAN } },
+ { KE_KEY, 0x143, { KEY_TOUCHPAD_TOGGLE } },
+ { KE_KEY, 0x17f, { KEY_FN } },
+ { KE_KEY, 0xb05, { KEY_PROG2 } },
+ { KE_KEY, 0xb06, { KEY_WWW } },
+ { KE_KEY, 0xb07, { KEY_MAIL } },
+ { KE_KEY, 0xb30, { KEY_STOP } },
+ { KE_KEY, 0xb31, { KEY_PREVIOUSSONG } },
+ { KE_KEY, 0xb32, { KEY_NEXTSONG } },
+ { KE_KEY, 0xb33, { KEY_PLAYPAUSE } },
+ { KE_KEY, 0xb5a, { KEY_MEDIA } },
+ { KE_IGNORE, 0x1430, { KEY_RESERVED } },
+ { KE_END, 0 },
+};
+
+/* utility
+ */
+
+static __inline__ void _set_bit(u32 * word, u32 mask, int value)
+{
+ *word = (*word & ~mask) | (mask * value);
+}
+
+/* acpi interface wrappers
+ */
+
+static int write_acpi_int(const char *methodName, int val)
+{
+ struct acpi_object_list params;
+ union acpi_object in_objs[1];
+ acpi_status status;
+
+ params.count = ARRAY_SIZE(in_objs);
+ params.pointer = in_objs;
+ in_objs[0].type = ACPI_TYPE_INTEGER;
+ in_objs[0].integer.value = val;
+
+ status = acpi_evaluate_object(NULL, (char *)methodName, &params, NULL);
+ return (status == AE_OK) ? 0 : -EIO;
+}
+
+/* Perform a raw HCI call. Here we don't care about input or output buffer
+ * format.
+ */
+static acpi_status hci_raw(struct toshiba_acpi_dev *dev,
+ const u32 in[HCI_WORDS], u32 out[HCI_WORDS])
+{
+ struct acpi_object_list params;
+ union acpi_object in_objs[HCI_WORDS];
+ struct acpi_buffer results;
+ union acpi_object out_objs[HCI_WORDS + 1];
+ acpi_status status;
+ int i;
+
+ params.count = HCI_WORDS;
+ params.pointer = in_objs;
+ for (i = 0; i < HCI_WORDS; ++i) {
+ in_objs[i].type = ACPI_TYPE_INTEGER;
+ in_objs[i].integer.value = in[i];
+ }
+
+ results.length = sizeof(out_objs);
+ results.pointer = out_objs;
+
+ status = acpi_evaluate_object(dev->acpi_dev->handle,
+ (char *)dev->method_hci, &params,
+ &results);
+ if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) {
+ for (i = 0; i < out_objs->package.count; ++i) {
+ out[i] = out_objs->package.elements[i].integer.value;
+ }
+ }
+
+ return status;
+}
+
+/* common hci tasks (get or set one or two value)
+ *
+ * In addition to the ACPI status, the HCI system returns a result which
+ * may be useful (such as "not supported").
+ */
+
+static acpi_status hci_write1(struct toshiba_acpi_dev *dev, u32 reg,
+ u32 in1, u32 *result)
+{
+ u32 in[HCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 };
+ u32 out[HCI_WORDS];
+ acpi_status status = hci_raw(dev, in, out);
+ *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
+ return status;
+}
+
+static acpi_status hci_read1(struct toshiba_acpi_dev *dev, u32 reg,
+ u32 *out1, u32 *result)
+{
+ u32 in[HCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 };
+ u32 out[HCI_WORDS];
+ acpi_status status = hci_raw(dev, in, out);
+ *out1 = out[2];
+ *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
+ return status;
+}
+
+static acpi_status hci_write2(struct toshiba_acpi_dev *dev, u32 reg,
+ u32 in1, u32 in2, u32 *result)
+{
+ u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 };
+ u32 out[HCI_WORDS];
+ acpi_status status = hci_raw(dev, in, out);
+ *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
+ return status;
+}
+
+static acpi_status hci_read2(struct toshiba_acpi_dev *dev, u32 reg,
+ u32 *out1, u32 *out2, u32 *result)
+{
+ u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 };
+ u32 out[HCI_WORDS];
+ acpi_status status = hci_raw(dev, in, out);
+ *out1 = out[2];
+ *out2 = out[3];
+ *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
+ return status;
+}
+
+/* Illumination support */
+static int toshiba_illumination_available(struct toshiba_acpi_dev *dev)
+{
+ u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 };
+ u32 out[HCI_WORDS];
+ acpi_status status;
+
+ in[0] = 0xf100;
+ status = hci_raw(dev, in, out);
+ if (ACPI_FAILURE(status)) {
+ pr_info("Illumination device not available\n");
+ return 0;
+ }
+ in[0] = 0xf400;
+ status = hci_raw(dev, in, out);
+ return 1;
+}
+
+static void toshiba_illumination_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct toshiba_acpi_dev *dev = container_of(cdev,
+ struct toshiba_acpi_dev, led_dev);
+ u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 };
+ u32 out[HCI_WORDS];
+ acpi_status status;
+
+ /* First request : initialize communication. */
+ in[0] = 0xf100;
+ status = hci_raw(dev, in, out);
+ if (ACPI_FAILURE(status)) {
+ pr_info("Illumination device not available\n");
+ return;
+ }
+
+ if (brightness) {
+ /* Switch the illumination on */
+ in[0] = 0xf400;
+ in[1] = 0x14e;
+ in[2] = 1;
+ status = hci_raw(dev, in, out);
+ if (ACPI_FAILURE(status)) {
+ pr_info("ACPI call for illumination failed\n");
+ return;
+ }
+ } else {
+ /* Switch the illumination off */
+ in[0] = 0xf400;
+ in[1] = 0x14e;
+ in[2] = 0;
+ status = hci_raw(dev, in, out);
+ if (ACPI_FAILURE(status)) {
+ pr_info("ACPI call for illumination failed.\n");
+ return;
+ }
+ }
+
+ /* Last request : close communication. */
+ in[0] = 0xf200;
+ in[1] = 0;
+ in[2] = 0;
+ hci_raw(dev, in, out);
+}
+
+static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev)
+{
+ struct toshiba_acpi_dev *dev = container_of(cdev,
+ struct toshiba_acpi_dev, led_dev);
+ u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 };
+ u32 out[HCI_WORDS];
+ acpi_status status;
+ enum led_brightness result;
+
+ /* First request : initialize communication. */
+ in[0] = 0xf100;
+ status = hci_raw(dev, in, out);
+ if (ACPI_FAILURE(status)) {
+ pr_info("Illumination device not available\n");
+ return LED_OFF;
+ }
+
+ /* Check the illumination */
+ in[0] = 0xf300;
+ in[1] = 0x14e;
+ status = hci_raw(dev, in, out);
+ if (ACPI_FAILURE(status)) {
+ pr_info("ACPI call for illumination failed.\n");
+ return LED_OFF;
+ }
+
+ result = out[2] ? LED_FULL : LED_OFF;
+
+ /* Last request : close communication. */
+ in[0] = 0xf200;
+ in[1] = 0;
+ in[2] = 0;
+ hci_raw(dev, in, out);
+
+ return result;
+}
+
+/* Bluetooth rfkill handlers */
+
+static u32 hci_get_bt_present(struct toshiba_acpi_dev *dev, bool *present)
+{
+ u32 hci_result;
+ u32 value, value2;
+
+ value = 0;
+ value2 = 0;
+ hci_read2(dev, HCI_WIRELESS, &value, &value2, &hci_result);
+ if (hci_result == HCI_SUCCESS)
+ *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false;
+
+ return hci_result;
+}
+
+static u32 hci_get_radio_state(struct toshiba_acpi_dev *dev, bool *radio_state)
+{
+ u32 hci_result;
+ u32 value, value2;
+
+ value = 0;
+ value2 = 0x0001;
+ hci_read2(dev, HCI_WIRELESS, &value, &value2, &hci_result);
+
+ *radio_state = value & HCI_WIRELESS_KILL_SWITCH;
+ return hci_result;
+}
+
+static int bt_rfkill_set_block(void *data, bool blocked)
+{
+ struct toshiba_acpi_dev *dev = data;
+ u32 result1, result2;
+ u32 value;
+ int err;
+ bool radio_state;
+
+ value = (blocked == false);
+
+ mutex_lock(&dev->mutex);
+ if (hci_get_radio_state(dev, &radio_state) != HCI_SUCCESS) {
+ err = -EIO;
+ goto out;
+ }
+
+ if (!radio_state) {
+ err = 0;
+ goto out;
+ }
+
+ hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1);
+ hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2);
+
+ if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS)
+ err = -EIO;
+ else
+ err = 0;
+ out:
+ mutex_unlock(&dev->mutex);
+ return err;
+}
+
+static void bt_rfkill_poll(struct rfkill *rfkill, void *data)
+{
+ bool new_rfk_state;
+ bool value;
+ u32 hci_result;
+ struct toshiba_acpi_dev *dev = data;
+
+ mutex_lock(&dev->mutex);
+
+ hci_result = hci_get_radio_state(dev, &value);
+ if (hci_result != HCI_SUCCESS) {
+ /* Can't do anything useful */
+ mutex_unlock(&dev->mutex);
+ return;
+ }
+
+ new_rfk_state = value;
+
+ mutex_unlock(&dev->mutex);
+
+ if (rfkill_set_hw_state(rfkill, !new_rfk_state))
+ bt_rfkill_set_block(data, true);
+}
+
+static const struct rfkill_ops toshiba_rfk_ops = {
+ .set_block = bt_rfkill_set_block,
+ .poll = bt_rfkill_poll,
+};
+
+static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled)
+{
+ u32 hci_result;
+ u32 status;
+
+ hci_read1(dev, HCI_TR_BACKLIGHT, &status, &hci_result);
+ *enabled = !status;
+ return hci_result == HCI_SUCCESS ? 0 : -EIO;
+}
+
+static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable)
+{
+ u32 hci_result;
+ u32 value = !enable;
+
+ hci_write1(dev, HCI_TR_BACKLIGHT, value, &hci_result);
+ return hci_result == HCI_SUCCESS ? 0 : -EIO;
+}
+
+static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ;
+
+static int __get_lcd_brightness(struct toshiba_acpi_dev *dev)
+{
+ u32 hci_result;
+ u32 value;
+ int brightness = 0;
+
+ if (dev->tr_backlight_supported) {
+ bool enabled;
+ int ret = get_tr_backlight_status(dev, &enabled);
+ if (ret)
+ return ret;
+ if (enabled)
+ return 0;
+ brightness++;
+ }
+
+ hci_read1(dev, HCI_LCD_BRIGHTNESS, &value, &hci_result);
+ if (hci_result == HCI_SUCCESS)
+ return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT);
+
+ return -EIO;
+}
+
+static int get_lcd_brightness(struct backlight_device *bd)
+{
+ struct toshiba_acpi_dev *dev = bl_get_data(bd);
+ return __get_lcd_brightness(dev);
+}
+
+static int lcd_proc_show(struct seq_file *m, void *v)
+{
+ struct toshiba_acpi_dev *dev = m->private;
+ int value;
+ int levels;
+
+ if (!dev->backlight_dev)
+ return -ENODEV;
+
+ levels = dev->backlight_dev->props.max_brightness + 1;
+ value = get_lcd_brightness(dev->backlight_dev);
+ if (value >= 0) {
+ seq_printf(m, "brightness: %d\n", value);
+ seq_printf(m, "brightness_levels: %d\n", levels);
+ return 0;
+ }
+
+ pr_err("Error reading LCD brightness\n");
+ return -EIO;
+}
+
+static int lcd_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, lcd_proc_show, PDE_DATA(inode));
+}
+
+static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value)
+{
+ u32 hci_result;
+
+ if (dev->tr_backlight_supported) {
+ bool enable = !value;
+ int ret = set_tr_backlight_status(dev, enable);
+ if (ret)
+ return ret;
+ if (value)
+ value--;
+ }
+
+ value = value << HCI_LCD_BRIGHTNESS_SHIFT;
+ hci_write1(dev, HCI_LCD_BRIGHTNESS, value, &hci_result);
+ return hci_result == HCI_SUCCESS ? 0 : -EIO;
+}
+
+static int set_lcd_status(struct backlight_device *bd)
+{
+ struct toshiba_acpi_dev *dev = bl_get_data(bd);
+ return set_lcd_brightness(dev, bd->props.brightness);
+}
+
+static ssize_t lcd_proc_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+ char cmd[42];
+ size_t len;
+ int value;
+ int ret;
+ int levels = dev->backlight_dev->props.max_brightness + 1;
+
+ len = min(count, sizeof(cmd) - 1);
+ if (copy_from_user(cmd, buf, len))
+ return -EFAULT;
+ cmd[len] = '\0';
+
+ if (sscanf(cmd, " brightness : %i", &value) == 1 &&
+ value >= 0 && value < levels) {
+ ret = set_lcd_brightness(dev, value);
+ if (ret == 0)
+ ret = count;
+ } else {
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static const struct file_operations lcd_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = lcd_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = lcd_proc_write,
+};
+
+static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status)
+{
+ u32 hci_result;
+
+ hci_read1(dev, HCI_VIDEO_OUT, status, &hci_result);
+ return hci_result == HCI_SUCCESS ? 0 : -EIO;
+}
+
+static int video_proc_show(struct seq_file *m, void *v)
+{
+ struct toshiba_acpi_dev *dev = m->private;
+ u32 value;
+ int ret;
+
+ ret = get_video_status(dev, &value);
+ if (!ret) {
+ int is_lcd = (value & HCI_VIDEO_OUT_LCD) ? 1 : 0;
+ int is_crt = (value & HCI_VIDEO_OUT_CRT) ? 1 : 0;
+ int is_tv = (value & HCI_VIDEO_OUT_TV) ? 1 : 0;
+ seq_printf(m, "lcd_out: %d\n", is_lcd);
+ seq_printf(m, "crt_out: %d\n", is_crt);
+ seq_printf(m, "tv_out: %d\n", is_tv);
+ }
+
+ return ret;
+}
+
+static int video_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, video_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t video_proc_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+ char *cmd, *buffer;
+ int ret;
+ int value;
+ int remain = count;
+ int lcd_out = -1;
+ int crt_out = -1;
+ int tv_out = -1;
+ u32 video_out;
+
+ cmd = kmalloc(count + 1, GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+ if (copy_from_user(cmd, buf, count)) {
+ kfree(cmd);
+ return -EFAULT;
+ }
+ cmd[count] = '\0';
+
+ buffer = cmd;
+
+ /* scan expression. Multiple expressions may be delimited with ;
+ *
+ * NOTE: to keep scanning simple, invalid fields are ignored
+ */
+ while (remain) {
+ if (sscanf(buffer, " lcd_out : %i", &value) == 1)
+ lcd_out = value & 1;
+ else if (sscanf(buffer, " crt_out : %i", &value) == 1)
+ crt_out = value & 1;
+ else if (sscanf(buffer, " tv_out : %i", &value) == 1)
+ tv_out = value & 1;
+ /* advance to one character past the next ; */
+ do {
+ ++buffer;
+ --remain;
+ }
+ while (remain && *(buffer - 1) != ';');
+ }
+
+ kfree(cmd);
+
+ ret = get_video_status(dev, &video_out);
+ if (!ret) {
+ unsigned int new_video_out = video_out;
+ if (lcd_out != -1)
+ _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out);
+ if (crt_out != -1)
+ _set_bit(&new_video_out, HCI_VIDEO_OUT_CRT, crt_out);
+ if (tv_out != -1)
+ _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out);
+ /* To avoid unnecessary video disruption, only write the new
+ * video setting if something changed. */
+ if (new_video_out != video_out)
+ ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out);
+ }
+
+ return ret ? ret : count;
+}
+
+static const struct file_operations video_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = video_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = video_proc_write,
+};
+
+static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status)
+{
+ u32 hci_result;
+
+ hci_read1(dev, HCI_FAN, status, &hci_result);
+ return hci_result == HCI_SUCCESS ? 0 : -EIO;
+}
+
+static int fan_proc_show(struct seq_file *m, void *v)
+{
+ struct toshiba_acpi_dev *dev = m->private;
+ int ret;
+ u32 value;
+
+ ret = get_fan_status(dev, &value);
+ if (!ret) {
+ seq_printf(m, "running: %d\n", (value > 0));
+ seq_printf(m, "force_on: %d\n", dev->force_fan);
+ }
+
+ return ret;
+}
+
+static int fan_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, fan_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t fan_proc_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+ char cmd[42];
+ size_t len;
+ int value;
+ u32 hci_result;
+
+ len = min(count, sizeof(cmd) - 1);
+ if (copy_from_user(cmd, buf, len))
+ return -EFAULT;
+ cmd[len] = '\0';
+
+ if (sscanf(cmd, " force_on : %i", &value) == 1 &&
+ value >= 0 && value <= 1) {
+ hci_write1(dev, HCI_FAN, value, &hci_result);
+ if (hci_result != HCI_SUCCESS)
+ return -EIO;
+ else
+ dev->force_fan = value;
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static const struct file_operations fan_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = fan_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = fan_proc_write,
+};
+
+static int keys_proc_show(struct seq_file *m, void *v)
+{
+ struct toshiba_acpi_dev *dev = m->private;
+ u32 hci_result;
+ u32 value;
+
+ if (!dev->key_event_valid && dev->system_event_supported) {
+ hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result);
+ if (hci_result == HCI_SUCCESS) {
+ dev->key_event_valid = 1;
+ dev->last_key_event = value;
+ } else if (hci_result == HCI_EMPTY) {
+ /* better luck next time */
+ } else if (hci_result == HCI_NOT_SUPPORTED) {
+ /* This is a workaround for an unresolved issue on
+ * some machines where system events sporadically
+ * become disabled. */
+ hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result);
+ pr_notice("Re-enabled hotkeys\n");
+ } else {
+ pr_err("Error reading hotkey status\n");
+ return -EIO;
+ }
+ }
+
+ seq_printf(m, "hotkey_ready: %d\n", dev->key_event_valid);
+ seq_printf(m, "hotkey: 0x%04x\n", dev->last_key_event);
+ return 0;
+}
+
+static int keys_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, keys_proc_show, PDE_DATA(inode));
+}
+
+static ssize_t keys_proc_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct toshiba_acpi_dev *dev = PDE_DATA(file_inode(file));
+ char cmd[42];
+ size_t len;
+ int value;
+
+ len = min(count, sizeof(cmd) - 1);
+ if (copy_from_user(cmd, buf, len))
+ return -EFAULT;
+ cmd[len] = '\0';
+
+ if (sscanf(cmd, " hotkey_ready : %i", &value) == 1 && value == 0) {
+ dev->key_event_valid = 0;
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static const struct file_operations keys_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = keys_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = keys_proc_write,
+};
+
+static int version_proc_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "driver: %s\n", TOSHIBA_ACPI_VERSION);
+ seq_printf(m, "proc_interface: %d\n", PROC_INTERFACE_VERSION);
+ return 0;
+}
+
+static int version_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, version_proc_show, PDE_DATA(inode));
+}
+
+static const struct file_operations version_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = version_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* proc and module init
+ */
+
+#define PROC_TOSHIBA "toshiba"
+
+static void create_toshiba_proc_entries(struct toshiba_acpi_dev *dev)
+{
+ if (dev->backlight_dev)
+ proc_create_data("lcd", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+ &lcd_proc_fops, dev);
+ if (dev->video_supported)
+ proc_create_data("video", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+ &video_proc_fops, dev);
+ if (dev->fan_supported)
+ proc_create_data("fan", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+ &fan_proc_fops, dev);
+ if (dev->hotkey_dev)
+ proc_create_data("keys", S_IRUGO | S_IWUSR, toshiba_proc_dir,
+ &keys_proc_fops, dev);
+ proc_create_data("version", S_IRUGO, toshiba_proc_dir,
+ &version_proc_fops, dev);
+}
+
+static void remove_toshiba_proc_entries(struct toshiba_acpi_dev *dev)
+{
+ if (dev->backlight_dev)
+ remove_proc_entry("lcd", toshiba_proc_dir);
+ if (dev->video_supported)
+ remove_proc_entry("video", toshiba_proc_dir);
+ if (dev->fan_supported)
+ remove_proc_entry("fan", toshiba_proc_dir);
+ if (dev->hotkey_dev)
+ remove_proc_entry("keys", toshiba_proc_dir);
+ remove_proc_entry("version", toshiba_proc_dir);
+}
+
+static const struct backlight_ops toshiba_backlight_data = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .get_brightness = get_lcd_brightness,
+ .update_status = set_lcd_status,
+};
+
+static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str,
+ struct serio *port)
+{
+ if (str & 0x20)
+ return false;
+
+ if (unlikely(data == 0xe0))
+ return false;
+
+ if ((data & 0x7f) == TOS1900_FN_SCAN) {
+ schedule_work(&toshiba_acpi->hotkey_work);
+ return true;
+ }
+
+ return false;
+}
+
+static void toshiba_acpi_hotkey_work(struct work_struct *work)
+{
+ acpi_handle ec_handle = ec_get_handle();
+ acpi_status status;
+
+ if (!ec_handle)
+ return;
+
+ status = acpi_evaluate_object(ec_handle, "NTFY", NULL, NULL);
+ if (ACPI_FAILURE(status))
+ pr_err("ACPI NTFY method execution failed\n");
+}
+
+/*
+ * Returns hotkey scancode, or < 0 on failure.
+ */
+static int toshiba_acpi_query_hotkey(struct toshiba_acpi_dev *dev)
+{
+ struct acpi_buffer buf;
+ union acpi_object out_obj;
+ acpi_status status;
+
+ buf.pointer = &out_obj;
+ buf.length = sizeof(out_obj);
+
+ status = acpi_evaluate_object(dev->acpi_dev->handle, "INFO",
+ NULL, &buf);
+ if (ACPI_FAILURE(status) || out_obj.type != ACPI_TYPE_INTEGER) {
+ pr_err("ACPI INFO method execution failed\n");
+ return -EIO;
+ }
+
+ return out_obj.integer.value;
+}
+
+static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev,
+ int scancode)
+{
+ if (scancode == 0x100)
+ return;
+
+ /* act on key press; ignore key release */
+ if (scancode & 0x80)
+ return;
+
+ if (!sparse_keymap_report_event(dev->hotkey_dev, scancode, 1, true))
+ pr_info("Unknown key %x\n", scancode);
+}
+
+static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
+{
+ acpi_status status;
+ acpi_handle ec_handle, handle;
+ int error;
+ u32 hci_result;
+
+ dev->hotkey_dev = input_allocate_device();
+ if (!dev->hotkey_dev) {
+ pr_info("Unable to register input device\n");
+ return -ENOMEM;
+ }
+
+ dev->hotkey_dev->name = "Toshiba input device";
+ dev->hotkey_dev->phys = "toshiba_acpi/input0";
+ dev->hotkey_dev->id.bustype = BUS_HOST;
+
+ error = sparse_keymap_setup(dev->hotkey_dev, toshiba_acpi_keymap, NULL);
+ if (error)
+ goto err_free_dev;
+
+ /*
+ * For some machines the SCI responsible for providing hotkey
+ * notification doesn't fire. We can trigger the notification
+ * whenever the Fn key is pressed using the NTFY method, if
+ * supported, so if it's present set up an i8042 key filter
+ * for this purpose.
+ */
+ status = AE_ERROR;
+ ec_handle = ec_get_handle();
+ if (ec_handle)
+ status = acpi_get_handle(ec_handle, "NTFY", &handle);
+
+ if (ACPI_SUCCESS(status)) {
+ INIT_WORK(&dev->hotkey_work, toshiba_acpi_hotkey_work);
+
+ error = i8042_install_filter(toshiba_acpi_i8042_filter);
+ if (error) {
+ pr_err("Error installing key filter\n");
+ goto err_free_keymap;
+ }
+
+ dev->ntfy_supported = 1;
+ }
+
+ /*
+ * Determine hotkey query interface. Prefer using the INFO
+ * method when it is available.
+ */
+ status = acpi_get_handle(dev->acpi_dev->handle, "INFO", &handle);
+ if (ACPI_SUCCESS(status)) {
+ dev->info_supported = 1;
+ } else {
+ hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result);
+ if (hci_result == HCI_SUCCESS)
+ dev->system_event_supported = 1;
+ }
+
+ if (!dev->info_supported && !dev->system_event_supported) {
+ pr_warn("No hotkey query interface found\n");
+ goto err_remove_filter;
+ }
+
+ status = acpi_evaluate_object(dev->acpi_dev->handle, "ENAB", NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_info("Unable to enable hotkeys\n");
+ error = -ENODEV;
+ goto err_remove_filter;
+ }
+
+ error = input_register_device(dev->hotkey_dev);
+ if (error) {
+ pr_info("Unable to register input device\n");
+ goto err_remove_filter;
+ }
+
+ hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &hci_result);
+ return 0;
+
+ err_remove_filter:
+ if (dev->ntfy_supported)
+ i8042_remove_filter(toshiba_acpi_i8042_filter);
+ err_free_keymap:
+ sparse_keymap_free(dev->hotkey_dev);
+ err_free_dev:
+ input_free_device(dev->hotkey_dev);
+ dev->hotkey_dev = NULL;
+ return error;
+}
+
+static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev)
+{
+ struct backlight_properties props;
+ int brightness;
+ int ret;
+ bool enabled;
+
+ /*
+ * Some machines don't support the backlight methods at all, and
+ * others support it read-only. Either of these is pretty useless,
+ * so only register the backlight device if the backlight method
+ * supports both reads and writes.
+ */
+ brightness = __get_lcd_brightness(dev);
+ if (brightness < 0)
+ return 0;
+ ret = set_lcd_brightness(dev, brightness);
+ if (ret) {
+ pr_debug("Backlight method is read-only, disabling backlight support\n");
+ return 0;
+ }
+
+ /* Determine whether or not BIOS supports transflective backlight */
+ ret = get_tr_backlight_status(dev, &enabled);
+ dev->tr_backlight_supported = !ret;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
+
+ /* adding an extra level and having 0 change to transflective mode */
+ if (dev->tr_backlight_supported)
+ props.max_brightness++;
+
+ dev->backlight_dev = backlight_device_register("toshiba",
+ &dev->acpi_dev->dev,
+ dev,
+ &toshiba_backlight_data,
+ &props);
+ if (IS_ERR(dev->backlight_dev)) {
+ ret = PTR_ERR(dev->backlight_dev);
+ pr_err("Could not register toshiba backlight device\n");
+ dev->backlight_dev = NULL;
+ return ret;
+ }
+
+ dev->backlight_dev->props.brightness = brightness;
+ return 0;
+}
+
+static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
+{
+ struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
+
+ remove_toshiba_proc_entries(dev);
+
+ if (dev->ntfy_supported) {
+ i8042_remove_filter(toshiba_acpi_i8042_filter);
+ cancel_work_sync(&dev->hotkey_work);
+ }
+
+ if (dev->hotkey_dev) {
+ input_unregister_device(dev->hotkey_dev);
+ sparse_keymap_free(dev->hotkey_dev);
+ }
+
+ if (dev->bt_rfk) {
+ rfkill_unregister(dev->bt_rfk);
+ rfkill_destroy(dev->bt_rfk);
+ }
+
+ if (dev->backlight_dev)
+ backlight_device_unregister(dev->backlight_dev);
+
+ if (dev->illumination_supported)
+ led_classdev_unregister(&dev->led_dev);
+
+ if (toshiba_acpi)
+ toshiba_acpi = NULL;
+
+ kfree(dev);
+
+ return 0;
+}
+
+static const char *find_hci_method(acpi_handle handle)
+{
+ acpi_status status;
+ acpi_handle hci_handle;
+
+ status = acpi_get_handle(handle, "GHCI", &hci_handle);
+ if (ACPI_SUCCESS(status))
+ return "GHCI";
+
+ status = acpi_get_handle(handle, "SPFC", &hci_handle);
+ if (ACPI_SUCCESS(status))
+ return "SPFC";
+
+ return NULL;
+}
+
+static int toshiba_acpi_add(struct acpi_device *acpi_dev)
+{
+ struct toshiba_acpi_dev *dev;
+ const char *hci_method;
+ u32 dummy;
+ bool bt_present;
+ int ret = 0;
+
+ if (toshiba_acpi)
+ return -EBUSY;
+
+ pr_info("Toshiba Laptop ACPI Extras version %s\n",
+ TOSHIBA_ACPI_VERSION);
+
+ hci_method = find_hci_method(acpi_dev->handle);
+ if (!hci_method) {
+ pr_err("HCI interface not found\n");
+ return -ENODEV;
+ }
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ dev->acpi_dev = acpi_dev;
+ dev->method_hci = hci_method;
+ acpi_dev->driver_data = dev;
+
+ if (toshiba_acpi_setup_keyboard(dev))
+ pr_info("Unable to activate hotkeys\n");
+
+ mutex_init(&dev->mutex);
+
+ ret = toshiba_acpi_setup_backlight(dev);
+ if (ret)
+ goto error;
+
+ /* Register rfkill switch for Bluetooth */
+ if (hci_get_bt_present(dev, &bt_present) == HCI_SUCCESS && bt_present) {
+ dev->bt_rfk = rfkill_alloc("Toshiba Bluetooth",
+ &acpi_dev->dev,
+ RFKILL_TYPE_BLUETOOTH,
+ &toshiba_rfk_ops,
+ dev);
+ if (!dev->bt_rfk) {
+ pr_err("unable to allocate rfkill device\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = rfkill_register(dev->bt_rfk);
+ if (ret) {
+ pr_err("unable to register rfkill device\n");
+ rfkill_destroy(dev->bt_rfk);
+ goto error;
+ }
+ }
+
+ if (toshiba_illumination_available(dev)) {
+ dev->led_dev.name = "toshiba::illumination";
+ dev->led_dev.max_brightness = 1;
+ dev->led_dev.brightness_set = toshiba_illumination_set;
+ dev->led_dev.brightness_get = toshiba_illumination_get;
+ if (!led_classdev_register(&acpi_dev->dev, &dev->led_dev))
+ dev->illumination_supported = 1;
+ }
+
+ /* Determine whether or not BIOS supports fan and video interfaces */
+
+ ret = get_video_status(dev, &dummy);
+ dev->video_supported = !ret;
+
+ ret = get_fan_status(dev, &dummy);
+ dev->fan_supported = !ret;
+
+ create_toshiba_proc_entries(dev);
+
+ toshiba_acpi = dev;
+
+ return 0;
+
+error:
+ toshiba_acpi_remove(acpi_dev);
+ return ret;
+}
+
+static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
+{
+ struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
+ u32 hci_result, value;
+ int retries = 3;
+ int scancode;
+
+ if (event != 0x80)
+ return;
+
+ if (dev->info_supported) {
+ scancode = toshiba_acpi_query_hotkey(dev);
+ if (scancode < 0)
+ pr_err("Failed to query hotkey event\n");
+ else if (scancode != 0)
+ toshiba_acpi_report_hotkey(dev, scancode);
+ } else if (dev->system_event_supported) {
+ do {
+ hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result);
+ switch (hci_result) {
+ case HCI_SUCCESS:
+ toshiba_acpi_report_hotkey(dev, (int)value);
+ break;
+ case HCI_NOT_SUPPORTED:
+ /*
+ * This is a workaround for an unresolved
+ * issue on some machines where system events
+ * sporadically become disabled.
+ */
+ hci_write1(dev, HCI_SYSTEM_EVENT, 1,
+ &hci_result);
+ pr_notice("Re-enabled hotkeys\n");
+ /* fall through */
+ default:
+ retries--;
+ break;
+ }
+ } while (retries && hci_result != HCI_EMPTY);
+ }
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int toshiba_acpi_suspend(struct device *device)
+{
+ struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
+ u32 result;
+
+ if (dev->hotkey_dev)
+ hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE, &result);
+
+ return 0;
+}
+
+static int toshiba_acpi_resume(struct device *device)
+{
+ struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
+ u32 result;
+
+ if (dev->hotkey_dev)
+ hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &result);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(toshiba_acpi_pm,
+ toshiba_acpi_suspend, toshiba_acpi_resume);
+
+static struct acpi_driver toshiba_acpi_driver = {
+ .name = "Toshiba ACPI driver",
+ .owner = THIS_MODULE,
+ .ids = toshiba_device_ids,
+ .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
+ .ops = {
+ .add = toshiba_acpi_add,
+ .remove = toshiba_acpi_remove,
+ .notify = toshiba_acpi_notify,
+ },
+ .drv.pm = &toshiba_acpi_pm,
+};
+
+static int __init toshiba_acpi_init(void)
+{
+ int ret;
+
+ /*
+ * Machines with this WMI guid aren't supported due to bugs in
+ * their AML. This check relies on wmi initializing before
+ * toshiba_acpi to guarantee guids have been identified.
+ */
+ if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID))
+ return -ENODEV;
+
+ toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir);
+ if (!toshiba_proc_dir) {
+ pr_err("Unable to create proc dir " PROC_TOSHIBA "\n");
+ return -ENODEV;
+ }
+
+ ret = acpi_bus_register_driver(&toshiba_acpi_driver);
+ if (ret) {
+ pr_err("Failed to register ACPI driver: %d\n", ret);
+ remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
+ }
+
+ return ret;
+}
+
+static void __exit toshiba_acpi_exit(void)
+{
+ acpi_bus_unregister_driver(&toshiba_acpi_driver);
+ if (toshiba_proc_dir)
+ remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
+}
+
+module_init(toshiba_acpi_init);
+module_exit(toshiba_acpi_exit);
diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c
new file mode 100644
index 00000000..74dd01ae
--- /dev/null
+++ b/drivers/platform/x86/toshiba_bluetooth.c
@@ -0,0 +1,131 @@
+/*
+ * Toshiba Bluetooth Enable Driver
+ *
+ * Copyright (C) 2009 Jes Sorensen <Jes.Sorensen@gmail.com>
+ *
+ * Thanks to Matthew Garrett for background info on ACPI innards which
+ * normal people aren't meant to understand :-)
+ *
+ * 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.
+ *
+ * Note the Toshiba Bluetooth RFKill switch seems to be a strange
+ * fish. It only provides a BT event when the switch is flipped to
+ * the 'on' position. When flipping it to 'off', the USB device is
+ * simply pulled away underneath us, without any BT event being
+ * delivered.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>");
+MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver");
+MODULE_LICENSE("GPL");
+
+
+static int toshiba_bt_rfkill_add(struct acpi_device *device);
+static int toshiba_bt_rfkill_remove(struct acpi_device *device);
+static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id bt_device_ids[] = {
+ { "TOS6205", 0},
+ { "", 0},
+};
+MODULE_DEVICE_TABLE(acpi, bt_device_ids);
+
+#ifdef CONFIG_PM_SLEEP
+static int toshiba_bt_resume(struct device *dev);
+#endif
+static SIMPLE_DEV_PM_OPS(toshiba_bt_pm, NULL, toshiba_bt_resume);
+
+static struct acpi_driver toshiba_bt_rfkill_driver = {
+ .name = "Toshiba BT",
+ .class = "Toshiba",
+ .ids = bt_device_ids,
+ .ops = {
+ .add = toshiba_bt_rfkill_add,
+ .remove = toshiba_bt_rfkill_remove,
+ .notify = toshiba_bt_rfkill_notify,
+ },
+ .owner = THIS_MODULE,
+ .drv.pm = &toshiba_bt_pm,
+};
+
+
+static int toshiba_bluetooth_enable(acpi_handle handle)
+{
+ acpi_status res1, res2;
+ u64 result;
+
+ /*
+ * Query ACPI to verify RFKill switch is set to 'on'.
+ * If not, we return silently, no need to report it as
+ * an error.
+ */
+ res1 = acpi_evaluate_integer(handle, "BTST", NULL, &result);
+ if (ACPI_FAILURE(res1))
+ return res1;
+ if (!(result & 0x01))
+ return 0;
+
+ pr_info("Re-enabling Toshiba Bluetooth\n");
+ res1 = acpi_evaluate_object(handle, "AUSB", NULL, NULL);
+ res2 = acpi_evaluate_object(handle, "BTPO", NULL, NULL);
+ if (!ACPI_FAILURE(res1) || !ACPI_FAILURE(res2))
+ return 0;
+
+ pr_warn("Failed to re-enable Toshiba Bluetooth\n");
+
+ return -ENODEV;
+}
+
+static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
+{
+ toshiba_bluetooth_enable(device->handle);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int toshiba_bt_resume(struct device *dev)
+{
+ return toshiba_bluetooth_enable(to_acpi_device(dev)->handle);
+}
+#endif
+
+static int toshiba_bt_rfkill_add(struct acpi_device *device)
+{
+ acpi_status status;
+ u64 bt_present;
+ int result = -ENODEV;
+
+ /*
+ * Some Toshiba laptops may have a fake TOS6205 device in
+ * their ACPI BIOS, so query the _STA method to see if there
+ * is really anything there, before trying to enable it.
+ */
+ status = acpi_evaluate_integer(device->handle, "_STA", NULL,
+ &bt_present);
+
+ if (!ACPI_FAILURE(status) && bt_present) {
+ pr_info("Detected Toshiba ACPI Bluetooth device - "
+ "installing RFKill handler\n");
+ result = toshiba_bluetooth_enable(device->handle);
+ }
+
+ return result;
+}
+
+static int toshiba_bt_rfkill_remove(struct acpi_device *device)
+{
+ /* clean up */
+ return 0;
+}
+
+module_acpi_driver(toshiba_bt_rfkill_driver);
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
new file mode 100644
index 00000000..e4ac38ac
--- /dev/null
+++ b/drivers/platform/x86/wmi.c
@@ -0,0 +1,986 @@
+/*
+ * ACPI-WMI mapping driver
+ *
+ * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ * GUID parsing code from ldm.c is:
+ * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org>
+ * Copyright (c) 2001-2007 Anton Altaparmakov
+ * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+ACPI_MODULE_NAME("wmi");
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
+MODULE_LICENSE("GPL");
+
+#define ACPI_WMI_CLASS "wmi"
+
+static DEFINE_MUTEX(wmi_data_lock);
+static LIST_HEAD(wmi_block_list);
+
+struct guid_block {
+ char guid[16];
+ union {
+ char object_id[2];
+ struct {
+ unsigned char notify_id;
+ unsigned char reserved;
+ };
+ };
+ u8 instance_count;
+ u8 flags;
+};
+
+struct wmi_block {
+ struct list_head list;
+ struct guid_block gblock;
+ acpi_handle handle;
+ wmi_notify_handler handler;
+ void *handler_data;
+ struct device dev;
+};
+
+
+/*
+ * If the GUID data block is marked as expensive, we must enable and
+ * explicitily disable data collection.
+ */
+#define ACPI_WMI_EXPENSIVE 0x1
+#define ACPI_WMI_METHOD 0x2 /* GUID is a method */
+#define ACPI_WMI_STRING 0x4 /* GUID takes & returns a string */
+#define ACPI_WMI_EVENT 0x8 /* GUID is an event */
+
+static bool debug_event;
+module_param(debug_event, bool, 0444);
+MODULE_PARM_DESC(debug_event,
+ "Log WMI Events [0/1]");
+
+static bool debug_dump_wdg;
+module_param(debug_dump_wdg, bool, 0444);
+MODULE_PARM_DESC(debug_dump_wdg,
+ "Dump available WMI interfaces [0/1]");
+
+static int acpi_wmi_remove(struct acpi_device *device);
+static int acpi_wmi_add(struct acpi_device *device);
+static void acpi_wmi_notify(struct acpi_device *device, u32 event);
+
+static const struct acpi_device_id wmi_device_ids[] = {
+ {"PNP0C14", 0},
+ {"pnp0c14", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
+
+static struct acpi_driver acpi_wmi_driver = {
+ .name = "wmi",
+ .class = ACPI_WMI_CLASS,
+ .ids = wmi_device_ids,
+ .ops = {
+ .add = acpi_wmi_add,
+ .remove = acpi_wmi_remove,
+ .notify = acpi_wmi_notify,
+ },
+};
+
+/*
+ * GUID parsing functions
+ */
+
+/**
+ * wmi_parse_hexbyte - Convert a ASCII hex number to a byte
+ * @src: Pointer to at least 2 characters to convert.
+ *
+ * Convert a two character ASCII hex string to a number.
+ *
+ * Return: 0-255 Success, the byte was parsed correctly
+ * -1 Error, an invalid character was supplied
+ */
+static int wmi_parse_hexbyte(const u8 *src)
+{
+ int h;
+ int value;
+
+ /* high part */
+ h = value = hex_to_bin(src[0]);
+ if (value < 0)
+ return -1;
+
+ /* low part */
+ value = hex_to_bin(src[1]);
+ if (value >= 0)
+ return (h << 4) | value;
+ return -1;
+}
+
+/**
+ * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary
+ * @src: Memory block holding binary GUID (16 bytes)
+ * @dest: Memory block to hold byte swapped binary GUID (16 bytes)
+ *
+ * Byte swap a binary GUID to match it's real GUID value
+ */
+static void wmi_swap_bytes(u8 *src, u8 *dest)
+{
+ int i;
+
+ for (i = 0; i <= 3; i++)
+ memcpy(dest + i, src + (3 - i), 1);
+
+ for (i = 0; i <= 1; i++)
+ memcpy(dest + 4 + i, src + (5 - i), 1);
+
+ for (i = 0; i <= 1; i++)
+ memcpy(dest + 6 + i, src + (7 - i), 1);
+
+ memcpy(dest + 8, src + 8, 8);
+}
+
+/**
+ * wmi_parse_guid - Convert GUID from ASCII to binary
+ * @src: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @dest: Memory block to hold binary GUID (16 bytes)
+ *
+ * N.B. The GUID need not be NULL terminated.
+ *
+ * Return: 'true' @dest contains binary GUID
+ * 'false' @dest contents are undefined
+ */
+static bool wmi_parse_guid(const u8 *src, u8 *dest)
+{
+ static const int size[] = { 4, 2, 2, 2, 6 };
+ int i, j, v;
+
+ if (src[8] != '-' || src[13] != '-' ||
+ src[18] != '-' || src[23] != '-')
+ return false;
+
+ for (j = 0; j < 5; j++, src++) {
+ for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) {
+ v = wmi_parse_hexbyte(src);
+ if (v < 0)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Convert a raw GUID to the ACII string representation
+ */
+static int wmi_gtoa(const char *in, char *out)
+{
+ int i;
+
+ for (i = 3; i >= 0; i--)
+ out += sprintf(out, "%02X", in[i] & 0xFF);
+
+ out += sprintf(out, "-");
+ out += sprintf(out, "%02X", in[5] & 0xFF);
+ out += sprintf(out, "%02X", in[4] & 0xFF);
+ out += sprintf(out, "-");
+ out += sprintf(out, "%02X", in[7] & 0xFF);
+ out += sprintf(out, "%02X", in[6] & 0xFF);
+ out += sprintf(out, "-");
+ out += sprintf(out, "%02X", in[8] & 0xFF);
+ out += sprintf(out, "%02X", in[9] & 0xFF);
+ out += sprintf(out, "-");
+
+ for (i = 10; i <= 15; i++)
+ out += sprintf(out, "%02X", in[i] & 0xFF);
+
+ *out = '\0';
+ return 0;
+}
+
+static bool find_guid(const char *guid_string, struct wmi_block **out)
+{
+ char tmp[16], guid_input[16];
+ struct wmi_block *wblock;
+ struct guid_block *block;
+ struct list_head *p;
+
+ wmi_parse_guid(guid_string, tmp);
+ wmi_swap_bytes(tmp, guid_input);
+
+ list_for_each(p, &wmi_block_list) {
+ wblock = list_entry(p, struct wmi_block, list);
+ block = &wblock->gblock;
+
+ if (memcmp(block->guid, guid_input, 16) == 0) {
+ if (out)
+ *out = wblock;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable)
+{
+ struct guid_block *block = NULL;
+ char method[5];
+ struct acpi_object_list input;
+ union acpi_object params[1];
+ acpi_status status;
+ acpi_handle handle;
+
+ block = &wblock->gblock;
+ handle = wblock->handle;
+
+ if (!block)
+ return AE_NOT_EXIST;
+
+ input.count = 1;
+ input.pointer = params;
+ params[0].type = ACPI_TYPE_INTEGER;
+ params[0].integer.value = enable;
+
+ snprintf(method, 5, "WE%02X", block->notify_id);
+ status = acpi_evaluate_object(handle, method, &input, NULL);
+
+ if (status != AE_OK && status != AE_NOT_FOUND)
+ return status;
+ else
+ return AE_OK;
+}
+
+/*
+ * Exported WMI functions
+ */
+/**
+ * wmi_evaluate_method - Evaluate a WMI method
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * @method_id: Method ID to call
+ * &in: Buffer containing input for the method call
+ * &out: Empty buffer to return the method results
+ *
+ * Call an ACPI-WMI method
+ */
+acpi_status wmi_evaluate_method(const char *guid_string, u8 instance,
+u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out)
+{
+ struct guid_block *block = NULL;
+ struct wmi_block *wblock = NULL;
+ acpi_handle handle;
+ acpi_status status;
+ struct acpi_object_list input;
+ union acpi_object params[3];
+ char method[5] = "WM";
+
+ if (!find_guid(guid_string, &wblock))
+ return AE_ERROR;
+
+ block = &wblock->gblock;
+ handle = wblock->handle;
+
+ if (!(block->flags & ACPI_WMI_METHOD))
+ return AE_BAD_DATA;
+
+ if (block->instance_count < instance)
+ return AE_BAD_PARAMETER;
+
+ input.count = 2;
+ input.pointer = params;
+ params[0].type = ACPI_TYPE_INTEGER;
+ params[0].integer.value = instance;
+ params[1].type = ACPI_TYPE_INTEGER;
+ params[1].integer.value = method_id;
+
+ if (in) {
+ input.count = 3;
+
+ if (block->flags & ACPI_WMI_STRING) {
+ params[2].type = ACPI_TYPE_STRING;
+ } else {
+ params[2].type = ACPI_TYPE_BUFFER;
+ }
+ params[2].buffer.length = in->length;
+ params[2].buffer.pointer = in->pointer;
+ }
+
+ strncat(method, block->object_id, 2);
+
+ status = acpi_evaluate_object(handle, method, &input, out);
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(wmi_evaluate_method);
+
+/**
+ * wmi_query_block - Return contents of a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &out: Empty buffer to return the contents of the data block to
+ *
+ * Return the contents of an ACPI-WMI data block to a buffer
+ */
+acpi_status wmi_query_block(const char *guid_string, u8 instance,
+struct acpi_buffer *out)
+{
+ struct guid_block *block = NULL;
+ struct wmi_block *wblock = NULL;
+ acpi_handle handle, wc_handle;
+ acpi_status status, wc_status = AE_ERROR;
+ struct acpi_object_list input, wc_input;
+ union acpi_object wc_params[1], wq_params[1];
+ char method[5];
+ char wc_method[5] = "WC";
+
+ if (!guid_string || !out)
+ return AE_BAD_PARAMETER;
+
+ if (!find_guid(guid_string, &wblock))
+ return AE_ERROR;
+
+ block = &wblock->gblock;
+ handle = wblock->handle;
+
+ if (block->instance_count < instance)
+ return AE_BAD_PARAMETER;
+
+ /* Check GUID is a data block */
+ if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+ return AE_ERROR;
+
+ input.count = 1;
+ input.pointer = wq_params;
+ wq_params[0].type = ACPI_TYPE_INTEGER;
+ wq_params[0].integer.value = instance;
+
+ /*
+ * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
+ * enable collection.
+ */
+ if (block->flags & ACPI_WMI_EXPENSIVE) {
+ wc_input.count = 1;
+ wc_input.pointer = wc_params;
+ wc_params[0].type = ACPI_TYPE_INTEGER;
+ wc_params[0].integer.value = 1;
+
+ strncat(wc_method, block->object_id, 2);
+
+ /*
+ * Some GUIDs break the specification by declaring themselves
+ * expensive, but have no corresponding WCxx method. So we
+ * should not fail if this happens.
+ */
+ wc_status = acpi_get_handle(handle, wc_method, &wc_handle);
+ if (ACPI_SUCCESS(wc_status))
+ wc_status = acpi_evaluate_object(handle, wc_method,
+ &wc_input, NULL);
+ }
+
+ strcpy(method, "WQ");
+ strncat(method, block->object_id, 2);
+
+ status = acpi_evaluate_object(handle, method, &input, out);
+
+ /*
+ * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
+ * the WQxx method failed - we should disable collection anyway.
+ */
+ if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) {
+ wc_params[0].integer.value = 0;
+ status = acpi_evaluate_object(handle,
+ wc_method, &wc_input, NULL);
+ }
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(wmi_query_block);
+
+/**
+ * wmi_set_block - Write to a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &in: Buffer containing new values for the data block
+ *
+ * Write the contents of the input buffer to an ACPI-WMI data block
+ */
+acpi_status wmi_set_block(const char *guid_string, u8 instance,
+const struct acpi_buffer *in)
+{
+ struct guid_block *block = NULL;
+ struct wmi_block *wblock = NULL;
+ acpi_handle handle;
+ struct acpi_object_list input;
+ union acpi_object params[2];
+ char method[5] = "WS";
+
+ if (!guid_string || !in)
+ return AE_BAD_DATA;
+
+ if (!find_guid(guid_string, &wblock))
+ return AE_ERROR;
+
+ block = &wblock->gblock;
+ handle = wblock->handle;
+
+ if (block->instance_count < instance)
+ return AE_BAD_PARAMETER;
+
+ /* Check GUID is a data block */
+ if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+ return AE_ERROR;
+
+ input.count = 2;
+ input.pointer = params;
+ params[0].type = ACPI_TYPE_INTEGER;
+ params[0].integer.value = instance;
+
+ if (block->flags & ACPI_WMI_STRING) {
+ params[1].type = ACPI_TYPE_STRING;
+ } else {
+ params[1].type = ACPI_TYPE_BUFFER;
+ }
+ params[1].buffer.length = in->length;
+ params[1].buffer.pointer = in->pointer;
+
+ strncat(method, block->object_id, 2);
+
+ return acpi_evaluate_object(handle, method, &input, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_set_block);
+
+static void wmi_dump_wdg(const struct guid_block *g)
+{
+ char guid_string[37];
+
+ wmi_gtoa(g->guid, guid_string);
+
+ pr_info("%s:\n", guid_string);
+ pr_info("\tobject_id: %c%c\n", g->object_id[0], g->object_id[1]);
+ pr_info("\tnotify_id: %02X\n", g->notify_id);
+ pr_info("\treserved: %02X\n", g->reserved);
+ pr_info("\tinstance_count: %d\n", g->instance_count);
+ pr_info("\tflags: %#x", g->flags);
+ if (g->flags) {
+ if (g->flags & ACPI_WMI_EXPENSIVE)
+ pr_cont(" ACPI_WMI_EXPENSIVE");
+ if (g->flags & ACPI_WMI_METHOD)
+ pr_cont(" ACPI_WMI_METHOD");
+ if (g->flags & ACPI_WMI_STRING)
+ pr_cont(" ACPI_WMI_STRING");
+ if (g->flags & ACPI_WMI_EVENT)
+ pr_cont(" ACPI_WMI_EVENT");
+ }
+ pr_cont("\n");
+
+}
+
+static void wmi_notify_debug(u32 value, void *context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = wmi_get_event_data(value, &response);
+ if (status != AE_OK) {
+ pr_info("bad event status 0x%x\n", status);
+ return;
+ }
+
+ obj = (union acpi_object *)response.pointer;
+
+ if (!obj)
+ return;
+
+ pr_info("DEBUG Event ");
+ switch(obj->type) {
+ case ACPI_TYPE_BUFFER:
+ pr_cont("BUFFER_TYPE - length %d\n", obj->buffer.length);
+ break;
+ case ACPI_TYPE_STRING:
+ pr_cont("STRING_TYPE - %s\n", obj->string.pointer);
+ break;
+ case ACPI_TYPE_INTEGER:
+ pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value);
+ break;
+ case ACPI_TYPE_PACKAGE:
+ pr_cont("PACKAGE_TYPE - %d elements\n", obj->package.count);
+ break;
+ default:
+ pr_cont("object type 0x%X\n", obj->type);
+ }
+ kfree(obj);
+}
+
+/**
+ * wmi_install_notify_handler - Register handler for WMI events
+ * @handler: Function to handle notifications
+ * @data: Data to be returned to handler when event is fired
+ *
+ * Register a handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_install_notify_handler(const char *guid,
+wmi_notify_handler handler, void *data)
+{
+ struct wmi_block *block;
+ acpi_status status = AE_NOT_EXIST;
+ char tmp[16], guid_input[16];
+ struct list_head *p;
+
+ if (!guid || !handler)
+ return AE_BAD_PARAMETER;
+
+ wmi_parse_guid(guid, tmp);
+ wmi_swap_bytes(tmp, guid_input);
+
+ list_for_each(p, &wmi_block_list) {
+ acpi_status wmi_status;
+ block = list_entry(p, struct wmi_block, list);
+
+ if (memcmp(block->gblock.guid, guid_input, 16) == 0) {
+ if (block->handler &&
+ block->handler != wmi_notify_debug)
+ return AE_ALREADY_ACQUIRED;
+
+ block->handler = handler;
+ block->handler_data = data;
+
+ wmi_status = wmi_method_enable(block, 1);
+ if ((wmi_status != AE_OK) ||
+ ((wmi_status == AE_OK) && (status == AE_NOT_EXIST)))
+ status = wmi_status;
+ }
+ }
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
+
+/**
+ * wmi_uninstall_notify_handler - Unregister handler for WMI events
+ *
+ * Unregister handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_remove_notify_handler(const char *guid)
+{
+ struct wmi_block *block;
+ acpi_status status = AE_NOT_EXIST;
+ char tmp[16], guid_input[16];
+ struct list_head *p;
+
+ if (!guid)
+ return AE_BAD_PARAMETER;
+
+ wmi_parse_guid(guid, tmp);
+ wmi_swap_bytes(tmp, guid_input);
+
+ list_for_each(p, &wmi_block_list) {
+ acpi_status wmi_status;
+ block = list_entry(p, struct wmi_block, list);
+
+ if (memcmp(block->gblock.guid, guid_input, 16) == 0) {
+ if (!block->handler ||
+ block->handler == wmi_notify_debug)
+ return AE_NULL_ENTRY;
+
+ if (debug_event) {
+ block->handler = wmi_notify_debug;
+ status = AE_OK;
+ } else {
+ wmi_status = wmi_method_enable(block, 0);
+ block->handler = NULL;
+ block->handler_data = NULL;
+ if ((wmi_status != AE_OK) ||
+ ((wmi_status == AE_OK) &&
+ (status == AE_NOT_EXIST)))
+ status = wmi_status;
+ }
+ }
+ }
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
+
+/**
+ * wmi_get_event_data - Get WMI data associated with an event
+ *
+ * @event: Event to find
+ * @out: Buffer to hold event data. out->pointer should be freed with kfree()
+ *
+ * Returns extra data associated with an event in WMI.
+ */
+acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
+{
+ struct acpi_object_list input;
+ union acpi_object params[1];
+ struct guid_block *gblock;
+ struct wmi_block *wblock;
+ struct list_head *p;
+
+ input.count = 1;
+ input.pointer = params;
+ params[0].type = ACPI_TYPE_INTEGER;
+ params[0].integer.value = event;
+
+ list_for_each(p, &wmi_block_list) {
+ wblock = list_entry(p, struct wmi_block, list);
+ gblock = &wblock->gblock;
+
+ if ((gblock->flags & ACPI_WMI_EVENT) &&
+ (gblock->notify_id == event))
+ return acpi_evaluate_object(wblock->handle, "_WED",
+ &input, out);
+ }
+
+ return AE_NOT_FOUND;
+}
+EXPORT_SYMBOL_GPL(wmi_get_event_data);
+
+/**
+ * wmi_has_guid - Check if a GUID is available
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ *
+ * Check if a given GUID is defined by _WDG
+ */
+bool wmi_has_guid(const char *guid_string)
+{
+ return find_guid(guid_string, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_has_guid);
+
+/*
+ * sysfs interface
+ */
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ char guid_string[37];
+ struct wmi_block *wblock;
+
+ wblock = dev_get_drvdata(dev);
+ if (!wblock)
+ return -ENOMEM;
+
+ wmi_gtoa(wblock->gblock.guid, guid_string);
+
+ return sprintf(buf, "wmi:%s\n", guid_string);
+}
+
+static struct device_attribute wmi_dev_attrs[] = {
+ __ATTR_RO(modalias),
+ __ATTR_NULL
+};
+
+static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ char guid_string[37];
+
+ struct wmi_block *wblock;
+
+ if (add_uevent_var(env, "MODALIAS="))
+ return -ENOMEM;
+
+ wblock = dev_get_drvdata(dev);
+ if (!wblock)
+ return -ENOMEM;
+
+ wmi_gtoa(wblock->gblock.guid, guid_string);
+
+ strcpy(&env->buf[env->buflen - 1], "wmi:");
+ memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
+ env->buflen += 40;
+
+ return 0;
+}
+
+static void wmi_dev_free(struct device *dev)
+{
+ struct wmi_block *wmi_block = container_of(dev, struct wmi_block, dev);
+
+ kfree(wmi_block);
+}
+
+static struct class wmi_class = {
+ .name = "wmi",
+ .dev_release = wmi_dev_free,
+ .dev_uevent = wmi_dev_uevent,
+ .dev_attrs = wmi_dev_attrs,
+};
+
+static int wmi_create_device(const struct guid_block *gblock,
+ struct wmi_block *wblock, acpi_handle handle)
+{
+ char guid_string[37];
+
+ wblock->dev.class = &wmi_class;
+
+ wmi_gtoa(gblock->guid, guid_string);
+ dev_set_name(&wblock->dev, guid_string);
+
+ dev_set_drvdata(&wblock->dev, wblock);
+
+ return device_register(&wblock->dev);
+}
+
+static void wmi_free_devices(void)
+{
+ struct wmi_block *wblock, *next;
+
+ /* Delete devices for all the GUIDs */
+ list_for_each_entry_safe(wblock, next, &wmi_block_list, list) {
+ list_del(&wblock->list);
+ if (wblock->dev.class)
+ device_unregister(&wblock->dev);
+ else
+ kfree(wblock);
+ }
+}
+
+static bool guid_already_parsed(const char *guid_string)
+{
+ struct wmi_block *wblock;
+
+ list_for_each_entry(wblock, &wmi_block_list, list)
+ if (memcmp(wblock->gblock.guid, guid_string, 16) == 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * Parse the _WDG method for the GUID data blocks
+ */
+static acpi_status parse_wdg(acpi_handle handle)
+{
+ struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+ union acpi_object *obj;
+ const struct guid_block *gblock;
+ struct wmi_block *wblock;
+ acpi_status status;
+ int retval;
+ u32 i, total;
+
+ status = acpi_evaluate_object(handle, "_WDG", NULL, &out);
+ if (ACPI_FAILURE(status))
+ return -ENXIO;
+
+ obj = (union acpi_object *) out.pointer;
+ if (!obj)
+ return -ENXIO;
+
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ retval = -ENXIO;
+ goto out_free_pointer;
+ }
+
+ gblock = (const struct guid_block *)obj->buffer.pointer;
+ total = obj->buffer.length / sizeof(struct guid_block);
+
+ for (i = 0; i < total; i++) {
+ if (debug_dump_wdg)
+ wmi_dump_wdg(&gblock[i]);
+
+ wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
+ if (!wblock)
+ return AE_NO_MEMORY;
+
+ wblock->handle = handle;
+ wblock->gblock = gblock[i];
+
+ /*
+ Some WMI devices, like those for nVidia hooks, have a
+ duplicate GUID. It's not clear what we should do in this
+ case yet, so for now, we'll just ignore the duplicate
+ for device creation.
+ */
+ if (!guid_already_parsed(gblock[i].guid)) {
+ retval = wmi_create_device(&gblock[i], wblock, handle);
+ if (retval) {
+ wmi_free_devices();
+ goto out_free_pointer;
+ }
+ }
+
+ list_add_tail(&wblock->list, &wmi_block_list);
+
+ if (debug_event) {
+ wblock->handler = wmi_notify_debug;
+ wmi_method_enable(wblock, 1);
+ }
+ }
+
+ retval = 0;
+
+out_free_pointer:
+ kfree(out.pointer);
+
+ return retval;
+}
+
+/*
+ * WMI can have EmbeddedControl access regions. In which case, we just want to
+ * hand these off to the EC driver.
+ */
+static acpi_status
+acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
+ u32 bits, u64 *value,
+ void *handler_context, void *region_context)
+{
+ int result = 0, i = 0;
+ u8 temp = 0;
+
+ if ((address > 0xFF) || !value)
+ return AE_BAD_PARAMETER;
+
+ if (function != ACPI_READ && function != ACPI_WRITE)
+ return AE_BAD_PARAMETER;
+
+ if (bits != 8)
+ return AE_BAD_PARAMETER;
+
+ if (function == ACPI_READ) {
+ result = ec_read(address, &temp);
+ (*value) |= ((u64)temp) << i;
+ } else {
+ temp = 0xff & ((*value) >> i);
+ result = ec_write(address, temp);
+ }
+
+ switch (result) {
+ case -EINVAL:
+ return AE_BAD_PARAMETER;
+ break;
+ case -ENODEV:
+ return AE_NOT_FOUND;
+ break;
+ case -ETIME:
+ return AE_TIME;
+ break;
+ default:
+ return AE_OK;
+ }
+}
+
+static void acpi_wmi_notify(struct acpi_device *device, u32 event)
+{
+ struct guid_block *block;
+ struct wmi_block *wblock;
+ struct list_head *p;
+ char guid_string[37];
+
+ list_for_each(p, &wmi_block_list) {
+ wblock = list_entry(p, struct wmi_block, list);
+ block = &wblock->gblock;
+
+ if ((block->flags & ACPI_WMI_EVENT) &&
+ (block->notify_id == event)) {
+ if (wblock->handler)
+ wblock->handler(event, wblock->handler_data);
+ if (debug_event) {
+ wmi_gtoa(wblock->gblock.guid, guid_string);
+ pr_info("DEBUG Event GUID: %s\n", guid_string);
+ }
+
+ acpi_bus_generate_netlink_event(
+ device->pnp.device_class, dev_name(&device->dev),
+ event, 0);
+ break;
+ }
+ }
+}
+
+static int acpi_wmi_remove(struct acpi_device *device)
+{
+ acpi_remove_address_space_handler(device->handle,
+ ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
+ wmi_free_devices();
+
+ return 0;
+}
+
+static int acpi_wmi_add(struct acpi_device *device)
+{
+ acpi_status status;
+ int error;
+
+ status = acpi_install_address_space_handler(device->handle,
+ ACPI_ADR_SPACE_EC,
+ &acpi_wmi_ec_space_handler,
+ NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ pr_err("Error installing EC region handler\n");
+ return -ENODEV;
+ }
+
+ error = parse_wdg(device->handle);
+ if (error) {
+ acpi_remove_address_space_handler(device->handle,
+ ACPI_ADR_SPACE_EC,
+ &acpi_wmi_ec_space_handler);
+ pr_err("Failed to parse WDG method\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static int __init acpi_wmi_init(void)
+{
+ int error;
+
+ if (acpi_disabled)
+ return -ENODEV;
+
+ error = class_register(&wmi_class);
+ if (error)
+ return error;
+
+ error = acpi_bus_register_driver(&acpi_wmi_driver);
+ if (error) {
+ pr_err("Error loading mapper\n");
+ class_unregister(&wmi_class);
+ return error;
+ }
+
+ pr_info("Mapper loaded\n");
+ return 0;
+}
+
+static void __exit acpi_wmi_exit(void)
+{
+ acpi_bus_unregister_driver(&acpi_wmi_driver);
+ class_unregister(&wmi_class);
+
+ pr_info("Mapper unloaded\n");
+}
+
+subsys_initcall(acpi_wmi_init);
+module_exit(acpi_wmi_exit);
diff --git a/drivers/platform/x86/xo1-rfkill.c b/drivers/platform/x86/xo1-rfkill.c
new file mode 100644
index 00000000..4bd17248
--- /dev/null
+++ b/drivers/platform/x86/xo1-rfkill.c
@@ -0,0 +1,84 @@
+/*
+ * Support for rfkill through the OLPC XO-1 laptop embedded controller
+ *
+ * Copyright (C) 2010 One Laptop per Child
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/olpc-ec.h>
+
+static bool card_blocked;
+
+static int rfkill_set_block(void *data, bool blocked)
+{
+ unsigned char cmd;
+ int r;
+
+ if (blocked == card_blocked)
+ return 0;
+
+ if (blocked)
+ cmd = EC_WLAN_ENTER_RESET;
+ else
+ cmd = EC_WLAN_LEAVE_RESET;
+
+ r = olpc_ec_cmd(cmd, NULL, 0, NULL, 0);
+ if (r == 0)
+ card_blocked = blocked;
+
+ return r;
+}
+
+static const struct rfkill_ops rfkill_ops = {
+ .set_block = rfkill_set_block,
+};
+
+static int xo1_rfkill_probe(struct platform_device *pdev)
+{
+ struct rfkill *rfk;
+ int r;
+
+ rfk = rfkill_alloc(pdev->name, &pdev->dev, RFKILL_TYPE_WLAN,
+ &rfkill_ops, NULL);
+ if (!rfk)
+ return -ENOMEM;
+
+ r = rfkill_register(rfk);
+ if (r) {
+ rfkill_destroy(rfk);
+ return r;
+ }
+
+ platform_set_drvdata(pdev, rfk);
+ return 0;
+}
+
+static int xo1_rfkill_remove(struct platform_device *pdev)
+{
+ struct rfkill *rfk = platform_get_drvdata(pdev);
+ rfkill_unregister(rfk);
+ rfkill_destroy(rfk);
+ return 0;
+}
+
+static struct platform_driver xo1_rfkill_driver = {
+ .driver = {
+ .name = "xo1-rfkill",
+ .owner = THIS_MODULE,
+ },
+ .probe = xo1_rfkill_probe,
+ .remove = xo1_rfkill_remove,
+};
+
+module_platform_driver(xo1_rfkill_driver);
+
+MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:xo1-rfkill");
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c
new file mode 100644
index 00000000..4b1377bd
--- /dev/null
+++ b/drivers/platform/x86/xo15-ebook.c
@@ -0,0 +1,173 @@
+/*
+ * OLPC XO-1.5 ebook switch driver
+ * (based on generic ACPI button driver)
+ *
+ * Copyright (C) 2009 Paul Fox <pgf@laptop.org>
+ * Copyright (C) 2010 One Laptop per Child
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+#define MODULE_NAME "xo15-ebook"
+
+#define XO15_EBOOK_CLASS MODULE_NAME
+#define XO15_EBOOK_TYPE_UNKNOWN 0x00
+#define XO15_EBOOK_NOTIFY_STATUS 0x80
+
+#define XO15_EBOOK_SUBCLASS "ebook"
+#define XO15_EBOOK_HID "XO15EBK"
+#define XO15_EBOOK_DEVICE_NAME "EBook Switch"
+
+ACPI_MODULE_NAME(MODULE_NAME);
+
+MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver");
+MODULE_LICENSE("GPL");
+
+static const struct acpi_device_id ebook_device_ids[] = {
+ { XO15_EBOOK_HID, 0 },
+ { "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, ebook_device_ids);
+
+struct ebook_switch {
+ struct input_dev *input;
+ char phys[32]; /* for input device */
+};
+
+static int ebook_send_state(struct acpi_device *device)
+{
+ struct ebook_switch *button = acpi_driver_data(device);
+ unsigned long long state;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, "EBK", NULL, &state);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ /* input layer checks if event is redundant */
+ input_report_switch(button->input, SW_TABLET_MODE, !state);
+ input_sync(button->input);
+ return 0;
+}
+
+static void ebook_switch_notify(struct acpi_device *device, u32 event)
+{
+ switch (event) {
+ case ACPI_FIXED_HARDWARE_EVENT:
+ case XO15_EBOOK_NOTIFY_STATUS:
+ ebook_send_state(device);
+ break;
+ default:
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+ "Unsupported event [0x%x]\n", event));
+ break;
+ }
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ebook_switch_resume(struct device *dev)
+{
+ return ebook_send_state(to_acpi_device(dev));
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(ebook_switch_pm, NULL, ebook_switch_resume);
+
+static int ebook_switch_add(struct acpi_device *device)
+{
+ struct ebook_switch *button;
+ struct input_dev *input;
+ const char *hid = acpi_device_hid(device);
+ char *name, *class;
+ int error;
+
+ button = kzalloc(sizeof(struct ebook_switch), GFP_KERNEL);
+ if (!button)
+ return -ENOMEM;
+
+ device->driver_data = button;
+
+ button->input = input = input_allocate_device();
+ if (!input) {
+ error = -ENOMEM;
+ goto err_free_button;
+ }
+
+ name = acpi_device_name(device);
+ class = acpi_device_class(device);
+
+ if (strcmp(hid, XO15_EBOOK_HID)) {
+ pr_err("Unsupported hid [%s]\n", hid);
+ error = -ENODEV;
+ goto err_free_input;
+ }
+
+ strcpy(name, XO15_EBOOK_DEVICE_NAME);
+ sprintf(class, "%s/%s", XO15_EBOOK_CLASS, XO15_EBOOK_SUBCLASS);
+
+ snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid);
+
+ input->name = name;
+ input->phys = button->phys;
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = &device->dev;
+
+ input->evbit[0] = BIT_MASK(EV_SW);
+ set_bit(SW_TABLET_MODE, input->swbit);
+
+ error = input_register_device(input);
+ if (error)
+ goto err_free_input;
+
+ ebook_send_state(device);
+
+ if (device->wakeup.flags.valid) {
+ /* Button's GPE is run-wake GPE */
+ acpi_enable_gpe(device->wakeup.gpe_device,
+ device->wakeup.gpe_number);
+ device_set_wakeup_enable(&device->dev, true);
+ }
+
+ return 0;
+
+ err_free_input:
+ input_free_device(input);
+ err_free_button:
+ kfree(button);
+ return error;
+}
+
+static int ebook_switch_remove(struct acpi_device *device)
+{
+ struct ebook_switch *button = acpi_driver_data(device);
+
+ input_unregister_device(button->input);
+ kfree(button);
+ return 0;
+}
+
+static struct acpi_driver xo15_ebook_driver = {
+ .name = MODULE_NAME,
+ .class = XO15_EBOOK_CLASS,
+ .ids = ebook_device_ids,
+ .ops = {
+ .add = ebook_switch_add,
+ .remove = ebook_switch_remove,
+ .notify = ebook_switch_notify,
+ },
+ .drv.pm = &ebook_switch_pm,
+};
+module_acpi_driver(xo15_ebook_driver);