diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/platform | |
Diffstat (limited to 'drivers/platform')
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 **)®_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 = ®S_##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 = ®S_##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", ®)) { + 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(¬es, 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(¶m, 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), + ¶ms + }; + 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(¶ms, &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(¶ms, &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(¶m, 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, ¶ms, 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, + ¶ms, &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 = ¶m; + 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 = ¶m; + 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 = ¶m; + 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, ¶ms, 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, ¶ms[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", ¶ms, &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", ¶ms, 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(¶m, 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, + ¶ms, 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 = ∈ + status = acpi_evaluate_object(handle, method, ¶ms, &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, ¶ms, 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(¤t_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, ¶ms, 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, ¶ms, + &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); |
