diff options
Diffstat (limited to 'drivers/platform')
35 files changed, 2428 insertions, 1617 deletions
diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 6f09da4dadb8..6aa120cd0574 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -391,7 +391,6 @@ static int ec_device_probe(struct platform_device *pdev) int retval = -ENOMEM; struct device *dev = &pdev->dev; struct cros_ec_platform *ec_platform = dev_get_platdata(dev); - dev_t devno = MKDEV(ec_major, pdev->id); struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); if (!ec) @@ -407,23 +406,11 @@ static int ec_device_probe(struct platform_device *pdev) cdev_init(&ec->cdev, &fops); /* - * Add the character device - * Link cdev to the class device to be sure device is not used - * before unbinding it. - */ - ec->cdev.kobj.parent = &ec->class_dev.kobj; - retval = cdev_add(&ec->cdev, devno, 1); - if (retval) { - dev_err(dev, ": failed to add character device\n"); - goto cdev_add_failed; - } - - /* * Add the class device * Link to the character device for creating the /dev entry * in devtmpfs. */ - ec->class_dev.devt = ec->cdev.dev; + ec->class_dev.devt = MKDEV(ec_major, pdev->id); ec->class_dev.class = &cros_class; ec->class_dev.parent = dev; ec->class_dev.release = __remove; @@ -431,13 +418,13 @@ static int ec_device_probe(struct platform_device *pdev) retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); if (retval) { dev_err(dev, "dev_set_name failed => %d\n", retval); - goto set_named_failed; + goto failed; } - retval = device_add(&ec->class_dev); + retval = cdev_device_add(&ec->cdev, &ec->class_dev); if (retval) { - dev_err(dev, "device_register failed => %d\n", retval); - goto dev_reg_failed; + dev_err(dev, "cdev_device_add failed => %d\n", retval); + goto failed; } /* check whether this EC is a sensor hub. */ @@ -446,12 +433,8 @@ static int ec_device_probe(struct platform_device *pdev) return 0; -dev_reg_failed: -set_named_failed: - dev_set_drvdata(dev, NULL); - cdev_del(&ec->cdev); -cdev_add_failed: - kfree(ec); +failed: + put_device(&ec->class_dev); return retval; } diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 2b21033f11f0..2de1e603bd2b 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -1,8 +1,8 @@ /* - * Copyright (C) 2011 Google, Inc. * Copyright (C) 2012 Intel, Inc. * Copyright (C) 2013 Intel, Inc. * Copyright (C) 2014 Linaro Limited + * Copyright (C) 2011-2016 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -46,6 +46,7 @@ * exchange is properly mapped during a transfer. */ + #include <linux/module.h> #include <linux/interrupt.h> #include <linux/kernel.h> @@ -63,122 +64,232 @@ #include <linux/acpi.h> /* + * Update this when something changes in the driver's behavior so the host + * can benefit from knowing it + */ +enum { + PIPE_DRIVER_VERSION = 2, + PIPE_CURRENT_DEVICE_VERSION = 2 +}; + +/* * 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_CHANNEL_HIGH 0x30 /* read/write: channel id */ -#define PIPE_REG_SIZE 0x0c /* read/write: buffer size */ -#define PIPE_REG_ADDRESS 0x10 /* write: physical address */ -#define PIPE_REG_ADDRESS_HIGH 0x34 /* 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 */ -#define PIPE_REG_VERSION 0x24 /* read: device version */ - -/* 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 */ -#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 */ +enum PipePollFlags { + PIPE_POLL_IN = 1 << 0, + PIPE_POLL_OUT = 1 << 1, + PIPE_POLL_HUP = 1 << 2 +}; /* 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 +enum PipeErrors { + PIPE_ERROR_INVAL = -1, + PIPE_ERROR_AGAIN = -2, + PIPE_ERROR_NOMEM = -3, + 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 { - unsigned long channel; - u32 size; - unsigned long address; - u32 cmd; - u32 result; - /* reserved for future extension */ +enum PipeWakeFlags { + PIPE_WAKE_CLOSED = 1 << 0, /* emulator closed pipe */ + PIPE_WAKE_READ = 1 << 1, /* pipe can now be read from */ + PIPE_WAKE_WRITE = 1 << 2 /* pipe can now be written to */ +}; + +/* Bit flags for the 'flags' field */ +enum PipeFlagsBits { + 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 */ +}; + +enum PipeRegs { + PIPE_REG_CMD = 0, + + PIPE_REG_SIGNAL_BUFFER_HIGH = 4, + PIPE_REG_SIGNAL_BUFFER = 8, + PIPE_REG_SIGNAL_BUFFER_COUNT = 12, + + PIPE_REG_OPEN_BUFFER_HIGH = 20, + PIPE_REG_OPEN_BUFFER = 24, + + PIPE_REG_VERSION = 36, + + PIPE_REG_GET_SIGNALLED = 48, +}; + +enum PipeCmdCode { + PIPE_CMD_OPEN = 1, /* to be used by the pipe device itself */ + PIPE_CMD_CLOSE, + PIPE_CMD_POLL, + PIPE_CMD_WRITE, + PIPE_CMD_WAKE_ON_WRITE, + PIPE_CMD_READ, + PIPE_CMD_WAKE_ON_READ, + + /* + * TODO(zyy): implement a deferred read/write execution to allow + * parallel processing of pipe operations on the host. + */ + PIPE_CMD_WAKE_ON_DONE_IO, +}; + +enum { + MAX_BUFFERS_PER_COMMAND = 336, + MAX_SIGNALLED_PIPES = 64, + INITIAL_PIPES_CAPACITY = 64 +}; + +struct goldfish_pipe_dev; +struct goldfish_pipe; +struct goldfish_pipe_command; + +/* A per-pipe command structure, shared with the host */ +struct goldfish_pipe_command { + s32 cmd; /* PipeCmdCode, guest -> host */ + s32 id; /* pipe id, guest -> host */ + s32 status; /* command execution status, host -> guest */ + s32 reserved; /* to pad to 64-bit boundary */ + union { + /* Parameters for PIPE_CMD_{READ,WRITE} */ + struct { + /* number of buffers, guest -> host */ + u32 buffers_count; + /* number of consumed bytes, host -> guest */ + s32 consumed_size; + /* buffer pointers, guest -> host */ + u64 ptrs[MAX_BUFFERS_PER_COMMAND]; + /* buffer sizes, guest -> host */ + u32 sizes[MAX_BUFFERS_PER_COMMAND]; + } rw_params; + }; +}; + +/* A single signalled pipe information */ +struct signalled_pipe_buffer { + u32 id; 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; - u32 version; +/* Parameters for the PIPE_CMD_OPEN command */ +struct open_command_param { + u64 command_buffer_ptr; + u32 rw_params_max_count; }; -static struct goldfish_pipe_dev pipe_dev[1]; +/* Device-level set of buffers shared with the host */ +struct goldfish_pipe_dev_buffers { + struct open_command_param open_command_params; + struct signalled_pipe_buffer signalled_pipe_buffers[ + MAX_SIGNALLED_PIPES]; +}; /* This data type models a given pipe instance */ struct goldfish_pipe { - struct goldfish_pipe_dev *dev; - struct mutex lock; + /* pipe ID - index into goldfish_pipe_dev::pipes array */ + u32 id; + /* The wake flags pipe is waiting for + * Note: not protected with any lock, uses atomic operations + * and barriers to make it thread-safe. + */ unsigned long flags; + /* wake flags host have signalled, + * - protected by goldfish_pipe_dev::lock + */ + unsigned long signalled_flags; + + /* A pointer to command buffer */ + struct goldfish_pipe_command *command_buffer; + + /* doubly linked list of signalled pipes, protected by + * goldfish_pipe_dev::lock + */ + struct goldfish_pipe *prev_signalled; + struct goldfish_pipe *next_signalled; + + /* + * A pipe's own lock. Protects the following: + * - *command_buffer - makes sure a command can safely write its + * parameters to the host and read the results back. + */ + struct mutex lock; + + /* A wake queue for sleeping until host signals an event */ wait_queue_head_t wake_queue; + /* Pointer to the parent goldfish_pipe_dev instance */ + struct goldfish_pipe_dev *dev; }; +/* 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 { + /* + * Global device spinlock. Protects the following members: + * - pipes, pipes_capacity + * - [*pipes, *pipes + pipes_capacity) - array data + * - first_signalled_pipe, + * goldfish_pipe::prev_signalled, + * goldfish_pipe::next_signalled, + * goldfish_pipe::signalled_flags - all singnalled-related fields, + * in all allocated pipes + * - open_command_params - PIPE_CMD_OPEN-related buffers + * + * It looks like a lot of different fields, but the trick is that + * the only operation that happens often is the signalled pipes array + * manipulation. That's why it's OK for now to keep the rest of the + * fields under the same lock. If we notice too much contention because + * of PIPE_CMD_OPEN, then we should add a separate lock there. + */ + spinlock_t lock; -/* 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 */ + /* + * Array of the pipes of |pipes_capacity| elements, + * indexed by goldfish_pipe::id + */ + struct goldfish_pipe **pipes; + u32 pipes_capacity; + + /* Pointers to the buffers host uses for interaction with this driver */ + struct goldfish_pipe_dev_buffers *buffers; + + /* Head of a doubly linked list of signalled pipes */ + struct goldfish_pipe *first_signalled_pipe; + + /* Some device-specific data */ + int irq; + int version; + unsigned char __iomem *base; }; +struct goldfish_pipe_dev pipe_dev[1] = {}; -static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd) +static int goldfish_cmd_locked(struct goldfish_pipe *pipe, enum PipeCmdCode cmd) { - unsigned long flags; - u32 status; - struct goldfish_pipe_dev *dev = pipe->dev; - - spin_lock_irqsave(&dev->lock, flags); - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(cmd, dev->base + PIPE_REG_COMMAND); - status = readl(dev->base + PIPE_REG_STATUS); - spin_unlock_irqrestore(&dev->lock, flags); - return status; + pipe->command_buffer->cmd = cmd; + /* failure by default */ + pipe->command_buffer->status = PIPE_ERROR_INVAL; + writel(pipe->id, pipe->dev->base + PIPE_REG_CMD); + return pipe->command_buffer->status; } -static void goldfish_cmd(struct goldfish_pipe *pipe, u32 cmd) +static int goldfish_cmd(struct goldfish_pipe *pipe, enum PipeCmdCode cmd) { - unsigned long flags; - struct goldfish_pipe_dev *dev = pipe->dev; + int status; - spin_lock_irqsave(&dev->lock, flags); - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(cmd, dev->base + PIPE_REG_COMMAND); - spin_unlock_irqrestore(&dev->lock, flags); + if (mutex_lock_interruptible(&pipe->lock)) + return PIPE_ERROR_IO; + status = goldfish_cmd_locked(pipe, cmd); + mutex_unlock(&pipe->lock); + return status; } -/* This function converts an error code returned by the emulator through +/* + * 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) @@ -195,184 +306,202 @@ static int goldfish_pipe_error_convert(int status) } } -/* - * 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) +static int pin_user_pages(unsigned long first_page, unsigned long last_page, + unsigned int last_page_size, int is_write, + struct page *pages[MAX_BUFFERS_PER_COMMAND], + unsigned int *iter_last_page_size) { - u32 aph, apl; - u64 paddr; - aph = readl(dev->base + PIPE_REG_PARAMS_ADDR_HIGH); - apl = readl(dev->base + PIPE_REG_PARAMS_ADDR_LOW); + int ret; + int requested_pages = ((last_page - first_page) >> PAGE_SHIFT) + 1; + + if (requested_pages > MAX_BUFFERS_PER_COMMAND) { + requested_pages = MAX_BUFFERS_PER_COMMAND; + *iter_last_page_size = PAGE_SIZE; + } else { + *iter_last_page_size = last_page_size; + } + + ret = get_user_pages_fast( + first_page, requested_pages, !is_write, pages); + if (ret <= 0) + return -EFAULT; + if (ret < requested_pages) + *iter_last_page_size = PAGE_SIZE; + return ret; - 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) +static void release_user_pages(struct page **pages, int pages_count, + int is_write, s32 consumed_size) { - dma_addr_t dma_handle; - struct access_params *aps; + int i; - aps = dmam_alloc_coherent(&pdev->dev, sizeof(struct access_params), - &dma_handle, GFP_KERNEL); - if (!aps) - return -ENOMEM; + for (i = 0; i < pages_count; i++) { + if (!is_write && consumed_size > 0) + set_page_dirty(pages[i]); + put_page(pages[i]); + } +} + +/* Populate the call parameters, merging adjacent pages together */ +static void populate_rw_params( + struct page **pages, int pages_count, + unsigned long address, unsigned long address_end, + unsigned long first_page, unsigned long last_page, + unsigned int iter_last_page_size, int is_write, + struct goldfish_pipe_command *command) +{ + /* + * Process the first page separately - it's the only page that + * needs special handling for its start address. + */ + unsigned long xaddr = page_to_phys(pages[0]); + unsigned long xaddr_prev = xaddr; + int buffer_idx = 0; + int i = 1; + int size_on_page = first_page == last_page + ? (int)(address_end - address) + : (PAGE_SIZE - (address & ~PAGE_MASK)); + command->rw_params.ptrs[0] = (u64)(xaddr | (address & ~PAGE_MASK)); + command->rw_params.sizes[0] = size_on_page; + for (; i < pages_count; ++i) { + xaddr = page_to_phys(pages[i]); + size_on_page = (i == pages_count - 1) ? + iter_last_page_size : PAGE_SIZE; + if (xaddr == xaddr_prev + PAGE_SIZE) { + command->rw_params.sizes[buffer_idx] += size_on_page; + } else { + ++buffer_idx; + command->rw_params.ptrs[buffer_idx] = (u64)xaddr; + command->rw_params.sizes[buffer_idx] = size_on_page; + } + xaddr_prev = xaddr; + } + command->rw_params.buffers_count = buffer_idx + 1; +} - writel(upper_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_HIGH); - writel(lower_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_LOW); +static int transfer_max_buffers(struct goldfish_pipe *pipe, + unsigned long address, unsigned long address_end, int is_write, + unsigned long last_page, unsigned int last_page_size, + s32 *consumed_size, int *status) +{ + static struct page *pages[MAX_BUFFERS_PER_COMMAND]; + unsigned long first_page = address & PAGE_MASK; + unsigned int iter_last_page_size; + int pages_count = pin_user_pages(first_page, last_page, + last_page_size, is_write, + pages, &iter_last_page_size); - if (valid_batchbuffer_addr(dev, aps)) { - dev->aps = aps; - return 0; - } else - return -1; + if (pages_count < 0) + return pages_count; + + /* Serialize access to the pipe command buffers */ + if (mutex_lock_interruptible(&pipe->lock)) + return -ERESTARTSYS; + + populate_rw_params(pages, pages_count, address, address_end, + first_page, last_page, iter_last_page_size, is_write, + pipe->command_buffer); + + /* Transfer the data */ + *status = goldfish_cmd_locked(pipe, + is_write ? PIPE_CMD_WRITE : PIPE_CMD_READ); + + *consumed_size = pipe->command_buffer->rw_params.consumed_size; + + release_user_pages(pages, pages_count, is_write, *consumed_size); + + mutex_unlock(&pipe->lock); + + return 0; } -/* 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) +static int wait_for_host_signal(struct goldfish_pipe *pipe, int is_write) { - struct access_params *aps = dev->aps; + u32 wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ; - if (aps == NULL) - return -1; + set_bit(wakeBit, &pipe->flags); + + /* Tell the emulator we're going to wait for a wake event */ + (void)goldfish_cmd(pipe, + is_write ? PIPE_CMD_WAKE_ON_WRITE : PIPE_CMD_WAKE_ON_READ); + + 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; + } - 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; } -static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, - size_t bufflen, int is_write) +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; - unsigned long address, address_end; int count = 0, ret = -EINVAL; + unsigned long address, address_end, last_page; + unsigned int last_page_size; /* If the emulator already closed the pipe, no need to go further */ - if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)) + if (unlikely(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)) + if (unlikely(!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 = (unsigned long)buffer; address_end = address + bufflen; + last_page = (address_end - 1) & PAGE_MASK; + last_page_size = ((address_end - 1) & ~PAGE_MASK) + 1; 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; - struct page *page; - - /* Either vaddr or paddr depending on the device version */ - unsigned long xaddr; + s32 consumed_size; + int status; - /* - * We grab the pages on a page-by-page basis in case user - * space gives us a potentially huge buffer but the read only - * returns a small amount, then there's no need to pin that - * much memory to the process. - */ - ret = get_user_pages_unlocked(address, 1, &page, - is_write ? 0 : FOLL_WRITE); + ret = transfer_max_buffers(pipe, address, address_end, is_write, + last_page, last_page_size, &consumed_size, + &status); if (ret < 0) break; - if (dev->version) { - /* Device version 1 or newer (qemu-android) expects the - * physical address. + if (consumed_size > 0) { + /* No matter what's the status, we've transferred + * something. */ - xaddr = page_to_phys(page) | (address & ~PAGE_MASK); - } else { - /* Device version 0 (classic emulator) expects the - * virtual address. - */ - xaddr = address; + count += consumed_size; + address += consumed_size; } - - /* Now, try to transfer the bytes in the current page */ - spin_lock_irqsave(&dev->lock, irq_flags); - if (access_with_param(dev, - is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER, - xaddr, avail, pipe, &status)) { - gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL, - dev->base + PIPE_REG_CHANNEL_HIGH); - writel(avail, dev->base + PIPE_REG_SIZE); - gf_write_ptr((void *)xaddr, - dev->base + PIPE_REG_ADDRESS, - dev->base + PIPE_REG_ADDRESS_HIGH); - writel(is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER, - dev->base + PIPE_REG_COMMAND); - status = readl(dev->base + PIPE_REG_STATUS); - } - spin_unlock_irqrestore(&dev->lock, irq_flags); - - if (status > 0 && !is_write) - set_page_dirty(page); - put_page(page); - - if (status > 0) { /* Correct transfer */ - count += status; - address += status; + if (status > 0) continue; - } else if (status == 0) { /* EOF */ + if (status == 0) { + /* EOF */ ret = 0; break; - } else if (status < 0 && count > 0) { + } + if (count > 0) { /* - * An error occurred and we already transferred - * something on one of the previous pages. + * An error occurred, but we already transferred + * something on one of the previous iterations. * Just return what we already copied and log this * err. - * - * Note: This seems like an incorrect approach but - * cannot change it until we check if any user space - * ABI relies on this behavior. */ if (status != PIPE_ERROR_AGAIN) - pr_info_ratelimited("goldfish_pipe: backend returned error %d on %s\n", + pr_info_ratelimited("goldfish_pipe: backend error %d on %s\n", status, is_write ? "write" : "read"); - ret = 0; break; } /* - * If the error is not PIPE_ERROR_AGAIN, or if we are not in + * If the error is not PIPE_ERROR_AGAIN, or if we are in * non-blocking mode, just return the error code. */ if (status != PIPE_ERROR_AGAIN || @@ -381,139 +510,214 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer, break; } - /* - * The backend blocked the read/write, wait until the backend - * tells us it's ready to process more data. - */ - 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, - is_write ? CMD_WAKE_ON_WRITE : CMD_WAKE_ON_READ); - - /* 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; + status = wait_for_host_signal(pipe, is_write); + if (status < 0) + return status; } - mutex_unlock(&pipe->lock); - if (ret < 0) - return ret; - else + if (count > 0) return count; + return ret; } static ssize_t goldfish_pipe_read(struct file *filp, char __user *buffer, - size_t bufflen, loff_t *ppos) + size_t bufflen, loff_t *ppos) { - return goldfish_pipe_read_write(filp, buffer, bufflen, 0); + return goldfish_pipe_read_write(filp, buffer, bufflen, + /* is_write */ 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); + return goldfish_pipe_read_write(filp, + /* cast away the const */(char __user *)buffer, bufflen, + /* is_write */ 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); + status = goldfish_cmd(pipe, PIPE_CMD_POLL); + if (status < 0) + return -ERESTARTSYS; 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) +static void signalled_pipes_add_locked(struct goldfish_pipe_dev *dev, + u32 id, u32 flags) { - struct goldfish_pipe_dev *dev = dev_id; - unsigned long irq_flags; - int count = 0; + struct goldfish_pipe *pipe; - /* - * We're going to read from the emulator a list of (channel,flags) - * pairs corresponding to the wake events that occurred 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 = 0; + if (WARN_ON(id >= dev->pipes_capacity)) + return; + + pipe = dev->pipes[id]; + if (!pipe) + return; + pipe->signalled_flags |= flags; + + if (pipe->prev_signalled || pipe->next_signalled + || dev->first_signalled_pipe == pipe) + return; /* already in the list */ + pipe->next_signalled = dev->first_signalled_pipe; + if (dev->first_signalled_pipe) + dev->first_signalled_pipe->prev_signalled = pipe; + dev->first_signalled_pipe = pipe; +} -#ifdef CONFIG_64BIT - channel = (u64)readl(dev->base + PIPE_REG_CHANNEL_HIGH) << 32; +static void signalled_pipes_remove_locked(struct goldfish_pipe_dev *dev, + struct goldfish_pipe *pipe) { + if (pipe->prev_signalled) + pipe->prev_signalled->next_signalled = pipe->next_signalled; + if (pipe->next_signalled) + pipe->next_signalled->prev_signalled = pipe->prev_signalled; + if (pipe == dev->first_signalled_pipe) + dev->first_signalled_pipe = pipe->next_signalled; + pipe->prev_signalled = NULL; + pipe->next_signalled = NULL; +} - if (channel == 0) - break; -#endif - channel |= readl(dev->base + PIPE_REG_CHANNEL); +static struct goldfish_pipe *signalled_pipes_pop_front( + struct goldfish_pipe_dev *dev, int *wakes) +{ + struct goldfish_pipe *pipe; + unsigned long flags; - if (channel == 0) - break; + spin_lock_irqsave(&dev->lock, flags); - /* Convert channel to struct pipe pointer + read wake flags */ - wakes = readl(dev->base + PIPE_REG_WAKES); - pipe = (struct goldfish_pipe *)(ptrdiff_t)channel; + pipe = dev->first_signalled_pipe; + if (pipe) { + *wakes = pipe->signalled_flags; + pipe->signalled_flags = 0; + /* + * This is an optimized version of + * signalled_pipes_remove_locked() + * - We want to make it as fast as possible to + * wake the sleeping pipe operations faster. + */ + dev->first_signalled_pipe = pipe->next_signalled; + if (dev->first_signalled_pipe) + dev->first_signalled_pipe->prev_signalled = NULL; + pipe->next_signalled = NULL; + } - /* Did the emulator just closed a pipe? */ + spin_unlock_irqrestore(&dev->lock, flags); + return pipe; +} + +static void goldfish_interrupt_task(unsigned long unused) +{ + struct goldfish_pipe_dev *dev = pipe_dev; + /* Iterate over the signalled pipes and wake them one by one */ + struct goldfish_pipe *pipe; + int wakes; + + while ((pipe = signalled_pipes_pop_front(dev, &wakes)) != NULL) { if (wakes & PIPE_WAKE_CLOSED) { - set_bit(BIT_CLOSED_ON_HOST, &pipe->flags); - wakes |= PIPE_WAKE_READ | PIPE_WAKE_WRITE; + pipe->flags = 1 << BIT_CLOSED_ON_HOST; + } else { + 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); } - 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() implies a write barrier, so don't + * explicitly add another one here. + */ wake_up_interruptible(&pipe->wake_queue); - count++; } - spin_unlock_irqrestore(&dev->lock, irq_flags); +} +DECLARE_TASKLET(goldfish_interrupt_tasklet, goldfish_interrupt_task, 0); - return (count == 0) ? IRQ_NONE : IRQ_HANDLED; +/* + * The general idea of the interrupt handling: + * + * 1. device raises an interrupt if there's at least one signalled pipe + * 2. IRQ handler reads the signalled pipes and their count from the device + * 3. device writes them into a shared buffer and returns the count + * it only resets the IRQ if it has returned all signalled pipes, + * otherwise it leaves it raised, so IRQ handler will be called + * again for the next chunk + * 4. IRQ handler adds all returned pipes to the device's signalled pipes list + * 5. IRQ handler launches a tasklet to process the signalled pipes from the + * list in a separate context + */ +static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) +{ + u32 count; + u32 i; + unsigned long flags; + struct goldfish_pipe_dev *dev = dev_id; + + if (dev != pipe_dev) + return IRQ_NONE; + + /* Request the signalled pipes from the device */ + spin_lock_irqsave(&dev->lock, flags); + + count = readl(dev->base + PIPE_REG_GET_SIGNALLED); + if (count == 0) { + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_NONE; + } + if (count > MAX_SIGNALLED_PIPES) + count = MAX_SIGNALLED_PIPES; + + for (i = 0; i < count; ++i) + signalled_pipes_add_locked(dev, + dev->buffers->signalled_pipe_buffers[i].id, + dev->buffers->signalled_pipe_buffers[i].flags); + + spin_unlock_irqrestore(&dev->lock, flags); + + tasklet_schedule(&goldfish_interrupt_tasklet); + return IRQ_HANDLED; +} + +static int get_free_pipe_id_locked(struct goldfish_pipe_dev *dev) +{ + int id; + + for (id = 0; id < dev->pipes_capacity; ++id) + if (!dev->pipes[id]) + return id; + + { + /* Reallocate the array */ + u32 new_capacity = 2 * dev->pipes_capacity; + struct goldfish_pipe **pipes = + kcalloc(new_capacity, sizeof(*pipes), GFP_KERNEL); + if (!pipes) + return -ENOMEM; + memcpy(pipes, dev->pipes, sizeof(*pipes) * dev->pipes_capacity); + kfree(dev->pipes); + dev->pipes = pipes; + id = dev->pipes_capacity; + dev->pipes_capacity = new_capacity; + } + return id; } /** - * goldfish_pipe_open - open a channel to the AVD + * goldfish_pipe_open - open a channel to the AVD * @inode: inode of device * @file: file struct of opener * @@ -525,12 +729,13 @@ static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id) */ 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; + unsigned long flags; + int id; + int status; /* Allocate new pipe kernel object */ - pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); + struct goldfish_pipe *pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); if (pipe == NULL) return -ENOMEM; @@ -539,29 +744,69 @@ static int goldfish_pipe_open(struct inode *inode, struct file *file) 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. + * Command buffer needs to be allocated on its own page to make sure + * it is physically contiguous in host's address space. */ + pipe->command_buffer = + (struct goldfish_pipe_command *)__get_free_page(GFP_KERNEL); + if (!pipe->command_buffer) { + status = -ENOMEM; + goto err_pipe; + } - status = goldfish_cmd_status(pipe, CMD_OPEN); - if (status < 0) { - kfree(pipe); - return status; + spin_lock_irqsave(&dev->lock, flags); + + id = get_free_pipe_id_locked(dev); + if (id < 0) { + status = id; + goto err_id_locked; } + dev->pipes[id] = pipe; + pipe->id = id; + pipe->command_buffer->id = id; + + /* Now tell the emulator we're opening a new pipe. */ + dev->buffers->open_command_params.rw_params_max_count = + MAX_BUFFERS_PER_COMMAND; + dev->buffers->open_command_params.command_buffer_ptr = + (u64)(unsigned long)__pa(pipe->command_buffer); + status = goldfish_cmd_locked(pipe, PIPE_CMD_OPEN); + spin_unlock_irqrestore(&dev->lock, flags); + if (status < 0) + goto err_cmd; /* All is done, save the pipe into the file's private data field */ file->private_data = pipe; return 0; + +err_cmd: + spin_lock_irqsave(&dev->lock, flags); + dev->pipes[id] = NULL; +err_id_locked: + spin_unlock_irqrestore(&dev->lock, flags); + free_page((unsigned long)pipe->command_buffer); +err_pipe: + kfree(pipe); + return status; } static int goldfish_pipe_release(struct inode *inode, struct file *filp) { + unsigned long flags; struct goldfish_pipe *pipe = filp->private_data; + struct goldfish_pipe_dev *dev = pipe->dev; /* The guest is closing the channel, so tell the emulator right now */ - goldfish_cmd(pipe, CMD_CLOSE); - kfree(pipe); + (void)goldfish_cmd(pipe, PIPE_CMD_CLOSE); + + spin_lock_irqsave(&dev->lock, flags); + dev->pipes[pipe->id] = NULL; + signalled_pipes_remove_locked(dev, pipe); + spin_unlock_irqrestore(&dev->lock, flags); + filp->private_data = NULL; + free_page((unsigned long)pipe->command_buffer); + kfree(pipe); return 0; } @@ -574,18 +819,91 @@ static const struct file_operations goldfish_pipe_fops = { .release = goldfish_pipe_release, }; -static struct miscdevice goldfish_pipe_device = { +static struct miscdevice goldfish_pipe_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "goldfish_pipe", .fops = &goldfish_pipe_fops, }; +static int goldfish_pipe_device_init(struct platform_device *pdev) +{ + char *page; + struct goldfish_pipe_dev *dev = pipe_dev; + int 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 for v2\n"); + return err; + } + + err = misc_register(&goldfish_pipe_dev); + if (err) { + dev_err(&pdev->dev, "unable to register v2 device\n"); + return err; + } + + dev->first_signalled_pipe = NULL; + dev->pipes_capacity = INITIAL_PIPES_CAPACITY; + dev->pipes = kcalloc(dev->pipes_capacity, sizeof(*dev->pipes), + GFP_KERNEL); + if (!dev->pipes) + return -ENOMEM; + + /* + * We're going to pass two buffers, open_command_params and + * signalled_pipe_buffers, to the host. This means each of those buffers + * needs to be contained in a single physical page. The easiest choice + * is to just allocate a page and place the buffers in it. + */ + if (WARN_ON(sizeof(*dev->buffers) > PAGE_SIZE)) + return -ENOMEM; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) { + kfree(dev->pipes); + return -ENOMEM; + } + dev->buffers = (struct goldfish_pipe_dev_buffers *)page; + + /* Send the buffer addresses to the host */ + { + u64 paddr = __pa(&dev->buffers->signalled_pipe_buffers); + + writel((u32)(unsigned long)(paddr >> 32), + dev->base + PIPE_REG_SIGNAL_BUFFER_HIGH); + writel((u32)(unsigned long)paddr, + dev->base + PIPE_REG_SIGNAL_BUFFER); + writel((u32)MAX_SIGNALLED_PIPES, + dev->base + PIPE_REG_SIGNAL_BUFFER_COUNT); + + paddr = __pa(&dev->buffers->open_command_params); + writel((u32)(unsigned long)(paddr >> 32), + dev->base + PIPE_REG_OPEN_BUFFER_HIGH); + writel((u32)(unsigned long)paddr, + dev->base + PIPE_REG_OPEN_BUFFER); + } + return 0; +} + +static void goldfish_pipe_device_deinit(struct platform_device *pdev) +{ + struct goldfish_pipe_dev *dev = pipe_dev; + + misc_deregister(&goldfish_pipe_dev); + kfree(dev->pipes); + free_page((unsigned long)dev->buffers); +} + static int goldfish_pipe_probe(struct platform_device *pdev) { int err; struct resource *r; struct goldfish_pipe_dev *dev = pipe_dev; + if (WARN_ON(sizeof(struct goldfish_pipe_command) > PAGE_SIZE)) + return -ENOMEM; + /* not thread safe, but this should not happen */ WARN_ON(dev->base != NULL); @@ -609,26 +927,21 @@ static int goldfish_pipe_probe(struct platform_device *pdev) } 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); - - /* Although the pipe device in the classic Android emulator does not - * recognize the 'version' register, it won't treat this as an error - * either and will simply return 0, which is fine. + /* + * Exchange the versions with the host device + * + * Note: v1 driver used to not report its version, so we write it before + * reading device version back: this allows the host implementation to + * detect the old driver (if there was no version write before read). */ + writel((u32)PIPE_DRIVER_VERSION, dev->base + PIPE_REG_VERSION); dev->version = readl(dev->base + PIPE_REG_VERSION); - return 0; + if (WARN_ON(dev->version < PIPE_CURRENT_DEVICE_VERSION)) + return -EINVAL; + + err = goldfish_pipe_device_init(pdev); + if (!err) + return 0; error: dev->base = NULL; @@ -638,7 +951,7 @@ error: static int goldfish_pipe_remove(struct platform_device *pdev) { struct goldfish_pipe_dev *dev = pipe_dev; - misc_deregister(&goldfish_pipe_device); + goldfish_pipe_device_deinit(pdev); dev->base = NULL; return 0; } @@ -655,17 +968,16 @@ static const struct of_device_id goldfish_pipe_of_match[] = { }; MODULE_DEVICE_TABLE(of, goldfish_pipe_of_match); -static struct platform_driver goldfish_pipe = { +static struct platform_driver goldfish_pipe_driver = { .probe = goldfish_pipe_probe, .remove = goldfish_pipe_remove, .driver = { .name = "goldfish_pipe", - .owner = THIS_MODULE, .of_match_table = goldfish_pipe_of_match, .acpi_match_table = ACPI_PTR(goldfish_pipe_acpi_match), } }; -module_platform_driver(goldfish_pipe); +module_platform_driver(goldfish_pipe_driver); MODULE_AUTHOR("David Turner <digit@google.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4bc88eb52712..8489020ecf44 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -141,6 +141,14 @@ config DELL_WMI_AIO To compile this driver as a module, choose M here: the module will be called dell-wmi-aio. +config DELL_WMI_LED + tristate "External LED on Dell Business Netbooks" + depends on LEDS_CLASS + depends on ACPI_WMI + help + This adds support for the Latitude 2100 and similar + notebooks that have an external LED. + config DELL_SMO8800 tristate "Dell Latitude freefall driver (ACPI SMO88XX)" depends on ACPI @@ -174,7 +182,8 @@ config FUJITSU_LAPTOP depends on INPUT depends on BACKLIGHT_CLASS_DEVICE depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on LEDS_CLASS || LEDS_CLASS=n + select INPUT_SPARSEKMAP + select LEDS_CLASS ---help--- This is a driver for laptops built by Fujitsu: @@ -772,6 +781,19 @@ config ACPI_CMPC keys as input device, backlight device, tablet and accelerometer devices. +config INTEL_CHT_INT33FE + tristate "Intel Cherry Trail ACPI INT33FE Driver" + depends on X86 && ACPI && I2C + ---help--- + This driver add support for the INT33FE ACPI device found on + some Intel Cherry Trail devices. + + The INT33FE ACPI device has a CRS table with I2cSerialBusV2 + resources for 3 devices: Maxim MAX17047 Fuel Gauge Controller, + FUSB302 USB Type-C Controller and PI3USB30532 USB switch. + This driver instantiates i2c-clients for these, so that standard + i2c drivers for these chips can bind to the them. + config INTEL_HID_EVENT tristate "INTEL HID Event" depends on ACPI @@ -1011,7 +1033,7 @@ config INTEL_PMC_IPC config INTEL_BXTWC_PMIC_TMU tristate "Intel BXT Whiskey Cove TMU Driver" depends on REGMAP - depends on INTEL_SOC_PMIC && INTEL_PMC_IPC + depends on INTEL_SOC_PMIC_BXTWC && INTEL_PMC_IPC ---help--- Select this driver to use Intel BXT Whiskey Cove PMIC TMU feature. This driver enables the alarm wakeup functionality in the TMU unit @@ -1079,7 +1101,7 @@ config INTEL_TURBO_MAX_3 config SILEAD_DMI bool "Tablets with Silead touchscreens" - depends on ACPI && DMI && I2C=y && INPUT + depends on ACPI && DMI && I2C=y && TOUCHSCREEN_SILEAD ---help--- Certain ACPI based tablets with Silead touchscreens do not have enough data in ACPI tables for the touchscreen driver to handle diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 299d0f9e40f7..182a3ed6605a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.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_DELL_WMI_LED) += dell-wmi-led.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o @@ -44,6 +45,7 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o +obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index dac0fbe87460..79fa5ab3fd00 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -1896,7 +1896,7 @@ static acpi_status __init acer_wmi_get_handle_cb(acpi_handle ah, u32 level, if (!strcmp(ctx, "SENR")) { if (acpi_bus_get_device(ah, &dev)) return AE_OK; - if (!strcmp(ACER_WMID_ACCEL_HID, acpi_device_hid(dev))) + if (strcmp(ACER_WMID_ACCEL_HID, acpi_device_hid(dev))) return AE_OK; } else return AE_OK; @@ -1917,8 +1917,7 @@ static int __init acer_wmi_get_handle(const char *name, const char *prop, handle = NULL; status = acpi_get_devices(prop, acer_wmi_get_handle_cb, (void *)name, &handle); - - if (ACPI_SUCCESS(status)) { + if (ACPI_SUCCESS(status) && handle) { *ah = handle; return 0; } else { @@ -1987,7 +1986,7 @@ static int __init acer_wmi_input_setup(void) acer_wmi_notify, NULL); if (ACPI_FAILURE(status)) { err = -EIO; - goto err_free_keymap; + goto err_free_dev; } err = input_register_device(acer_wmi_input_dev); @@ -1998,8 +1997,6 @@ static int __init acer_wmi_input_setup(void) 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; @@ -2008,7 +2005,6 @@ err_free_dev: 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); } @@ -2290,8 +2286,8 @@ static int __init acer_wmi_init(void) if (err) return err; err = acer_wmi_accel_setup(); - if (err) - return err; + if (err && err != -ENODEV) + pr_warn("Cannot enable accelerometer\n"); } err = platform_driver_register(&acer_platform_driver); diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index a66be137324c..623d322447a2 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -60,6 +60,7 @@ struct apple_gmux_data { /* switcheroo data */ acpi_handle dhandle; int gpe; + bool external_switchable; enum vga_switcheroo_client_id switch_state_display; enum vga_switcheroo_client_id switch_state_ddc; enum vga_switcheroo_client_id switch_state_external; @@ -358,6 +359,19 @@ static const struct backlight_ops gmux_bl_ops = { * ports while the discrete GPU is asleep, but currently we do not make use * of this feature. * + * Our switching policy for the external port is that on those generations + * which are able to switch it fully, the port is switched together with the + * panel when IGD / DIS commands are issued to vga_switcheroo. It is thus + * possible to drive e.g. a beamer on battery power with the integrated GPU. + * The user may manually switch to the discrete GPU if more performance is + * needed. + * + * On all newer generations, the external port can only be driven by the + * discrete GPU. If a display is plugged in while the panel is switched to + * the integrated GPU, *both* GPUs will be in use for maximum performance. + * To decrease power consumption, the user may manually switch to the + * discrete GPU, thereby suspending the integrated GPU. + * * gmux' initial switch state on bootup is user configurable via the EFI * variable ``gpu-power-prefs-fa4ce28d-b62f-4c99-9cc3-6815686e30f9`` (5th byte, * 1 = IGD, 0 = DIS). Based on this setting, the EFI firmware tells gmux to @@ -414,7 +428,8 @@ static int gmux_switchto(enum vga_switcheroo_client_id id) { apple_gmux_data->switch_state_ddc = id; apple_gmux_data->switch_state_display = id; - apple_gmux_data->switch_state_external = id; + if (apple_gmux_data->external_switchable) + apple_gmux_data->switch_state_external = id; gmux_write_switch_state(apple_gmux_data); @@ -601,6 +616,11 @@ static struct pci_dev *gmux_get_io_pdev(void) return NULL; } +static int is_thunderbolt(struct device *dev, void *data) +{ + return to_pci_dev(dev)->is_thunderbolt; +} + static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) { struct apple_gmux_data *gmux_data; @@ -755,6 +775,15 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) gmux_data->gpe = -1; } + /* + * If Thunderbolt is present, the external DP port is not fully + * switchable. Force its AUX channel to the discrete GPU. + */ + gmux_data->external_switchable = + !bus_for_each_dev(&pci_bus_type, NULL, NULL, is_thunderbolt); + if (!gmux_data->external_switchable) + gmux_write8(gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3); + apple_gmux_data = gmux_data; init_completion(&gmux_data->powerchange_done); gmux_enable_interrupts(gmux_data); diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 28551f5a2e07..c4768be24ba9 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -1516,14 +1516,12 @@ static int asus_input_init(struct asus_laptop *asus) error = input_register_device(input); if (error) { pr_warn("Unable to register input device\n"); - goto err_free_keymap; + goto err_free_dev; } asus->inputdev = input; return 0; -err_free_keymap: - sparse_keymap_free(input); err_free_dev: input_free_device(input); return error; @@ -1531,10 +1529,8 @@ err_free_dev: static void asus_input_exit(struct asus_laptop *asus) { - if (asus->inputdev) { - sparse_keymap_free(asus->inputdev); + if (asus->inputdev) input_unregister_device(asus->inputdev); - } asus->inputdev = NULL; } diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index dea98ffb6f60..5269a01d9bdd 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -111,6 +111,10 @@ static struct quirk_entry quirk_asus_x550lb = { .xusb2pr = 0x01D9, }; +static struct quirk_entry quirk_asus_ux330uak = { + .wmi_force_als_set = true, +}; + static int dmi_matched(const struct dmi_system_id *dmi) { pr_info("Identified laptop model '%s'\n", dmi->ident); @@ -144,6 +148,15 @@ static const struct dmi_system_id asus_quirks[] = { }, { .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. X302UA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "X302UA"), + }, + .driver_data = &quirk_asus_wapf4, + }, + { + .callback = dmi_matched, .ident = "ASUSTeK COMPUTER INC. X401U", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), @@ -369,6 +382,15 @@ static const struct dmi_system_id asus_quirks[] = { }, { .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. UX330UAK", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "UX330UAK"), + }, + .driver_data = &quirk_asus_ux330uak, + }, + { + .callback = dmi_matched, .ident = "ASUSTeK COMPUTER INC. X550LB", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 8fe5890bf539..6c7d86074b38 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -269,12 +269,10 @@ static int asus_wmi_input_init(struct asus_wmi *asus) err = input_register_device(asus->inputdev); if (err) - goto err_free_keymap; + goto err_free_dev; return 0; -err_free_keymap: - sparse_keymap_free(asus->inputdev); err_free_dev: input_free_device(asus->inputdev); return err; @@ -282,10 +280,8 @@ err_free_dev: static void asus_wmi_input_exit(struct asus_wmi *asus) { - if (asus->inputdev) { - sparse_keymap_free(asus->inputdev); + if (asus->inputdev) input_unregister_device(asus->inputdev); - } asus->inputdev = NULL; } @@ -1109,6 +1105,15 @@ static void asus_wmi_set_xusb2pr(struct asus_wmi *asus) } /* + * Some devices dont support or have borcken get_als method + * but still support set method. + */ +static void asus_wmi_set_als(void) +{ + asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL); +} + +/* * Hwmon device */ static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan, @@ -1761,7 +1766,7 @@ ASUS_WMI_CREATE_DEVICE_ATTR(cardr, 0644, ASUS_WMI_DEVID_CARDREADER); ASUS_WMI_CREATE_DEVICE_ATTR(lid_resume, 0644, ASUS_WMI_DEVID_LID_RESUME); ASUS_WMI_CREATE_DEVICE_ATTR(als_enable, 0644, ASUS_WMI_DEVID_ALS_ENABLE); -static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr, +static ssize_t cpufv_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value, rv; @@ -1778,7 +1783,7 @@ static ssize_t store_cpufv(struct device *dev, struct device_attribute *attr, return count; } -static DEVICE_ATTR(cpufv, S_IRUGO | S_IWUSR, NULL, store_cpufv); +static DEVICE_ATTR_WO(cpufv); static struct attribute *platform_attributes[] = { &dev_attr_cpufv.attr, @@ -2117,6 +2122,9 @@ static int asus_wmi_add(struct platform_device *pdev) goto fail_rfkill; } + if (asus->driver->quirks->wmi_force_als_set) + asus_wmi_set_als(); + /* Some Asus desktop boards export an acpi-video backlight interface, stop this from showing up */ chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE); diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index c9589d9342bb..6c1311f4b04d 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -44,6 +44,7 @@ struct quirk_entry { bool store_backlight_power; bool wmi_backlight_power; bool wmi_backlight_native; + bool wmi_force_als_set; int wapf; /* * For machines with AMD graphic chips, it will send out WMI event diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index f57dd282a002..ec202094bd50 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -29,6 +29,7 @@ #include <linux/mm.h> #include <linux/i8042.h> #include <linux/debugfs.h> +#include <linux/dell-led.h> #include <linux/seq_file.h> #include <acpi/video.h> #include "dell-rbtn.h" @@ -42,6 +43,9 @@ #define KBD_LED_AUTO_50_TOKEN 0x02EB #define KBD_LED_AUTO_75_TOKEN 0x02EC #define KBD_LED_AUTO_100_TOKEN 0x02F6 +#define GLOBAL_MIC_MUTE_ENABLE 0x0364 +#define GLOBAL_MIC_MUTE_DISABLE 0x0365 +#define KBD_LED_AC_TOKEN 0x0451 struct quirk_entry { u8 touchpad_led; @@ -1024,7 +1028,7 @@ static void touchpad_led_exit(void) * bit 2 Pointing stick * bit 3 Any mouse * bits 4-7 Reserved for future use - * cbRES2, byte3 Current Timeout + * cbRES2, byte3 Current Timeout on battery * bits 7:6 Timeout units indicator: * 00b Seconds * 01b Minutes @@ -1036,6 +1040,15 @@ static void touchpad_led_exit(void) * cbRES3, byte0 Current setting of ALS value that turns the light on or off. * cbRES3, byte1 Current ALS reading * cbRES3, byte2 Current keyboard light level. + * cbRES3, byte3 Current timeout on AC Power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * Bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte2 + * are set upon return from the upon return from the [Get Feature information] call. * * cbArg1 0x2 = Set New State * cbRES1 Standard return codes (0, -1, -2) @@ -1058,7 +1071,7 @@ static void touchpad_led_exit(void) * bit 2 Pointing stick * bit 3 Any mouse * bits 4-7 Reserved for future use - * cbArg2, byte3 Desired Timeout + * cbArg2, byte3 Desired Timeout on battery * bits 7:6 Timeout units indicator: * 00b Seconds * 01b Minutes @@ -1067,6 +1080,13 @@ static void touchpad_led_exit(void) * bits 5:0 Timeout value (0-63) in sec/min/hr/day * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. * cbArg3, byte2 Desired keyboard light level. + * cbArg3, byte3 Desired Timeout on AC power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day */ @@ -1112,6 +1132,8 @@ struct kbd_state { u8 triggers; u8 timeout_value; u8 timeout_unit; + u8 timeout_value_ac; + u8 timeout_unit_ac; u8 als_setting; u8 als_value; u8 level; @@ -1131,6 +1153,7 @@ static u16 kbd_token_bits; static struct kbd_info kbd_info; static bool kbd_als_supported; static bool kbd_triggers_supported; +static bool kbd_timeout_ac_supported; static u8 kbd_mode_levels[16]; static int kbd_mode_levels_count; @@ -1139,6 +1162,7 @@ static u8 kbd_previous_level; static u8 kbd_previous_mode_bit; static bool kbd_led_present; +static DEFINE_MUTEX(kbd_led_mutex); /* * NOTE: there are three ways to set the keyboard backlight level. @@ -1269,6 +1293,8 @@ static int kbd_get_state(struct kbd_state *state) state->als_setting = buffer->output[2] & 0xFF; state->als_value = (buffer->output[2] >> 8) & 0xFF; state->level = (buffer->output[2] >> 16) & 0xFF; + state->timeout_value_ac = (buffer->output[2] >> 24) & 0x3F; + state->timeout_unit_ac = (buffer->output[2] >> 30) & 0x3; out: dell_smbios_release_buffer(); @@ -1288,6 +1314,8 @@ static int kbd_set_state(struct kbd_state *state) buffer->input[1] |= (state->timeout_unit & 0x3) << 30; buffer->input[2] = state->als_setting & 0xFF; buffer->input[2] |= (state->level & 0xFF) << 16; + buffer->input[2] |= (state->timeout_value_ac & 0x3F) << 24; + buffer->input[2] |= (state->timeout_unit_ac & 0x3) << 30; dell_smbios_send_request(4, 11); ret = buffer->output[0]; dell_smbios_release_buffer(); @@ -1394,6 +1422,13 @@ static inline int kbd_init_info(void) if (ret) return ret; + /* NOTE: Old models without KBD_LED_AC_TOKEN token supports only one + * timeout value which is shared for both battery and AC power + * settings. So do not try to set AC values on old models. + */ + if (dell_smbios_find_token(KBD_LED_AC_TOKEN)) + kbd_timeout_ac_supported = true; + kbd_get_state(&state); /* NOTE: timeout value is stored in 6 bits so max value is 63 */ @@ -1568,35 +1603,56 @@ static ssize_t kbd_led_timeout_store(struct device *dev, } } + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; new_state = state; - new_state.timeout_value = value; - new_state.timeout_unit = unit; + + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + new_state.timeout_value_ac = value; + new_state.timeout_unit_ac = unit; + } else { + new_state.timeout_value = value; + new_state.timeout_unit = unit; + } ret = kbd_set_state_safe(&new_state, &state); if (ret) - return ret; + goto out; - return count; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_timeout_show(struct device *dev, struct device_attribute *attr, char *buf) { struct kbd_state state; + int value; int ret; int len; + u8 unit; ret = kbd_get_state(&state); if (ret) return ret; - len = sprintf(buf, "%d", state.timeout_value); + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + value = state.timeout_value_ac; + unit = state.timeout_unit_ac; + } else { + value = state.timeout_value; + unit = state.timeout_unit; + } + + len = sprintf(buf, "%d", value); - switch (state.timeout_unit) { + switch (unit) { case KBD_TIMEOUT_SECONDS: return len + sprintf(buf+len, "s\n"); case KBD_TIMEOUT_MINUTES: @@ -1640,9 +1696,11 @@ static ssize_t kbd_led_triggers_store(struct device *dev, if (trigger[0] != '+' && trigger[0] != '-') return -EINVAL; + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; if (kbd_triggers_supported) triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); @@ -1656,48 +1714,62 @@ static ssize_t kbd_led_triggers_store(struct device *dev, if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) continue; if (trigger[0] == '+' && - triggers_enabled && (state.triggers & BIT(i))) - return count; + triggers_enabled && (state.triggers & BIT(i))) { + ret = count; + goto out; + } if (trigger[0] == '-' && - (!triggers_enabled || !(state.triggers & BIT(i)))) - return count; + (!triggers_enabled || !(state.triggers & BIT(i)))) { + ret = count; + goto out; + } trigger_bit = i; break; } } - if (trigger_bit != -1) { - new_state = state; - if (trigger[0] == '+') - new_state.triggers |= BIT(trigger_bit); - else { - new_state.triggers &= ~BIT(trigger_bit); - /* NOTE: trackstick bit (2) must be disabled when - * disabling touchpad bit (1), otherwise touchpad - * bit (1) will not be disabled */ - if (trigger_bit == 1) - new_state.triggers &= ~BIT(2); - } - if ((kbd_info.triggers & new_state.triggers) != - new_state.triggers) - return -EINVAL; - if (new_state.triggers && !triggers_enabled) { - new_state.mode_bit = KBD_MODE_BIT_TRIGGER; - kbd_set_level(&new_state, kbd_previous_level); - } else if (new_state.triggers == 0) { - kbd_set_level(&new_state, 0); - } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) - return -EINVAL; - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - return ret; - if (new_state.mode_bit != KBD_MODE_BIT_OFF) - kbd_previous_mode_bit = new_state.mode_bit; - return count; + if (trigger_bit == -1) { + ret = -EINVAL; + goto out; } - return -EINVAL; + new_state = state; + if (trigger[0] == '+') + new_state.triggers |= BIT(trigger_bit); + else { + new_state.triggers &= ~BIT(trigger_bit); + /* + * NOTE: trackstick bit (2) must be disabled when + * disabling touchpad bit (1), otherwise touchpad + * bit (1) will not be disabled + */ + if (trigger_bit == 1) + new_state.triggers &= ~BIT(2); + } + if ((kbd_info.triggers & new_state.triggers) != + new_state.triggers) { + ret = -EINVAL; + goto out; + } + if (new_state.triggers && !triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else if (new_state.triggers == 0) { + kbd_set_level(&new_state, 0); + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + if (new_state.mode_bit != KBD_MODE_BIT_OFF) + kbd_previous_mode_bit = new_state.mode_bit; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_triggers_show(struct device *dev, @@ -1754,12 +1826,16 @@ static ssize_t kbd_led_als_enabled_store(struct device *dev, if (ret) return ret; + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; - if (enable == kbd_is_als_mode_bit(state.mode_bit)) - return count; + if (enable == kbd_is_als_mode_bit(state.mode_bit)) { + ret = count; + goto out; + } new_state = state; @@ -1779,15 +1855,20 @@ static ssize_t kbd_led_als_enabled_store(struct device *dev, new_state.mode_bit = KBD_MODE_BIT_ON; } } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) - return -EINVAL; + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } ret = kbd_set_state_safe(&new_state, &state); if (ret) - return ret; + goto out; kbd_previous_mode_bit = new_state.mode_bit; - return count; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_als_enabled_show(struct device *dev, @@ -1822,18 +1903,23 @@ static ssize_t kbd_led_als_setting_store(struct device *dev, if (ret) return ret; + mutex_lock(&kbd_led_mutex); + ret = kbd_get_state(&state); if (ret) - return ret; + goto out; new_state = state; new_state.als_setting = setting; ret = kbd_set_state_safe(&new_state, &state); if (ret) - return ret; + goto out; - return count; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static ssize_t kbd_led_als_setting_show(struct device *dev, @@ -1918,31 +2004,37 @@ static int kbd_led_level_set(struct led_classdev *led_cdev, u16 num; int ret; + mutex_lock(&kbd_led_mutex); + if (kbd_get_max_level()) { ret = kbd_get_state(&state); if (ret) - return ret; + goto out; new_state = state; ret = kbd_set_level(&new_state, value); if (ret) - return ret; - return kbd_set_state_safe(&new_state, &state); - } - - if (kbd_get_valid_token_counts()) { + goto out; + ret = kbd_set_state_safe(&new_state, &state); + } else if (kbd_get_valid_token_counts()) { for (num = kbd_token_bits; num != 0 && value > 0; --value) num &= num - 1; /* clear the first bit set */ if (num == 0) - return 0; - return kbd_set_token_bit(ffs(num) - 1); + ret = 0; + else + ret = kbd_set_token_bit(ffs(num) - 1); + } else { + pr_warn("Keyboard brightness level control not supported\n"); + ret = -ENXIO; } - pr_warn("Keyboard brightness level control not supported\n"); - return -ENXIO; +out: + mutex_unlock(&kbd_led_mutex); + return ret; } static struct led_classdev kbd_led = { .name = "dell::kbd_backlight", + .flags = LED_BRIGHT_HW_CHANGED, .brightness_set_blocking = kbd_led_level_set, .brightness_get = kbd_led_level_get, .groups = kbd_led_groups, @@ -1950,6 +2042,8 @@ static struct led_classdev kbd_led = { static int __init kbd_led_init(struct device *dev) { + int ret; + kbd_init(); if (!kbd_led_present) return -ENODEV; @@ -1961,7 +2055,11 @@ static int __init kbd_led_init(struct device *dev) if (kbd_led.max_brightness) kbd_led.max_brightness--; } - return led_classdev_register(dev, &kbd_led); + ret = led_classdev_register(dev, &kbd_led); + if (ret) + kbd_led_present = false; + + return ret; } static void brightness_set_exit(struct led_classdev *led_cdev, @@ -1978,6 +2076,51 @@ static void kbd_led_exit(void) led_classdev_unregister(&kbd_led); } +static int dell_laptop_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + switch (action) { + case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED: + if (!kbd_led_present) + break; + + led_classdev_notify_brightness_hw_changed(&kbd_led, + kbd_led_level_get(&kbd_led)); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block dell_laptop_notifier = { + .notifier_call = dell_laptop_notifier_call, +}; + +int dell_micmute_led_set(int state) +{ + struct calling_interface_buffer *buffer; + struct calling_interface_token *token; + + if (state == 0) + token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE); + else if (state == 1) + token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); + else + return -EINVAL; + + if (!token) + return -ENODEV; + + buffer = dell_smbios_get_buffer(); + buffer->input[0] = token->location; + buffer->input[1] = token->value; + dell_smbios_send_request(1, 0); + dell_smbios_release_buffer(); + + return state; +} +EXPORT_SYMBOL_GPL(dell_micmute_led_set); + static int __init dell_init(void) { struct calling_interface_buffer *buffer; @@ -2021,6 +2164,8 @@ static int __init dell_init(void) debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, &dell_debugfs_fops); + dell_laptop_register_notifier(&dell_laptop_notifier); + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) return 0; @@ -2053,11 +2198,17 @@ static int __init dell_init(void) dell_backlight_device->props.brightness = dell_get_intensity(dell_backlight_device); + if (dell_backlight_device->props.brightness < 0) { + ret = dell_backlight_device->props.brightness; + goto fail_get_brightness; + } backlight_update_status(dell_backlight_device); } return 0; +fail_get_brightness: + backlight_device_unregister(dell_backlight_device); fail_backlight: dell_cleanup_rfkill(); fail_rfkill: @@ -2072,6 +2223,7 @@ fail_platform_driver: static void __exit dell_exit(void) { + dell_laptop_unregister_notifier(&dell_laptop_notifier); debugfs_remove_recursive(dell_laptop_dir); if (quirks && quirks->touchpad_led) touchpad_led_exit(); diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index d2412ab097da..0a5723468bff 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -105,6 +105,26 @@ struct calling_interface_token *dell_smbios_find_token(int tokenid) } EXPORT_SYMBOL_GPL(dell_smbios_find_token); +static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head); + +int dell_laptop_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&dell_laptop_chain_head, nb); +} +EXPORT_SYMBOL_GPL(dell_laptop_register_notifier); + +int dell_laptop_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb); +} +EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier); + +void dell_laptop_call_notifier(unsigned long action, void *data) +{ + blocking_notifier_call_chain(&dell_laptop_chain_head, action, data); +} +EXPORT_SYMBOL_GPL(dell_laptop_call_notifier); + static void __init parse_da_table(const struct dmi_header *dm) { /* Final token is a terminator, so we don't want to copy it */ diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index ec7d40ae5e6e..45cbc2292cd3 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -16,6 +16,8 @@ #ifndef _DELL_SMBIOS_H_ #define _DELL_SMBIOS_H_ +struct notifier_block; + /* This structure will be modified by the firmware when we enter * system management mode, hence the volatiles */ @@ -43,4 +45,13 @@ void dell_smbios_release_buffer(void); void dell_smbios_send_request(int class, int select); struct calling_interface_token *dell_smbios_find_token(int tokenid); + +enum dell_laptop_notifier_actions { + DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, +}; + +int dell_laptop_register_notifier(struct notifier_block *nb); +int dell_laptop_unregister_notifier(struct notifier_block *nb); +void dell_laptop_call_notifier(unsigned long action, void *data); + #endif diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c index dbc97a33bbc8..50c2078715d6 100644 --- a/drivers/platform/x86/dell-wmi-aio.c +++ b/drivers/platform/x86/dell-wmi-aio.c @@ -152,12 +152,10 @@ static int __init dell_wmi_aio_input_setup(void) err = input_register_device(dell_wmi_aio_input_dev); if (err) { pr_info("Unable to register input device\n"); - goto err_free_keymap; + goto err_free_dev; } 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; @@ -192,7 +190,6 @@ static int __init dell_wmi_aio_init(void) 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; } @@ -206,7 +203,6 @@ static void __exit dell_wmi_aio_exit(void) 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); } diff --git a/drivers/platform/x86/dell-wmi-led.c b/drivers/platform/x86/dell-wmi-led.c new file mode 100644 index 000000000000..a0c7e99530ef --- /dev/null +++ b/drivers/platform/x86/dell-wmi-led.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2010 Dell Inc. + * Louis Davis <louis_davis@dell.com> + * Jim Dailey <jim_dailey@dell.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. + * + */ + +#include <linux/acpi.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/module.h> + +MODULE_AUTHOR("Louis Davis/Jim Dailey"); +MODULE_DESCRIPTION("Dell LED Control Driver"); +MODULE_LICENSE("GPL"); + +#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" +MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); + +/* Error Result Codes: */ +#define INVALID_DEVICE_ID 250 +#define INVALID_PARAMETER 251 +#define INVALID_BUFFER 252 +#define INTERFACE_ERROR 253 +#define UNSUPPORTED_COMMAND 254 +#define UNSPECIFIED_ERROR 255 + +/* Device ID */ +#define DEVICE_ID_PANEL_BACK 1 + +/* LED Commands */ +#define CMD_LED_ON 16 +#define CMD_LED_OFF 17 +#define CMD_LED_BLINK 18 + +struct bios_args { + unsigned char length; + unsigned char result_code; + unsigned char device_id; + unsigned char command; + unsigned char on_time; + unsigned char off_time; +}; + +static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id, + u8 command, u8 on_time, u8 off_time) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct bios_args *bios_return; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + u8 return_code; + + struct bios_args args = { + .length = length, + .result_code = result_code, + .device_id = device_id, + .command = command, + .on_time = on_time, + .off_time = off_time + }; + + input.length = sizeof(struct bios_args); + input.pointer = &args; + + status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 1, 1, &input, &output); + if (ACPI_FAILURE(status)) + return status; + + obj = output.pointer; + + if (!obj) + return -EINVAL; + if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return -EINVAL; + } + + bios_return = ((struct bios_args *)obj->buffer.pointer); + return_code = bios_return->result_code; + + kfree(obj); + + return return_code; +} + +static int led_on(void) +{ + return dell_led_perform_fn(3, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_ON, /* Command */ + 0, /* not used */ + 0); /* not used */ +} + +static int led_off(void) +{ + return dell_led_perform_fn(3, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_OFF, /* Command */ + 0, /* not used */ + 0); /* not used */ +} + +static int led_blink(unsigned char on_eighths, unsigned char off_eighths) +{ + return dell_led_perform_fn(5, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_BLINK, /* Command */ + on_eighths, /* blink on in eigths of a second */ + off_eighths); /* blink off in eights of a second */ +} + +static void dell_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value == LED_OFF) + led_off(); + else + led_on(); +} + +static int dell_led_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + unsigned long on_eighths; + unsigned long off_eighths; + + /* + * The Dell LED delay is based on 125ms intervals. + * Need to round up to next interval. + */ + + on_eighths = DIV_ROUND_UP(*delay_on, 125); + on_eighths = clamp_t(unsigned long, on_eighths, 1, 255); + *delay_on = on_eighths * 125; + + off_eighths = DIV_ROUND_UP(*delay_off, 125); + off_eighths = clamp_t(unsigned long, off_eighths, 1, 255); + *delay_off = off_eighths * 125; + + led_blink(on_eighths, off_eighths); + + return 0; +} + +static struct led_classdev dell_led = { + .name = "dell::lid", + .brightness = LED_OFF, + .max_brightness = 1, + .brightness_set = dell_led_set, + .blink_set = dell_led_blink, + .flags = LED_CORE_SUSPENDRESUME, +}; + +static int __init dell_led_init(void) +{ + int error = 0; + + if (!wmi_has_guid(DELL_LED_BIOS_GUID)) + return -ENODEV; + + error = led_off(); + if (error != 0) + return -ENODEV; + + return led_classdev_register(NULL, &dell_led); +} + +static void __exit dell_led_exit(void) +{ + led_classdev_unregister(&dell_led); + + led_off(); +} + +module_init(dell_led_init); +module_exit(dell_led_exit); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 75e637047d36..8a64c7967753 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -329,6 +329,10 @@ static void dell_wmi_process_key(int type, int code) if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) return; + if (key->keycode == KEY_KBDILLUMTOGGLE) + dell_laptop_call_notifier( + DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); + sparse_keymap_report_entry(dell_wmi_input_dev, key, 1, true); } @@ -603,23 +607,15 @@ static int __init dell_wmi_input_setup(void) err = input_register_device(dell_wmi_input_dev); if (err) - goto err_free_keymap; + goto err_free_dev; 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); -} - /* * Descriptor buffer is 128 byte long and contains: * @@ -740,7 +736,7 @@ static int __init dell_wmi_init(void) status = wmi_install_notify_handler(DELL_EVENT_GUID, dell_wmi_notify, NULL); if (ACPI_FAILURE(status)) { - dell_wmi_input_destroy(); + input_unregister_device(dell_wmi_input_dev); pr_err("Unable to register notify handler - %d\n", status); return -ENODEV; } @@ -752,7 +748,7 @@ static int __init dell_wmi_init(void) if (err) { pr_err("Failed to enable WMI events\n"); wmi_remove_notify_handler(DELL_EVENT_GUID); - dell_wmi_input_destroy(); + input_unregister_device(dell_wmi_input_dev); return err; } } @@ -766,6 +762,6 @@ static void __exit dell_wmi_exit(void) if (wmi_requires_smbios_request) dell_wmi_events_set_enabled(false); wmi_remove_notify_handler(DELL_EVENT_GUID); - dell_wmi_input_destroy(); + input_unregister_device(dell_wmi_input_dev); } module_exit(dell_wmi_exit); diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 8cdf315f9730..2426399e1e04 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -150,6 +150,8 @@ static const struct key_entry eeepc_keymap[] = { { KE_KEY, 0x32, { KEY_SWITCHVIDEOMODE } }, { KE_KEY, 0x37, { KEY_F13 } }, /* Disable Touchpad */ { KE_KEY, 0x38, { KEY_F14 } }, + { KE_IGNORE, 0x50, { KEY_RESERVED } }, /* AC plugged */ + { KE_IGNORE, 0x51, { KEY_RESERVED } }, /* AC unplugged */ { KE_END, 0 }, }; @@ -1205,14 +1207,12 @@ static int eeepc_input_init(struct eeepc_laptop *eeepc) error = input_register_device(input); if (error) { pr_err("Unable to register input device\n"); - goto err_free_keymap; + goto err_free_dev; } eeepc->inputdev = input; return 0; -err_free_keymap: - sparse_keymap_free(input); err_free_dev: input_free_device(input); return error; @@ -1220,10 +1220,8 @@ err_free_dev: static void eeepc_input_exit(struct eeepc_laptop *eeepc) { - if (eeepc->inputdev) { - sparse_keymap_free(eeepc->inputdev); + if (eeepc->inputdev) input_unregister_device(eeepc->inputdev); - } eeepc->inputdev = NULL; } diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index e12cc3504d48..7f49d92914c9 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -32,18 +32,9 @@ * 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 implements a vendor-specific backlight control interface for + * Fujitsu laptops and provides support for hotkeys present on certain Fujitsu + * laptops. * * 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 @@ -66,12 +57,11 @@ #include <linux/backlight.h> #include <linux/fb.h> #include <linux/input.h> +#include <linux/input/sparse-keymap.h> #include <linux/kfifo.h> +#include <linux/leds.h> #include <linux/platform_device.h> #include <linux/slab.h> -#if IS_ENABLED(CONFIG_LEDS_CLASS) -#include <linux/leds.h> -#endif #include <acpi/video.h> #define FUJITSU_DRIVER_VERSION "0.6.0" @@ -102,7 +92,6 @@ #define FLAG_LID 0x100 #define FLAG_DOCK 0x200 -#if IS_ENABLED(CONFIG_LEDS_CLASS) /* FUNC interface - LED control */ #define FUNC_LED_OFF 0x1 #define FUNC_LED_ON 0x30001 @@ -112,7 +101,6 @@ #define RADIO_LED_ON 0x20 #define ECO_LED 0x10000 #define ECO_LED_ON 0x80000 -#endif /* Hotkey details */ #define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */ @@ -143,21 +131,16 @@ /* Device controlling the backlight and associated keys */ struct fujitsu_bl { 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, keycode5; - unsigned int max_brightness; - unsigned int brightness_changed; unsigned int brightness_level; }; static struct fujitsu_bl *fujitsu_bl; static int use_alt_lcd_levels = -1; -static int disable_brightness_adjust = -1; +static bool disable_brightness_adjust; /* Device used to access hotkeys and other features on the laptop */ struct fujitsu_laptop { @@ -170,247 +153,77 @@ struct fujitsu_laptop { spinlock_t fifo_lock; int flags_supported; int flags_state; - int logolamp_registered; - int kblamps_registered; - int radio_led_registered; - int eco_led_registered; }; static struct fujitsu_laptop *fujitsu_laptop; -#if IS_ENABLED(CONFIG_LEDS_CLASS) -static enum led_brightness logolamp_get(struct led_classdev *cdev); -static int 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_blocking = logolamp_set -}; - -static enum led_brightness kblamps_get(struct led_classdev *cdev); -static int 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_blocking = kblamps_set -}; - -static enum led_brightness radio_led_get(struct led_classdev *cdev); -static int radio_led_set(struct led_classdev *cdev, - enum led_brightness brightness); - -static struct led_classdev radio_led = { - .name = "fujitsu::radio_led", - .default_trigger = "rfkill-any", - .brightness_get = radio_led_get, - .brightness_set_blocking = radio_led_set -}; - -static enum led_brightness eco_led_get(struct led_classdev *cdev); -static int eco_led_set(struct led_classdev *cdev, - enum led_brightness brightness); - -static struct led_classdev eco_led = { - .name = "fujitsu::eco_led", - .brightness_get = eco_led_get, - .brightness_set_blocking = eco_led_set -}; -#endif - #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG static u32 dbg_level = 0x03; #endif /* Fujitsu ACPI interface function */ -static int call_fext_func(int cmd, int arg0, int arg1, int arg2) +static int call_fext_func(int func, int op, int feature, int state) { - 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 } + { .integer.type = ACPI_TYPE_INTEGER, .integer.value = func }, + { .integer.type = ACPI_TYPE_INTEGER, .integer.value = op }, + { .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature }, + { .integer.type = ACPI_TYPE_INTEGER, .integer.value = state } }; - struct acpi_object_list arg_list = { 4, ¶ms[0] }; + struct acpi_object_list arg_list = { 4, params }; unsigned long long value; - acpi_handle handle = NULL; - - status = acpi_get_handle(fujitsu_laptop->acpi_handle, "FUNC", &handle); - if (ACPI_FAILURE(status)) { - vdbg_printk(FUJLAPTOP_DBG_ERROR, - "FUNC interface is not present\n"); - return -ENODEV; - } + acpi_status status; - params[0].integer.value = cmd; - params[1].integer.value = arg0; - params[2].integer.value = arg1; - params[3].integer.value = arg2; - - status = acpi_evaluate_integer(handle, NULL, &arg_list, &value); + status = acpi_evaluate_integer(fujitsu_laptop->acpi_handle, "FUNC", + &arg_list, &value); 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); + vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate FUNC\n"); 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)value); + vdbg_printk(FUJLAPTOP_DBG_TRACE, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n", + func, op, feature, state, (int)value); return value; } -#if IS_ENABLED(CONFIG_LEDS_CLASS) -/* LED class callbacks */ - -static int logolamp_set(struct led_classdev *cdev, - enum led_brightness brightness) -{ - int poweron = FUNC_LED_ON, always = FUNC_LED_ON; - int ret; - - if (brightness < LED_HALF) - poweron = FUNC_LED_OFF; - - if (brightness < LED_FULL) - always = FUNC_LED_OFF; - - ret = call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron); - if (ret < 0) - return ret; - - return call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always); -} - -static int kblamps_set(struct led_classdev *cdev, - enum led_brightness brightness) -{ - if (brightness >= LED_FULL) - return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_ON); - else - return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF); -} - -static int radio_led_set(struct led_classdev *cdev, - enum led_brightness brightness) -{ - if (brightness >= LED_FULL) - return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, RADIO_LED_ON); - else - return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, 0x0); -} - -static int eco_led_set(struct led_classdev *cdev, - enum led_brightness brightness) -{ - int curr; - - curr = call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0); - if (brightness >= LED_FULL) - return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, curr | ECO_LED_ON); - else - return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, curr & ~ECO_LED_ON); -} - -static enum led_brightness logolamp_get(struct led_classdev *cdev) -{ - int ret; - - ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0); - if (ret == FUNC_LED_ON) - return LED_FULL; - - ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0); - if (ret == FUNC_LED_ON) - return LED_HALF; - - return LED_OFF; -} - -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; -} - -static enum led_brightness radio_led_get(struct led_classdev *cdev) -{ - enum led_brightness brightness = LED_OFF; - - if (call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON) - brightness = LED_FULL; - - return brightness; -} - -static enum led_brightness eco_led_get(struct led_classdev *cdev) -{ - enum led_brightness brightness = LED_OFF; - - if (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_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; - acpi_handle handle = NULL; - - vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBLL [%d]\n", - level); + acpi_status status; + char *method; - if (level < 0 || level >= fujitsu_bl->max_brightness) - return -EINVAL; - - status = acpi_get_handle(fujitsu_bl->acpi_handle, "SBLL", &handle); - if (ACPI_FAILURE(status)) { - vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBLL not present\n"); - return -ENODEV; + switch (use_alt_lcd_levels) { + case -1: + if (acpi_has_method(fujitsu_bl->acpi_handle, "SBL2")) + method = "SBL2"; + else + method = "SBLL"; + break; + case 1: + method = "SBL2"; + break; + default: + method = "SBLL"; + break; } - - status = acpi_execute_simple_method(handle, NULL, level); - if (ACPI_FAILURE(status)) - return -ENODEV; - - return 0; -} - -static int set_lcd_level_alt(int level) -{ - acpi_status status = AE_OK; - acpi_handle handle = NULL; - - vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via SBL2 [%d]\n", - level); + vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via %s [%d]\n", + method, level); if (level < 0 || level >= fujitsu_bl->max_brightness) return -EINVAL; - status = acpi_get_handle(fujitsu_bl->acpi_handle, "SBL2", &handle); + status = acpi_execute_simple_method(fujitsu_bl->acpi_handle, method, + level); if (ACPI_FAILURE(status)) { - vdbg_printk(FUJLAPTOP_DBG_ERROR, "SBL2 not present\n"); + vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate %s\n", + method); return -ENODEV; } - status = acpi_execute_simple_method(handle, NULL, level); - if (ACPI_FAILURE(status)) - return -ENODEV; + fujitsu_bl->brightness_level = level; return 0; } @@ -429,11 +242,6 @@ static int get_lcd_level(void) fujitsu_bl->brightness_level = state & 0x0fffffff; - if (state & 0x80000000) - fujitsu_bl->brightness_changed = 1; - else - fujitsu_bl->brightness_changed = 0; - return fujitsu_bl->brightness_level; } @@ -458,30 +266,17 @@ static int get_max_brightness(void) static int bl_get_brightness(struct backlight_device *b) { - return get_lcd_level(); + return b->props.power == FB_BLANK_POWERDOWN ? 0 : get_lcd_level(); } static int bl_update_status(struct backlight_device *b) { - int ret; if (b->props.power == FB_BLANK_POWERDOWN) - ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3); + 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; + call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0); + + return set_lcd_level(b->props.brightness); } static const struct backlight_ops fujitsu_bl_ops = { @@ -489,84 +284,8 @@ static const struct backlight_ops fujitsu_bl_ops = { .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_bl->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_bl->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) +static ssize_t lid_show(struct device *dev, struct device_attribute *attr, + char *buf) { if (!(fujitsu_laptop->flags_supported & FLAG_LID)) return sprintf(buf, "unknown\n"); @@ -576,9 +295,8 @@ show_lid_state(struct device *dev, return sprintf(buf, "closed\n"); } -static ssize_t -show_dock_state(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t dock_show(struct device *dev, struct device_attribute *attr, + char *buf) { if (!(fujitsu_laptop->flags_supported & FLAG_DOCK)) return sprintf(buf, "unknown\n"); @@ -588,9 +306,8 @@ show_dock_state(struct device *dev, return sprintf(buf, "undocked\n"); } -static ssize_t -show_radios_state(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t radios_show(struct device *dev, struct device_attribute *attr, + char *buf) { if (!(fujitsu_laptop->flags_supported & FLAG_RFKILL)) return sprintf(buf, "unknown\n"); @@ -600,18 +317,11 @@ show_radios_state(struct device *dev, 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 DEVICE_ATTR_RO(lid); +static DEVICE_ATTR_RO(dock); +static DEVICE_ATTR_RO(radios); static struct attribute *fujitsu_pf_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, @@ -628,69 +338,66 @@ static struct platform_driver fujitsu_pf_driver = { } }; -static void __init dmi_check_cb_common(const struct dmi_system_id *id) -{ - pr_info("Identified laptop model '%s'\n", id->ident); -} +/* ACPI device for LCD brightness control */ -static int __init dmi_check_cb_s6410(const struct dmi_system_id *id) -{ - dmi_check_cb_common(id); - fujitsu_bl->keycode1 = KEY_SCREENLOCK; /* "Lock" */ - fujitsu_bl->keycode2 = KEY_HELP; /* "Mobility Center" */ - return 1; -} +static const struct key_entry keymap_backlight[] = { + { KE_KEY, true, { KEY_BRIGHTNESSUP } }, + { KE_KEY, false, { KEY_BRIGHTNESSDOWN } }, + { KE_END, 0 } +}; -static int __init dmi_check_cb_s6420(const struct dmi_system_id *id) +static int acpi_fujitsu_bl_input_setup(struct acpi_device *device) { - dmi_check_cb_common(id); - fujitsu_bl->keycode1 = KEY_SCREENLOCK; /* "Lock" */ - fujitsu_bl->keycode2 = KEY_HELP; /* "Mobility Center" */ - return 1; + struct fujitsu_bl *fujitsu_bl = acpi_driver_data(device); + int ret; + + fujitsu_bl->input = devm_input_allocate_device(&device->dev); + if (!fujitsu_bl->input) + return -ENOMEM; + + snprintf(fujitsu_bl->phys, sizeof(fujitsu_bl->phys), + "%s/video/input0", acpi_device_hid(device)); + + fujitsu_bl->input->name = acpi_device_name(device); + fujitsu_bl->input->phys = fujitsu_bl->phys; + fujitsu_bl->input->id.bustype = BUS_HOST; + fujitsu_bl->input->id.product = 0x06; + + ret = sparse_keymap_setup(fujitsu_bl->input, keymap_backlight, NULL); + if (ret) + return ret; + + return input_register_device(fujitsu_bl->input); } -static int __init dmi_check_cb_p8010(const struct dmi_system_id *id) +static int fujitsu_backlight_register(struct acpi_device *device) { - dmi_check_cb_common(id); - fujitsu_bl->keycode1 = KEY_HELP; /* "Support" */ - fujitsu_bl->keycode3 = KEY_SWITCHVIDEOMODE; /* "Presentation" */ - fujitsu_bl->keycode4 = KEY_WWW; /* "Internet" */ - return 1; -} + const struct backlight_properties props = { + .brightness = fujitsu_bl->brightness_level, + .max_brightness = fujitsu_bl->max_brightness - 1, + .type = BACKLIGHT_PLATFORM + }; + struct backlight_device *bd; -static const struct dmi_system_id fujitsu_dmi_table[] __initconst = { - { - .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}, - {} -}; + bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop", + &device->dev, NULL, + &fujitsu_bl_ops, &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); -/* ACPI device for LCD brightness control */ + fujitsu_bl->bl_device = bd; + + return 0; +} static int acpi_fujitsu_bl_add(struct acpi_device *device) { int state = 0; - struct input_dev *input; int error; + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) + return -ENODEV; + if (!device) return -EINVAL; @@ -699,41 +406,20 @@ static int acpi_fujitsu_bl_add(struct acpi_device *device) sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS); device->driver_data = fujitsu_bl; - fujitsu_bl->input = input = input_allocate_device(); - if (!input) { - error = -ENOMEM; - goto err_stop; - } - - snprintf(fujitsu_bl->phys, sizeof(fujitsu_bl->phys), - "%s/video/input0", acpi_device_hid(device)); - - input->name = acpi_device_name(device); - input->phys = fujitsu_bl->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); + error = acpi_fujitsu_bl_input_setup(device); if (error) - goto err_free_input_dev; + return error; error = acpi_bus_update_power(fujitsu_bl->acpi_handle, &state); if (error) { pr_err("Error reading power state\n"); - goto err_unregister_input_dev; + return error; } pr_info("ACPI: %s [%s] (%s)\n", acpi_device_name(device), acpi_device_bid(device), !device->power.state ? "on" : "off"); - fujitsu_bl->dev = device; - if (acpi_has_method(device->handle, METHOD_NAME__INI)) { vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n"); if (ACPI_FAILURE @@ -742,45 +428,13 @@ static int acpi_fujitsu_bl_add(struct acpi_device *device) pr_err("_INI Method failed\n"); } - if (use_alt_lcd_levels == -1) { - if (acpi_has_method(NULL, "\\_SB.PCI0.LPCB.FJEX.SBL2")) - 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); - } - - /* 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_bl->max_brightness = FUJITSU_LCD_N_LEVELS; get_lcd_level(); - return 0; - -err_unregister_input_dev: - input_unregister_device(input); - input = NULL; -err_free_input_dev: - input_free_device(input); -err_stop: - return error; -} - -static int acpi_fujitsu_bl_remove(struct acpi_device *device) -{ - struct fujitsu_bl *fujitsu_bl = acpi_driver_data(device); - struct input_dev *input = fujitsu_bl->input; - - input_unregister_device(input); - - fujitsu_bl->acpi_handle = NULL; + error = fujitsu_backlight_register(device); + if (error) + return error; return 0; } @@ -790,62 +444,332 @@ static int acpi_fujitsu_bl_remove(struct acpi_device *device) static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event) { struct input_dev *input; - int keycode; int oldb, newb; input = fujitsu_bl->input; - switch (event) { - case ACPI_FUJITSU_NOTIFY_CODE1: - keycode = 0; - oldb = fujitsu_bl->brightness_level; - get_lcd_level(); - newb = fujitsu_bl->brightness_level; - - vdbg_printk(FUJLAPTOP_DBG_TRACE, - "brightness button event [%i -> %i (%i)]\n", - oldb, newb, fujitsu_bl->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); - } - 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); - } - keycode = KEY_BRIGHTNESSDOWN; - } - break; - default: - keycode = KEY_UNKNOWN; + if (event != ACPI_FUJITSU_NOTIFY_CODE1) { vdbg_printk(FUJLAPTOP_DBG_WARN, "unsupported event [0x%x]\n", event); - break; + sparse_keymap_report_event(input, -1, 1, true); + return; } - if (keycode != 0) { - input_report_key(input, keycode, 1); - input_sync(input); - input_report_key(input, keycode, 0); - input_sync(input); - } + oldb = fujitsu_bl->brightness_level; + get_lcd_level(); + newb = fujitsu_bl->brightness_level; + + vdbg_printk(FUJLAPTOP_DBG_TRACE, "brightness button event [%i -> %i]\n", + oldb, newb); + + if (oldb == newb) + return; + + if (!disable_brightness_adjust) + set_lcd_level(newb); + + sparse_keymap_report_event(input, oldb < newb, 1, true); } /* ACPI device for hotkey handling */ +static const struct key_entry keymap_default[] = { + { KE_KEY, KEY1_CODE, { KEY_PROG1 } }, + { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, + { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, + { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, + { KE_KEY, KEY5_CODE, { KEY_RFKILL } }, + { KE_KEY, BIT(26), { KEY_TOUCHPAD_TOGGLE } }, + { KE_END, 0 } +}; + +static const struct key_entry keymap_s64x0[] = { + { KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } }, /* "Lock" */ + { KE_KEY, KEY2_CODE, { KEY_HELP } }, /* "Mobility Center */ + { KE_KEY, KEY3_CODE, { KEY_PROG3 } }, + { KE_KEY, KEY4_CODE, { KEY_PROG4 } }, + { KE_END, 0 } +}; + +static const struct key_entry keymap_p8010[] = { + { KE_KEY, KEY1_CODE, { KEY_HELP } }, /* "Support" */ + { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, + { KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } }, /* "Presentation" */ + { KE_KEY, KEY4_CODE, { KEY_WWW } }, /* "WWW" */ + { KE_END, 0 } +}; + +static const struct key_entry *keymap = keymap_default; + +static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id) +{ + pr_info("Identified laptop model '%s'\n", id->ident); + keymap = id->driver_data; + return 1; +} + +static const struct dmi_system_id fujitsu_laptop_dmi_table[] = { + { + .callback = fujitsu_laptop_dmi_keymap_override, + .ident = "Fujitsu Siemens S6410", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"), + }, + .driver_data = (void *)keymap_s64x0 + }, + { + .callback = fujitsu_laptop_dmi_keymap_override, + .ident = "Fujitsu Siemens S6420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"), + }, + .driver_data = (void *)keymap_s64x0 + }, + { + .callback = fujitsu_laptop_dmi_keymap_override, + .ident = "Fujitsu LifeBook P8010", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"), + }, + .driver_data = (void *)keymap_p8010 + }, + {} +}; + +static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device) +{ + struct fujitsu_laptop *fujitsu_laptop = acpi_driver_data(device); + int ret; + + fujitsu_laptop->input = devm_input_allocate_device(&device->dev); + if (!fujitsu_laptop->input) + return -ENOMEM; + + snprintf(fujitsu_laptop->phys, sizeof(fujitsu_laptop->phys), + "%s/video/input0", acpi_device_hid(device)); + + fujitsu_laptop->input->name = acpi_device_name(device); + fujitsu_laptop->input->phys = fujitsu_laptop->phys; + fujitsu_laptop->input->id.bustype = BUS_HOST; + fujitsu_laptop->input->id.product = 0x06; + + dmi_check_system(fujitsu_laptop_dmi_table); + ret = sparse_keymap_setup(fujitsu_laptop->input, keymap, NULL); + if (ret) + return ret; + + return input_register_device(fujitsu_laptop->input); +} + +static int fujitsu_laptop_platform_add(void) +{ + int ret; + + fujitsu_laptop->pf_device = platform_device_alloc("fujitsu-laptop", -1); + if (!fujitsu_laptop->pf_device) + return -ENOMEM; + + ret = platform_device_add(fujitsu_laptop->pf_device); + if (ret) + goto err_put_platform_device; + + ret = sysfs_create_group(&fujitsu_laptop->pf_device->dev.kobj, + &fujitsu_pf_attribute_group); + if (ret) + goto err_del_platform_device; + + return 0; + +err_del_platform_device: + platform_device_del(fujitsu_laptop->pf_device); +err_put_platform_device: + platform_device_put(fujitsu_laptop->pf_device); + + return ret; +} + +static void fujitsu_laptop_platform_remove(void) +{ + sysfs_remove_group(&fujitsu_laptop->pf_device->dev.kobj, + &fujitsu_pf_attribute_group); + platform_device_unregister(fujitsu_laptop->pf_device); +} + +static int logolamp_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + int poweron = FUNC_LED_ON, always = FUNC_LED_ON; + int ret; + + if (brightness < LED_HALF) + poweron = FUNC_LED_OFF; + + if (brightness < LED_FULL) + always = FUNC_LED_OFF; + + ret = call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron); + if (ret < 0) + return ret; + + return call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always); +} + +static enum led_brightness logolamp_get(struct led_classdev *cdev) +{ + int ret; + + ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0); + if (ret == FUNC_LED_ON) + return LED_FULL; + + ret = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0); + if (ret == FUNC_LED_ON) + return LED_HALF; + + return LED_OFF; +} + +static struct led_classdev logolamp_led = { + .name = "fujitsu::logolamp", + .brightness_set_blocking = logolamp_set, + .brightness_get = logolamp_get +}; + +static int kblamps_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + if (brightness >= LED_FULL) + return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, + FUNC_LED_ON); + else + return call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, + FUNC_LED_OFF); +} + +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; +} + +static struct led_classdev kblamps_led = { + .name = "fujitsu::kblamps", + .brightness_set_blocking = kblamps_set, + .brightness_get = kblamps_get +}; + +static int radio_led_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + if (brightness >= LED_FULL) + return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, + RADIO_LED_ON); + else + return call_fext_func(FUNC_FLAGS, 0x5, RADIO_LED_ON, 0x0); +} + +static enum led_brightness radio_led_get(struct led_classdev *cdev) +{ + enum led_brightness brightness = LED_OFF; + + if (call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON) + brightness = LED_FULL; + + return brightness; +} + +static struct led_classdev radio_led = { + .name = "fujitsu::radio_led", + .brightness_set_blocking = radio_led_set, + .brightness_get = radio_led_get, + .default_trigger = "rfkill-any" +}; + +static int eco_led_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + int curr; + + curr = call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0); + if (brightness >= LED_FULL) + return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, + curr | ECO_LED_ON); + else + return call_fext_func(FUNC_LEDS, 0x1, ECO_LED, + curr & ~ECO_LED_ON); +} + +static enum led_brightness eco_led_get(struct led_classdev *cdev) +{ + enum led_brightness brightness = LED_OFF; + + if (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON) + brightness = LED_FULL; + + return brightness; +} + +static struct led_classdev eco_led = { + .name = "fujitsu::eco_led", + .brightness_set_blocking = eco_led_set, + .brightness_get = eco_led_get +}; + +static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device) +{ + int result; + + if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { + result = devm_led_classdev_register(&device->dev, + &logolamp_led); + if (result) + return result; + } + + if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) && + (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) { + result = devm_led_classdev_register(&device->dev, &kblamps_led); + if (result) + return result; + } + + /* + * BTNI bit 24 seems to indicate the presence of a radio toggle + * button in place of a slide switch, and all such machines appear + * to also have an RF LED. Therefore use bit 24 as an indicator + * that an RF LED is present. + */ + if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) { + result = devm_led_classdev_register(&device->dev, &radio_led); + if (result) + return result; + } + + /* Support for eco led is not always signaled in bit corresponding + * to the bit used to control the led. According to the DSDT table, + * bit 14 seems to indicate presence of said led as well. + * Confirm by testing the status. + */ + if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) && + (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) { + result = devm_led_classdev_register(&device->dev, &eco_led); + if (result) + return result; + } + + return 0; +} + static int acpi_fujitsu_laptop_add(struct acpi_device *device) { - int result = 0; int state = 0; - struct input_dev *input; int error; int i; @@ -867,38 +791,14 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device) goto err_stop; } - fujitsu_laptop->input = input = input_allocate_device(); - if (!input) { - error = -ENOMEM; - goto err_free_fifo; - } - - snprintf(fujitsu_laptop->phys, sizeof(fujitsu_laptop->phys), - "%s/video/input0", acpi_device_hid(device)); - - input->name = acpi_device_name(device); - input->phys = fujitsu_laptop->phys; - input->id.bustype = BUS_HOST; - input->id.product = 0x06; - input->dev.parent = &device->dev; - - set_bit(EV_KEY, input->evbit); - set_bit(fujitsu_bl->keycode1, input->keybit); - set_bit(fujitsu_bl->keycode2, input->keybit); - set_bit(fujitsu_bl->keycode3, input->keybit); - set_bit(fujitsu_bl->keycode4, input->keybit); - set_bit(fujitsu_bl->keycode5, input->keybit); - set_bit(KEY_TOUCHPAD_TOGGLE, input->keybit); - set_bit(KEY_UNKNOWN, input->keybit); - - error = input_register_device(input); + error = acpi_fujitsu_laptop_input_setup(device); if (error) - goto err_free_input_dev; + goto err_free_fifo; error = acpi_bus_update_power(fujitsu_laptop->acpi_handle, &state); if (error) { pr_err("Error reading power state\n"); - goto err_unregister_input_dev; + goto err_free_fifo; } pr_info("ACPI: %s [%s] (%s)\n", @@ -936,72 +836,25 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device) /* 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 IS_ENABLED(CONFIG_LEDS_CLASS) - if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) { - result = led_classdev_register(&fujitsu_bl->pf_device->dev, - &logolamp_led); - if (result == 0) { - fujitsu_laptop->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_bl->pf_device->dev, - &kblamps_led); - if (result == 0) { - fujitsu_laptop->kblamps_registered = 1; - } else { - pr_err("Could not register LED handler for keyboard lamps, error %i\n", - result); - } + /* Sync backlight power status */ + if (fujitsu_bl->bl_device && + acpi_video_get_backlight_type() == acpi_backlight_vendor) { + if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3) + fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN; + else + fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK; } - /* - * BTNI bit 24 seems to indicate the presence of a radio toggle - * button in place of a slide switch, and all such machines appear - * to also have an RF LED. Therefore use bit 24 as an indicator - * that an RF LED is present. - */ - if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) { - result = led_classdev_register(&fujitsu_bl->pf_device->dev, - &radio_led); - if (result == 0) { - fujitsu_laptop->radio_led_registered = 1; - } else { - pr_err("Could not register LED handler for radio LED, error %i\n", - result); - } - } + error = acpi_fujitsu_laptop_leds_register(device); + if (error) + goto err_free_fifo; - /* Support for eco led is not always signaled in bit corresponding - * to the bit used to control the led. According to the DSDT table, - * bit 14 seems to indicate presence of said led as well. - * Confirm by testing the status. - */ - if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) && - (call_fext_func(FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) { - result = led_classdev_register(&fujitsu_bl->pf_device->dev, - &eco_led); - if (result == 0) { - fujitsu_laptop->eco_led_registered = 1; - } else { - pr_err("Could not register LED handler for eco LED, error %i\n", - result); - } - } -#endif + error = fujitsu_laptop_platform_add(); + if (error) + goto err_free_fifo; - return result; + return 0; -err_unregister_input_dev: - input_unregister_device(input); - input = NULL; -err_free_input_dev: - input_free_device(input); err_free_fifo: kfifo_free(&fujitsu_laptop->fifo); err_stop: @@ -1011,86 +864,62 @@ err_stop: static int acpi_fujitsu_laptop_remove(struct acpi_device *device) { struct fujitsu_laptop *fujitsu_laptop = acpi_driver_data(device); - struct input_dev *input = fujitsu_laptop->input; - -#if IS_ENABLED(CONFIG_LEDS_CLASS) - if (fujitsu_laptop->logolamp_registered) - led_classdev_unregister(&logolamp_led); - - if (fujitsu_laptop->kblamps_registered) - led_classdev_unregister(&kblamps_led); - if (fujitsu_laptop->radio_led_registered) - led_classdev_unregister(&radio_led); - - if (fujitsu_laptop->eco_led_registered) - led_classdev_unregister(&eco_led); -#endif - - input_unregister_device(input); + fujitsu_laptop_platform_remove(); kfifo_free(&fujitsu_laptop->fifo); - fujitsu_laptop->acpi_handle = NULL; - return 0; } -static void acpi_fujitsu_laptop_press(int keycode) +static void acpi_fujitsu_laptop_press(int scancode) { struct input_dev *input = fujitsu_laptop->input; int status; status = kfifo_in_locked(&fujitsu_laptop->fifo, - (unsigned char *)&keycode, sizeof(keycode), + (unsigned char *)&scancode, sizeof(scancode), &fujitsu_laptop->fifo_lock); - if (status != sizeof(keycode)) { + if (status != sizeof(scancode)) { vdbg_printk(FUJLAPTOP_DBG_WARN, - "Could not push keycode [0x%x]\n", keycode); + "Could not push scancode [0x%x]\n", scancode); return; } - input_report_key(input, keycode, 1); - input_sync(input); + sparse_keymap_report_event(input, scancode, 1, false); vdbg_printk(FUJLAPTOP_DBG_TRACE, - "Push keycode into ringbuffer [%d]\n", keycode); + "Push scancode into ringbuffer [0x%x]\n", scancode); } static void acpi_fujitsu_laptop_release(void) { struct input_dev *input = fujitsu_laptop->input; - int keycode, status; + int scancode, status; while (true) { status = kfifo_out_locked(&fujitsu_laptop->fifo, - (unsigned char *)&keycode, - sizeof(keycode), + (unsigned char *)&scancode, + sizeof(scancode), &fujitsu_laptop->fifo_lock); - if (status != sizeof(keycode)) + if (status != sizeof(scancode)) return; - input_report_key(input, keycode, 0); - input_sync(input); + sparse_keymap_report_event(input, scancode, 0, false); vdbg_printk(FUJLAPTOP_DBG_TRACE, - "Pop keycode from ringbuffer [%d]\n", keycode); + "Pop scancode from ringbuffer [0x%x]\n", scancode); } } static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event) { struct input_dev *input; - int keycode; - unsigned int irb = 1; - int i; + int scancode, i = 0; + unsigned int irb; input = fujitsu_laptop->input; if (event != ACPI_FUJITSU_NOTIFY_CODE1) { - 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); + sparse_keymap_report_event(input, -1, 1, true); return; } @@ -1098,40 +927,16 @@ static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event) fujitsu_laptop->flags_state = call_fext_func(FUNC_FLAGS, 0x4, 0x0, 0x0); - 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_bl->keycode1; - break; - case KEY2_CODE: - keycode = fujitsu_bl->keycode2; - break; - case KEY3_CODE: - keycode = fujitsu_bl->keycode3; - break; - case KEY4_CODE: - keycode = fujitsu_bl->keycode4; - break; - case KEY5_CODE: - keycode = fujitsu_bl->keycode5; - break; - case 0: - keycode = 0; - break; - default: + while ((irb = call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 && + i++ < MAX_HOTKEY_RINGBUFFER_SIZE) { + scancode = irb & 0x4ff; + if (sparse_keymap_entry_from_scancode(input, scancode)) + acpi_fujitsu_laptop_press(scancode); + else if (scancode == 0) + acpi_fujitsu_laptop_release(); + else vdbg_printk(FUJLAPTOP_DBG_WARN, "Unknown GIRB result [%x]\n", irb); - keycode = -1; - break; - } - - if (keycode > 0) - acpi_fujitsu_laptop_press(keycode); - else if (keycode == 0) - acpi_fujitsu_laptop_release(); } /* On some models (first seen on the Skylake-based Lifebook @@ -1139,14 +944,8 @@ static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event) * handled in software; its state is queried using FUNC_FLAGS */ if ((fujitsu_laptop->flags_supported & BIT(26)) && - (call_fext_func(FUNC_FLAGS, 0x1, 0x0, 0x0) & BIT(26))) { - keycode = KEY_TOUCHPAD_TOGGLE; - input_report_key(input, keycode, 1); - input_sync(input); - input_report_key(input, keycode, 0); - input_sync(input); - } - + (call_fext_func(FUNC_FLAGS, 0x1, 0x0, 0x0) & BIT(26))) + sparse_keymap_report_event(input, BIT(26), 1, true); } /* Initialization */ @@ -1162,7 +961,6 @@ static struct acpi_driver acpi_fujitsu_bl_driver = { .ids = fujitsu_bl_device_ids, .ops = { .add = acpi_fujitsu_bl_add, - .remove = acpi_fujitsu_bl_remove, .notify = acpi_fujitsu_bl_notify, }, }; @@ -1192,7 +990,7 @@ MODULE_DEVICE_TABLE(acpi, fujitsu_ids); static int __init fujitsu_init(void) { - int ret, max_brightness; + int ret; if (acpi_disabled) return -ENODEV; @@ -1200,100 +998,40 @@ static int __init fujitsu_init(void) fujitsu_bl = kzalloc(sizeof(struct fujitsu_bl), GFP_KERNEL); if (!fujitsu_bl) return -ENOMEM; - fujitsu_bl->keycode1 = KEY_PROG1; - fujitsu_bl->keycode2 = KEY_PROG2; - fujitsu_bl->keycode3 = KEY_PROG3; - fujitsu_bl->keycode4 = KEY_PROG4; - fujitsu_bl->keycode5 = KEY_RFKILL; - dmi_check_system(fujitsu_dmi_table); ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver); if (ret) - goto fail_acpi; + goto err_free_fujitsu_bl; /* Register platform stuff */ - fujitsu_bl->pf_device = platform_device_alloc("fujitsu-laptop", -1); - if (!fujitsu_bl->pf_device) { - ret = -ENOMEM; - goto fail_platform_driver; - } - - ret = platform_device_add(fujitsu_bl->pf_device); - if (ret) - goto fail_platform_device1; - - ret = - sysfs_create_group(&fujitsu_bl->pf_device->dev.kobj, - &fujitsu_pf_attribute_group); - if (ret) - goto fail_platform_device2; - - /* Register backlight stuff */ - - if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { - struct backlight_properties props; - - memset(&props, 0, sizeof(struct backlight_properties)); - max_brightness = fujitsu_bl->max_brightness; - props.type = BACKLIGHT_PLATFORM; - props.max_brightness = max_brightness - 1; - fujitsu_bl->bl_device = backlight_device_register("fujitsu-laptop", - NULL, NULL, - &fujitsu_bl_ops, - &props); - if (IS_ERR(fujitsu_bl->bl_device)) { - ret = PTR_ERR(fujitsu_bl->bl_device); - fujitsu_bl->bl_device = NULL; - goto fail_sysfs_group; - } - fujitsu_bl->bl_device->props.brightness = fujitsu_bl->brightness_level; - } - ret = platform_driver_register(&fujitsu_pf_driver); if (ret) - goto fail_backlight; + goto err_unregister_acpi; /* Register laptop driver */ fujitsu_laptop = kzalloc(sizeof(struct fujitsu_laptop), GFP_KERNEL); if (!fujitsu_laptop) { ret = -ENOMEM; - goto fail_laptop; + goto err_unregister_platform_driver; } ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver); if (ret) - goto fail_laptop1; - - /* Sync backlight power status (needs FUJ02E3 device, hence deferred) */ - if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { - if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3) - fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN; - else - fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK; - } + goto err_free_fujitsu_laptop; pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n"); return 0; -fail_laptop1: +err_free_fujitsu_laptop: kfree(fujitsu_laptop); -fail_laptop: +err_unregister_platform_driver: platform_driver_unregister(&fujitsu_pf_driver); -fail_backlight: - backlight_device_unregister(fujitsu_bl->bl_device); -fail_sysfs_group: - sysfs_remove_group(&fujitsu_bl->pf_device->dev.kobj, - &fujitsu_pf_attribute_group); -fail_platform_device2: - platform_device_del(fujitsu_bl->pf_device); -fail_platform_device1: - platform_device_put(fujitsu_bl->pf_device); -fail_platform_driver: +err_unregister_acpi: acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); -fail_acpi: +err_free_fujitsu_bl: kfree(fujitsu_bl); return ret; @@ -1307,13 +1045,6 @@ static void __exit fujitsu_cleanup(void) platform_driver_unregister(&fujitsu_pf_driver); - backlight_device_unregister(fujitsu_bl->bl_device); - - sysfs_remove_group(&fujitsu_bl->pf_device->dev.kobj, - &fujitsu_pf_attribute_group); - - platform_device_unregister(fujitsu_bl->pf_device); - acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver); kfree(fujitsu_bl); @@ -1324,11 +1055,10 @@ static void __exit fujitsu_cleanup(void) 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 ."); +module_param(use_alt_lcd_levels, int, 0644); +MODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)"); +module_param(disable_brightness_adjust, bool, 0644); +MODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment"); #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG module_param_named(debug, dbg_level, uint, 0644); MODULE_PARM_DESC(debug, "Sets debug level bit-mask"); diff --git a/drivers/platform/x86/hp-wireless.c b/drivers/platform/x86/hp-wireless.c index 988eedbd7c63..d6ea5e998fb8 100644 --- a/drivers/platform/x86/hp-wireless.c +++ b/drivers/platform/x86/hp-wireless.c @@ -1,7 +1,7 @@ /* - * hp-wireless button for Windows 8 + * Airplane mode button for HP & Xiaomi laptops * - * Copyright (C) 2014 Alex Hung <alex.hung@canonical.com> + * Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.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 @@ -29,11 +29,13 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alex Hung"); MODULE_ALIAS("acpi*:HPQ6001:*"); +MODULE_ALIAS("acpi*:WSTADEF:*"); static struct input_dev *hpwl_input_dev; static const struct acpi_device_id hpwl_ids[] = { {"HPQ6001", 0}, + {"WSTADEF", 0}, {"", 0}, }; @@ -108,23 +110,4 @@ static struct acpi_driver hpwl_driver = { }, }; -static int __init hpwl_init(void) -{ - int err; - - pr_info("Initializing HPQ6001 module\n"); - err = acpi_bus_register_driver(&hpwl_driver); - if (err) - pr_err("Unable to register HP wireless control driver.\n"); - - return err; -} - -static void __exit hpwl_exit(void) -{ - pr_info("Exiting HPQ6001 module\n"); - acpi_bus_unregister_driver(&hpwl_driver); -} - -module_init(hpwl_init); -module_exit(hpwl_exit); +module_acpi_driver(hpwl_driver); diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 96ffda493266..0df4209648d1 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -48,41 +48,29 @@ 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_BIOS_QUERY 0x9 -#define HPWMI_FEATURE_QUERY 0xb -#define HPWMI_HOTKEY_QUERY 0xc -#define HPWMI_FEATURE2_QUERY 0xd -#define HPWMI_WIRELESS2_QUERY 0x1b -#define HPWMI_POSTCODEERROR_QUERY 0x2a - enum hp_wmi_radio { - HPWMI_WIFI = 0, - HPWMI_BLUETOOTH = 1, - HPWMI_WWAN = 2, - HPWMI_GPS = 3, + HPWMI_WIFI = 0x0, + HPWMI_BLUETOOTH = 0x1, + HPWMI_WWAN = 0x2, + HPWMI_GPS = 0x3, }; 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, + HPWMI_DOCK_EVENT = 0x01, + HPWMI_PARK_HDD = 0x02, + HPWMI_SMART_ADAPTER = 0x03, + HPWMI_BEZEL_BUTTON = 0x04, + HPWMI_WIRELESS = 0x05, + HPWMI_CPU_BATTERY_THROTTLE = 0x06, + HPWMI_LOCK_SWITCH = 0x07, + HPWMI_LID_SWITCH = 0x08, + HPWMI_SCREEN_ROTATION = 0x09, + 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 { @@ -93,6 +81,39 @@ struct bios_args { u32 data; }; +enum hp_wmi_commandtype { + HPWMI_DISPLAY_QUERY = 0x01, + HPWMI_HDDTEMP_QUERY = 0x02, + HPWMI_ALS_QUERY = 0x03, + HPWMI_HARDWARE_QUERY = 0x04, + HPWMI_WIRELESS_QUERY = 0x05, + HPWMI_BATTERY_QUERY = 0x07, + HPWMI_BIOS_QUERY = 0x09, + HPWMI_FEATURE_QUERY = 0x0b, + HPWMI_HOTKEY_QUERY = 0x0c, + HPWMI_FEATURE2_QUERY = 0x0d, + HPWMI_WIRELESS2_QUERY = 0x1b, + HPWMI_POSTCODEERROR_QUERY = 0x2a, +}; + +enum hp_wmi_command { + HPWMI_READ = 0x01, + HPWMI_WRITE = 0x02, + HPWMI_ODM = 0x03, +}; + +enum hp_wmi_hardware_mask { + HPWMI_DOCK_MASK = 0x01, + HPWMI_TABLET_MASK = 0x04, +}; + +#define BIOS_ARGS_INIT(write, ctype, size) \ + (struct bios_args) { .signature = 0x55434553, \ + .command = (write) ? 0x2 : 0x1, \ + .commandtype = (ctype), \ + .datasize = (size), \ + .data = 0 } + struct bios_return { u32 sigpass; u32 return_code; @@ -170,8 +191,8 @@ 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 + * query: The commandtype (enum hp_wmi_commandtype) + * write: The command (enum hp_wmi_command) * buffer: Buffer used as input and/or output * insize: Size of input buffer * outsize: Size of output buffer @@ -182,27 +203,27 @@ static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; * -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 + * size. E.g. Battery info query 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) + * ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ, buffer, 1, 128) */ -static int hp_wmi_perform_query(int query, int write, void *buffer, - int insize, int outsize) +static int hp_wmi_perform_query(int query, enum hp_wmi_command command, + 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, + .command = command, .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; + int ret = 0; if (WARN_ON(insize > sizeof(args.data))) return -EINVAL; @@ -214,91 +235,61 @@ static int hp_wmi_perform_query(int query, int write, void *buffer, if (!obj) return -EINVAL; - else if (obj->type != ACPI_TYPE_BUFFER) { - kfree(obj); - return -EINVAL; + + if (obj->type != ACPI_TYPE_BUFFER) { + ret = -EINVAL; + goto out_free; } bios_return = (struct bios_return *)obj->buffer.pointer; - rc = bios_return->return_code; + ret = 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 (ret) { + if (ret != HPWMI_RET_UNKNOWN_CMDTYPE) + pr_warn("query 0x%x returned error 0x%x\n", query, ret); + goto out_free; } - if (!outsize) { - /* ignore output data */ - kfree(obj); - return 0; - } + /* Ignore output data of zero size */ + if (!outsize) + goto out_free; 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); + +out_free: kfree(obj); - return 0; + return ret; } -static int hp_wmi_display_state(void) +static int hp_wmi_read_int(int query) { - int state = 0; - int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, &state, - sizeof(state), sizeof(state)); - if (ret) - return -EINVAL; - return state; -} + int val = 0, ret; -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; -} + ret = hp_wmi_perform_query(query, HPWMI_READ, &val, + sizeof(val), sizeof(val)); -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; + return ret < 0 ? ret : -EINVAL; + + return val; } -static int hp_wmi_dock_state(void) +static int hp_wmi_hw_state(int mask) { - int state = 0; - int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, &state, - sizeof(state), sizeof(state)); + int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY); - if (ret) - return -EINVAL; + if (state < 0) + return state; 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 __init hp_wmi_bios_2008_later(void) { int state = 0; - int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, 0, &state, + int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state, sizeof(state), sizeof(state)); if (!ret) return 1; @@ -309,7 +300,7 @@ static int __init hp_wmi_bios_2008_later(void) static int __init hp_wmi_bios_2009_later(void) { int state = 0; - int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, 0, &state, + int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state, sizeof(state), sizeof(state)); if (!ret) return 1; @@ -320,11 +311,10 @@ static int __init hp_wmi_bios_2009_later(void) static int __init hp_wmi_enable_hotkeys(void) { int value = 0x6e; - int ret = hp_wmi_perform_query(HPWMI_BIOS_QUERY, 1, &value, + int ret = hp_wmi_perform_query(HPWMI_BIOS_QUERY, HPWMI_WRITE, &value, sizeof(value), 0); - if (ret) - return -EINVAL; - return 0; + + return ret <= 0 ? ret : -EINVAL; } static int hp_wmi_set_block(void *data, bool blocked) @@ -333,11 +323,10 @@ static int hp_wmi_set_block(void *data, bool blocked) int query = BIT(r + 8) | ((!blocked) << r); int ret; - ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, + ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE, &query, sizeof(query), 0); - if (ret) - return -EINVAL; - return 0; + + return ret <= 0 ? ret : -EINVAL; } static const struct rfkill_ops hp_wmi_rfkill_ops = { @@ -346,47 +335,38 @@ static const struct rfkill_ops hp_wmi_rfkill_ops = { 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 */ + int mask = 0x200 << (r * 8); - mask = 0x200 << (r * 8); + int wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY); - if (wireless & mask) - return false; - else - return true; + /* TBD: Pass error */ + WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY"); + + return !(wireless & mask); } 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 */ + int mask = 0x800 << (r * 8); - mask = 0x800 << (r * 8); + int wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY); - if (wireless & mask) - return false; - else - return true; + /* TBD: Pass error */ + WARN_ONCE(wireless < 0, "error executing HPWMI_WIRELESS_QUERY"); + + return !(wireless & mask); } 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 }; + int ret; - if (hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 1, - buffer, sizeof(buffer), 0)) - return -EINVAL; - return 0; + ret = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_WRITE, + buffer, sizeof(buffer), 0); + + return ret <= 0 ? ret : -EINVAL; } static const struct rfkill_ops hp_wmi_rfkill2_ops = { @@ -395,10 +375,10 @@ static const struct rfkill_ops hp_wmi_rfkill2_ops = { static int hp_wmi_rfkill2_refresh(void) { - int err, i; struct bios_rfkill2_state state; + int err, i; - err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, 0, &state, + err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, 0, sizeof(state)); if (err) return err; @@ -422,119 +402,113 @@ static int hp_wmi_rfkill2_refresh(void) return 0; } -static int hp_wmi_post_code_state(void) -{ - int state = 0; - int ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 0, &state, - sizeof(state), sizeof(state)); - if (ret) - return -EINVAL; - return state; -} - -static ssize_t show_display(struct device *dev, struct device_attribute *attr, +static ssize_t display_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_display_state(); + int value = hp_wmi_read_int(HPWMI_DISPLAY_QUERY); if (value < 0) - return -EINVAL; + return value; return sprintf(buf, "%d\n", value); } -static ssize_t show_hddtemp(struct device *dev, struct device_attribute *attr, +static ssize_t hddtemp_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_hddtemp_state(); + int value = hp_wmi_read_int(HPWMI_HDDTEMP_QUERY); if (value < 0) - return -EINVAL; + return value; return sprintf(buf, "%d\n", value); } -static ssize_t show_als(struct device *dev, struct device_attribute *attr, +static ssize_t als_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_als_state(); + int value = hp_wmi_read_int(HPWMI_ALS_QUERY); if (value < 0) - return -EINVAL; + return value; return sprintf(buf, "%d\n", value); } -static ssize_t show_dock(struct device *dev, struct device_attribute *attr, +static ssize_t dock_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_dock_state(); + int value = hp_wmi_hw_state(HPWMI_DOCK_MASK); if (value < 0) - return -EINVAL; + return value; return sprintf(buf, "%d\n", value); } -static ssize_t show_tablet(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t tablet_show(struct device *dev, struct device_attribute *attr, + char *buf) { - int value = hp_wmi_tablet_state(); + int value = hp_wmi_hw_state(HPWMI_TABLET_MASK); if (value < 0) - return -EINVAL; + return value; return sprintf(buf, "%d\n", value); } -static ssize_t show_postcode(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t postcode_show(struct device *dev, struct device_attribute *attr, + char *buf) { /* Get the POST error code of previous boot failure. */ - int value = hp_wmi_post_code_state(); + int value = hp_wmi_read_int(HPWMI_POSTCODEERROR_QUERY); if (value < 0) - return -EINVAL; + return value; return sprintf(buf, "0x%x\n", value); } -static ssize_t set_als(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t als_store(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, + int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp, sizeof(tmp), sizeof(tmp)); if (ret) - return -EINVAL; + return ret < 0 ? ret : -EINVAL; return count; } -static ssize_t set_postcode(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t postcode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { + long unsigned int tmp2; int ret; u32 tmp; - long unsigned int tmp2; ret = kstrtoul(buf, 10, &tmp2); - if (ret || tmp2 != 1) - return -EINVAL; + if (!ret && tmp2 != 1) + ret = -EINVAL; + if (ret) + goto out; /* Clear the POST error code. It is kept until until cleared. */ tmp = (u32) tmp2; - ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, 1, &tmp, + ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp, sizeof(tmp), sizeof(tmp)); + +out: if (ret) - return -EINVAL; + return ret < 0 ? ret : -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 DEVICE_ATTR(postcode, S_IRUGO | S_IWUSR, show_postcode, set_postcode); +static DEVICE_ATTR_RO(display); +static DEVICE_ATTR_RO(hddtemp); +static DEVICE_ATTR_RW(als); +static DEVICE_ATTR_RO(dock); +static DEVICE_ATTR_RO(tablet); +static DEVICE_ATTR_RW(postcode); 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; + union acpi_object *obj; acpi_status status; + u32 *location; + int key_code; status = wmi_get_event_data(value, &response); if (status != AE_OK) { @@ -572,10 +546,12 @@ static void hp_wmi_notify(u32 value, void *context) 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()); + if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_hw_state(HPWMI_DOCK_MASK)); + if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_hw_state(HPWMI_TABLET_MASK)); input_sync(hp_wmi_input_dev); break; case HPWMI_PARK_HDD: @@ -583,11 +559,8 @@ static void hp_wmi_notify(u32 value, void *context) 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) + key_code = hp_wmi_read_int(HPWMI_HOTKEY_QUERY); + if (key_code < 0) break; if (!sparse_keymap_report_event(hp_wmi_input_dev, @@ -643,7 +616,7 @@ static void hp_wmi_notify(u32 value, void *context) static int __init hp_wmi_input_setup(void) { acpi_status status; - int err; + int err, val; hp_wmi_input_dev = input_allocate_device(); if (!hp_wmi_input_dev) @@ -654,17 +627,26 @@ static int __init hp_wmi_input_setup(void) 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); + + /* Dock */ + val = hp_wmi_hw_state(HPWMI_DOCK_MASK); + if (!(val < 0)) { + __set_bit(SW_DOCK, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_DOCK, val); + } + + /* Tablet mode */ + val = hp_wmi_hw_state(HPWMI_TABLET_MASK); + if (!(val < 0)) { + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); + } 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); if (!hp_wmi_bios_2009_later() && hp_wmi_bios_2008_later()) @@ -673,7 +655,7 @@ static int __init hp_wmi_input_setup(void) status = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL); if (ACPI_FAILURE(status)) { err = -EIO; - goto err_free_keymap; + goto err_free_dev; } err = input_register_device(hp_wmi_input_dev); @@ -684,8 +666,6 @@ static int __init hp_wmi_input_setup(void) 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; @@ -694,7 +674,6 @@ static int __init hp_wmi_input_setup(void) 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); } @@ -710,15 +689,13 @@ static void cleanup_sysfs(struct platform_device *device) static int __init hp_wmi_rfkill_setup(struct platform_device *device) { - int err; - int wireless = 0; + int err, wireless; - err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, &wireless, - sizeof(wireless), sizeof(wireless)); - if (err) - return err; + wireless = hp_wmi_read_int(HPWMI_WIRELESS_QUERY); + if (wireless < 0) + return wireless; - err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, &wireless, + err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, HPWMI_WRITE, &wireless, sizeof(wireless), 0); if (err) return err; @@ -795,12 +772,13 @@ register_wifi_error: static int __init 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, + int err, i; + + err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, 0, sizeof(state)); if (err) - return err; + return err < 0 ? err : -EINVAL; if (state.count > HPWMI_MAX_RFKILL2_DEVICES) { pr_warn("unable to parse 0x1b query output\n"); @@ -950,10 +928,12 @@ static int hp_wmi_resume_handler(struct device *device) * 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()); + if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_DOCK, + hp_wmi_hw_state(HPWMI_DOCK_MASK)); + if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, + hp_wmi_hw_state(HPWMI_TABLET_MASK)); input_sync(hp_wmi_input_dev); } @@ -991,9 +971,9 @@ static struct platform_driver hp_wmi_driver = { 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); + int err; if (!bios_capable && !event_capable) return -ENODEV; diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index f46ece2ce3c4..24ca9fbe31cc 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -604,14 +604,12 @@ static int ideapad_input_init(struct ideapad_private *priv) error = input_register_device(inputdev); if (error) { pr_err("Unable to register input device\n"); - goto err_free_keymap; + goto err_free_dev; } priv->inputdev = inputdev; return 0; -err_free_keymap: - sparse_keymap_free(inputdev); err_free_dev: input_free_device(inputdev); return error; @@ -619,7 +617,6 @@ err_free_dev: static void ideapad_input_exit(struct ideapad_private *priv) { - sparse_keymap_free(priv->inputdev); input_unregister_device(priv->inputdev); priv->inputdev = NULL; } @@ -872,6 +869,20 @@ static const struct dmi_system_id no_hw_rfkill_list[] = { }, }, { + .ident = "Lenovo V310-15ISK", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15ISK"), + }, + }, + { + .ident = "Lenovo ideapad 310-15IKB", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IKB"), + }, + }, + { .ident = "Lenovo ideapad Y700-15ACZ", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c index bcf438f38781..63ba2cbd04c2 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -77,14 +77,12 @@ struct intel_hid_priv { struct input_dev *array; }; -static int intel_hid_set_enable(struct device *device, int enable) +static int intel_hid_set_enable(struct device *device, bool enable) { - union acpi_object arg0 = { ACPI_TYPE_INTEGER }; - struct acpi_object_list args = { 1, &arg0 }; acpi_status status; - arg0.integer.value = enable; - status = acpi_evaluate_object(ACPI_HANDLE(device), "HDSM", &args, NULL); + status = acpi_execute_simple_method(ACPI_HANDLE(device), "HDSM", + enable); if (ACPI_FAILURE(status)) { dev_warn(device, "failed to %sable hotkeys\n", enable ? "en" : "dis"); @@ -120,7 +118,7 @@ static void intel_button_array_enable(struct device *device, bool enable) static int intel_hid_pl_suspend_handler(struct device *device) { - intel_hid_set_enable(device, 0); + intel_hid_set_enable(device, false); intel_button_array_enable(device, false); return 0; @@ -128,7 +126,7 @@ static int intel_hid_pl_suspend_handler(struct device *device) static int intel_hid_pl_resume_handler(struct device *device) { - intel_hid_set_enable(device, 1); + intel_hid_set_enable(device, true); intel_button_array_enable(device, true); return 0; @@ -136,6 +134,7 @@ static int intel_hid_pl_resume_handler(struct device *device) static const struct dev_pm_ops intel_hid_pl_pm_ops = { .freeze = intel_hid_pl_suspend_handler, + .thaw = intel_hid_pl_resume_handler, .restore = intel_hid_pl_resume_handler, .suspend = intel_hid_pl_suspend_handler, .resume = intel_hid_pl_resume_handler, @@ -146,28 +145,18 @@ static int intel_hid_input_setup(struct platform_device *device) struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); int ret; - priv->input_dev = input_allocate_device(); + priv->input_dev = devm_input_allocate_device(&device->dev); if (!priv->input_dev) return -ENOMEM; ret = sparse_keymap_setup(priv->input_dev, intel_hid_keymap, NULL); if (ret) - goto err_free_device; + return ret; - priv->input_dev->dev.parent = &device->dev; priv->input_dev->name = "Intel HID events"; priv->input_dev->id.bustype = BUS_HOST; - set_bit(KEY_RFKILL, priv->input_dev->keybit); - - ret = input_register_device(priv->input_dev); - if (ret) - goto err_free_device; - return 0; - -err_free_device: - input_free_device(priv->input_dev); - return ret; + return input_register_device(priv->input_dev); } static int intel_button_array_input_setup(struct platform_device *device) @@ -184,20 +173,12 @@ static int intel_button_array_input_setup(struct platform_device *device) if (ret) return ret; - priv->array->dev.parent = &device->dev; priv->array->name = "Intel HID 5 button array"; priv->array->id.bustype = BUS_HOST; return input_register_device(priv->array); } -static void intel_hid_input_destroy(struct platform_device *device) -{ - struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); - - input_unregister_device(priv->input_dev); -} - static void notify_handler(acpi_handle handle, u32 event, void *context) { struct platform_device *device = context; @@ -272,12 +253,10 @@ static int intel_hid_probe(struct platform_device *device) ACPI_DEVICE_NOTIFY, notify_handler, device); - if (ACPI_FAILURE(status)) { - err = -EBUSY; - goto err_remove_input; - } + if (ACPI_FAILURE(status)) + return -EBUSY; - err = intel_hid_set_enable(&device->dev, 1); + err = intel_hid_set_enable(&device->dev, true); if (err) goto err_remove_notify; @@ -296,9 +275,6 @@ static int intel_hid_probe(struct platform_device *device) err_remove_notify: acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); -err_remove_input: - intel_hid_input_destroy(device); - return err; } @@ -307,8 +283,7 @@ static int intel_hid_remove(struct platform_device *device) acpi_handle handle = ACPI_HANDLE(&device->dev); acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler); - intel_hid_input_destroy(device); - intel_hid_set_enable(&device->dev, 0); + intel_hid_set_enable(&device->dev, false); intel_button_array_enable(&device->dev, false); /* diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 554e82ebe83c..c2035e121ac2 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -37,6 +37,10 @@ static const struct acpi_device_id intel_vbtn_ids[] = { static const struct key_entry intel_vbtn_keymap[] = { { KE_IGNORE, 0xC0, { KEY_POWER } }, /* power key press */ { KE_KEY, 0xC1, { KEY_POWER } }, /* power key release */ + { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */ + { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */ + { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */ + { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ { KE_END }, }; diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c new file mode 100644 index 000000000000..6a1b2ca5b6fe --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe.c @@ -0,0 +1,143 @@ +/* + * Intel Cherry Trail ACPI INT33FE pseudo device driver + * + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.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. + * + * Some Intel Cherry Trail based device which ship with Windows 10, have + * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2 + * resources, for 4 different chips attached to various i2c busses: + * 1. The Whiskey Cove pmic, which is also described by the INT34D3 ACPI device + * 2. Maxim MAX17047 Fuel Gauge Controller + * 3. FUSB302 USB Type-C Controller + * 4. PI3USB30532 USB switch + * + * So this driver is a stub / pseudo driver whose only purpose is to + * instantiate i2c-clients for chips 2 - 4, so that standard i2c drivers + * for these chips can bind to the them. + */ + +#include <linux/acpi.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> + +#define EXPECTED_PTYPE 4 + +struct cht_int33fe_data { + struct i2c_client *max17047; + struct i2c_client *fusb302; + struct i2c_client *pi3usb30532; +}; + +static int cht_int33fe_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct i2c_board_info board_info; + struct cht_int33fe_data *data; + unsigned long long ptyp; + acpi_status status; + int fusb302_irq; + + status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); + if (ACPI_FAILURE(status)) { + dev_err(dev, "Error getting PTYPE\n"); + return -ENODEV; + } + + /* + * The same ACPI HID is used for different configurations check PTYP + * to ensure that we are dealing with the expected config. + */ + if (ptyp != EXPECTED_PTYPE) + return -ENODEV; + + /* The FUSB302 uses the irq at index 1 and is the only irq user */ + fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); + if (fusb302_irq < 0) { + if (fusb302_irq != -EPROBE_DEFER) + dev_err(dev, "Error getting FUSB302 irq\n"); + return fusb302_irq; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); + + data->max17047 = i2c_acpi_new_device(dev, 1, &board_info); + if (!data->max17047) + return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */ + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "fusb302", I2C_NAME_SIZE); + board_info.irq = fusb302_irq; + + data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); + if (!data->fusb302) + goto out_unregister_max17047; + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); + + data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); + if (!data->pi3usb30532) + goto out_unregister_fusb302; + + i2c_set_clientdata(client, data); + + return 0; + +out_unregister_fusb302: + i2c_unregister_device(data->fusb302); + +out_unregister_max17047: + i2c_unregister_device(data->max17047); + + return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */ +} + +static int cht_int33fe_remove(struct i2c_client *i2c) +{ + struct cht_int33fe_data *data = i2c_get_clientdata(i2c); + + i2c_unregister_device(data->pi3usb30532); + i2c_unregister_device(data->fusb302); + i2c_unregister_device(data->max17047); + + return 0; +} + +static const struct i2c_device_id cht_int33fe_i2c_id[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, cht_int33fe_i2c_id); + +static const struct acpi_device_id cht_int33fe_acpi_ids[] = { + { "INT33FE", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); + +static struct i2c_driver cht_int33fe_driver = { + .driver = { + .name = "Intel Cherry Trail ACPI INT33FE driver", + .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), + }, + .probe_new = cht_int33fe_probe, + .remove = cht_int33fe_remove, + .id_table = cht_int33fe_i2c_id, + .disable_i2c_core_irq_mapping = true, +}; + +module_i2c_driver(cht_int33fe_driver); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index 0651d47b8eeb..e4d4dfe3e1d1 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -57,10 +57,6 @@ #define IPC_WRITE_BUFFER 0x80 #define IPC_READ_BUFFER 0x90 -/* PMC Global Control Registers */ -#define GCR_TELEM_DEEP_S0IX_OFFSET 0x1078 -#define GCR_TELEM_SHLW_S0IX_OFFSET 0x1080 - /* Residency with clock rate at 19.2MHz to usecs */ #define S0IX_RESIDENCY_IN_USECS(d, s) \ ({ \ @@ -82,7 +78,7 @@ /* exported resources from IFWI */ #define PLAT_RESOURCE_IPC_INDEX 0 #define PLAT_RESOURCE_IPC_SIZE 0x1000 -#define PLAT_RESOURCE_GCR_OFFSET 0x1008 +#define PLAT_RESOURCE_GCR_OFFSET 0x1000 #define PLAT_RESOURCE_GCR_SIZE 0x1000 #define PLAT_RESOURCE_BIOS_DATA_INDEX 1 #define PLAT_RESOURCE_BIOS_IFACE_INDEX 2 @@ -112,6 +108,13 @@ #define TCO_PMC_OFFSET 0x8 #define TCO_PMC_SIZE 0x4 +/* PMC register bit definitions */ + +/* PMC_CFG_REG bit masks */ +#define PMC_CFG_NO_REBOOT_MASK (1 << 4) +#define PMC_CFG_NO_REBOOT_EN (1 << 4) +#define PMC_CFG_NO_REBOOT_DIS (0 << 4) + static struct intel_pmc_ipc_dev { struct device *dev; void __iomem *ipc_base; @@ -126,8 +129,7 @@ static struct intel_pmc_ipc_dev { struct platform_device *tco_dev; /* gcr */ - resource_size_t gcr_base; - int gcr_size; + void __iomem *gcr_mem_base; bool has_gcr_regs; /* punit */ @@ -196,7 +198,128 @@ static inline u32 ipc_data_readl(u32 offset) static inline u64 gcr_data_readq(u32 offset) { - return readq(ipcdev.ipc_base + offset); + return readq(ipcdev.gcr_mem_base + offset); +} + +static inline int is_gcr_valid(u32 offset) +{ + if (!ipcdev.has_gcr_regs) + return -EACCES; + + if (offset > PLAT_RESOURCE_GCR_SIZE) + return -EINVAL; + + return 0; +} + +/** + * intel_pmc_gcr_read() - Read PMC GCR register + * @offset: offset of GCR register from GCR address base + * @data: data pointer for storing the register output + * + * Reads the PMC GCR register of given offset. + * + * Return: negative value on error or 0 on success. + */ +int intel_pmc_gcr_read(u32 offset, u32 *data) +{ + int ret; + + mutex_lock(&ipclock); + + ret = is_gcr_valid(offset); + if (ret < 0) { + mutex_unlock(&ipclock); + return ret; + } + + *data = readl(ipcdev.gcr_mem_base + offset); + + mutex_unlock(&ipclock); + + return 0; +} +EXPORT_SYMBOL_GPL(intel_pmc_gcr_read); + +/** + * intel_pmc_gcr_write() - Write PMC GCR register + * @offset: offset of GCR register from GCR address base + * @data: register update value + * + * Writes the PMC GCR register of given offset with given + * value. + * + * Return: negative value on error or 0 on success. + */ +int intel_pmc_gcr_write(u32 offset, u32 data) +{ + int ret; + + mutex_lock(&ipclock); + + ret = is_gcr_valid(offset); + if (ret < 0) { + mutex_unlock(&ipclock); + return ret; + } + + writel(data, ipcdev.gcr_mem_base + offset); + + mutex_unlock(&ipclock); + + return 0; +} +EXPORT_SYMBOL_GPL(intel_pmc_gcr_write); + +/** + * intel_pmc_gcr_update() - Update PMC GCR register bits + * @offset: offset of GCR register from GCR address base + * @mask: bit mask for update operation + * @val: update value + * + * Updates the bits of given GCR register as specified by + * @mask and @val. + * + * Return: negative value on error or 0 on success. + */ +int intel_pmc_gcr_update(u32 offset, u32 mask, u32 val) +{ + u32 new_val; + int ret = 0; + + mutex_lock(&ipclock); + + ret = is_gcr_valid(offset); + if (ret < 0) + goto gcr_ipc_unlock; + + new_val = readl(ipcdev.gcr_mem_base + offset); + + new_val &= ~mask; + new_val |= val & mask; + + writel(new_val, ipcdev.gcr_mem_base + offset); + + new_val = readl(ipcdev.gcr_mem_base + offset); + + /* check whether the bit update is successful */ + if ((new_val & mask) != (val & mask)) { + ret = -EIO; + goto gcr_ipc_unlock; + } + +gcr_ipc_unlock: + mutex_unlock(&ipclock); + return ret; +} +EXPORT_SYMBOL_GPL(intel_pmc_gcr_update); + +static int update_no_reboot_bit(void *priv, bool set) +{ + u32 value = set ? PMC_CFG_NO_REBOOT_EN : PMC_CFG_NO_REBOOT_DIS; + + return intel_pmc_gcr_update(PMC_GCR_PMC_CFG_REG, + PMC_CFG_NO_REBOOT_MASK, value); } static int intel_pmc_ipc_check_status(void) @@ -516,15 +639,13 @@ static struct resource tco_res[] = { { .flags = IORESOURCE_IO, }, - /* GCS */ - { - .flags = IORESOURCE_MEM, - }, }; static struct itco_wdt_platform_data tco_info = { .name = "Apollo Lake SoC", .version = 5, + .no_reboot_priv = &ipcdev, + .update_no_reboot_bit = update_no_reboot_bit, }; #define TELEMETRY_RESOURCE_PUNIT_SSRAM 0 @@ -581,10 +702,6 @@ static int ipc_create_tco_device(void) res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET; res->end = res->start + SMI_EN_SIZE - 1; - res = tco_res + TCO_RESOURCE_GCR_MEM; - res->start = ipcdev.gcr_base + TCO_PMC_OFFSET; - res->end = res->start + TCO_PMC_SIZE - 1; - pdev = platform_device_register_full(&pdevinfo); if (IS_ERR(pdev)) return PTR_ERR(pdev); @@ -746,8 +863,7 @@ static int ipc_plat_get_res(struct platform_device *pdev) } ipcdev.ipc_base = addr; - ipcdev.gcr_base = res->start + PLAT_RESOURCE_GCR_OFFSET; - ipcdev.gcr_size = PLAT_RESOURCE_GCR_SIZE; + ipcdev.gcr_mem_base = addr + PLAT_RESOURCE_GCR_OFFSET; dev_info(&pdev->dev, "ipc res: %pR\n", res); ipcdev.telem_res_inval = 0; @@ -782,8 +898,8 @@ int intel_pmc_s0ix_counter_read(u64 *data) if (!ipcdev.has_gcr_regs) return -EACCES; - deep = gcr_data_readq(GCR_TELEM_DEEP_S0IX_OFFSET); - shlw = gcr_data_readq(GCR_TELEM_SHLW_S0IX_OFFSET); + deep = gcr_data_readq(PMC_GCR_TELEM_DEEP_S0IX_REG); + shlw = gcr_data_readq(PMC_GCR_TELEM_SHLW_S0IX_REG); *data = S0IX_RESIDENCY_IN_USECS(deep, shlw); diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index e81daff65f62..f7cf981502cd 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -491,6 +491,69 @@ int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen, } EXPORT_SYMBOL(intel_scu_ipc_command); +#define IPC_SPTR 0x08 +#define IPC_DPTR 0x0C + +/** + * intel_scu_ipc_raw_command() - IPC command with data and pointers + * @cmd: IPC command code. + * @sub: IPC command sub type. + * @in: input data of this IPC command. + * @inlen: input data length in dwords. + * @out: output data of this IPC command. + * @outlen: output data length in dwords. + * @sptr: data writing to SPTR register. + * @dptr: data writing to DPTR register. + * + * Send an IPC command to SCU with input/output data and source/dest pointers. + * + * Return: an IPC error code or 0 on success. + */ +int intel_scu_ipc_raw_command(int cmd, int sub, u8 *in, int inlen, + u32 *out, int outlen, u32 dptr, u32 sptr) +{ + struct intel_scu_ipc_dev *scu = &ipcdev; + int inbuflen = DIV_ROUND_UP(inlen, 4); + u32 inbuf[4]; + int i, err; + + /* Up to 16 bytes */ + if (inbuflen > 4) + return -EINVAL; + + mutex_lock(&ipclock); + if (scu->dev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } + + writel(dptr, scu->ipc_base + IPC_DPTR); + writel(sptr, scu->ipc_base + IPC_SPTR); + + /* + * SRAM controller doesn't support 8-bit writes, it only + * supports 32-bit writes, so we have to copy input data into + * the temporary buffer, and SCU FW will use the inlen to + * determine the actual input data length in the temporary + * buffer. + */ + memcpy(inbuf, in, inlen); + + for (i = 0; i < inbuflen; i++) + ipc_data_writel(scu, inbuf[i], 4 * i); + + ipc_command(scu, (inlen << 16) | (sub << 12) | cmd); + err = intel_scu_ipc_check_status(scu); + if (!err) { + for (i = 0; i < outlen; i++) + *out++ = ipc_data_readl(scu, 4 * i); + } + + mutex_unlock(&ipclock); + return err; +} +EXPORT_SYMBOL_GPL(intel_scu_ipc_raw_command); + /* I2C commands */ #define IPC_I2C_WRITE 1 /* I2C Write command */ #define IPC_I2C_READ 2 /* I2C Read command */ @@ -566,21 +629,17 @@ static irqreturn_t ioc(int irq, void *dev_id) */ static int ipc_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - int platform; /* Platform type */ int err; struct intel_scu_ipc_dev *scu = &ipcdev; struct intel_scu_ipc_pdata_t *pdata; - platform = intel_mid_identify_cpu(); - if (platform == 0) - return -ENODEV; - if (scu->dev) /* We support only one SCU */ return -EBUSY; pdata = (struct intel_scu_ipc_pdata_t *)id->driver_data; + if (!pdata) + return -ENODEV; - scu->dev = &pdev->dev; scu->irq_mode = pdata->irq_mode; err = pcim_enable_device(pdev); @@ -593,39 +652,34 @@ static int ipc_probe(struct pci_dev *pdev, const struct pci_device_id *id) init_completion(&scu->cmd_complete); - err = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_scu_ipc", - scu); - if (err) - return err; - scu->ipc_base = pcim_iomap_table(pdev)[0]; scu->i2c_base = ioremap_nocache(pdata->i2c_base, pdata->i2c_len); if (!scu->i2c_base) return -ENOMEM; + err = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_scu_ipc", + scu); + if (err) + return err; + + /* Assign device at last */ + scu->dev = &pdev->dev; + intel_scu_devices_create(); pci_set_drvdata(pdev, scu); return 0; } +#define SCU_DEVICE(id, pdata) {PCI_VDEVICE(INTEL, id), (kernel_ulong_t)&pdata} + static const struct pci_device_id pci_ids[] = { - { - PCI_VDEVICE(INTEL, PCI_DEVICE_ID_LINCROFT), - (kernel_ulong_t)&intel_scu_ipc_lincroft_pdata, - }, { - PCI_VDEVICE(INTEL, PCI_DEVICE_ID_PENWELL), - (kernel_ulong_t)&intel_scu_ipc_penwell_pdata, - }, { - PCI_VDEVICE(INTEL, PCI_DEVICE_ID_CLOVERVIEW), - (kernel_ulong_t)&intel_scu_ipc_penwell_pdata, - }, { - PCI_VDEVICE(INTEL, PCI_DEVICE_ID_TANGIER), - (kernel_ulong_t)&intel_scu_ipc_tangier_pdata, - }, { - 0, - } + SCU_DEVICE(PCI_DEVICE_ID_LINCROFT, intel_scu_ipc_lincroft_pdata), + SCU_DEVICE(PCI_DEVICE_ID_PENWELL, intel_scu_ipc_penwell_pdata), + SCU_DEVICE(PCI_DEVICE_ID_CLOVERVIEW, intel_scu_ipc_penwell_pdata), + SCU_DEVICE(PCI_DEVICE_ID_TANGIER, intel_scu_ipc_tangier_pdata), + {} }; static struct pci_driver ipc_driver = { diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index 42317704629d..9e90827c176a 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -976,23 +976,15 @@ static int __init msi_laptop_input_setup(void) err = input_register_device(msi_laptop_input_dev); if (err) - goto err_free_keymap; + goto err_free_dev; 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; @@ -1037,7 +1029,7 @@ static int __init load_scm_model_init(struct platform_device *sdev) return 0; fail_filter: - msi_laptop_input_destroy(); + input_unregister_device(msi_laptop_input_dev); fail_input: rfkill_cleanup(); @@ -1158,7 +1150,7 @@ static void __exit msi_cleanup(void) { if (quirks->load_scm_model) { i8042_remove_filter(msi_laptop_i8042_filter); - msi_laptop_input_destroy(); + input_unregister_device(msi_laptop_input_dev); cancel_delayed_work_sync(&msi_rfkill_dwork); cancel_work_sync(&msi_rfkill_work); rfkill_cleanup(); diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 9a32f8627ecc..f6209b739ec0 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -281,14 +281,12 @@ static int __init msi_wmi_input_setup(void) err = input_register_device(msi_wmi_input_dev); if (err) - goto err_free_keymap; + goto err_free_dev; last_pressed = 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; @@ -342,10 +340,8 @@ 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); + if (event_wmi) input_unregister_device(msi_wmi_input_dev); - } return err; } @@ -353,7 +349,6 @@ 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); } backlight_device_unregister(backlight); diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 59b8eb626dcc..975f4e100dbd 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -520,29 +520,17 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc) if (error) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unable to register input device\n")); - goto err_free_keymap; + goto err_free_dev; } 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 @@ -640,7 +628,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device) out_backlight: backlight_device_unregister(pcc->backlight); out_input: - acpi_pcc_destroy_input(pcc); + input_unregister_device(pcc->input_dev); out_sinf: kfree(pcc->sinf); out_hotkey: @@ -660,7 +648,7 @@ static int acpi_pcc_hotkey_remove(struct acpi_device *device) backlight_device_unregister(pcc->backlight); - acpi_pcc_destroy_input(pcc); + input_unregister_device(pcc->input_dev); kfree(pcc->sinf); kfree(pcc); diff --git a/drivers/platform/x86/silead_dmi.c b/drivers/platform/x86/silead_dmi.c index 02e11fdbf375..a3a57d93cf06 100644 --- a/drivers/platform/x86/silead_dmi.c +++ b/drivers/platform/x86/silead_dmi.c @@ -22,10 +22,10 @@ struct silead_ts_dmi_data { const char *acpi_name; - struct property_entry *properties; + const struct property_entry *properties; }; -static struct property_entry cube_iwork8_air_props[] = { +static const struct property_entry cube_iwork8_air_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 1660), PROPERTY_ENTRY_U32("touchscreen-size-y", 900), PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), @@ -39,7 +39,7 @@ static const struct silead_ts_dmi_data cube_iwork8_air_data = { .properties = cube_iwork8_air_props, }; -static struct property_entry jumper_ezpad_mini3_props[] = { +static const struct property_entry jumper_ezpad_mini3_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 1700), PROPERTY_ENTRY_U32("touchscreen-size-y", 1150), PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), @@ -53,6 +53,33 @@ static const struct silead_ts_dmi_data jumper_ezpad_mini3_data = { .properties = jumper_ezpad_mini3_props, }; +static const struct property_entry dexp_ursus_7w_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 890), + PROPERTY_ENTRY_U32("touchscreen-size-y", 630), + PROPERTY_ENTRY_STRING("firmware-name", "gsl1686-dexp-ursus-7w.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + { } +}; + +static const struct silead_ts_dmi_data dexp_ursus_7w_data = { + .acpi_name = "MSSL1680:00", + .properties = dexp_ursus_7w_props, +}; + +static const struct property_entry surftab_wintron70_st70416_6_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 884), + PROPERTY_ENTRY_U32("touchscreen-size-y", 632), + PROPERTY_ENTRY_STRING("firmware-name", + "gsl1686-surftab-wintron70-st70416-6.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + { } +}; + +static const struct silead_ts_dmi_data surftab_wintron70_st70416_6_data = { + .acpi_name = "MSSL1680:00", + .properties = surftab_wintron70_st70416_6_props, +}; + static const struct dmi_system_id silead_ts_dmi_table[] = { { /* CUBE iwork8 Air */ @@ -72,24 +99,37 @@ static const struct dmi_system_id silead_ts_dmi_table[] = { DMI_MATCH(DMI_BIOS_VERSION, "jumperx.T87.KFBNEEA"), }, }, + { + /* DEXP Ursus 7W */ + .driver_data = (void *)&dexp_ursus_7w_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "7W"), + }, + }, + { + /* Trekstor Surftab Wintron 7.0 ST70416-6 */ + .driver_data = (void *)&surftab_wintron70_st70416_6_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "ST70416-6"), + /* Exact match, different versions need different fw */ + DMI_MATCH(DMI_BIOS_VERSION, "TREK.G.WI71C.JGBMRBA04"), + }, + }, { }, }; -static void silead_ts_dmi_add_props(struct device *dev) +static const struct silead_ts_dmi_data *silead_ts_data; + +static void silead_ts_dmi_add_props(struct i2c_client *client) { - struct i2c_client *client = to_i2c_client(dev); - const struct dmi_system_id *dmi_id; - const struct silead_ts_dmi_data *ts_data; + struct device *dev = &client->dev; int error; - dmi_id = dmi_first_match(silead_ts_dmi_table); - if (!dmi_id) - return; - - ts_data = dmi_id->driver_data; if (has_acpi_companion(dev) && - !strncmp(ts_data->acpi_name, client->name, I2C_NAME_SIZE)) { - error = device_add_properties(dev, ts_data->properties); + !strncmp(silead_ts_data->acpi_name, client->name, I2C_NAME_SIZE)) { + error = device_add_properties(dev, silead_ts_data->properties); if (error) dev_err(dev, "failed to add properties: %d\n", error); } @@ -99,10 +139,13 @@ static int silead_ts_dmi_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { struct device *dev = data; + struct i2c_client *client; switch (action) { case BUS_NOTIFY_ADD_DEVICE: - silead_ts_dmi_add_props(dev); + client = i2c_verify_client(dev); + if (client) + silead_ts_dmi_add_props(client); break; default: @@ -118,8 +161,15 @@ static struct notifier_block silead_ts_dmi_notifier = { static int __init silead_ts_dmi_init(void) { + const struct dmi_system_id *dmi_id; int error; + dmi_id = dmi_first_match(silead_ts_dmi_table); + if (!dmi_id) + return 0; /* Not an error */ + + silead_ts_data = dmi_id->driver_data; + error = bus_register_notifier(&i2c_bus_type, &silead_ts_dmi_notifier); if (error) pr_err("%s: failed to register i2c bus notifier: %d\n", diff --git a/drivers/platform/x86/surface3_button.c b/drivers/platform/x86/surface3_button.c index 8bfd7f613d36..57f51476bb65 100644 --- a/drivers/platform/x86/surface3_button.c +++ b/drivers/platform/x86/surface3_button.c @@ -196,9 +196,10 @@ static int surface3_button_probe(struct i2c_client *client, strlen(SURFACE_BUTTON_OBJ_NAME))) return -ENODEV; - if (gpiod_count(dev, KBUILD_MODNAME) <= 0) { + error = gpiod_count(dev, NULL); + if (error < 0) { dev_dbg(dev, "no GPIO attached, ignoring...\n"); - return -ENODEV; + return error; } priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 1d18b32628ec..7b6cb0c69b02 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1922,7 +1922,9 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_UNK7, TP_ACPI_HOTKEYSCAN_UNK8, - TP_ACPI_HOTKEYSCAN_MUTE2, + /* Adaptive keyboard keycodes */ + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START, + TP_ACPI_HOTKEYSCAN_MUTE2 = TP_ACPI_HOTKEYSCAN_ADAPTIVE_START, TP_ACPI_HOTKEYSCAN_BRIGHTNESS_ZERO, TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL, TP_ACPI_HOTKEYSCAN_CLOUD, @@ -1943,6 +1945,15 @@ enum { /* hot key scan codes (derived from ACPI DSDT) */ TP_ACPI_HOTKEYSCAN_CAMERA_MODE, TP_ACPI_HOTKEYSCAN_ROTATE_DISPLAY, + /* Lenovo extended keymap, starting at 0x1300 */ + TP_ACPI_HOTKEYSCAN_EXTENDED_START, + /* first new observed key (star, favorites) is 0x1311 */ + TP_ACPI_HOTKEYSCAN_STAR = 69, + TP_ACPI_HOTKEYSCAN_CLIPPING_TOOL2, + TP_ACPI_HOTKEYSCAN_UNK25, + TP_ACPI_HOTKEYSCAN_BLUETOOTH, + TP_ACPI_HOTKEYSCAN_KEYBOARD, + /* Hotkey keymap size */ TPACPI_HOTKEY_MAP_LEN }; @@ -3250,6 +3261,15 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + + /* No assignment, used for newer Lenovo models */ + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN + }, /* Generic keymap for Lenovo ThinkPads */ @@ -3335,6 +3355,29 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) KEY_RESERVED, /* Microphone cancellation */ KEY_RESERVED, /* Camera mode */ KEY_RESERVED, /* Rotate display, 0x116 */ + + /* + * These are found in 2017 models (e.g. T470s, X270). + * The lowest known value is 0x311, which according to + * the manual should launch a user defined favorite + * application. + * + * The offset for these is TP_ACPI_HOTKEYSCAN_EXTENDED_START, + * corresponding to 0x34. + */ + + /* (assignments unknown, please report if found) */ + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, KEY_UNKNOWN, + KEY_UNKNOWN, + + KEY_FAVORITES, /* Favorite app, 0x311 */ + KEY_RESERVED, /* Clipping tool */ + KEY_RESERVED, + KEY_BLUETOOTH, /* Bluetooth */ + KEY_KEYBOARD /* Keyboard, 0x315 */ }, }; @@ -3656,7 +3699,6 @@ static const int adaptive_keyboard_modes[] = { #define DFR_CHANGE_ROW 0x101 #define DFR_SHOW_QUICKVIEW_ROW 0x102 #define FIRST_ADAPTIVE_KEY 0x103 -#define ADAPTIVE_KEY_OFFSET 0x020 /* press Fn key a while second, it will switch to Function Mode. Then * release Fn key, previous mode be restored. @@ -3746,13 +3788,15 @@ static bool adaptive_keyboard_hotkey_notify_hotkey(unsigned int scancode) default: if (scancode < FIRST_ADAPTIVE_KEY || - scancode >= FIRST_ADAPTIVE_KEY + TPACPI_HOTKEY_MAP_LEN - - ADAPTIVE_KEY_OFFSET) { + scancode >= FIRST_ADAPTIVE_KEY + + TP_ACPI_HOTKEYSCAN_EXTENDED_START - + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) { pr_info("Unhandled adaptive keyboard key: 0x%x\n", scancode); return false; } - keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + ADAPTIVE_KEY_OFFSET]; + keycode = hotkey_keycode_map[scancode - FIRST_ADAPTIVE_KEY + + TP_ACPI_HOTKEYSCAN_ADAPTIVE_START]; if (keycode != KEY_RESERVED) { mutex_lock(&tpacpi_inputdev_send_mutex); @@ -3777,19 +3821,44 @@ static bool hotkey_notify_hotkey(const u32 hkey, *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; + /* + * Original events are in the 0x10XX range, the adaptive keyboard + * found in 2014 X1 Carbon emits events are of 0x11XX. In 2017 + * models, additional keys are emitted through 0x13XX. + */ + switch ((hkey >> 8) & 0xf) { + case 0: + if (scancode > 0 && + scancode <= TP_ACPI_HOTKEYSCAN_ADAPTIVE_START) { + /* HKEY event 0x1001 is scancode 0x00 */ + 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 true; - } else { + break; + + case 1: return adaptive_keyboard_hotkey_notify_hotkey(scancode); + + case 3: + /* Extended keycodes start at 0x300 and our offset into the map + * TP_ACPI_HOTKEYSCAN_EXTENDED_START. The calculated scancode + * will be positive, but might not be in the correct range. + */ + scancode -= (0x300 - TP_ACPI_HOTKEYSCAN_EXTENDED_START); + if (scancode >= TP_ACPI_HOTKEYSCAN_EXTENDED_START && + scancode < TPACPI_HOTKEY_MAP_LEN) { + tpacpi_input_send_key(scancode); + return true; + } + break; } + return false; } diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index e597de05e6c2..70205d222da9 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -113,14 +113,12 @@ static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) error = input_register_device(input); if (error) { pr_err("Unable to register input device\n"); - goto err_free_keymap; + goto err_free_dev; } hkey->inputdev = input; return 0; - err_free_keymap: - sparse_keymap_free(input); err_free_dev: input_free_device(input); return error; @@ -157,7 +155,6 @@ static int acpi_topstar_remove(struct acpi_device *device) acpi_topstar_fncx_switch(device, false); - sparse_keymap_free(tps_hkey->inputdev); input_unregister_device(tps_hkey->inputdev); kfree(tps_hkey); diff --git a/drivers/platform/x86/toshiba-wmi.c b/drivers/platform/x86/toshiba-wmi.c index 2df07ee8f3c3..440528676170 100644 --- a/drivers/platform/x86/toshiba-wmi.c +++ b/drivers/platform/x86/toshiba-wmi.c @@ -96,7 +96,7 @@ static int __init toshiba_wmi_input_setup(void) toshiba_wmi_notify, NULL); if (ACPI_FAILURE(status)) { err = -EIO; - goto err_free_keymap; + goto err_free_dev; } err = input_register_device(toshiba_wmi_input_dev); @@ -107,8 +107,6 @@ static int __init toshiba_wmi_input_setup(void) err_remove_notifier: wmi_remove_notify_handler(WMI_EVENT_GUID); - err_free_keymap: - sparse_keymap_free(toshiba_wmi_input_dev); err_free_dev: input_free_device(toshiba_wmi_input_dev); return err; @@ -117,7 +115,6 @@ static int __init toshiba_wmi_input_setup(void) static void toshiba_wmi_input_destroy(void) { wmi_remove_notify_handler(WMI_EVENT_GUID); - sparse_keymap_free(toshiba_wmi_input_dev); input_unregister_device(toshiba_wmi_input_dev); } diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 074bf2fa1c55..d0daf75cbed1 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -2849,7 +2849,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) error = i8042_install_filter(toshiba_acpi_i8042_filter); if (error) { pr_err("Error installing key filter\n"); - goto err_free_keymap; + goto err_free_dev; } dev->ntfy_supported = 1; @@ -2880,8 +2880,6 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) 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; @@ -3018,10 +3016,8 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev) cancel_work_sync(&dev->hotkey_work); } - if (dev->hotkey_dev) { + if (dev->hotkey_dev) input_unregister_device(dev->hotkey_dev); - sparse_keymap_free(dev->hotkey_dev); - } backlight_device_unregister(dev->backlight_dev); |