mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-08 03:40:35 +09:00
Merge remote-tracking branch 'lsk/v3.10/topic/android-fixes' into linux-linaro-lsk-android
The cpufreq_interactive changes have been merged upstream and the local version dropped. Conflicts: drivers/cpufreq/cpufreq_interactive.c
This commit is contained in:
@@ -369,6 +369,8 @@ is not associated with a file:
|
||||
[stack:1001] = the stack of the thread with tid 1001
|
||||
[vdso] = the "virtual dynamic shared object",
|
||||
the kernel system call handler
|
||||
[anon:<name>] = an anonymous mapping that has been
|
||||
named by userspace
|
||||
|
||||
or if empty, the mapping is anonymous.
|
||||
|
||||
@@ -419,6 +421,7 @@ KernelPageSize: 4 kB
|
||||
MMUPageSize: 4 kB
|
||||
Locked: 374 kB
|
||||
VmFlags: rd ex mr mw me de
|
||||
Name: name from userspace
|
||||
|
||||
the first of these lines shows the same information as is displayed for the
|
||||
mapping in /proc/PID/maps. The remaining lines show the size of the mapping
|
||||
@@ -469,6 +472,9 @@ Note that there is no guarantee that every flag and associated mnemonic will
|
||||
be present in all further kernel releases. Things get changed, the flags may
|
||||
be vanished or the reverse -- new added.
|
||||
|
||||
The "Name" field will only be present on a mapping that has been named by
|
||||
userspace, and will show the name passed in by userspace.
|
||||
|
||||
This file is only present if the CONFIG_MMU kernel configuration option is
|
||||
enabled.
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ Currently, these files are in /proc/sys/vm:
|
||||
- dirty_writeback_centisecs
|
||||
- drop_caches
|
||||
- extfrag_threshold
|
||||
- extra_free_kbytes
|
||||
- hugepages_treat_as_movable
|
||||
- hugetlb_shm_group
|
||||
- laptop_mode
|
||||
@@ -198,6 +199,21 @@ fragmentation index is <= extfrag_threshold. The default value is 500.
|
||||
|
||||
==============================================================
|
||||
|
||||
extra_free_kbytes
|
||||
|
||||
This parameter tells the VM to keep extra free memory between the threshold
|
||||
where background reclaim (kswapd) kicks in, and the threshold where direct
|
||||
reclaim (by allocating processes) kicks in.
|
||||
|
||||
This is useful for workloads that require low latency memory allocations
|
||||
and have a bounded burstiness in memory allocations, for example a
|
||||
realtime application that receives and transmits network traffic
|
||||
(causing in-kernel memory allocations) with a maximum total message burst
|
||||
size of 200MB may need 200MB of extra free memory to avoid direct reclaim
|
||||
related latencies.
|
||||
|
||||
==============================================================
|
||||
|
||||
hugepages_treat_as_movable
|
||||
|
||||
This parameter is only useful when kernelcore= is specified at boot time to
|
||||
|
||||
@@ -11,7 +11,6 @@ CONFIG_COMPACTION=y
|
||||
CONFIG_ANDROID_RAM_CONSOLE=y
|
||||
CONFIG_ANDROID_TIMED_GPIO=y
|
||||
CONFIG_BACKLIGHT_LCD_SUPPORT=y
|
||||
CONFIG_BATTERY_ANDROID=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
CONFIG_BLK_DEV_RAM_SIZE=8192
|
||||
|
||||
@@ -144,6 +144,8 @@ int kgdb_arch_handle_exception(int exception_vector, int signo,
|
||||
|
||||
static int kgdb_brk_fn(struct pt_regs *regs, unsigned int instr)
|
||||
{
|
||||
if (user_mode(regs))
|
||||
return -1;
|
||||
kgdb_handle_exception(1, SIGTRAP, 0, regs);
|
||||
|
||||
return 0;
|
||||
@@ -151,6 +153,8 @@ static int kgdb_brk_fn(struct pt_regs *regs, unsigned int instr)
|
||||
|
||||
static int kgdb_compiled_brk_fn(struct pt_regs *regs, unsigned int instr)
|
||||
{
|
||||
if (user_mode(regs))
|
||||
return -1;
|
||||
compiled_break = 1;
|
||||
kgdb_handle_exception(1, SIGTRAP, 0, regs);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ static cpumask_t speedchange_cpumask;
|
||||
static spinlock_t speedchange_cpumask_lock;
|
||||
static struct mutex gov_lock;
|
||||
|
||||
/* Target load. Lower values result in higher CPU speeds. */
|
||||
#define DEFAULT_TARGET_LOAD 90
|
||||
static unsigned int default_target_loads[] = {DEFAULT_TARGET_LOAD};
|
||||
|
||||
@@ -71,31 +72,25 @@ static unsigned int default_above_hispeed_delay[] = {
|
||||
|
||||
struct cpufreq_interactive_tunables {
|
||||
int usage_count;
|
||||
|
||||
/* Hi speed to bump to from lo speed when load burst (default max) */
|
||||
unsigned int hispeed_freq;
|
||||
|
||||
/* Go to hi speed when CPU load at or above this value. */
|
||||
#define DEFAULT_GO_HISPEED_LOAD 99
|
||||
unsigned long go_hispeed_load;
|
||||
|
||||
/* Target load. Lower values result in higher CPU speeds. */
|
||||
spinlock_t target_loads_lock;
|
||||
unsigned int *target_loads;
|
||||
int ntarget_loads;
|
||||
|
||||
/*
|
||||
* The minimum amount of time to spend at a frequency before we can ramp
|
||||
* down.
|
||||
*/
|
||||
#define DEFAULT_MIN_SAMPLE_TIME (80 * USEC_PER_MSEC)
|
||||
unsigned long min_sample_time;
|
||||
|
||||
/*
|
||||
* The sample rate of the timer used to increase frequency
|
||||
*/
|
||||
unsigned long timer_rate;
|
||||
|
||||
/*
|
||||
* Wait this long before raising speed above hispeed, by default a
|
||||
* single timer interval.
|
||||
@@ -103,21 +98,18 @@ struct cpufreq_interactive_tunables {
|
||||
spinlock_t above_hispeed_delay_lock;
|
||||
unsigned int *above_hispeed_delay;
|
||||
int nabove_hispeed_delay;
|
||||
|
||||
/* Non-zero means indefinite speed boost active */
|
||||
int boost_val;
|
||||
/* Duration of a boost pulse in usecs */
|
||||
/* Duration of a boot pulse in usecs */
|
||||
int boostpulse_duration_val;
|
||||
/* End time of boost pulse in ktime converted to usecs */
|
||||
u64 boostpulse_endtime;
|
||||
|
||||
/*
|
||||
* Max additional time to wait in idle, beyond timer_rate, at speeds
|
||||
* above minimum before wakeup to reduce speed, or -1 if unnecessary.
|
||||
*/
|
||||
#define DEFAULT_TIMER_SLACK (4 * DEFAULT_TIMER_RATE)
|
||||
int timer_slack_val;
|
||||
|
||||
bool io_is_busy;
|
||||
};
|
||||
|
||||
@@ -137,15 +129,15 @@ static void cpufreq_interactive_timer_resched(
|
||||
spin_lock_irqsave(&pcpu->load_lock, flags);
|
||||
pcpu->time_in_idle =
|
||||
get_cpu_idle_time(smp_processor_id(),
|
||||
&pcpu->time_in_idle_timestamp,
|
||||
tunables->io_is_busy);
|
||||
&pcpu->time_in_idle_timestamp,
|
||||
tunables->io_is_busy);
|
||||
pcpu->cputime_speedadj = 0;
|
||||
pcpu->cputime_speedadj_timestamp = pcpu->time_in_idle_timestamp;
|
||||
expires = jiffies + usecs_to_jiffies(tunables->timer_rate);
|
||||
mod_timer_pinned(&pcpu->cpu_timer, expires);
|
||||
|
||||
if (tunables->timer_slack_val >= 0 &&
|
||||
pcpu->target_freq > pcpu->policy->min) {
|
||||
pcpu->target_freq > pcpu->policy->min) {
|
||||
expires += usecs_to_jiffies(tunables->timer_slack_val);
|
||||
mod_timer_pinned(&pcpu->cpu_slack_timer, expires);
|
||||
}
|
||||
@@ -158,16 +150,17 @@ static void cpufreq_interactive_timer_resched(
|
||||
* function.
|
||||
*/
|
||||
static void cpufreq_interactive_timer_start(
|
||||
struct cpufreq_interactive_tunables *tunables, int cpu)
|
||||
struct cpufreq_interactive_tunables *tunables, int cpu)
|
||||
{
|
||||
struct cpufreq_interactive_cpuinfo *pcpu = &per_cpu(cpuinfo, cpu);
|
||||
unsigned long expires = jiffies + usecs_to_jiffies(tunables->timer_rate);
|
||||
unsigned long expires = jiffies +
|
||||
usecs_to_jiffies(tunables->timer_rate);
|
||||
unsigned long flags;
|
||||
|
||||
pcpu->cpu_timer.expires = expires;
|
||||
add_timer_on(&pcpu->cpu_timer, cpu);
|
||||
if (tunables->timer_slack_val >= 0 &&
|
||||
pcpu->target_freq > pcpu->policy->min) {
|
||||
pcpu->target_freq > pcpu->policy->min) {
|
||||
expires += usecs_to_jiffies(tunables->timer_slack_val);
|
||||
pcpu->cpu_slack_timer.expires = expires;
|
||||
add_timer_on(&pcpu->cpu_slack_timer, cpu);
|
||||
@@ -183,8 +176,8 @@ static void cpufreq_interactive_timer_start(
|
||||
}
|
||||
|
||||
static unsigned int freq_to_above_hispeed_delay(
|
||||
struct cpufreq_interactive_tunables *tunables,
|
||||
unsigned int freq)
|
||||
struct cpufreq_interactive_tunables *tunables,
|
||||
unsigned int freq)
|
||||
{
|
||||
int i;
|
||||
unsigned int ret;
|
||||
@@ -201,8 +194,8 @@ static unsigned int freq_to_above_hispeed_delay(
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned int freq_to_targetload(struct cpufreq_interactive_tunables
|
||||
*tunables, unsigned int freq)
|
||||
static unsigned int freq_to_targetload(
|
||||
struct cpufreq_interactive_tunables *tunables, unsigned int freq)
|
||||
{
|
||||
int i;
|
||||
unsigned int ret;
|
||||
@@ -211,7 +204,7 @@ static unsigned int freq_to_targetload(struct cpufreq_interactive_tunables
|
||||
spin_lock_irqsave(&tunables->target_loads_lock, flags);
|
||||
|
||||
for (i = 0; i < tunables->ntarget_loads - 1 &&
|
||||
freq >= tunables->target_loads[i+1]; i += 2)
|
||||
freq >= tunables->target_loads[i+1]; i += 2)
|
||||
;
|
||||
|
||||
ret = tunables->target_loads[i];
|
||||
@@ -706,8 +699,9 @@ err:
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
static ssize_t show_target_loads(struct cpufreq_interactive_tunables *tunables,
|
||||
char *buf)
|
||||
static ssize_t show_target_loads(
|
||||
struct cpufreq_interactive_tunables *tunables,
|
||||
char *buf)
|
||||
{
|
||||
int i;
|
||||
ssize_t ret = 0;
|
||||
@@ -719,13 +713,14 @@ static ssize_t show_target_loads(struct cpufreq_interactive_tunables *tunables,
|
||||
ret += sprintf(buf + ret, "%u%s", tunables->target_loads[i],
|
||||
i & 0x1 ? ":" : " ");
|
||||
|
||||
ret += sprintf(buf + ret, "\n");
|
||||
ret += sprintf(buf + --ret, "\n");
|
||||
spin_unlock_irqrestore(&tunables->target_loads_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t store_target_loads(struct cpufreq_interactive_tunables *tunables,
|
||||
const char *buf, size_t count)
|
||||
static ssize_t store_target_loads(
|
||||
struct cpufreq_interactive_tunables *tunables,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ntokens;
|
||||
unsigned int *new_target_loads = NULL;
|
||||
@@ -744,8 +739,8 @@ static ssize_t store_target_loads(struct cpufreq_interactive_tunables *tunables,
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_above_hispeed_delay(struct cpufreq_interactive_tunables
|
||||
*tunables, char *buf)
|
||||
static ssize_t show_above_hispeed_delay(
|
||||
struct cpufreq_interactive_tunables *tunables, char *buf)
|
||||
{
|
||||
int i;
|
||||
ssize_t ret = 0;
|
||||
@@ -758,13 +753,14 @@ static ssize_t show_above_hispeed_delay(struct cpufreq_interactive_tunables
|
||||
tunables->above_hispeed_delay[i],
|
||||
i & 0x1 ? ":" : " ");
|
||||
|
||||
ret += sprintf(buf + ret, "\n");
|
||||
ret += sprintf(buf + --ret, "\n");
|
||||
spin_unlock_irqrestore(&tunables->above_hispeed_delay_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t store_above_hispeed_delay(struct cpufreq_interactive_tunables
|
||||
*tunables, const char *buf, size_t count)
|
||||
static ssize_t store_above_hispeed_delay(
|
||||
struct cpufreq_interactive_tunables *tunables,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ntokens;
|
||||
unsigned int *new_above_hispeed_delay = NULL;
|
||||
@@ -1012,6 +1008,18 @@ store_gov_pol_sys(boostpulse);
|
||||
show_store_gov_pol_sys(boostpulse_duration);
|
||||
show_store_gov_pol_sys(io_is_busy);
|
||||
|
||||
#define gov_sys_attr_rw(_name) \
|
||||
static struct global_attr _name##_gov_sys = \
|
||||
__ATTR(_name, 0644, show_##_name##_gov_sys, store_##_name##_gov_sys)
|
||||
|
||||
#define gov_pol_attr_rw(_name) \
|
||||
static struct freq_attr _name##_gov_pol = \
|
||||
__ATTR(_name, 0644, show_##_name##_gov_pol, store_##_name##_gov_pol)
|
||||
|
||||
#define gov_sys_pol_attr_rw(_name) \
|
||||
gov_sys_attr_rw(_name); \
|
||||
gov_pol_attr_rw(_name)
|
||||
|
||||
gov_sys_pol_attr_rw(target_loads);
|
||||
gov_sys_pol_attr_rw(above_hispeed_delay);
|
||||
gov_sys_pol_attr_rw(hispeed_freq);
|
||||
@@ -1200,6 +1208,8 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy,
|
||||
pcpu->hispeed_validate_time =
|
||||
pcpu->floor_validate_time;
|
||||
down_write(&pcpu->enable_sem);
|
||||
del_timer_sync(&pcpu->cpu_timer);
|
||||
del_timer_sync(&pcpu->cpu_slack_timer);
|
||||
cpufreq_interactive_timer_start(tunables, j);
|
||||
pcpu->governor_enabled = 1;
|
||||
up_write(&pcpu->enable_sem);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
obj-$(CONFIG_ION) += ion.o ion_heap.o ion_page_pool.o ion_system_heap.o \
|
||||
ion_carveout_heap.o ion_chunk_heap.o
|
||||
ion_carveout_heap.o ion_chunk_heap.o ion_cma_heap.o
|
||||
ifdef CONFIG_COMPAT
|
||||
obj-$(CONFIG_ION) += compat_ion.o
|
||||
endif
|
||||
obj-$(CONFIG_ION_TEGRA) += tegra/
|
||||
|
||||
172
drivers/gpu/ion/compat_ion.c
Normal file
172
drivers/gpu/ion/compat_ion.c
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* drivers/gpu/ion/compat_ion.c
|
||||
*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/ion.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "compat_ion.h"
|
||||
|
||||
/* See include/linux/ion.h for the definition of these structs */
|
||||
struct compat_ion_allocation_data {
|
||||
compat_size_t len;
|
||||
compat_size_t align;
|
||||
compat_uint_t heap_id_mask;
|
||||
compat_uint_t flags;
|
||||
compat_int_t handle;
|
||||
};
|
||||
|
||||
struct compat_ion_custom_data {
|
||||
compat_uint_t cmd;
|
||||
compat_ulong_t arg;
|
||||
};
|
||||
|
||||
static int compat_get_ion_allocation_data(
|
||||
struct compat_ion_allocation_data __user *data32,
|
||||
struct ion_allocation_data __user *data)
|
||||
{
|
||||
compat_size_t s;
|
||||
compat_uint_t u;
|
||||
compat_int_t i;
|
||||
int err;
|
||||
|
||||
err = get_user(s, &data32->len);
|
||||
err |= put_user(s, &data->len);
|
||||
err |= get_user(s, &data32->align);
|
||||
err |= put_user(s, &data->align);
|
||||
err |= get_user(u, &data32->heap_id_mask);
|
||||
err |= put_user(u, &data->heap_id_mask);
|
||||
err |= get_user(u, &data32->flags);
|
||||
err |= put_user(u, &data->flags);
|
||||
err |= get_user(i, &data32->handle);
|
||||
err |= put_user(i, &data->handle);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int compat_put_ion_allocation_data(
|
||||
struct compat_ion_allocation_data __user *data32,
|
||||
struct ion_allocation_data __user *data)
|
||||
{
|
||||
compat_size_t s;
|
||||
compat_uint_t u;
|
||||
compat_int_t i;
|
||||
int err;
|
||||
|
||||
err = get_user(s, &data->len);
|
||||
err |= put_user(s, &data32->len);
|
||||
err |= get_user(s, &data->align);
|
||||
err |= put_user(s, &data32->align);
|
||||
err |= get_user(u, &data->heap_id_mask);
|
||||
err |= put_user(u, &data32->heap_id_mask);
|
||||
err |= get_user(u, &data->flags);
|
||||
err |= put_user(u, &data32->flags);
|
||||
err |= get_user(i, &data->handle);
|
||||
err |= put_user(i, &data32->handle);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int compat_get_ion_custom_data(
|
||||
struct compat_ion_custom_data __user *data32,
|
||||
struct ion_custom_data __user *data)
|
||||
{
|
||||
compat_uint_t cmd;
|
||||
compat_ulong_t arg;
|
||||
int err;
|
||||
|
||||
err = get_user(cmd, &data32->cmd);
|
||||
err |= put_user(cmd, &data->cmd);
|
||||
err |= get_user(arg, &data32->arg);
|
||||
err |= put_user(arg, &data->arg);
|
||||
|
||||
return err;
|
||||
};
|
||||
|
||||
long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
long ret;
|
||||
|
||||
if (!filp->f_op || !filp->f_op->unlocked_ioctl)
|
||||
return -ENOTTY;
|
||||
|
||||
switch (cmd) {
|
||||
case ION_IOC_ALLOC:
|
||||
{
|
||||
struct compat_ion_allocation_data __user *data32;
|
||||
struct ion_allocation_data __user *data;
|
||||
int err;
|
||||
|
||||
data32 = compat_ptr(arg);
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (data == NULL)
|
||||
return -EFAULT;
|
||||
|
||||
err = compat_get_ion_allocation_data(data32, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
ret = filp->f_op->unlocked_ioctl(filp, cmd,
|
||||
(unsigned long)data);
|
||||
err = compat_put_ion_allocation_data(data32, data);
|
||||
return ret ? ret : err;
|
||||
}
|
||||
case ION_IOC_FREE:
|
||||
{
|
||||
struct compat_ion_allocation_data __user *data32;
|
||||
struct ion_allocation_data __user *data;
|
||||
int err;
|
||||
|
||||
data32 = compat_ptr(arg);
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (data == NULL)
|
||||
return -EFAULT;
|
||||
|
||||
err = compat_get_ion_allocation_data(data32, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return filp->f_op->unlocked_ioctl(filp, cmd,
|
||||
(unsigned long)data);
|
||||
}
|
||||
case ION_IOC_CUSTOM: {
|
||||
struct compat_ion_custom_data __user *data32;
|
||||
struct ion_custom_data __user *data;
|
||||
int err;
|
||||
|
||||
data32 = compat_ptr(arg);
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (data == NULL)
|
||||
return -EFAULT;
|
||||
|
||||
err = compat_get_ion_custom_data(data32, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return filp->f_op->unlocked_ioctl(filp, cmd,
|
||||
(unsigned long)data);
|
||||
}
|
||||
case ION_IOC_SHARE:
|
||||
case ION_IOC_MAP:
|
||||
case ION_IOC_IMPORT:
|
||||
case ION_IOC_SYNC:
|
||||
return filp->f_op->unlocked_ioctl(filp, cmd,
|
||||
(unsigned long)compat_ptr(arg));
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
}
|
||||
30
drivers/gpu/ion/compat_ion.h
Normal file
30
drivers/gpu/ion/compat_ion.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
|
||||
* drivers/gpu/ion/compat_ion.h
|
||||
*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_COMPAT_ION_H
|
||||
#define _LINUX_COMPAT_ION_H
|
||||
|
||||
#if IS_ENABLED(CONFIG_COMPAT)
|
||||
|
||||
long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
|
||||
|
||||
#else
|
||||
|
||||
#define compat_ion_ioctl NULL
|
||||
|
||||
#endif /* CONFIG_COMPAT */
|
||||
#endif /* _LINUX_COMPAT_ION_H */
|
||||
@@ -32,10 +32,13 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/idr.h>
|
||||
|
||||
#include "ion_priv.h"
|
||||
#include "compat_ion.h"
|
||||
|
||||
/**
|
||||
* struct ion_device - the metadata of the ion device node
|
||||
@@ -63,6 +66,7 @@ struct ion_device {
|
||||
* @node: node in the tree of all clients
|
||||
* @dev: backpointer to ion device
|
||||
* @handles: an rb tree of all the handles in this client
|
||||
* @idr: an idr space for allocating handle ids
|
||||
* @lock: lock protecting the tree of handles
|
||||
* @name: used for debugging
|
||||
* @task: used for debugging
|
||||
@@ -75,6 +79,7 @@ struct ion_client {
|
||||
struct rb_node node;
|
||||
struct ion_device *dev;
|
||||
struct rb_root handles;
|
||||
struct idr idr;
|
||||
struct mutex lock;
|
||||
const char *name;
|
||||
struct task_struct *task;
|
||||
@@ -89,7 +94,7 @@ struct ion_client {
|
||||
* @buffer: pointer to the buffer
|
||||
* @node: node in the client's handle rbtree
|
||||
* @kmap_cnt: count of times this client has mapped to kernel
|
||||
* @dmap_cnt: count of times this client has mapped for dma
|
||||
* @id: client-unique id allocated by client->idr
|
||||
*
|
||||
* Modifications to node, map_cnt or mapping should be protected by the
|
||||
* lock in the client. Other fields are never changed after initialization.
|
||||
@@ -100,17 +105,38 @@ struct ion_handle {
|
||||
struct ion_buffer *buffer;
|
||||
struct rb_node node;
|
||||
unsigned int kmap_cnt;
|
||||
int id;
|
||||
};
|
||||
|
||||
bool ion_buffer_fault_user_mappings(struct ion_buffer *buffer)
|
||||
{
|
||||
return ((buffer->flags & ION_FLAG_CACHED) &&
|
||||
!(buffer->flags & ION_FLAG_CACHED_NEEDS_SYNC));
|
||||
return ((buffer->flags & ION_FLAG_CACHED) &&
|
||||
!(buffer->flags & ION_FLAG_CACHED_NEEDS_SYNC));
|
||||
}
|
||||
|
||||
bool ion_buffer_cached(struct ion_buffer *buffer)
|
||||
{
|
||||
return !!(buffer->flags & ION_FLAG_CACHED);
|
||||
return !!(buffer->flags & ION_FLAG_CACHED);
|
||||
}
|
||||
|
||||
static inline struct page *ion_buffer_page(struct page *page)
|
||||
{
|
||||
return (struct page *)((unsigned long)page & ~(1UL));
|
||||
}
|
||||
|
||||
static inline bool ion_buffer_page_is_dirty(struct page *page)
|
||||
{
|
||||
return !!((unsigned long)page & 1UL);
|
||||
}
|
||||
|
||||
static inline void ion_buffer_page_dirty(struct page **page)
|
||||
{
|
||||
*page = (struct page *)((unsigned long)(*page) | 1UL);
|
||||
}
|
||||
|
||||
static inline void ion_buffer_page_clean(struct page **page)
|
||||
{
|
||||
*page = (struct page *)((unsigned long)(*page) & ~(1UL));
|
||||
}
|
||||
|
||||
/* this function should only be called while dev->lock is held */
|
||||
@@ -139,8 +165,6 @@ static void ion_buffer_add(struct ion_device *dev,
|
||||
rb_insert_color(&buffer->node, &dev->buffers);
|
||||
}
|
||||
|
||||
static int ion_buffer_alloc_dirty(struct ion_buffer *buffer);
|
||||
|
||||
/* this function should only be called while dev->lock is held */
|
||||
static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
|
||||
struct ion_device *dev,
|
||||
@@ -178,24 +202,32 @@ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
|
||||
buffer->size = len;
|
||||
|
||||
table = heap->ops->map_dma(heap, buffer);
|
||||
if (IS_ERR_OR_NULL(table)) {
|
||||
if (WARN_ONCE(table == NULL, "heap->ops->map_dma should return ERR_PTR on error"))
|
||||
table = ERR_PTR(-EINVAL);
|
||||
if (IS_ERR(table)) {
|
||||
heap->ops->free(buffer);
|
||||
kfree(buffer);
|
||||
return ERR_PTR(PTR_ERR(table));
|
||||
}
|
||||
buffer->sg_table = table;
|
||||
if (ion_buffer_fault_user_mappings(buffer)) {
|
||||
for_each_sg(buffer->sg_table->sgl, sg, buffer->sg_table->nents,
|
||||
i) {
|
||||
if (sg_dma_len(sg) == PAGE_SIZE)
|
||||
continue;
|
||||
pr_err("%s: cached mappings that will be faulted in "
|
||||
"must have pagewise sg_lists\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
int num_pages = PAGE_ALIGN(buffer->size) / PAGE_SIZE;
|
||||
struct scatterlist *sg;
|
||||
int i, j, k = 0;
|
||||
|
||||
buffer->pages = vmalloc(sizeof(struct page *) * num_pages);
|
||||
if (!buffer->pages) {
|
||||
ret = -ENOMEM;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
for_each_sg(table->sgl, sg, table->nents, i) {
|
||||
struct page *page = sg_page(sg);
|
||||
|
||||
for (j = 0; j < sg_dma_len(sg) / PAGE_SIZE; j++)
|
||||
buffer->pages[k++] = page++;
|
||||
}
|
||||
|
||||
ret = ion_buffer_alloc_dirty(buffer);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
@@ -222,6 +254,9 @@ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
|
||||
err:
|
||||
heap->ops->unmap_dma(heap, buffer);
|
||||
heap->ops->free(buffer);
|
||||
err1:
|
||||
if (buffer->pages)
|
||||
vfree(buffer->pages);
|
||||
err2:
|
||||
kfree(buffer);
|
||||
return ERR_PTR(ret);
|
||||
@@ -233,8 +268,8 @@ void ion_buffer_destroy(struct ion_buffer *buffer)
|
||||
buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
|
||||
buffer->heap->ops->unmap_dma(buffer->heap, buffer);
|
||||
buffer->heap->ops->free(buffer);
|
||||
if (buffer->flags & ION_FLAG_CACHED)
|
||||
kfree(buffer->dirty);
|
||||
if (buffer->pages)
|
||||
vfree(buffer->pages);
|
||||
kfree(buffer);
|
||||
}
|
||||
|
||||
@@ -326,6 +361,7 @@ static void ion_handle_destroy(struct kref *kref)
|
||||
ion_handle_kmap_put(handle);
|
||||
mutex_unlock(&buffer->lock);
|
||||
|
||||
idr_remove(&client->idr, handle->id);
|
||||
if (!RB_EMPTY_NODE(&handle->node))
|
||||
rb_erase(&handle->node, &client->handles);
|
||||
|
||||
@@ -353,47 +389,56 @@ static int ion_handle_put(struct ion_handle *handle)
|
||||
static struct ion_handle *ion_handle_lookup(struct ion_client *client,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
struct rb_node *n;
|
||||
struct rb_node *n = client->handles.rb_node;
|
||||
|
||||
for (n = rb_first(&client->handles); n; n = rb_next(n)) {
|
||||
struct ion_handle *handle = rb_entry(n, struct ion_handle,
|
||||
node);
|
||||
if (handle->buffer == buffer)
|
||||
return handle;
|
||||
while (n) {
|
||||
struct ion_handle *entry = rb_entry(n, struct ion_handle, node);
|
||||
if (buffer < entry->buffer)
|
||||
n = n->rb_left;
|
||||
else if (buffer > entry->buffer)
|
||||
n = n->rb_right;
|
||||
else
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static struct ion_handle *ion_uhandle_get(struct ion_client *client, int id)
|
||||
{
|
||||
return idr_find(&client->idr, id);
|
||||
}
|
||||
|
||||
static bool ion_handle_validate(struct ion_client *client, struct ion_handle *handle)
|
||||
{
|
||||
struct rb_node *n = client->handles.rb_node;
|
||||
|
||||
while (n) {
|
||||
struct ion_handle *handle_node = rb_entry(n, struct ion_handle,
|
||||
node);
|
||||
if (handle < handle_node)
|
||||
n = n->rb_left;
|
||||
else if (handle > handle_node)
|
||||
n = n->rb_right;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return (ion_uhandle_get(client, handle->id) == handle);
|
||||
}
|
||||
|
||||
static void ion_handle_add(struct ion_client *client, struct ion_handle *handle)
|
||||
static int ion_handle_add(struct ion_client *client, struct ion_handle *handle)
|
||||
{
|
||||
int rc;
|
||||
struct rb_node **p = &client->handles.rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct ion_handle *entry;
|
||||
|
||||
do {
|
||||
int id;
|
||||
rc = idr_pre_get(&client->idr, GFP_KERNEL);
|
||||
if (!rc)
|
||||
return -ENOMEM;
|
||||
rc = idr_get_new_above(&client->idr, handle, 1, &id);
|
||||
handle->id = id;
|
||||
} while (rc == -EAGAIN);
|
||||
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
entry = rb_entry(parent, struct ion_handle, node);
|
||||
|
||||
if (handle < entry)
|
||||
if (handle->buffer < entry->buffer)
|
||||
p = &(*p)->rb_left;
|
||||
else if (handle > entry)
|
||||
else if (handle->buffer > entry->buffer)
|
||||
p = &(*p)->rb_right;
|
||||
else
|
||||
WARN(1, "%s: buffer already found.", __func__);
|
||||
@@ -401,6 +446,8 @@ static void ion_handle_add(struct ion_client *client, struct ion_handle *handle)
|
||||
|
||||
rb_link_node(&handle->node, parent, p);
|
||||
rb_insert_color(&handle->node, &client->handles);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
|
||||
@@ -411,6 +458,7 @@ struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
|
||||
struct ion_device *dev = client->dev;
|
||||
struct ion_buffer *buffer = NULL;
|
||||
struct ion_heap *heap;
|
||||
int ret;
|
||||
|
||||
pr_debug("%s: len %d align %d heap_id_mask %u flags %x\n", __func__,
|
||||
len, align, heap_id_mask, flags);
|
||||
@@ -431,7 +479,7 @@ struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
|
||||
if (!((1 << heap->id) & heap_id_mask))
|
||||
continue;
|
||||
buffer = ion_buffer_create(heap, dev, len, align, flags);
|
||||
if (!IS_ERR_OR_NULL(buffer))
|
||||
if (!IS_ERR(buffer))
|
||||
break;
|
||||
}
|
||||
up_read(&dev->lock);
|
||||
@@ -450,12 +498,16 @@ struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
|
||||
*/
|
||||
ion_buffer_put(buffer);
|
||||
|
||||
if (!IS_ERR(handle)) {
|
||||
mutex_lock(&client->lock);
|
||||
ion_handle_add(client, handle);
|
||||
mutex_unlock(&client->lock);
|
||||
}
|
||||
if (IS_ERR(handle))
|
||||
return handle;
|
||||
|
||||
mutex_lock(&client->lock);
|
||||
ret = ion_handle_add(client, handle);
|
||||
if (ret) {
|
||||
ion_handle_put(handle);
|
||||
handle = ERR_PTR(ret);
|
||||
}
|
||||
mutex_unlock(&client->lock);
|
||||
|
||||
return handle;
|
||||
}
|
||||
@@ -515,7 +567,9 @@ static void *ion_buffer_kmap_get(struct ion_buffer *buffer)
|
||||
return buffer->vaddr;
|
||||
}
|
||||
vaddr = buffer->heap->ops->map_kernel(buffer->heap, buffer);
|
||||
if (IS_ERR_OR_NULL(vaddr))
|
||||
if (WARN_ONCE(vaddr == NULL, "heap->ops->map_kernel should return ERR_PTR on error"))
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (IS_ERR(vaddr))
|
||||
return vaddr;
|
||||
buffer->vaddr = vaddr;
|
||||
buffer->kmap_cnt++;
|
||||
@@ -532,7 +586,7 @@ static void *ion_handle_kmap_get(struct ion_handle *handle)
|
||||
return buffer->vaddr;
|
||||
}
|
||||
vaddr = ion_buffer_kmap_get(buffer);
|
||||
if (IS_ERR_OR_NULL(vaddr))
|
||||
if (IS_ERR(vaddr))
|
||||
return vaddr;
|
||||
handle->kmap_cnt++;
|
||||
return vaddr;
|
||||
@@ -673,6 +727,7 @@ struct ion_client *ion_client_create(struct ion_device *dev,
|
||||
|
||||
client->dev = dev;
|
||||
client->handles = RB_ROOT;
|
||||
idr_init(&client->idr);
|
||||
mutex_init(&client->lock);
|
||||
client->name = name;
|
||||
client->task = task;
|
||||
@@ -713,6 +768,10 @@ void ion_client_destroy(struct ion_client *client)
|
||||
node);
|
||||
ion_handle_destroy(&handle->ref);
|
||||
}
|
||||
|
||||
idr_remove_all(&client->idr);
|
||||
idr_destroy(&client->idr);
|
||||
|
||||
down_write(&dev->lock);
|
||||
if (client->task)
|
||||
put_task_struct(client->task);
|
||||
@@ -764,17 +823,6 @@ static void ion_unmap_dma_buf(struct dma_buf_attachment *attachment,
|
||||
{
|
||||
}
|
||||
|
||||
static int ion_buffer_alloc_dirty(struct ion_buffer *buffer)
|
||||
{
|
||||
unsigned long pages = buffer->sg_table->nents;
|
||||
unsigned long length = (pages + BITS_PER_LONG - 1)/BITS_PER_LONG;
|
||||
|
||||
buffer->dirty = kzalloc(length * sizeof(unsigned long), GFP_KERNEL);
|
||||
if (!buffer->dirty)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ion_vma_list {
|
||||
struct list_head list;
|
||||
struct vm_area_struct *vma;
|
||||
@@ -784,9 +832,9 @@ static void ion_buffer_sync_for_device(struct ion_buffer *buffer,
|
||||
struct device *dev,
|
||||
enum dma_data_direction dir)
|
||||
{
|
||||
struct scatterlist *sg;
|
||||
int i;
|
||||
struct ion_vma_list *vma_list;
|
||||
int pages = PAGE_ALIGN(buffer->size) / PAGE_SIZE;
|
||||
int i;
|
||||
|
||||
pr_debug("%s: syncing for device %s\n", __func__,
|
||||
dev ? dev_name(dev) : "null");
|
||||
@@ -795,11 +843,12 @@ static void ion_buffer_sync_for_device(struct ion_buffer *buffer,
|
||||
return;
|
||||
|
||||
mutex_lock(&buffer->lock);
|
||||
for_each_sg(buffer->sg_table->sgl, sg, buffer->sg_table->nents, i) {
|
||||
if (!test_bit(i, buffer->dirty))
|
||||
continue;
|
||||
dma_sync_sg_for_device(dev, sg, 1, dir);
|
||||
clear_bit(i, buffer->dirty);
|
||||
for (i = 0; i < pages; i++) {
|
||||
struct page *page = buffer->pages[i];
|
||||
|
||||
if (ion_buffer_page_is_dirty(page))
|
||||
__dma_page_cpu_to_dev(page, 0, PAGE_SIZE, dir);
|
||||
ion_buffer_page_clean(buffer->pages + i);
|
||||
}
|
||||
list_for_each_entry(vma_list, &buffer->vmas, list) {
|
||||
struct vm_area_struct *vma = vma_list->vma;
|
||||
@@ -813,21 +862,18 @@ static void ion_buffer_sync_for_device(struct ion_buffer *buffer,
|
||||
int ion_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
|
||||
{
|
||||
struct ion_buffer *buffer = vma->vm_private_data;
|
||||
struct scatterlist *sg;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&buffer->lock);
|
||||
set_bit(vmf->pgoff, buffer->dirty);
|
||||
ion_buffer_page_dirty(buffer->pages + vmf->pgoff);
|
||||
|
||||
for_each_sg(buffer->sg_table->sgl, sg, buffer->sg_table->nents, i) {
|
||||
if (i != vmf->pgoff)
|
||||
continue;
|
||||
dma_sync_sg_for_cpu(NULL, sg, 1, DMA_BIDIRECTIONAL);
|
||||
vm_insert_page(vma, (unsigned long)vmf->virtual_address,
|
||||
sg_page(sg));
|
||||
break;
|
||||
}
|
||||
BUG_ON(!buffer->pages || !buffer->pages[vmf->pgoff]);
|
||||
ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address,
|
||||
ion_buffer_page(buffer->pages[vmf->pgoff]));
|
||||
mutex_unlock(&buffer->lock);
|
||||
if (ret)
|
||||
return VM_FAULT_ERROR;
|
||||
|
||||
return VM_FAULT_NOPAGE;
|
||||
}
|
||||
|
||||
@@ -939,8 +985,6 @@ static int ion_dma_buf_begin_cpu_access(struct dma_buf *dmabuf, size_t start,
|
||||
mutex_unlock(&buffer->lock);
|
||||
if (IS_ERR(vaddr))
|
||||
return PTR_ERR(vaddr);
|
||||
if (!vaddr)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1017,9 +1061,10 @@ struct ion_handle *ion_import_dma_buf(struct ion_client *client, int fd)
|
||||
struct dma_buf *dmabuf;
|
||||
struct ion_buffer *buffer;
|
||||
struct ion_handle *handle;
|
||||
int ret;
|
||||
|
||||
dmabuf = dma_buf_get(fd);
|
||||
if (IS_ERR_OR_NULL(dmabuf))
|
||||
if (IS_ERR(dmabuf))
|
||||
return ERR_PTR(PTR_ERR(dmabuf));
|
||||
/* if this memory came from ion */
|
||||
|
||||
@@ -1034,14 +1079,18 @@ struct ion_handle *ion_import_dma_buf(struct ion_client *client, int fd)
|
||||
mutex_lock(&client->lock);
|
||||
/* if a handle exists for this buffer just take a reference to it */
|
||||
handle = ion_handle_lookup(client, buffer);
|
||||
if (!IS_ERR_OR_NULL(handle)) {
|
||||
if (!IS_ERR(handle)) {
|
||||
ion_handle_get(handle);
|
||||
goto end;
|
||||
}
|
||||
handle = ion_handle_create(client, buffer);
|
||||
if (IS_ERR_OR_NULL(handle))
|
||||
if (IS_ERR(handle))
|
||||
goto end;
|
||||
ion_handle_add(client, handle);
|
||||
ret = ion_handle_add(client, handle);
|
||||
if (ret) {
|
||||
ion_handle_put(handle);
|
||||
handle = ERR_PTR(ret);
|
||||
}
|
||||
end:
|
||||
mutex_unlock(&client->lock);
|
||||
dma_buf_put(dmabuf);
|
||||
@@ -1055,7 +1104,7 @@ static int ion_sync_for_device(struct ion_client *client, int fd)
|
||||
struct ion_buffer *buffer;
|
||||
|
||||
dmabuf = dma_buf_get(fd);
|
||||
if (IS_ERR_OR_NULL(dmabuf))
|
||||
if (IS_ERR(dmabuf))
|
||||
return PTR_ERR(dmabuf);
|
||||
|
||||
/* if this memory came from ion */
|
||||
@@ -1081,17 +1130,20 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
case ION_IOC_ALLOC:
|
||||
{
|
||||
struct ion_allocation_data data;
|
||||
struct ion_handle *handle;
|
||||
|
||||
if (copy_from_user(&data, (void __user *)arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
data.handle = ion_alloc(client, data.len, data.align,
|
||||
handle = ion_alloc(client, data.len, data.align,
|
||||
data.heap_id_mask, data.flags);
|
||||
|
||||
if (IS_ERR(data.handle))
|
||||
return PTR_ERR(data.handle);
|
||||
if (IS_ERR(handle))
|
||||
return PTR_ERR(handle);
|
||||
|
||||
data.handle = handle->id;
|
||||
|
||||
if (copy_to_user((void __user *)arg, &data, sizeof(data))) {
|
||||
ion_free(client, data.handle);
|
||||
ion_free(client, handle);
|
||||
return -EFAULT;
|
||||
}
|
||||
break;
|
||||
@@ -1099,27 +1151,29 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
case ION_IOC_FREE:
|
||||
{
|
||||
struct ion_handle_data data;
|
||||
bool valid;
|
||||
struct ion_handle *handle;
|
||||
|
||||
if (copy_from_user(&data, (void __user *)arg,
|
||||
sizeof(struct ion_handle_data)))
|
||||
return -EFAULT;
|
||||
mutex_lock(&client->lock);
|
||||
valid = ion_handle_validate(client, data.handle);
|
||||
handle = ion_uhandle_get(client, data.handle);
|
||||
mutex_unlock(&client->lock);
|
||||
if (!valid)
|
||||
if (!handle)
|
||||
return -EINVAL;
|
||||
ion_free(client, data.handle);
|
||||
ion_free(client, handle);
|
||||
break;
|
||||
}
|
||||
case ION_IOC_SHARE:
|
||||
case ION_IOC_MAP:
|
||||
{
|
||||
struct ion_fd_data data;
|
||||
struct ion_handle *handle;
|
||||
|
||||
if (copy_from_user(&data, (void __user *)arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
data.fd = ion_share_dma_buf_fd(client, data.handle);
|
||||
handle = ion_uhandle_get(client, data.handle);
|
||||
data.fd = ion_share_dma_buf_fd(client, handle);
|
||||
if (copy_to_user((void __user *)arg, &data, sizeof(data)))
|
||||
return -EFAULT;
|
||||
if (data.fd < 0)
|
||||
@@ -1129,15 +1183,17 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
case ION_IOC_IMPORT:
|
||||
{
|
||||
struct ion_fd_data data;
|
||||
struct ion_handle *handle;
|
||||
int ret = 0;
|
||||
if (copy_from_user(&data, (void __user *)arg,
|
||||
sizeof(struct ion_fd_data)))
|
||||
return -EFAULT;
|
||||
data.handle = ion_import_dma_buf(client, data.fd);
|
||||
if (IS_ERR(data.handle)) {
|
||||
ret = PTR_ERR(data.handle);
|
||||
data.handle = NULL;
|
||||
}
|
||||
handle = ion_import_dma_buf(client, data.fd);
|
||||
if (IS_ERR(handle))
|
||||
ret = PTR_ERR(handle);
|
||||
else
|
||||
data.handle = handle->id;
|
||||
|
||||
if (copy_to_user((void __user *)arg, &data,
|
||||
sizeof(struct ion_fd_data)))
|
||||
return -EFAULT;
|
||||
@@ -1189,7 +1245,7 @@ static int ion_open(struct inode *inode, struct file *file)
|
||||
|
||||
pr_debug("%s: %d\n", __func__, __LINE__);
|
||||
client = ion_client_create(dev, "user");
|
||||
if (IS_ERR_OR_NULL(client))
|
||||
if (IS_ERR(client))
|
||||
return PTR_ERR(client);
|
||||
file->private_data = client;
|
||||
|
||||
@@ -1201,6 +1257,7 @@ static const struct file_operations ion_fops = {
|
||||
.open = ion_open,
|
||||
.release = ion_release,
|
||||
.unlocked_ioctl = ion_ioctl,
|
||||
.compat_ioctl = compat_ion_ioctl,
|
||||
};
|
||||
|
||||
static size_t ion_debug_heap_total(struct ion_client *client,
|
||||
@@ -1271,6 +1328,9 @@ static int ion_debug_heap_show(struct seq_file *s, void *unused)
|
||||
seq_printf(s, "%16.s %16u\n", "total orphaned",
|
||||
total_orphaned_size);
|
||||
seq_printf(s, "%16.s %16u\n", "total ", total_size);
|
||||
if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
|
||||
seq_printf(s, "%16.s %16u\n", "deferred free",
|
||||
heap->free_list_size);
|
||||
seq_printf(s, "----------------------------------------------------\n");
|
||||
|
||||
if (heap->debug_show)
|
||||
@@ -1382,7 +1442,7 @@ struct ion_device *ion_device_create(long (*custom_ioctl)
|
||||
}
|
||||
|
||||
idev->debug_root = debugfs_create_dir("ion", NULL);
|
||||
if (IS_ERR_OR_NULL(idev->debug_root))
|
||||
if (!idev->debug_root)
|
||||
pr_err("ion: failed to create debug files.\n");
|
||||
|
||||
idev->custom_ioctl = custom_ioctl;
|
||||
|
||||
@@ -112,13 +112,18 @@ void ion_carveout_heap_unmap_dma(struct ion_heap *heap,
|
||||
void *ion_carveout_heap_map_kernel(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
void *ret;
|
||||
int mtype = MT_MEMORY_NONCACHED;
|
||||
|
||||
if (buffer->flags & ION_FLAG_CACHED)
|
||||
mtype = MT_MEMORY;
|
||||
|
||||
return __arm_ioremap(buffer->priv_phys, buffer->size,
|
||||
ret = __arm_ioremap(buffer->priv_phys, buffer->size,
|
||||
mtype);
|
||||
if (ret == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ion_carveout_heap_unmap_kernel(struct ion_heap *heap,
|
||||
|
||||
@@ -47,15 +47,15 @@ static int ion_chunk_heap_allocate(struct ion_heap *heap,
|
||||
struct scatterlist *sg;
|
||||
int ret, i;
|
||||
unsigned long num_chunks;
|
||||
unsigned long allocated_size;
|
||||
|
||||
if (ion_buffer_fault_user_mappings(buffer))
|
||||
return -ENOMEM;
|
||||
|
||||
num_chunks = ALIGN(size, chunk_heap->chunk_size) /
|
||||
chunk_heap->chunk_size;
|
||||
buffer->size = num_chunks * chunk_heap->chunk_size;
|
||||
allocated_size = ALIGN(size, chunk_heap->chunk_size);
|
||||
num_chunks = allocated_size / chunk_heap->chunk_size;
|
||||
|
||||
if (buffer->size > chunk_heap->size - chunk_heap->allocated)
|
||||
if (allocated_size > chunk_heap->size - chunk_heap->allocated)
|
||||
return -ENOMEM;
|
||||
|
||||
table = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
|
||||
@@ -78,7 +78,7 @@ static int ion_chunk_heap_allocate(struct ion_heap *heap,
|
||||
}
|
||||
|
||||
buffer->priv_virt = table;
|
||||
chunk_heap->allocated += buffer->size;
|
||||
chunk_heap->allocated += allocated_size;
|
||||
return 0;
|
||||
err:
|
||||
sg = table->sgl;
|
||||
@@ -100,6 +100,9 @@ static void ion_chunk_heap_free(struct ion_buffer *buffer)
|
||||
struct sg_table *table = buffer->priv_virt;
|
||||
struct scatterlist *sg;
|
||||
int i;
|
||||
unsigned long allocated_size;
|
||||
|
||||
allocated_size = ALIGN(buffer->size, chunk_heap->chunk_size);
|
||||
|
||||
ion_heap_buffer_zero(buffer);
|
||||
|
||||
@@ -111,7 +114,7 @@ static void ion_chunk_heap_free(struct ion_buffer *buffer)
|
||||
gen_pool_free(chunk_heap->pool, page_to_phys(sg_page(sg)),
|
||||
sg_dma_len(sg));
|
||||
}
|
||||
chunk_heap->allocated -= buffer->size;
|
||||
chunk_heap->allocated -= allocated_size;
|
||||
sg_free_table(table);
|
||||
kfree(table);
|
||||
}
|
||||
|
||||
205
drivers/gpu/ion/ion_cma_heap.c
Normal file
205
drivers/gpu/ion/ion_cma_heap.c
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* drivers/gpu/ion/ion_cma_heap.c
|
||||
*
|
||||
* Copyright (C) Linaro 2012
|
||||
* Author: <benjamin.gaignard@linaro.org> for ST-Ericsson.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/ion.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
/* for ion_heap_ops structure */
|
||||
#include "ion_priv.h"
|
||||
|
||||
#define ION_CMA_ALLOCATE_FAILED -1
|
||||
|
||||
struct ion_cma_heap {
|
||||
struct ion_heap heap;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
#define to_cma_heap(x) container_of(x, struct ion_cma_heap, heap)
|
||||
|
||||
struct ion_cma_buffer_info {
|
||||
void *cpu_addr;
|
||||
dma_addr_t handle;
|
||||
struct sg_table *table;
|
||||
};
|
||||
|
||||
/*
|
||||
* Create scatter-list for the already allocated DMA buffer.
|
||||
* This function could be replaced by dma_common_get_sgtable
|
||||
* as soon as it will avalaible.
|
||||
*/
|
||||
int ion_cma_get_sgtable(struct device *dev, struct sg_table *sgt,
|
||||
void *cpu_addr, dma_addr_t handle, size_t size)
|
||||
{
|
||||
struct page *page = virt_to_page(cpu_addr);
|
||||
int ret;
|
||||
|
||||
ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
|
||||
if (unlikely(ret))
|
||||
return ret;
|
||||
|
||||
sg_set_page(sgt->sgl, page, PAGE_ALIGN(size), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ION CMA heap operations functions */
|
||||
static int ion_cma_allocate(struct ion_heap *heap, struct ion_buffer *buffer,
|
||||
unsigned long len, unsigned long align,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct ion_cma_heap *cma_heap = to_cma_heap(heap);
|
||||
struct device *dev = cma_heap->dev;
|
||||
struct ion_cma_buffer_info *info;
|
||||
|
||||
dev_dbg(dev, "Request buffer allocation len %ld\n", len);
|
||||
|
||||
info = kzalloc(sizeof(struct ion_cma_buffer_info), GFP_KERNEL);
|
||||
if (!info) {
|
||||
dev_err(dev, "Can't allocate buffer info\n");
|
||||
return ION_CMA_ALLOCATE_FAILED;
|
||||
}
|
||||
|
||||
info->cpu_addr = dma_alloc_coherent(dev, len, &(info->handle), 0);
|
||||
|
||||
if (!info->cpu_addr) {
|
||||
dev_err(dev, "Fail to allocate buffer\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
info->table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
|
||||
if (!info->table) {
|
||||
dev_err(dev, "Fail to allocate sg table\n");
|
||||
goto free_mem;
|
||||
}
|
||||
|
||||
if (ion_cma_get_sgtable
|
||||
(dev, info->table, info->cpu_addr, info->handle, len))
|
||||
goto free_table;
|
||||
/* keep this for memory release */
|
||||
buffer->priv_virt = info;
|
||||
dev_dbg(dev, "Allocate buffer %p\n", buffer);
|
||||
return 0;
|
||||
|
||||
free_table:
|
||||
kfree(info->table);
|
||||
free_mem:
|
||||
dma_free_coherent(dev, len, info->cpu_addr, info->handle);
|
||||
err:
|
||||
kfree(info);
|
||||
return ION_CMA_ALLOCATE_FAILED;
|
||||
}
|
||||
|
||||
static void ion_cma_free(struct ion_buffer *buffer)
|
||||
{
|
||||
struct ion_cma_heap *cma_heap = to_cma_heap(buffer->heap);
|
||||
struct device *dev = cma_heap->dev;
|
||||
struct ion_cma_buffer_info *info = buffer->priv_virt;
|
||||
|
||||
dev_dbg(dev, "Release buffer %p\n", buffer);
|
||||
/* release memory */
|
||||
dma_free_coherent(dev, buffer->size, info->cpu_addr, info->handle);
|
||||
/* release sg table */
|
||||
sg_free_table(info->table);
|
||||
kfree(info->table);
|
||||
kfree(info);
|
||||
}
|
||||
|
||||
/* return physical address in addr */
|
||||
static int ion_cma_phys(struct ion_heap *heap, struct ion_buffer *buffer,
|
||||
ion_phys_addr_t *addr, size_t *len)
|
||||
{
|
||||
struct ion_cma_heap *cma_heap = to_cma_heap(buffer->heap);
|
||||
struct device *dev = cma_heap->dev;
|
||||
struct ion_cma_buffer_info *info = buffer->priv_virt;
|
||||
|
||||
dev_dbg(dev, "Return buffer %p physical address 0x%x\n", buffer,
|
||||
info->handle);
|
||||
|
||||
*addr = info->handle;
|
||||
*len = buffer->size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sg_table *ion_cma_heap_map_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
struct ion_cma_buffer_info *info = buffer->priv_virt;
|
||||
|
||||
return info->table;
|
||||
}
|
||||
|
||||
void ion_cma_heap_unmap_dma(struct ion_heap *heap,
|
||||
struct ion_buffer *buffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static int ion_cma_mmap(struct ion_heap *mapper, struct ion_buffer *buffer,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct ion_cma_heap *cma_heap = to_cma_heap(buffer->heap);
|
||||
struct device *dev = cma_heap->dev;
|
||||
struct ion_cma_buffer_info *info = buffer->priv_virt;
|
||||
|
||||
return dma_mmap_coherent(dev, vma, info->cpu_addr, info->handle,
|
||||
buffer->size);
|
||||
}
|
||||
|
||||
void *ion_cma_map_kernel(struct ion_heap *heap, struct ion_buffer *buffer)
|
||||
{
|
||||
struct ion_cma_buffer_info *info = buffer->priv_virt;
|
||||
/* kernel memory mapping has been done at allocation time */
|
||||
return info->cpu_addr;
|
||||
}
|
||||
|
||||
static struct ion_heap_ops ion_cma_ops = {
|
||||
.allocate = ion_cma_allocate,
|
||||
.free = ion_cma_free,
|
||||
.map_dma = ion_cma_heap_map_dma,
|
||||
.unmap_dma = ion_cma_heap_unmap_dma,
|
||||
.phys = ion_cma_phys,
|
||||
.map_user = ion_cma_mmap,
|
||||
.map_kernel = ion_cma_map_kernel,
|
||||
};
|
||||
|
||||
struct ion_heap *ion_cma_heap_create(struct ion_platform_heap *data)
|
||||
{
|
||||
struct ion_cma_heap *cma_heap;
|
||||
|
||||
cma_heap = kzalloc(sizeof(struct ion_cma_heap), GFP_KERNEL);
|
||||
|
||||
if (!cma_heap)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
cma_heap->heap.ops = &ion_cma_ops;
|
||||
/* get device from private heaps data, later it will be
|
||||
* used to make the link with reserved CMA memory */
|
||||
cma_heap->dev = data->priv;
|
||||
cma_heap->heap.type = ION_HEAP_TYPE_DMA;
|
||||
return &cma_heap->heap;
|
||||
}
|
||||
|
||||
void ion_cma_heap_destroy(struct ion_heap *heap)
|
||||
{
|
||||
struct ion_cma_heap *cma_heap = to_cma_heap(heap);
|
||||
|
||||
kfree(cma_heap);
|
||||
}
|
||||
@@ -56,6 +56,9 @@ void *ion_heap_map_kernel(struct ion_heap *heap,
|
||||
vaddr = vmap(pages, npages, VM_MAP, pgprot);
|
||||
vfree(pages);
|
||||
|
||||
if (vaddr == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
return vaddr;
|
||||
}
|
||||
|
||||
@@ -134,8 +137,22 @@ end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ion_heap_free_page(struct ion_buffer *buffer, struct page *page,
|
||||
unsigned int order)
|
||||
struct page *ion_heap_alloc_pages(struct ion_buffer *buffer, gfp_t gfp_flags,
|
||||
unsigned int order)
|
||||
{
|
||||
struct page *page = alloc_pages(gfp_flags, order);
|
||||
|
||||
if (!page)
|
||||
return page;
|
||||
|
||||
if (ion_buffer_fault_user_mappings(buffer))
|
||||
split_page(page, order);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
void ion_heap_free_pages(struct ion_buffer *buffer, struct page *page,
|
||||
unsigned int order)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -254,6 +271,9 @@ struct ion_heap *ion_heap_create(struct ion_platform_heap *heap_data)
|
||||
case ION_HEAP_TYPE_CHUNK:
|
||||
heap = ion_chunk_heap_create(heap_data);
|
||||
break;
|
||||
case ION_HEAP_TYPE_DMA:
|
||||
heap = ion_cma_heap_create(heap_data);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid heap type %d\n", __func__,
|
||||
heap_data->type);
|
||||
@@ -290,6 +310,9 @@ void ion_heap_destroy(struct ion_heap *heap)
|
||||
case ION_HEAP_TYPE_CHUNK:
|
||||
ion_chunk_heap_destroy(heap);
|
||||
break;
|
||||
case ION_HEAP_TYPE_DMA:
|
||||
ion_cma_heap_destroy(heap);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid heap type %d\n", __func__,
|
||||
heap->type);
|
||||
|
||||
@@ -45,9 +45,8 @@ struct ion_buffer *ion_handle_buffer(struct ion_handle *handle);
|
||||
* @vaddr: the kenrel mapping if kmap_cnt is not zero
|
||||
* @dmap_cnt: number of times the buffer is mapped for dma
|
||||
* @sg_table: the sg table for the buffer if dmap_cnt is not zero
|
||||
* @dirty: bitmask representing which pages of this buffer have
|
||||
* been dirtied by the cpu and need cache maintenance
|
||||
* before dma
|
||||
* @pages: flat array of pages in the buffer -- used by fault
|
||||
* handler and only valid for buffers that are faulted in
|
||||
* @vmas: list of vma's mapping this buffer
|
||||
* @handle_count: count of handles referencing this buffer
|
||||
* @task_comm: taskcomm of last client to reference this buffer in a
|
||||
@@ -74,7 +73,7 @@ struct ion_buffer {
|
||||
void *vaddr;
|
||||
int dmap_cnt;
|
||||
struct sg_table *sg_table;
|
||||
unsigned long *dirty;
|
||||
struct page **pages;
|
||||
struct list_head vmas;
|
||||
/* used to track orphaned buffers */
|
||||
int handle_count;
|
||||
@@ -94,6 +93,9 @@ void ion_buffer_destroy(struct ion_buffer *buffer);
|
||||
* @map_kernel map memory to the kernel
|
||||
* @unmap_kernel unmap memory to the kernel
|
||||
* @map_user map memory to userspace
|
||||
*
|
||||
* allocate, phys, and map_user return 0 on success, -errno on error.
|
||||
* map_dma and map_kernel return pointer on success, ERR_PTR on error.
|
||||
*/
|
||||
struct ion_heap_ops {
|
||||
int (*allocate) (struct ion_heap *heap,
|
||||
@@ -212,6 +214,19 @@ int ion_heap_map_user(struct ion_heap *, struct ion_buffer *,
|
||||
struct vm_area_struct *);
|
||||
int ion_heap_buffer_zero(struct ion_buffer *buffer);
|
||||
|
||||
/**
|
||||
* ion_heap_alloc_pages - allocate pages from alloc_pages
|
||||
* @buffer: the buffer to allocate for, used to extract the flags
|
||||
* @gfp_flags: the gfp_t for the allocation
|
||||
* @order: the order of the allocatoin
|
||||
*
|
||||
* This funciton allocations from alloc pages and also does any other
|
||||
* necessary operations based on the buffer->flags. For buffers which
|
||||
* will be faulted in the pages are split using split_page
|
||||
*/
|
||||
struct page *ion_heap_alloc_pages(struct ion_buffer *buffer, gfp_t gfp_flags,
|
||||
unsigned int order);
|
||||
|
||||
/**
|
||||
* ion_heap_init_deferred_free -- initialize deferred free functionality
|
||||
* @heap: the heap
|
||||
@@ -269,6 +284,9 @@ void ion_carveout_heap_destroy(struct ion_heap *);
|
||||
|
||||
struct ion_heap *ion_chunk_heap_create(struct ion_platform_heap *);
|
||||
void ion_chunk_heap_destroy(struct ion_heap *);
|
||||
struct ion_heap *ion_cma_heap_create(struct ion_platform_heap *);
|
||||
void ion_cma_heap_destroy(struct ion_heap *);
|
||||
|
||||
/**
|
||||
* kernel api to allocate/free from carveout -- used when carveout is
|
||||
* used to back an architecture specific custom heap
|
||||
|
||||
@@ -64,7 +64,6 @@ static struct page *alloc_buffer_page(struct ion_system_heap *heap,
|
||||
unsigned long order)
|
||||
{
|
||||
bool cached = ion_buffer_cached(buffer);
|
||||
bool split_pages = ion_buffer_fault_user_mappings(buffer);
|
||||
struct ion_page_pool *pool = heap->pools[order_to_index(order)];
|
||||
struct page *page;
|
||||
|
||||
@@ -75,7 +74,7 @@ static struct page *alloc_buffer_page(struct ion_system_heap *heap,
|
||||
|
||||
if (order > 4)
|
||||
gfp_flags = high_order_gfp_flags;
|
||||
page = alloc_pages(gfp_flags, order);
|
||||
page = ion_heap_alloc_pages(buffer, gfp_flags, order);
|
||||
if (!page)
|
||||
return 0;
|
||||
arm_dma_ops.sync_single_for_device(NULL,
|
||||
@@ -85,8 +84,6 @@ static struct page *alloc_buffer_page(struct ion_system_heap *heap,
|
||||
if (!page)
|
||||
return 0;
|
||||
|
||||
if (split_pages)
|
||||
split_page(page, order);
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -153,7 +150,6 @@ static int ion_system_heap_allocate(struct ion_heap *heap,
|
||||
int i = 0;
|
||||
long size_remaining = PAGE_ALIGN(size);
|
||||
unsigned int max_order = orders[0];
|
||||
bool split_pages = ion_buffer_fault_user_mappings(buffer);
|
||||
|
||||
INIT_LIST_HEAD(&pages);
|
||||
while (size_remaining > 0) {
|
||||
@@ -170,28 +166,15 @@ static int ion_system_heap_allocate(struct ion_heap *heap,
|
||||
if (!table)
|
||||
goto err;
|
||||
|
||||
if (split_pages)
|
||||
ret = sg_alloc_table(table, PAGE_ALIGN(size) / PAGE_SIZE,
|
||||
GFP_KERNEL);
|
||||
else
|
||||
ret = sg_alloc_table(table, i, GFP_KERNEL);
|
||||
|
||||
ret = sg_alloc_table(table, i, GFP_KERNEL);
|
||||
if (ret)
|
||||
goto err1;
|
||||
|
||||
sg = table->sgl;
|
||||
list_for_each_entry_safe(info, tmp_info, &pages, list) {
|
||||
struct page *page = info->page;
|
||||
if (split_pages) {
|
||||
for (i = 0; i < (1 << info->order); i++) {
|
||||
sg_set_page(sg, page + i, PAGE_SIZE, 0);
|
||||
sg = sg_next(sg);
|
||||
}
|
||||
} else {
|
||||
sg_set_page(sg, page, (1 << info->order) * PAGE_SIZE,
|
||||
0);
|
||||
sg = sg_next(sg);
|
||||
}
|
||||
sg_set_page(sg, page, (1 << info->order) * PAGE_SIZE, 0);
|
||||
sg = sg_next(sg);
|
||||
list_del(&info->list);
|
||||
kfree(info);
|
||||
}
|
||||
|
||||
@@ -204,16 +204,6 @@ config BATTERY_MAX17042
|
||||
with MAX17042. This driver also supports max17047/50 chips which are
|
||||
improved version of max17042.
|
||||
|
||||
config BATTERY_ANDROID
|
||||
tristate "Battery driver for Android"
|
||||
help
|
||||
Say Y to enable generic support for battery charging according
|
||||
to common Android policies.
|
||||
This driver adds periodic battery level and health monitoring,
|
||||
kernel log reporting and other debugging features, common board
|
||||
battery file glue logic for battery/case temperature sensors,
|
||||
etc.
|
||||
|
||||
config BATTERY_Z2
|
||||
tristate "Z2 battery driver"
|
||||
depends on I2C && MACH_ZIPIT2
|
||||
|
||||
@@ -16,7 +16,6 @@ obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
|
||||
obj-$(CONFIG_TEST_POWER) += test_power.o
|
||||
|
||||
obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
|
||||
obj-$(CONFIG_BATTERY_ANDROID) += android_battery.o
|
||||
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
|
||||
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
|
||||
obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
|
||||
|
||||
@@ -1,692 +0,0 @@
|
||||
/*
|
||||
* android_battery.c
|
||||
* Android Battery Driver
|
||||
*
|
||||
* Copyright (C) 2012 Google, Inc.
|
||||
* Copyright (C) 2012 Samsung Electronics
|
||||
*
|
||||
* Based on work by himihee.seo@samsung.com, ms925.kim@samsung.com, and
|
||||
* joshua.chang@samsung.com.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/alarmtimer.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/platform_data/android_battery.h>
|
||||
|
||||
#define FAST_POLL (1 * 60)
|
||||
#define SLOW_POLL (10 * 60)
|
||||
|
||||
struct android_bat_data {
|
||||
struct android_bat_platform_data *pdata;
|
||||
struct android_bat_callbacks callbacks;
|
||||
|
||||
struct device *dev;
|
||||
|
||||
struct power_supply psy_bat;
|
||||
|
||||
struct wake_lock monitor_wake_lock;
|
||||
struct wake_lock charger_wake_lock;
|
||||
|
||||
int charge_source;
|
||||
|
||||
int batt_temp;
|
||||
int batt_current;
|
||||
unsigned int batt_health;
|
||||
unsigned int batt_vcell;
|
||||
unsigned int batt_soc;
|
||||
unsigned int charging_status;
|
||||
bool recharging;
|
||||
unsigned long charging_start_time;
|
||||
|
||||
struct workqueue_struct *monitor_wqueue;
|
||||
struct work_struct monitor_work;
|
||||
struct work_struct charger_work;
|
||||
|
||||
struct alarm monitor_alarm;
|
||||
ktime_t last_poll;
|
||||
|
||||
struct dentry *debugfs_entry;
|
||||
};
|
||||
|
||||
static enum power_supply_property android_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_TEMP,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
POWER_SUPPLY_PROP_TECHNOLOGY,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(android_bat_state_lock);
|
||||
|
||||
static void android_bat_update_data(struct android_bat_data *battery);
|
||||
static int android_bat_enable_charging(struct android_bat_data *battery,
|
||||
bool enable);
|
||||
|
||||
static char *charge_source_str(int charge_source)
|
||||
{
|
||||
switch (charge_source) {
|
||||
case CHARGE_SOURCE_NONE:
|
||||
return "none";
|
||||
case CHARGE_SOURCE_AC:
|
||||
return "ac";
|
||||
case CHARGE_SOURCE_USB:
|
||||
return "usb";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
static int android_bat_get_property(struct power_supply *ps,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct android_bat_data *battery =
|
||||
container_of(ps, struct android_bat_data, psy_bat);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
val->intval = battery->charging_status;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
val->intval = battery->batt_health;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP:
|
||||
val->intval = battery->batt_temp;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||
android_bat_update_data(battery);
|
||||
val->intval = battery->batt_vcell;
|
||||
if (val->intval == -1)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = battery->batt_soc;
|
||||
if (val->intval == -1)
|
||||
return -EINVAL;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
||||
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
android_bat_update_data(battery);
|
||||
val->intval = battery->batt_current;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void android_bat_get_temp(struct android_bat_data *battery)
|
||||
{
|
||||
int batt_temp = 42; /* 4.2C */
|
||||
int health = battery->batt_health;
|
||||
|
||||
if (battery->pdata->get_temperature)
|
||||
battery->pdata->get_temperature(&batt_temp);
|
||||
|
||||
if (battery->charge_source != CHARGE_SOURCE_NONE) {
|
||||
if (batt_temp >= battery->pdata->temp_high_threshold) {
|
||||
if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
|
||||
health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
|
||||
pr_info("battery overheat (%d>=%d), " \
|
||||
"charging unavailable\n",
|
||||
batt_temp,
|
||||
battery->pdata->temp_high_threshold);
|
||||
battery->batt_health =
|
||||
POWER_SUPPLY_HEALTH_OVERHEAT;
|
||||
}
|
||||
} else if (batt_temp <= battery->pdata->temp_high_recovery &&
|
||||
batt_temp >= battery->pdata->temp_low_recovery) {
|
||||
if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
|
||||
health == POWER_SUPPLY_HEALTH_COLD) {
|
||||
pr_info("battery recovery (%d,%d~%d)," \
|
||||
"charging available\n",
|
||||
batt_temp,
|
||||
battery->pdata->temp_low_recovery,
|
||||
battery->pdata->temp_high_recovery);
|
||||
battery->batt_health =
|
||||
POWER_SUPPLY_HEALTH_GOOD;
|
||||
}
|
||||
} else if (batt_temp <= battery->pdata->temp_low_threshold) {
|
||||
if (health != POWER_SUPPLY_HEALTH_COLD &&
|
||||
health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
|
||||
pr_info("battery cold (%d <= %d)," \
|
||||
"charging unavailable\n",
|
||||
batt_temp,
|
||||
battery->pdata->temp_low_threshold);
|
||||
battery->batt_health =
|
||||
POWER_SUPPLY_HEALTH_COLD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
battery->batt_temp = batt_temp;
|
||||
}
|
||||
|
||||
/*
|
||||
* android_bat_state_lock not held, may call back into
|
||||
* android_bat_charge_source_changed. Gathering data here can be
|
||||
* non-atomic; updating our state based on the data may need to be
|
||||
* atomic.
|
||||
*/
|
||||
|
||||
static void android_bat_update_data(struct android_bat_data *battery)
|
||||
{
|
||||
int ret;
|
||||
int v;
|
||||
|
||||
if (battery->pdata->poll_charge_source)
|
||||
battery->charge_source = battery->pdata->poll_charge_source();
|
||||
|
||||
if (battery->pdata->get_voltage_now) {
|
||||
ret = battery->pdata->get_voltage_now();
|
||||
battery->batt_vcell = ret >= 0 ? ret : 4242000;
|
||||
}
|
||||
|
||||
if (battery->pdata->get_capacity) {
|
||||
ret = battery->pdata->get_capacity();
|
||||
battery->batt_soc = ret >= 0 ? ret : 42;
|
||||
}
|
||||
|
||||
if (battery->pdata->get_current_now) {
|
||||
ret = battery->pdata->get_current_now(&v);
|
||||
|
||||
if (!ret)
|
||||
battery->batt_current = v;
|
||||
}
|
||||
|
||||
android_bat_get_temp(battery);
|
||||
}
|
||||
|
||||
static void android_bat_set_charge_time(struct android_bat_data *battery,
|
||||
bool enable)
|
||||
{
|
||||
if (enable && !battery->charging_start_time) {
|
||||
struct timespec cur_time;
|
||||
|
||||
get_monotonic_boottime(&cur_time);
|
||||
/* record start time for charge timeout timer */
|
||||
battery->charging_start_time = cur_time.tv_sec;
|
||||
} else if (!enable) {
|
||||
/* clear charge timeout timer */
|
||||
battery->charging_start_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int android_bat_enable_charging(struct android_bat_data *battery,
|
||||
bool enable)
|
||||
{
|
||||
if (enable && (battery->batt_health != POWER_SUPPLY_HEALTH_GOOD)) {
|
||||
battery->charging_status =
|
||||
POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
if (battery->pdata && battery->pdata->set_charging_current)
|
||||
battery->pdata->set_charging_current
|
||||
(battery->charge_source);
|
||||
}
|
||||
|
||||
if (battery->pdata && battery->pdata->set_charging_enable)
|
||||
battery->pdata->set_charging_enable(enable);
|
||||
|
||||
android_bat_set_charge_time(battery, enable);
|
||||
pr_info("battery: enable=%d charger: %s\n", enable,
|
||||
charge_source_str(battery->charge_source));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool android_bat_charge_timeout(struct android_bat_data *battery,
|
||||
unsigned long timeout)
|
||||
{
|
||||
struct timespec cur_time;
|
||||
|
||||
if (!battery->charging_start_time)
|
||||
return 0;
|
||||
|
||||
get_monotonic_boottime(&cur_time);
|
||||
pr_debug("%s: Start time: %ld, End time: %ld, current time: %ld\n",
|
||||
__func__, battery->charging_start_time,
|
||||
battery->charging_start_time + timeout,
|
||||
cur_time.tv_sec);
|
||||
return cur_time.tv_sec >= battery->charging_start_time + timeout;
|
||||
}
|
||||
|
||||
static void android_bat_charging_timer(struct android_bat_data *battery)
|
||||
{
|
||||
if (!battery->charging_start_time &&
|
||||
battery->charging_status == POWER_SUPPLY_STATUS_CHARGING) {
|
||||
android_bat_enable_charging(battery, true);
|
||||
battery->recharging = true;
|
||||
pr_debug("%s: charge status charging but timer is expired\n",
|
||||
__func__);
|
||||
} else if (battery->charging_start_time == 0) {
|
||||
pr_debug("%s: charging_start_time never initialized\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (android_bat_charge_timeout(
|
||||
battery,
|
||||
battery->recharging ? battery->pdata->recharging_time :
|
||||
battery->pdata->full_charging_time)) {
|
||||
android_bat_enable_charging(battery, false);
|
||||
if (battery->batt_vcell >
|
||||
battery->pdata->recharging_voltage &&
|
||||
battery->batt_soc == 100)
|
||||
battery->charging_status =
|
||||
POWER_SUPPLY_STATUS_FULL;
|
||||
battery->recharging = false;
|
||||
battery->charging_start_time = 0;
|
||||
pr_info("battery: charging timer expired\n");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void android_bat_charge_source_changed(struct android_bat_callbacks *ptr,
|
||||
int charge_source)
|
||||
{
|
||||
struct android_bat_data *battery =
|
||||
container_of(ptr, struct android_bat_data, callbacks);
|
||||
|
||||
wake_lock(&battery->charger_wake_lock);
|
||||
mutex_lock(&android_bat_state_lock);
|
||||
battery->charge_source = charge_source;
|
||||
|
||||
pr_info("battery: charge source type was changed: %s\n",
|
||||
charge_source_str(battery->charge_source));
|
||||
|
||||
mutex_unlock(&android_bat_state_lock);
|
||||
queue_work(battery->monitor_wqueue, &battery->charger_work);
|
||||
}
|
||||
|
||||
static void android_bat_set_full_status(struct android_bat_callbacks *ptr)
|
||||
{
|
||||
struct android_bat_data *battery =
|
||||
container_of(ptr, struct android_bat_data, callbacks);
|
||||
|
||||
mutex_lock(&android_bat_state_lock);
|
||||
pr_info("battery: battery full\n");
|
||||
battery->charging_status = POWER_SUPPLY_STATUS_FULL;
|
||||
android_bat_enable_charging(battery, false);
|
||||
battery->recharging = false;
|
||||
mutex_unlock(&android_bat_state_lock);
|
||||
power_supply_changed(&battery->psy_bat);
|
||||
}
|
||||
|
||||
static void android_bat_charger_work(struct work_struct *work)
|
||||
{
|
||||
struct android_bat_data *battery =
|
||||
container_of(work, struct android_bat_data, charger_work);
|
||||
|
||||
mutex_lock(&android_bat_state_lock);
|
||||
|
||||
switch (battery->charge_source) {
|
||||
case CHARGE_SOURCE_NONE:
|
||||
battery->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
android_bat_enable_charging(battery, false);
|
||||
battery->batt_health = POWER_SUPPLY_HEALTH_GOOD;
|
||||
battery->recharging = false;
|
||||
battery->charging_start_time = 0;
|
||||
break;
|
||||
case CHARGE_SOURCE_USB:
|
||||
case CHARGE_SOURCE_AC:
|
||||
/*
|
||||
* If charging status indicates a charger was already
|
||||
* connected prior to this and the status is something
|
||||
* other than charging ("full" or "not-charging"), leave
|
||||
* the status alone.
|
||||
*/
|
||||
if (battery->charging_status ==
|
||||
POWER_SUPPLY_STATUS_DISCHARGING ||
|
||||
battery->charging_status == POWER_SUPPLY_STATUS_UNKNOWN)
|
||||
battery->charging_status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
|
||||
/*
|
||||
* Don't re-enable charging if the battery is full and we
|
||||
* are not actively re-charging it, or if "not-charging"
|
||||
* status is set.
|
||||
*/
|
||||
if (!((battery->charging_status == POWER_SUPPLY_STATUS_FULL
|
||||
&& !battery->recharging) || battery->charging_status ==
|
||||
POWER_SUPPLY_STATUS_NOT_CHARGING))
|
||||
android_bat_enable_charging(battery, true);
|
||||
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Invalid charger type\n", __func__);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&android_bat_state_lock);
|
||||
wake_lock_timeout(&battery->charger_wake_lock, HZ * 2);
|
||||
power_supply_changed(&battery->psy_bat);
|
||||
}
|
||||
|
||||
|
||||
static void android_bat_monitor_set_alarm(struct android_bat_data *battery,
|
||||
int seconds)
|
||||
{
|
||||
alarm_start(&battery->monitor_alarm,
|
||||
ktime_add(battery->last_poll, ktime_set(seconds, 0)));
|
||||
}
|
||||
|
||||
static void android_bat_monitor_work(struct work_struct *work)
|
||||
{
|
||||
struct android_bat_data *battery =
|
||||
container_of(work, struct android_bat_data, monitor_work);
|
||||
struct timespec cur_time;
|
||||
|
||||
wake_lock(&battery->monitor_wake_lock);
|
||||
android_bat_update_data(battery);
|
||||
mutex_lock(&android_bat_state_lock);
|
||||
|
||||
switch (battery->charging_status) {
|
||||
case POWER_SUPPLY_STATUS_FULL:
|
||||
if (battery->batt_vcell < battery->pdata->recharging_voltage &&
|
||||
!battery->recharging) {
|
||||
battery->recharging = true;
|
||||
android_bat_enable_charging(battery, true);
|
||||
pr_info("battery: start recharging, v=%d\n",
|
||||
battery->batt_vcell/1000);
|
||||
}
|
||||
break;
|
||||
case POWER_SUPPLY_STATUS_DISCHARGING:
|
||||
break;
|
||||
case POWER_SUPPLY_STATUS_CHARGING:
|
||||
switch (battery->batt_health) {
|
||||
case POWER_SUPPLY_HEALTH_OVERHEAT:
|
||||
case POWER_SUPPLY_HEALTH_COLD:
|
||||
case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
|
||||
case POWER_SUPPLY_HEALTH_DEAD:
|
||||
case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
|
||||
battery->charging_status =
|
||||
POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
android_bat_enable_charging(battery, false);
|
||||
|
||||
pr_info("battery: Not charging, health=%d\n",
|
||||
battery->batt_health);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
||||
if (battery->batt_health == POWER_SUPPLY_HEALTH_GOOD) {
|
||||
pr_info("battery: battery health recovered\n");
|
||||
if (battery->charge_source != CHARGE_SOURCE_NONE) {
|
||||
android_bat_enable_charging(battery, true);
|
||||
battery->charging_status
|
||||
= POWER_SUPPLY_STATUS_CHARGING;
|
||||
} else {
|
||||
battery->charging_status
|
||||
= POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Undefined battery status: %d\n", __func__,
|
||||
battery->charging_status);
|
||||
break;
|
||||
}
|
||||
|
||||
android_bat_charging_timer(battery);
|
||||
get_monotonic_boottime(&cur_time);
|
||||
pr_info("battery: l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n",
|
||||
battery->batt_soc, battery->batt_vcell/1000,
|
||||
battery->batt_current, battery->batt_temp < 0 ? "-" : "",
|
||||
abs(battery->batt_temp / 10), abs(battery->batt_temp % 10),
|
||||
battery->batt_health, battery->charging_status,
|
||||
battery->recharging ? "r" : "",
|
||||
battery->charging_start_time ?
|
||||
cur_time.tv_sec - battery->charging_start_time : 0,
|
||||
charge_source_str(battery->charge_source));
|
||||
mutex_unlock(&android_bat_state_lock);
|
||||
power_supply_changed(&battery->psy_bat);
|
||||
battery->last_poll = ktime_get_boottime();
|
||||
android_bat_monitor_set_alarm(battery, FAST_POLL);
|
||||
wake_unlock(&battery->monitor_wake_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
static enum alarmtimer_restart android_bat_monitor_alarm(
|
||||
struct alarm *alarm, ktime_t now)
|
||||
{
|
||||
struct android_bat_data *battery =
|
||||
container_of(alarm, struct android_bat_data, monitor_alarm);
|
||||
|
||||
wake_lock(&battery->monitor_wake_lock);
|
||||
queue_work(battery->monitor_wqueue, &battery->monitor_work);
|
||||
return ALARMTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static int android_power_debug_dump(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct android_bat_data *battery = s->private;
|
||||
struct timespec cur_time;
|
||||
|
||||
android_bat_update_data(battery);
|
||||
get_monotonic_boottime(&cur_time);
|
||||
mutex_lock(&android_bat_state_lock);
|
||||
seq_printf(s, "l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n",
|
||||
battery->batt_soc, battery->batt_vcell/1000,
|
||||
battery->batt_current, battery->batt_temp < 0 ? "-" : "",
|
||||
abs(battery->batt_temp / 10), abs(battery->batt_temp % 10),
|
||||
battery->batt_health, battery->charging_status,
|
||||
battery->recharging ? "r" : "",
|
||||
battery->charging_start_time ?
|
||||
cur_time.tv_sec - battery->charging_start_time : 0,
|
||||
charge_source_str(battery->charge_source));
|
||||
mutex_unlock(&android_bat_state_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int android_power_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, android_power_debug_dump, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations android_power_debug_fops = {
|
||||
.open = android_power_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int android_bat_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct android_bat_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct android_bat_data *battery;
|
||||
int ret = 0;
|
||||
|
||||
dev_info(&pdev->dev, "Android Battery Driver\n");
|
||||
battery = kzalloc(sizeof(*battery), GFP_KERNEL);
|
||||
if (!battery)
|
||||
return -ENOMEM;
|
||||
|
||||
battery->pdata = pdata;
|
||||
if (!battery->pdata) {
|
||||
pr_err("%s : No platform data\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err_pdata;
|
||||
}
|
||||
|
||||
battery->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, battery);
|
||||
battery->batt_health = POWER_SUPPLY_HEALTH_GOOD;
|
||||
|
||||
battery->psy_bat.name = "android-battery",
|
||||
battery->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
battery->psy_bat.properties = android_battery_props,
|
||||
battery->psy_bat.num_properties = ARRAY_SIZE(android_battery_props),
|
||||
battery->psy_bat.get_property = android_bat_get_property,
|
||||
|
||||
battery->batt_vcell = -1;
|
||||
battery->batt_soc = -1;
|
||||
|
||||
wake_lock_init(&battery->monitor_wake_lock, WAKE_LOCK_SUSPEND,
|
||||
"android-battery-monitor");
|
||||
wake_lock_init(&battery->charger_wake_lock, WAKE_LOCK_SUSPEND,
|
||||
"android-chargerdetect");
|
||||
|
||||
ret = power_supply_register(&pdev->dev, &battery->psy_bat);
|
||||
if (ret) {
|
||||
dev_err(battery->dev, "%s: failed to register psy_bat\n",
|
||||
__func__);
|
||||
goto err_psy_bat_reg;
|
||||
}
|
||||
|
||||
battery->monitor_wqueue =
|
||||
alloc_workqueue(dev_name(&pdev->dev), WQ_FREEZABLE, 1);
|
||||
if (!battery->monitor_wqueue) {
|
||||
dev_err(battery->dev, "%s: fail to create workqueue\n",
|
||||
__func__);
|
||||
goto err_wq;
|
||||
}
|
||||
|
||||
INIT_WORK(&battery->monitor_work, android_bat_monitor_work);
|
||||
INIT_WORK(&battery->charger_work, android_bat_charger_work);
|
||||
|
||||
battery->callbacks.charge_source_changed =
|
||||
android_bat_charge_source_changed;
|
||||
battery->callbacks.battery_set_full =
|
||||
android_bat_set_full_status;
|
||||
if (battery->pdata && battery->pdata->register_callbacks)
|
||||
battery->pdata->register_callbacks(&battery->callbacks);
|
||||
|
||||
/* get initial charger status */
|
||||
if (battery->pdata->poll_charge_source)
|
||||
battery->charge_source = battery->pdata->poll_charge_source();
|
||||
|
||||
wake_lock(&battery->charger_wake_lock);
|
||||
queue_work(battery->monitor_wqueue, &battery->charger_work);
|
||||
|
||||
wake_lock(&battery->monitor_wake_lock);
|
||||
battery->last_poll = ktime_get_boottime();
|
||||
alarm_init(&battery->monitor_alarm, ALARM_BOOTTIME,
|
||||
android_bat_monitor_alarm);
|
||||
queue_work(battery->monitor_wqueue, &battery->monitor_work);
|
||||
|
||||
battery->debugfs_entry =
|
||||
debugfs_create_file("android-power", S_IRUGO, NULL,
|
||||
battery, &android_power_debug_fops);
|
||||
if (!battery->debugfs_entry)
|
||||
pr_err("failed to create android-power debugfs entry\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err_wq:
|
||||
power_supply_unregister(&battery->psy_bat);
|
||||
err_psy_bat_reg:
|
||||
wake_lock_destroy(&battery->monitor_wake_lock);
|
||||
wake_lock_destroy(&battery->charger_wake_lock);
|
||||
err_pdata:
|
||||
kfree(battery);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int android_bat_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct android_bat_data *battery = platform_get_drvdata(pdev);
|
||||
|
||||
alarm_cancel(&battery->monitor_alarm);
|
||||
flush_workqueue(battery->monitor_wqueue);
|
||||
destroy_workqueue(battery->monitor_wqueue);
|
||||
power_supply_unregister(&battery->psy_bat);
|
||||
wake_lock_destroy(&battery->monitor_wake_lock);
|
||||
wake_lock_destroy(&battery->charger_wake_lock);
|
||||
debugfs_remove(battery->debugfs_entry);
|
||||
kfree(battery);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int android_bat_suspend(struct device *dev)
|
||||
{
|
||||
struct android_bat_data *battery = dev_get_drvdata(dev);
|
||||
|
||||
cancel_work_sync(&battery->monitor_work);
|
||||
android_bat_monitor_set_alarm(
|
||||
battery,
|
||||
battery->charge_source == CHARGE_SOURCE_NONE ?
|
||||
SLOW_POLL : FAST_POLL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void android_bat_resume(struct device *dev)
|
||||
{
|
||||
struct android_bat_data *battery = dev_get_drvdata(dev);
|
||||
|
||||
android_bat_monitor_set_alarm(battery, FAST_POLL);
|
||||
return;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops android_bat_pm_ops = {
|
||||
.prepare = android_bat_suspend,
|
||||
.complete = android_bat_resume,
|
||||
};
|
||||
|
||||
static struct platform_driver android_bat_driver = {
|
||||
.driver = {
|
||||
.name = "android-battery",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &android_bat_pm_ops,
|
||||
},
|
||||
.probe = android_bat_probe,
|
||||
.remove = android_bat_remove,
|
||||
};
|
||||
|
||||
static int __init android_bat_init(void)
|
||||
{
|
||||
return platform_driver_register(&android_bat_driver);
|
||||
}
|
||||
|
||||
static void __exit android_bat_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&android_bat_driver);
|
||||
}
|
||||
|
||||
late_initcall(android_bat_init);
|
||||
module_exit(android_bat_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Android battery driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
#include <linux/kconfig.h>
|
||||
#include "sync.h"
|
||||
|
||||
struct sw_sync_timeline {
|
||||
@@ -35,10 +36,27 @@ struct sw_sync_pt {
|
||||
u32 value;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_SW_SYNC)
|
||||
struct sw_sync_timeline *sw_sync_timeline_create(const char *name);
|
||||
void sw_sync_timeline_inc(struct sw_sync_timeline *obj, u32 inc);
|
||||
|
||||
struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj, u32 value);
|
||||
#else
|
||||
static inline struct sw_sync_timeline *sw_sync_timeline_create(const char *name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void sw_sync_timeline_inc(struct sw_sync_timeline *obj, u32 inc)
|
||||
{
|
||||
}
|
||||
|
||||
static inline struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj,
|
||||
u32 value)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif /* IS_ENABLED(CONFIG_SW_SYNC) */
|
||||
|
||||
#endif /* __KERNEL __ */
|
||||
|
||||
|
||||
@@ -79,27 +79,27 @@ static void sync_timeline_free(struct kref *kref)
|
||||
container_of(kref, struct sync_timeline, kref);
|
||||
unsigned long flags;
|
||||
|
||||
if (obj->ops->release_obj)
|
||||
obj->ops->release_obj(obj);
|
||||
|
||||
spin_lock_irqsave(&sync_timeline_list_lock, flags);
|
||||
list_del(&obj->sync_timeline_list);
|
||||
spin_unlock_irqrestore(&sync_timeline_list_lock, flags);
|
||||
|
||||
if (obj->ops->release_obj)
|
||||
obj->ops->release_obj(obj);
|
||||
|
||||
kfree(obj);
|
||||
}
|
||||
|
||||
void sync_timeline_destroy(struct sync_timeline *obj)
|
||||
{
|
||||
obj->destroyed = true;
|
||||
smp_wmb();
|
||||
|
||||
/*
|
||||
* If this is not the last reference, signal any children
|
||||
* that their parent is going away.
|
||||
* signal any children that their parent is going away.
|
||||
*/
|
||||
sync_timeline_signal(obj);
|
||||
|
||||
if (!kref_put(&obj->kref, sync_timeline_free))
|
||||
sync_timeline_signal(obj);
|
||||
kref_put(&obj->kref, sync_timeline_free);
|
||||
}
|
||||
EXPORT_SYMBOL(sync_timeline_destroy);
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
||||
return sprintf(buf, "%s\n", sdev->name);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, state_show, NULL);
|
||||
static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, name_show, NULL);
|
||||
static DEVICE_ATTR(state, S_IRUGO, state_show, NULL);
|
||||
static DEVICE_ATTR(name, S_IRUGO, name_show, NULL);
|
||||
|
||||
void switch_set_state(struct switch_dev *sdev, int state)
|
||||
{
|
||||
|
||||
@@ -1490,7 +1490,7 @@ static int __init init(void)
|
||||
err = usb_composite_probe(&android_usb_driver);
|
||||
if (err) {
|
||||
pr_err("%s: failed to probe driver %d", __func__, err);
|
||||
goto err_create;
|
||||
goto err_probe;
|
||||
}
|
||||
|
||||
/* HACK: exchange composite's setup with ours */
|
||||
@@ -1499,6 +1499,8 @@ static int __init init(void)
|
||||
|
||||
return 0;
|
||||
|
||||
err_probe:
|
||||
device_destroy(android_class, dev->dev->devt);
|
||||
err_create:
|
||||
kfree(dev);
|
||||
err_dev:
|
||||
|
||||
@@ -410,15 +410,6 @@ static int mtp_create_bulk_endpoints(struct mtp_dev *dev,
|
||||
ep->driver_data = dev; /* claim the endpoint */
|
||||
dev->ep_out = ep;
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, out_desc);
|
||||
if (!ep) {
|
||||
DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name);
|
||||
ep->driver_data = dev; /* claim the endpoint */
|
||||
dev->ep_out = ep;
|
||||
|
||||
ep = usb_ep_autoconfig(cdev->gadget, intr_desc);
|
||||
if (!ep) {
|
||||
DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n");
|
||||
|
||||
@@ -2497,6 +2497,7 @@ source "drivers/video/omap2/Kconfig"
|
||||
source "drivers/video/exynos/Kconfig"
|
||||
source "drivers/video/mmp/Kconfig"
|
||||
source "drivers/video/backlight/Kconfig"
|
||||
source "drivers/video/adf/Kconfig"
|
||||
|
||||
if VT
|
||||
source "drivers/video/console/Kconfig"
|
||||
|
||||
@@ -12,6 +12,7 @@ fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \
|
||||
modedb.o fbcvt.o
|
||||
fb-objs := $(fb-y)
|
||||
|
||||
obj-$(CONFIG_ADF) += adf/
|
||||
obj-$(CONFIG_VT) += console/
|
||||
obj-$(CONFIG_LOGO) += logo/
|
||||
obj-y += backlight/
|
||||
|
||||
14
drivers/video/adf/Kconfig
Normal file
14
drivers/video/adf/Kconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
menuconfig ADF
|
||||
depends on SYNC
|
||||
depends on DMA_SHARED_BUFFER
|
||||
tristate "Atomic Display Framework"
|
||||
|
||||
menuconfig ADF_FBDEV
|
||||
depends on ADF
|
||||
depends on FB
|
||||
tristate "Helper for implementing the fbdev API in ADF drivers"
|
||||
|
||||
menuconfig ADF_MEMBLOCK
|
||||
depends on ADF
|
||||
depends on HAVE_MEMBLOCK
|
||||
tristate "Helper for using memblocks as buffers in ADF drivers"
|
||||
15
drivers/video/adf/Makefile
Normal file
15
drivers/video/adf/Makefile
Normal file
@@ -0,0 +1,15 @@
|
||||
ccflags-y := -Idrivers/staging/android
|
||||
|
||||
CFLAGS_adf.o := -I$(src)
|
||||
|
||||
obj-$(CONFIG_ADF) += adf.o \
|
||||
adf_client.o \
|
||||
adf_fops.o \
|
||||
adf_format.o \
|
||||
adf_sysfs.o
|
||||
|
||||
obj-$(CONFIG_COMPAT) += adf_fops32.o
|
||||
|
||||
obj-$(CONFIG_ADF_FBDEV) += adf_fbdev.o
|
||||
|
||||
obj-$(CONFIG_ADF_MEMBLOCK) += adf_memblock.o
|
||||
1168
drivers/video/adf/adf.c
Normal file
1168
drivers/video/adf/adf.c
Normal file
File diff suppressed because it is too large
Load Diff
71
drivers/video/adf/adf.h
Normal file
71
drivers/video/adf/adf.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __VIDEO_ADF_ADF_H
|
||||
#define __VIDEO_ADF_ADF_H
|
||||
|
||||
#include <linux/idr.h>
|
||||
#include <linux/list.h>
|
||||
#include <video/adf.h>
|
||||
#include "sync.h"
|
||||
|
||||
struct adf_event_refcount {
|
||||
struct rb_node node;
|
||||
enum adf_event_type type;
|
||||
int refcount;
|
||||
};
|
||||
|
||||
void adf_buffer_cleanup(struct adf_buffer *buf);
|
||||
void adf_buffer_mapping_cleanup(struct adf_buffer_mapping *mapping,
|
||||
struct adf_buffer *buf);
|
||||
void adf_post_cleanup(struct adf_device *dev, struct adf_pending_post *post);
|
||||
|
||||
struct adf_attachment_list *adf_attachment_find(struct list_head *list,
|
||||
struct adf_overlay_engine *eng, struct adf_interface *intf);
|
||||
int adf_attachment_validate(struct adf_device *dev,
|
||||
struct adf_overlay_engine *eng, struct adf_interface *intf);
|
||||
void adf_attachment_free(struct adf_attachment_list *attachment);
|
||||
|
||||
struct adf_event_refcount *adf_obj_find_event_refcount(struct adf_obj *obj,
|
||||
enum adf_event_type type);
|
||||
|
||||
static inline int adf_obj_check_supports_event(struct adf_obj *obj,
|
||||
enum adf_event_type type)
|
||||
{
|
||||
if (!obj->ops || !obj->ops->supports_event)
|
||||
return -EOPNOTSUPP;
|
||||
if (!obj->ops->supports_event(obj, type))
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int adf_device_attach_op(struct adf_device *dev,
|
||||
struct adf_overlay_engine *eng, struct adf_interface *intf)
|
||||
{
|
||||
if (!dev->ops->attach)
|
||||
return 0;
|
||||
|
||||
return dev->ops->attach(dev, eng, intf);
|
||||
}
|
||||
|
||||
static inline int adf_device_detach_op(struct adf_device *dev,
|
||||
struct adf_overlay_engine *eng, struct adf_interface *intf)
|
||||
{
|
||||
if (!dev->ops->detach)
|
||||
return 0;
|
||||
|
||||
return dev->ops->detach(dev, eng, intf);
|
||||
}
|
||||
|
||||
#endif /* __VIDEO_ADF_ADF_H */
|
||||
807
drivers/video/adf/adf_client.c
Normal file
807
drivers/video/adf/adf_client.c
Normal file
@@ -0,0 +1,807 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "sw_sync.h"
|
||||
|
||||
#include <video/adf.h>
|
||||
#include <video/adf_client.h>
|
||||
#include <video/adf_format.h>
|
||||
|
||||
#include "adf.h"
|
||||
|
||||
static inline bool vsync_active(u8 state)
|
||||
{
|
||||
return state == DRM_MODE_DPMS_ON || state == DRM_MODE_DPMS_STANDBY;
|
||||
}
|
||||
|
||||
/**
|
||||
* adf_interface_blank - set interface's DPMS state
|
||||
*
|
||||
* @intf: the interface
|
||||
* @state: one of %DRM_MODE_DPMS_*
|
||||
*
|
||||
* Returns 0 on success or -errno on failure.
|
||||
*/
|
||||
int adf_interface_blank(struct adf_interface *intf, u8 state)
|
||||
{
|
||||
struct adf_device *dev = adf_interface_parent(intf);
|
||||
u8 prev_state;
|
||||
bool disable_vsync;
|
||||
bool enable_vsync;
|
||||
int ret = 0;
|
||||
struct adf_event_refcount *vsync_refcount;
|
||||
|
||||
if (!intf->ops || !intf->ops->blank)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
if (state != DRM_MODE_DPMS_ON)
|
||||
flush_kthread_worker(&dev->post_worker);
|
||||
mutex_lock(&intf->base.event_lock);
|
||||
|
||||
vsync_refcount = adf_obj_find_event_refcount(&intf->base,
|
||||
ADF_EVENT_VSYNC);
|
||||
if (!vsync_refcount) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
prev_state = intf->dpms_state;
|
||||
if (prev_state == state) {
|
||||
ret = -EBUSY;
|
||||
goto done;
|
||||
}
|
||||
|
||||
disable_vsync = vsync_active(prev_state) &&
|
||||
!vsync_active(state) &&
|
||||
vsync_refcount->refcount;
|
||||
enable_vsync = !vsync_active(prev_state) &&
|
||||
vsync_active(state) &&
|
||||
vsync_refcount->refcount;
|
||||
|
||||
if (disable_vsync)
|
||||
intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC,
|
||||
false);
|
||||
|
||||
ret = intf->ops->blank(intf, state);
|
||||
if (ret < 0) {
|
||||
if (disable_vsync)
|
||||
intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC,
|
||||
true);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (enable_vsync)
|
||||
intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC,
|
||||
true);
|
||||
|
||||
intf->dpms_state = state;
|
||||
done:
|
||||
mutex_unlock(&intf->base.event_lock);
|
||||
mutex_unlock(&dev->client_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_blank);
|
||||
|
||||
/**
|
||||
* adf_interface_blank - get interface's current DPMS state
|
||||
*
|
||||
* @intf: the interface
|
||||
*
|
||||
* Returns one of %DRM_MODE_DPMS_*.
|
||||
*/
|
||||
u8 adf_interface_dpms_state(struct adf_interface *intf)
|
||||
{
|
||||
struct adf_device *dev = adf_interface_parent(intf);
|
||||
u8 dpms_state;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
dpms_state = intf->dpms_state;
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
return dpms_state;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_dpms_state);
|
||||
|
||||
/**
|
||||
* adf_interface_current_mode - get interface's current display mode
|
||||
*
|
||||
* @intf: the interface
|
||||
* @mode: returns the current mode
|
||||
*/
|
||||
void adf_interface_current_mode(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *mode)
|
||||
{
|
||||
struct adf_device *dev = adf_interface_parent(intf);
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
memcpy(mode, &intf->current_mode, sizeof(*mode));
|
||||
mutex_unlock(&dev->client_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_current_mode);
|
||||
|
||||
/**
|
||||
* adf_interface_modelist - get interface's modelist
|
||||
*
|
||||
* @intf: the interface
|
||||
* @modelist: storage for the modelist (optional)
|
||||
* @n_modes: length of @modelist
|
||||
*
|
||||
* If @modelist is not NULL, adf_interface_modelist() will copy up to @n_modes
|
||||
* modelist entries into @modelist.
|
||||
*
|
||||
* Returns the length of the modelist.
|
||||
*/
|
||||
size_t adf_interface_modelist(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *modelist, size_t n_modes)
|
||||
{
|
||||
unsigned long flags;
|
||||
size_t retval;
|
||||
|
||||
read_lock_irqsave(&intf->hotplug_modelist_lock, flags);
|
||||
if (modelist)
|
||||
memcpy(modelist, intf->modelist, sizeof(modelist[0]) *
|
||||
min(n_modes, intf->n_modes));
|
||||
retval = intf->n_modes;
|
||||
read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_modelist);
|
||||
|
||||
/**
|
||||
* adf_interface_set_mode - set interface's display mode
|
||||
*
|
||||
* @intf: the interface
|
||||
* @mode: the new mode
|
||||
*
|
||||
* Returns 0 on success or -errno on failure.
|
||||
*/
|
||||
int adf_interface_set_mode(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *mode)
|
||||
{
|
||||
struct adf_device *dev = adf_interface_parent(intf);
|
||||
int ret = 0;
|
||||
|
||||
if (!intf->ops || !intf->ops->modeset)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
flush_kthread_worker(&dev->post_worker);
|
||||
|
||||
ret = intf->ops->modeset(intf, mode);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
memcpy(&intf->current_mode, mode, sizeof(*mode));
|
||||
done:
|
||||
mutex_unlock(&dev->client_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_set_mode);
|
||||
|
||||
/**
|
||||
* adf_interface_screen_size - get size of screen connected to interface
|
||||
*
|
||||
* @intf: the interface
|
||||
* @width_mm: returns the screen width in mm
|
||||
* @height_mm: returns the screen width in mm
|
||||
*
|
||||
* Returns 0 on success or -errno on failure.
|
||||
*/
|
||||
int adf_interface_get_screen_size(struct adf_interface *intf, u16 *width_mm,
|
||||
u16 *height_mm)
|
||||
{
|
||||
struct adf_device *dev = adf_interface_parent(intf);
|
||||
int ret;
|
||||
|
||||
if (!intf->ops || !intf->ops->screen_size)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
ret = intf->ops->screen_size(intf, width_mm, height_mm);
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_get_screen_size);
|
||||
|
||||
/**
|
||||
* adf_overlay_engine_supports_format - returns whether a format is in an
|
||||
* overlay engine's supported list
|
||||
*
|
||||
* @eng: the overlay engine
|
||||
* @format: format fourcc
|
||||
*/
|
||||
bool adf_overlay_engine_supports_format(struct adf_overlay_engine *eng,
|
||||
u32 format)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < eng->ops->n_supported_formats; i++)
|
||||
if (format == eng->ops->supported_formats[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_overlay_engine_supports_format);
|
||||
|
||||
static int adf_buffer_validate(struct adf_buffer *buf)
|
||||
{
|
||||
struct adf_overlay_engine *eng = buf->overlay_engine;
|
||||
struct device *dev = &eng->base.dev;
|
||||
struct adf_device *parent = adf_overlay_engine_parent(eng);
|
||||
u8 hsub, vsub, num_planes, cpp[ADF_MAX_PLANES], i;
|
||||
|
||||
if (!adf_overlay_engine_supports_format(eng, buf->format)) {
|
||||
char format_str[ADF_FORMAT_STR_SIZE];
|
||||
adf_format_str(buf->format, format_str);
|
||||
dev_err(dev, "unsupported format %s\n", format_str);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!adf_format_is_standard(buf->format))
|
||||
return parent->ops->validate_custom_format(parent, buf);
|
||||
|
||||
hsub = adf_format_horz_chroma_subsampling(buf->format);
|
||||
vsub = adf_format_vert_chroma_subsampling(buf->format);
|
||||
num_planes = adf_format_num_planes(buf->format);
|
||||
for (i = 0; i < num_planes; i++)
|
||||
cpp[i] = adf_format_plane_cpp(buf->format, i);
|
||||
|
||||
return adf_format_validate_yuv(parent, buf, num_planes, hsub, vsub,
|
||||
cpp);
|
||||
}
|
||||
|
||||
static int adf_buffer_map(struct adf_device *dev, struct adf_buffer *buf,
|
||||
struct adf_buffer_mapping *mapping)
|
||||
{
|
||||
int ret = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < buf->n_planes; i++) {
|
||||
struct dma_buf_attachment *attachment;
|
||||
struct sg_table *sg_table;
|
||||
|
||||
attachment = dma_buf_attach(buf->dma_bufs[i], dev->dev);
|
||||
if (IS_ERR(attachment)) {
|
||||
ret = PTR_ERR(attachment);
|
||||
dev_err(&dev->base.dev, "attaching plane %u failed: %d\n",
|
||||
i, ret);
|
||||
goto done;
|
||||
}
|
||||
mapping->attachments[i] = attachment;
|
||||
|
||||
sg_table = dma_buf_map_attachment(attachment, DMA_TO_DEVICE);
|
||||
if (IS_ERR(sg_table)) {
|
||||
ret = PTR_ERR(sg_table);
|
||||
dev_err(&dev->base.dev, "mapping plane %u failed: %d",
|
||||
i, ret);
|
||||
goto done;
|
||||
} else if (!sg_table) {
|
||||
ret = -ENOMEM;
|
||||
dev_err(&dev->base.dev, "mapping plane %u failed\n", i);
|
||||
goto done;
|
||||
}
|
||||
mapping->sg_tables[i] = sg_table;
|
||||
}
|
||||
|
||||
done:
|
||||
if (ret < 0)
|
||||
adf_buffer_mapping_cleanup(mapping, buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sync_fence *adf_sw_complete_fence(struct adf_device *dev)
|
||||
{
|
||||
struct sync_pt *pt;
|
||||
struct sync_fence *complete_fence;
|
||||
|
||||
if (!dev->timeline) {
|
||||
dev->timeline = sw_sync_timeline_create(dev->base.name);
|
||||
if (!dev->timeline)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
dev->timeline_max = 1;
|
||||
}
|
||||
|
||||
dev->timeline_max++;
|
||||
pt = sw_sync_pt_create(dev->timeline, dev->timeline_max);
|
||||
if (!pt)
|
||||
goto err_pt_create;
|
||||
complete_fence = sync_fence_create(dev->base.name, pt);
|
||||
if (!complete_fence)
|
||||
goto err_fence_create;
|
||||
|
||||
return complete_fence;
|
||||
|
||||
err_fence_create:
|
||||
sync_pt_free(pt);
|
||||
err_pt_create:
|
||||
dev->timeline_max--;
|
||||
return ERR_PTR(-ENOSYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* adf_device_post - flip to a new set of buffers
|
||||
*
|
||||
* @dev: device targeted by the flip
|
||||
* @intfs: interfaces targeted by the flip
|
||||
* @n_intfs: number of targeted interfaces
|
||||
* @bufs: description of buffers displayed
|
||||
* @n_bufs: number of buffers displayed
|
||||
* @custom_data: driver-private data
|
||||
* @custom_data_size: size of driver-private data
|
||||
*
|
||||
* adf_device_post() will copy @intfs, @bufs, and @custom_data, so they may
|
||||
* point to variables on the stack. adf_device_post() also takes its own
|
||||
* reference on each of the dma-bufs in @bufs. The adf_device_post_nocopy()
|
||||
* variant transfers ownership of these resources to ADF instead.
|
||||
*
|
||||
* On success, returns a sync fence which signals when the buffers are removed
|
||||
* from the screen. On failure, returns ERR_PTR(-errno).
|
||||
*/
|
||||
struct sync_fence *adf_device_post(struct adf_device *dev,
|
||||
struct adf_interface **intfs, size_t n_intfs,
|
||||
struct adf_buffer *bufs, size_t n_bufs, void *custom_data,
|
||||
size_t custom_data_size)
|
||||
{
|
||||
struct adf_interface **intfs_copy = NULL;
|
||||
struct adf_buffer *bufs_copy = NULL;
|
||||
void *custom_data_copy = NULL;
|
||||
struct sync_fence *ret;
|
||||
size_t i;
|
||||
|
||||
intfs_copy = kzalloc(sizeof(intfs_copy[0]) * n_intfs, GFP_KERNEL);
|
||||
if (!intfs_copy)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
bufs_copy = kzalloc(sizeof(bufs_copy[0]) * n_bufs, GFP_KERNEL);
|
||||
if (!bufs_copy) {
|
||||
ret = ERR_PTR(-ENOMEM);
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
custom_data_copy = kzalloc(custom_data_size, GFP_KERNEL);
|
||||
if (!custom_data_copy) {
|
||||
ret = ERR_PTR(-ENOMEM);
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
for (i = 0; i < n_bufs; i++) {
|
||||
size_t j;
|
||||
for (j = 0; j < bufs[i].n_planes; j++)
|
||||
get_dma_buf(bufs[i].dma_bufs[j]);
|
||||
}
|
||||
|
||||
memcpy(intfs_copy, intfs, sizeof(intfs_copy[0]) * n_intfs);
|
||||
memcpy(bufs_copy, bufs, sizeof(bufs_copy[0]) * n_bufs);
|
||||
memcpy(custom_data_copy, custom_data, custom_data_size);
|
||||
|
||||
ret = adf_device_post_nocopy(dev, intfs_copy, n_intfs, bufs_copy,
|
||||
n_bufs, custom_data_copy, custom_data_size);
|
||||
if (IS_ERR(ret))
|
||||
goto err_post;
|
||||
|
||||
return ret;
|
||||
|
||||
err_post:
|
||||
for (i = 0; i < n_bufs; i++) {
|
||||
size_t j;
|
||||
for (j = 0; j < bufs[i].n_planes; j++)
|
||||
dma_buf_put(bufs[i].dma_bufs[j]);
|
||||
}
|
||||
err_alloc:
|
||||
kfree(custom_data_copy);
|
||||
kfree(bufs_copy);
|
||||
kfree(intfs_copy);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_post);
|
||||
|
||||
/**
|
||||
* adf_device_post_nocopy - flip to a new set of buffers
|
||||
*
|
||||
* adf_device_post_nocopy() has the same behavior as adf_device_post(),
|
||||
* except ADF does not copy @intfs, @bufs, or @custom_data, and it does
|
||||
* not take an extra reference on the dma-bufs in @bufs.
|
||||
*
|
||||
* @intfs, @bufs, and @custom_data must point to buffers allocated by
|
||||
* kmalloc(). On success, ADF takes ownership of these buffers and the dma-bufs
|
||||
* in @bufs, and will kfree()/dma_buf_put() them when they are no longer needed.
|
||||
* On failure, adf_device_post_nocopy() does NOT take ownership of these
|
||||
* buffers or the dma-bufs, and the caller must clean them up.
|
||||
*
|
||||
* adf_device_post_nocopy() is mainly intended for implementing ADF's ioctls.
|
||||
* Clients may find the nocopy variant useful in limited cases, but most should
|
||||
* call adf_device_post() instead.
|
||||
*/
|
||||
struct sync_fence *adf_device_post_nocopy(struct adf_device *dev,
|
||||
struct adf_interface **intfs, size_t n_intfs,
|
||||
struct adf_buffer *bufs, size_t n_bufs,
|
||||
void *custom_data, size_t custom_data_size)
|
||||
{
|
||||
struct adf_pending_post *cfg;
|
||||
struct adf_buffer_mapping *mappings;
|
||||
struct sync_fence *ret;
|
||||
size_t i;
|
||||
int err;
|
||||
|
||||
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
|
||||
if (!cfg)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mappings = kzalloc(sizeof(mappings[0]) * n_bufs, GFP_KERNEL);
|
||||
if (!mappings) {
|
||||
ret = ERR_PTR(-ENOMEM);
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
|
||||
for (i = 0; i < n_bufs; i++) {
|
||||
err = adf_buffer_validate(&bufs[i]);
|
||||
if (err < 0) {
|
||||
ret = ERR_PTR(err);
|
||||
goto err_buf;
|
||||
}
|
||||
|
||||
err = adf_buffer_map(dev, &bufs[i], &mappings[i]);
|
||||
if (err < 0) {
|
||||
ret = ERR_PTR(err);
|
||||
goto err_buf;
|
||||
}
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&cfg->head);
|
||||
cfg->config.n_bufs = n_bufs;
|
||||
cfg->config.bufs = bufs;
|
||||
cfg->config.mappings = mappings;
|
||||
cfg->config.custom_data = custom_data;
|
||||
cfg->config.custom_data_size = custom_data_size;
|
||||
|
||||
err = dev->ops->validate(dev, &cfg->config, &cfg->state);
|
||||
if (err < 0) {
|
||||
ret = ERR_PTR(err);
|
||||
goto err_buf;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->post_lock);
|
||||
|
||||
if (dev->ops->complete_fence)
|
||||
ret = dev->ops->complete_fence(dev, &cfg->config,
|
||||
cfg->state);
|
||||
else
|
||||
ret = adf_sw_complete_fence(dev);
|
||||
|
||||
if (IS_ERR(ret))
|
||||
goto err_fence;
|
||||
|
||||
list_add_tail(&cfg->head, &dev->post_list);
|
||||
queue_kthread_work(&dev->post_worker, &dev->post_work);
|
||||
mutex_unlock(&dev->post_lock);
|
||||
mutex_unlock(&dev->client_lock);
|
||||
kfree(intfs);
|
||||
return ret;
|
||||
|
||||
err_fence:
|
||||
mutex_unlock(&dev->post_lock);
|
||||
|
||||
err_buf:
|
||||
for (i = 0; i < n_bufs; i++)
|
||||
adf_buffer_mapping_cleanup(&mappings[i], &bufs[i]);
|
||||
|
||||
mutex_unlock(&dev->client_lock);
|
||||
kfree(mappings);
|
||||
|
||||
err_alloc:
|
||||
kfree(cfg);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_post_nocopy);
|
||||
|
||||
static void adf_attachment_list_to_array(struct adf_device *dev,
|
||||
struct list_head *src, struct adf_attachment *dst, size_t size)
|
||||
{
|
||||
struct adf_attachment_list *entry;
|
||||
size_t i = 0;
|
||||
|
||||
if (!dst)
|
||||
return;
|
||||
|
||||
list_for_each_entry(entry, src, head) {
|
||||
if (i == size)
|
||||
return;
|
||||
dst[i] = entry->attachment;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* adf_device_attachments - get device's list of active attachments
|
||||
*
|
||||
* @dev: the device
|
||||
* @attachments: storage for the attachment list (optional)
|
||||
* @n_attachments: length of @attachments
|
||||
*
|
||||
* If @attachments is not NULL, adf_device_attachments() will copy up to
|
||||
* @n_attachments entries into @attachments.
|
||||
*
|
||||
* Returns the length of the active attachment list.
|
||||
*/
|
||||
size_t adf_device_attachments(struct adf_device *dev,
|
||||
struct adf_attachment *attachments, size_t n_attachments)
|
||||
{
|
||||
size_t retval;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
adf_attachment_list_to_array(dev, &dev->attached, attachments,
|
||||
n_attachments);
|
||||
retval = dev->n_attached;
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_attachments);
|
||||
|
||||
/**
|
||||
* adf_device_attachments_allowed - get device's list of allowed attachments
|
||||
*
|
||||
* @dev: the device
|
||||
* @attachments: storage for the attachment list (optional)
|
||||
* @n_attachments: length of @attachments
|
||||
*
|
||||
* If @attachments is not NULL, adf_device_attachments_allowed() will copy up to
|
||||
* @n_attachments entries into @attachments.
|
||||
*
|
||||
* Returns the length of the allowed attachment list.
|
||||
*/
|
||||
size_t adf_device_attachments_allowed(struct adf_device *dev,
|
||||
struct adf_attachment *attachments, size_t n_attachments)
|
||||
{
|
||||
size_t retval;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
adf_attachment_list_to_array(dev, &dev->attach_allowed, attachments,
|
||||
n_attachments);
|
||||
retval = dev->n_attach_allowed;
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_attachments_allowed);
|
||||
|
||||
/**
|
||||
* adf_device_attached - return whether an overlay engine and interface are
|
||||
* attached
|
||||
*
|
||||
* @dev: the parent device
|
||||
* @eng: the overlay engine
|
||||
* @intf: the interface
|
||||
*/
|
||||
bool adf_device_attached(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf)
|
||||
{
|
||||
struct adf_attachment_list *attachment;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
attachment = adf_attachment_find(&dev->attached, eng, intf);
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
return attachment != NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_attached);
|
||||
|
||||
/**
|
||||
* adf_device_attach_allowed - return whether the ADF device supports attaching
|
||||
* an overlay engine and interface
|
||||
*
|
||||
* @dev: the parent device
|
||||
* @eng: the overlay engine
|
||||
* @intf: the interface
|
||||
*/
|
||||
bool adf_device_attach_allowed(struct adf_device *dev,
|
||||
struct adf_overlay_engine *eng, struct adf_interface *intf)
|
||||
{
|
||||
struct adf_attachment_list *attachment;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
attachment = adf_attachment_find(&dev->attach_allowed, eng, intf);
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
return attachment != NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_attach_allowed);
|
||||
/**
|
||||
* adf_device_attach - attach an overlay engine to an interface
|
||||
*
|
||||
* @dev: the parent device
|
||||
* @eng: the overlay engine
|
||||
* @intf: the interface
|
||||
*
|
||||
* Returns 0 on success, -%EINVAL if attaching @intf and @eng is not allowed,
|
||||
* -%EALREADY if @intf and @eng are already attached, or -errno on any other
|
||||
* failure.
|
||||
*/
|
||||
int adf_device_attach(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf)
|
||||
{
|
||||
int ret;
|
||||
struct adf_attachment_list *attachment = NULL;
|
||||
|
||||
ret = adf_attachment_validate(dev, eng, intf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
|
||||
if (dev->n_attached == ADF_MAX_ATTACHMENTS) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!adf_attachment_find(&dev->attach_allowed, eng, intf)) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (adf_attachment_find(&dev->attached, eng, intf)) {
|
||||
ret = -EALREADY;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = adf_device_attach_op(dev, eng, intf);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
attachment = kzalloc(sizeof(*attachment), GFP_KERNEL);
|
||||
if (!attachment) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
attachment->attachment.interface = intf;
|
||||
attachment->attachment.overlay_engine = eng;
|
||||
list_add_tail(&attachment->head, &dev->attached);
|
||||
dev->n_attached++;
|
||||
|
||||
done:
|
||||
mutex_unlock(&dev->client_lock);
|
||||
if (ret < 0)
|
||||
kfree(attachment);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_attach);
|
||||
|
||||
/**
|
||||
* adf_device_detach - detach an overlay engine from an interface
|
||||
*
|
||||
* @dev: the parent device
|
||||
* @eng: the overlay engine
|
||||
* @intf: the interface
|
||||
*
|
||||
* Returns 0 on success, -%EINVAL if @intf and @eng are not attached,
|
||||
* or -errno on any other failure.
|
||||
*/
|
||||
int adf_device_detach(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf)
|
||||
{
|
||||
int ret;
|
||||
struct adf_attachment_list *attachment;
|
||||
|
||||
ret = adf_attachment_validate(dev, eng, intf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
|
||||
attachment = adf_attachment_find(&dev->attached, eng, intf);
|
||||
if (!attachment) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = adf_device_detach_op(dev, eng, intf);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
adf_attachment_free(attachment);
|
||||
dev->n_attached--;
|
||||
done:
|
||||
mutex_unlock(&dev->client_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_device_detach);
|
||||
|
||||
/**
|
||||
* adf_interface_simple_buffer_alloc - allocate a simple buffer
|
||||
*
|
||||
* @intf: target interface
|
||||
* @w: width in pixels
|
||||
* @h: height in pixels
|
||||
* @format: format fourcc
|
||||
* @dma_buf: returns the allocated buffer
|
||||
* @offset: returns the byte offset of the allocated buffer's first pixel
|
||||
* @pitch: returns the allocated buffer's pitch
|
||||
*
|
||||
* See &struct adf_simple_buffer_alloc for a description of simple buffers and
|
||||
* their limitations.
|
||||
*
|
||||
* Returns 0 on success or -errno on failure.
|
||||
*/
|
||||
int adf_interface_simple_buffer_alloc(struct adf_interface *intf, u16 w, u16 h,
|
||||
u32 format, struct dma_buf **dma_buf, u32 *offset, u32 *pitch)
|
||||
{
|
||||
if (!intf->ops || !intf->ops->alloc_simple_buffer)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (!adf_format_is_rgb(format))
|
||||
return -EINVAL;
|
||||
|
||||
return intf->ops->alloc_simple_buffer(intf, w, h, format, dma_buf,
|
||||
offset, pitch);
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_simple_buffer_alloc);
|
||||
|
||||
/**
|
||||
* adf_interface_simple_post - flip to a single buffer
|
||||
*
|
||||
* @intf: interface targeted by the flip
|
||||
* @buf: buffer to display
|
||||
*
|
||||
* adf_interface_simple_post() can be used generically for simple display
|
||||
* configurations, since the client does not need to provide any driver-private
|
||||
* configuration data.
|
||||
*
|
||||
* adf_interface_simple_post() has the same copying semantics as
|
||||
* adf_device_post().
|
||||
*
|
||||
* On success, returns a sync fence which signals when the buffer is removed
|
||||
* from the screen. On failure, returns ERR_PTR(-errno).
|
||||
*/
|
||||
struct sync_fence *adf_interface_simple_post(struct adf_interface *intf,
|
||||
struct adf_buffer *buf)
|
||||
{
|
||||
size_t custom_data_size = 0;
|
||||
void *custom_data = NULL;
|
||||
struct sync_fence *ret;
|
||||
|
||||
if (intf->ops && intf->ops->describe_simple_post) {
|
||||
int err;
|
||||
|
||||
custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL);
|
||||
if (!custom_data) {
|
||||
ret = ERR_PTR(-ENOMEM);
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = intf->ops->describe_simple_post(intf, buf, custom_data,
|
||||
&custom_data_size);
|
||||
if (err < 0) {
|
||||
ret = ERR_PTR(err);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
ret = adf_device_post(adf_interface_parent(intf), &intf, 1, buf, 1,
|
||||
custom_data, custom_data_size);
|
||||
done:
|
||||
kfree(custom_data);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_interface_simple_post);
|
||||
651
drivers/video/adf/adf_fbdev.c
Normal file
651
drivers/video/adf/adf_fbdev.c
Normal file
@@ -0,0 +1,651 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include <video/adf.h>
|
||||
#include <video/adf_client.h>
|
||||
#include <video/adf_fbdev.h>
|
||||
#include <video/adf_format.h>
|
||||
|
||||
#include "adf.h"
|
||||
|
||||
struct adf_fbdev_format {
|
||||
u32 fourcc;
|
||||
u32 bpp;
|
||||
u32 r_length;
|
||||
u32 g_length;
|
||||
u32 b_length;
|
||||
u32 a_length;
|
||||
u32 r_offset;
|
||||
u32 g_offset;
|
||||
u32 b_offset;
|
||||
u32 a_offset;
|
||||
};
|
||||
|
||||
static const struct adf_fbdev_format format_table[] = {
|
||||
{DRM_FORMAT_RGB332, 8, 3, 3, 2, 0, 5, 2, 0, 0},
|
||||
{DRM_FORMAT_BGR233, 8, 3, 3, 2, 0, 0, 3, 5, 0},
|
||||
|
||||
{DRM_FORMAT_XRGB4444, 16, 4, 4, 4, 0, 8, 4, 0, 0},
|
||||
{DRM_FORMAT_XBGR4444, 16, 4, 4, 4, 0, 0, 4, 8, 0},
|
||||
{DRM_FORMAT_RGBX4444, 16, 4, 4, 4, 0, 12, 8, 4, 0},
|
||||
{DRM_FORMAT_BGRX4444, 16, 4, 4, 4, 0, 0, 4, 8, 0},
|
||||
|
||||
{DRM_FORMAT_ARGB4444, 16, 4, 4, 4, 4, 8, 4, 0, 12},
|
||||
{DRM_FORMAT_ABGR4444, 16, 4, 4, 4, 4, 0, 4, 8, 12},
|
||||
{DRM_FORMAT_RGBA4444, 16, 4, 4, 4, 4, 12, 8, 4, 0},
|
||||
{DRM_FORMAT_BGRA4444, 16, 4, 4, 4, 4, 0, 4, 8, 0},
|
||||
|
||||
{DRM_FORMAT_XRGB1555, 16, 5, 5, 5, 0, 10, 5, 0, 0},
|
||||
{DRM_FORMAT_XBGR1555, 16, 5, 5, 5, 0, 0, 5, 10, 0},
|
||||
{DRM_FORMAT_RGBX5551, 16, 5, 5, 5, 0, 11, 6, 1, 0},
|
||||
{DRM_FORMAT_BGRX5551, 16, 5, 5, 5, 0, 1, 6, 11, 0},
|
||||
|
||||
{DRM_FORMAT_ARGB1555, 16, 5, 5, 5, 1, 10, 5, 0, 15},
|
||||
{DRM_FORMAT_ABGR1555, 16, 5, 5, 5, 1, 0, 5, 10, 15},
|
||||
{DRM_FORMAT_RGBA5551, 16, 5, 5, 5, 1, 11, 6, 1, 0},
|
||||
{DRM_FORMAT_BGRA5551, 16, 5, 5, 5, 1, 1, 6, 11, 0},
|
||||
|
||||
{DRM_FORMAT_RGB565, 16, 5, 6, 5, 0, 11, 5, 0, 0},
|
||||
{DRM_FORMAT_BGR565, 16, 5, 6, 5, 0, 0, 5, 11, 0},
|
||||
|
||||
{DRM_FORMAT_RGB888, 24, 8, 8, 8, 0, 16, 8, 0, 0},
|
||||
{DRM_FORMAT_BGR888, 24, 8, 8, 8, 0, 0, 8, 16, 0},
|
||||
|
||||
{DRM_FORMAT_XRGB8888, 32, 8, 8, 8, 0, 16, 8, 0, 0},
|
||||
{DRM_FORMAT_XBGR8888, 32, 8, 8, 8, 0, 0, 8, 16, 0},
|
||||
{DRM_FORMAT_RGBX8888, 32, 8, 8, 8, 0, 24, 16, 8, 0},
|
||||
{DRM_FORMAT_BGRX8888, 32, 8, 8, 8, 0, 8, 16, 24, 0},
|
||||
|
||||
{DRM_FORMAT_ARGB8888, 32, 8, 8, 8, 8, 16, 8, 0, 24},
|
||||
{DRM_FORMAT_ABGR8888, 32, 8, 8, 8, 8, 0, 8, 16, 24},
|
||||
{DRM_FORMAT_RGBA8888, 32, 8, 8, 8, 8, 24, 16, 8, 0},
|
||||
{DRM_FORMAT_BGRA8888, 32, 8, 8, 8, 8, 8, 16, 24, 0},
|
||||
|
||||
{DRM_FORMAT_XRGB2101010, 32, 10, 10, 10, 0, 20, 10, 0, 0},
|
||||
{DRM_FORMAT_XBGR2101010, 32, 10, 10, 10, 0, 0, 10, 20, 0},
|
||||
{DRM_FORMAT_RGBX1010102, 32, 10, 10, 10, 0, 22, 12, 2, 0},
|
||||
{DRM_FORMAT_BGRX1010102, 32, 10, 10, 10, 0, 2, 12, 22, 0},
|
||||
|
||||
{DRM_FORMAT_ARGB2101010, 32, 10, 10, 10, 2, 20, 10, 0, 30},
|
||||
{DRM_FORMAT_ABGR2101010, 32, 10, 10, 10, 2, 0, 10, 20, 30},
|
||||
{DRM_FORMAT_RGBA1010102, 32, 10, 10, 10, 2, 22, 12, 2, 0},
|
||||
{DRM_FORMAT_BGRA1010102, 32, 10, 10, 10, 2, 2, 12, 22, 0},
|
||||
};
|
||||
|
||||
static u32 drm_fourcc_from_fb_var(struct fb_var_screeninfo *var)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < ARRAY_SIZE(format_table); i++) {
|
||||
const struct adf_fbdev_format *f = &format_table[i];
|
||||
if (var->red.length == f->r_length &&
|
||||
var->red.offset == f->r_offset &&
|
||||
var->green.length == f->g_length &&
|
||||
var->green.offset == f->g_offset &&
|
||||
var->blue.length == f->b_length &&
|
||||
var->blue.offset == f->b_offset &&
|
||||
var->transp.length == f->a_length &&
|
||||
(var->transp.length == 0 ||
|
||||
var->transp.offset == f->a_offset))
|
||||
return f->fourcc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct adf_fbdev_format *fbdev_format_info(u32 format)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < ARRAY_SIZE(format_table); i++) {
|
||||
const struct adf_fbdev_format *f = &format_table[i];
|
||||
if (f->fourcc == format)
|
||||
return f;
|
||||
}
|
||||
|
||||
BUG();
|
||||
}
|
||||
|
||||
void adf_modeinfo_to_fb_videomode(const struct drm_mode_modeinfo *mode,
|
||||
struct fb_videomode *vmode)
|
||||
{
|
||||
memset(vmode, 0, sizeof(*vmode));
|
||||
|
||||
vmode->refresh = mode->vrefresh;
|
||||
|
||||
vmode->xres = mode->hdisplay;
|
||||
vmode->yres = mode->vdisplay;
|
||||
|
||||
vmode->pixclock = mode->clock ? KHZ2PICOS(mode->clock) : 0;
|
||||
vmode->left_margin = mode->htotal - mode->hsync_end;
|
||||
vmode->right_margin = mode->hsync_start - mode->hdisplay;
|
||||
vmode->upper_margin = mode->vtotal - mode->vsync_end;
|
||||
vmode->lower_margin = mode->vsync_start - mode->vdisplay;
|
||||
vmode->hsync_len = mode->hsync_end - mode->hsync_start;
|
||||
vmode->vsync_len = mode->vsync_end - mode->vsync_start;
|
||||
|
||||
vmode->sync = 0;
|
||||
if (mode->flags | DRM_MODE_FLAG_PHSYNC)
|
||||
vmode->sync |= FB_SYNC_HOR_HIGH_ACT;
|
||||
if (mode->flags | DRM_MODE_FLAG_PVSYNC)
|
||||
vmode->sync |= FB_SYNC_VERT_HIGH_ACT;
|
||||
if (mode->flags | DRM_MODE_FLAG_PCSYNC)
|
||||
vmode->sync |= FB_SYNC_COMP_HIGH_ACT;
|
||||
if (mode->flags | DRM_MODE_FLAG_BCAST)
|
||||
vmode->sync |= FB_SYNC_BROADCAST;
|
||||
|
||||
vmode->vmode = 0;
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
||||
vmode->vmode |= FB_VMODE_INTERLACED;
|
||||
if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
|
||||
vmode->vmode |= FB_VMODE_DOUBLE;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_modeinfo_to_fb_videomode);
|
||||
|
||||
void adf_modeinfo_from_fb_videomode(const struct fb_videomode *vmode,
|
||||
struct drm_mode_modeinfo *mode)
|
||||
{
|
||||
memset(mode, 0, sizeof(*mode));
|
||||
|
||||
mode->hdisplay = vmode->xres;
|
||||
mode->hsync_start = mode->hdisplay + vmode->right_margin;
|
||||
mode->hsync_end = mode->hsync_start + vmode->hsync_len;
|
||||
mode->htotal = mode->hsync_end + vmode->left_margin;
|
||||
|
||||
mode->vdisplay = vmode->yres;
|
||||
mode->vsync_start = mode->vdisplay + vmode->lower_margin;
|
||||
mode->vsync_end = mode->vsync_start + vmode->vsync_len;
|
||||
mode->vtotal = mode->vsync_end + vmode->upper_margin;
|
||||
|
||||
mode->clock = vmode->pixclock ? PICOS2KHZ(vmode->pixclock) : 0;
|
||||
|
||||
mode->flags = 0;
|
||||
if (vmode->sync & FB_SYNC_HOR_HIGH_ACT)
|
||||
mode->flags |= DRM_MODE_FLAG_PHSYNC;
|
||||
if (vmode->sync & FB_SYNC_VERT_HIGH_ACT)
|
||||
mode->flags |= DRM_MODE_FLAG_PVSYNC;
|
||||
if (vmode->sync & FB_SYNC_COMP_HIGH_ACT)
|
||||
mode->flags |= DRM_MODE_FLAG_PCSYNC;
|
||||
if (vmode->sync & FB_SYNC_BROADCAST)
|
||||
mode->flags |= DRM_MODE_FLAG_BCAST;
|
||||
if (vmode->vmode & FB_VMODE_INTERLACED)
|
||||
mode->flags |= DRM_MODE_FLAG_INTERLACE;
|
||||
if (vmode->vmode & FB_VMODE_DOUBLE)
|
||||
mode->flags |= DRM_MODE_FLAG_DBLSCAN;
|
||||
|
||||
if (vmode->refresh)
|
||||
mode->vrefresh = vmode->refresh;
|
||||
else
|
||||
adf_modeinfo_set_vrefresh(mode);
|
||||
|
||||
if (vmode->name)
|
||||
strlcpy(mode->name, vmode->name, sizeof(mode->name));
|
||||
else
|
||||
adf_modeinfo_set_name(mode);
|
||||
}
|
||||
EXPORT_SYMBOL(adf_modeinfo_from_fb_videomode);
|
||||
|
||||
static int adf_fbdev_post(struct adf_fbdev *fbdev)
|
||||
{
|
||||
struct adf_buffer buf;
|
||||
struct sync_fence *complete_fence;
|
||||
int ret = 0;
|
||||
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.overlay_engine = fbdev->eng;
|
||||
buf.w = fbdev->info->var.xres;
|
||||
buf.h = fbdev->info->var.yres;
|
||||
buf.format = fbdev->format;
|
||||
buf.dma_bufs[0] = fbdev->dma_buf;
|
||||
buf.offset[0] = fbdev->offset +
|
||||
fbdev->info->var.yoffset * fbdev->pitch +
|
||||
fbdev->info->var.xoffset *
|
||||
(fbdev->info->var.bits_per_pixel / 8);
|
||||
buf.pitch[0] = fbdev->pitch;
|
||||
buf.n_planes = 1;
|
||||
|
||||
complete_fence = adf_interface_simple_post(fbdev->intf, &buf);
|
||||
if (IS_ERR(complete_fence)) {
|
||||
ret = PTR_ERR(complete_fence);
|
||||
goto done;
|
||||
}
|
||||
|
||||
sync_fence_put(complete_fence);
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const u16 vga_palette[][3] = {
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
{0x0000, 0x0000, 0xAAAA},
|
||||
{0x0000, 0xAAAA, 0x0000},
|
||||
{0x0000, 0xAAAA, 0xAAAA},
|
||||
{0xAAAA, 0x0000, 0x0000},
|
||||
{0xAAAA, 0x0000, 0xAAAA},
|
||||
{0xAAAA, 0x5555, 0x0000},
|
||||
{0xAAAA, 0xAAAA, 0xAAAA},
|
||||
{0x5555, 0x5555, 0x5555},
|
||||
{0x5555, 0x5555, 0xFFFF},
|
||||
{0x5555, 0xFFFF, 0x5555},
|
||||
{0x5555, 0xFFFF, 0xFFFF},
|
||||
{0xFFFF, 0x5555, 0x5555},
|
||||
{0xFFFF, 0x5555, 0xFFFF},
|
||||
{0xFFFF, 0xFFFF, 0x5555},
|
||||
{0xFFFF, 0xFFFF, 0xFFFF},
|
||||
};
|
||||
|
||||
static int adf_fb_alloc(struct adf_fbdev *fbdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = adf_interface_simple_buffer_alloc(fbdev->intf,
|
||||
fbdev->default_xres_virtual,
|
||||
fbdev->default_yres_virtual,
|
||||
fbdev->default_format,
|
||||
&fbdev->dma_buf, &fbdev->offset, &fbdev->pitch);
|
||||
if (ret < 0) {
|
||||
dev_err(fbdev->info->dev, "allocating fb failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
fbdev->vaddr = dma_buf_vmap(fbdev->dma_buf);
|
||||
if (!fbdev->vaddr) {
|
||||
ret = -ENOMEM;
|
||||
dev_err(fbdev->info->dev, "vmapping fb failed\n");
|
||||
goto err_vmap;
|
||||
}
|
||||
fbdev->info->fix.line_length = fbdev->pitch;
|
||||
fbdev->info->var.xres_virtual = fbdev->default_xres_virtual;
|
||||
fbdev->info->var.yres_virtual = fbdev->default_yres_virtual;
|
||||
fbdev->info->fix.smem_len = fbdev->dma_buf->size;
|
||||
fbdev->info->screen_base = fbdev->vaddr;
|
||||
|
||||
return 0;
|
||||
|
||||
err_vmap:
|
||||
dma_buf_put(fbdev->dma_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void adf_fb_destroy(struct adf_fbdev *fbdev)
|
||||
{
|
||||
dma_buf_vunmap(fbdev->dma_buf, fbdev->vaddr);
|
||||
dma_buf_put(fbdev->dma_buf);
|
||||
}
|
||||
|
||||
static void adf_fbdev_set_format(struct adf_fbdev *fbdev, u32 format)
|
||||
{
|
||||
size_t i;
|
||||
const struct adf_fbdev_format *info = fbdev_format_info(format);
|
||||
for (i = 0; i < ARRAY_SIZE(vga_palette); i++) {
|
||||
u16 r = vga_palette[i][0];
|
||||
u16 g = vga_palette[i][1];
|
||||
u16 b = vga_palette[i][2];
|
||||
|
||||
r >>= (16 - info->r_length);
|
||||
g >>= (16 - info->g_length);
|
||||
b >>= (16 - info->b_length);
|
||||
|
||||
fbdev->pseudo_palette[i] =
|
||||
(r << info->r_offset) |
|
||||
(g << info->g_offset) |
|
||||
(b << info->b_offset);
|
||||
|
||||
if (info->a_length) {
|
||||
u16 a = BIT(info->a_length) - 1;
|
||||
fbdev->pseudo_palette[i] |= (a << info->a_offset);
|
||||
}
|
||||
}
|
||||
|
||||
fbdev->info->var.bits_per_pixel = adf_format_bpp(format);
|
||||
fbdev->info->var.red.length = info->r_length;
|
||||
fbdev->info->var.red.offset = info->r_offset;
|
||||
fbdev->info->var.green.length = info->g_length;
|
||||
fbdev->info->var.green.offset = info->g_offset;
|
||||
fbdev->info->var.blue.length = info->b_length;
|
||||
fbdev->info->var.blue.offset = info->b_offset;
|
||||
fbdev->info->var.transp.length = info->a_length;
|
||||
fbdev->info->var.transp.offset = info->a_offset;
|
||||
fbdev->format = format;
|
||||
}
|
||||
|
||||
static void adf_fbdev_fill_modelist(struct adf_fbdev *fbdev)
|
||||
{
|
||||
struct drm_mode_modeinfo *modelist;
|
||||
struct fb_videomode fbmode;
|
||||
size_t n_modes, i;
|
||||
int ret = 0;
|
||||
|
||||
n_modes = adf_interface_modelist(fbdev->intf, NULL, 0);
|
||||
modelist = kzalloc(sizeof(modelist[0]) * n_modes, GFP_KERNEL);
|
||||
if (!modelist) {
|
||||
dev_warn(fbdev->info->dev, "allocating new modelist failed; keeping old modelist\n");
|
||||
return;
|
||||
}
|
||||
adf_interface_modelist(fbdev->intf, modelist, n_modes);
|
||||
|
||||
fb_destroy_modelist(&fbdev->info->modelist);
|
||||
|
||||
for (i = 0; i < n_modes; i++) {
|
||||
adf_modeinfo_to_fb_videomode(&modelist[i], &fbmode);
|
||||
ret = fb_add_videomode(&fbmode, &fbdev->info->modelist);
|
||||
if (ret < 0)
|
||||
dev_warn(fbdev->info->dev, "adding mode %s to modelist failed: %d\n",
|
||||
modelist[i].name, ret);
|
||||
}
|
||||
|
||||
kfree(modelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* adf_fbdev_open - default implementation of fbdev open op
|
||||
*/
|
||||
int adf_fbdev_open(struct fb_info *info, int user)
|
||||
{
|
||||
struct adf_fbdev *fbdev = info->par;
|
||||
int ret;
|
||||
|
||||
if (!fbdev->open) {
|
||||
struct drm_mode_modeinfo mode;
|
||||
struct fb_videomode fbmode;
|
||||
struct adf_device *dev = adf_interface_parent(fbdev->intf);
|
||||
|
||||
ret = adf_device_attach(dev, fbdev->eng, fbdev->intf);
|
||||
if (ret < 0 && ret != -EALREADY)
|
||||
return ret;
|
||||
|
||||
ret = adf_fb_alloc(fbdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
adf_interface_current_mode(fbdev->intf, &mode);
|
||||
adf_modeinfo_to_fb_videomode(&mode, &fbmode);
|
||||
fb_videomode_to_var(&fbdev->info->var, &fbmode);
|
||||
|
||||
adf_fbdev_set_format(fbdev, fbdev->default_format);
|
||||
adf_fbdev_fill_modelist(fbdev);
|
||||
}
|
||||
|
||||
ret = adf_fbdev_post(fbdev);
|
||||
if (ret < 0) {
|
||||
if (!fbdev->open)
|
||||
adf_fb_destroy(fbdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
fbdev->open = true;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_open);
|
||||
|
||||
/**
|
||||
* adf_fbdev_release - default implementation of fbdev release op
|
||||
*/
|
||||
int adf_fbdev_release(struct fb_info *info, int user)
|
||||
{
|
||||
struct adf_fbdev *fbdev = info->par;
|
||||
adf_fb_destroy(fbdev);
|
||||
fbdev->open = false;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_release);
|
||||
|
||||
/**
|
||||
* adf_fbdev_check_var - default implementation of fbdev check_var op
|
||||
*/
|
||||
int adf_fbdev_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
|
||||
{
|
||||
struct adf_fbdev *fbdev = info->par;
|
||||
bool valid_format = true;
|
||||
u32 format = drm_fourcc_from_fb_var(var);
|
||||
u32 pitch = var->xres_virtual * var->bits_per_pixel / 8;
|
||||
|
||||
if (!format) {
|
||||
dev_dbg(info->dev, "%s: unrecognized format\n", __func__);
|
||||
valid_format = false;
|
||||
}
|
||||
|
||||
if (valid_format && var->grayscale) {
|
||||
dev_dbg(info->dev, "%s: grayscale modes not supported\n",
|
||||
__func__);
|
||||
valid_format = false;
|
||||
}
|
||||
|
||||
if (valid_format && var->nonstd) {
|
||||
dev_dbg(info->dev, "%s: nonstandard formats not supported\n",
|
||||
__func__);
|
||||
valid_format = false;
|
||||
}
|
||||
|
||||
if (valid_format && !adf_overlay_engine_supports_format(fbdev->eng,
|
||||
format)) {
|
||||
char format_str[ADF_FORMAT_STR_SIZE];
|
||||
adf_format_str(format, format_str);
|
||||
dev_dbg(info->dev, "%s: format %s not supported by overlay engine %s\n",
|
||||
__func__, format_str, fbdev->eng->base.name);
|
||||
valid_format = false;
|
||||
}
|
||||
|
||||
if (valid_format && pitch > fbdev->pitch) {
|
||||
dev_dbg(info->dev, "%s: fb pitch too small for var (pitch = %u, xres_virtual = %u, bits_per_pixel = %u)\n",
|
||||
__func__, fbdev->pitch, var->xres_virtual,
|
||||
var->bits_per_pixel);
|
||||
valid_format = false;
|
||||
}
|
||||
|
||||
if (valid_format && var->yres_virtual > fbdev->default_yres_virtual) {
|
||||
dev_dbg(info->dev, "%s: fb height too small for var (h = %u, yres_virtual = %u)\n",
|
||||
__func__, fbdev->default_yres_virtual,
|
||||
var->yres_virtual);
|
||||
valid_format = false;
|
||||
}
|
||||
|
||||
if (valid_format) {
|
||||
var->activate = info->var.activate;
|
||||
var->height = info->var.height;
|
||||
var->width = info->var.width;
|
||||
var->accel_flags = info->var.accel_flags;
|
||||
var->rotate = info->var.rotate;
|
||||
var->colorspace = info->var.colorspace;
|
||||
/* userspace can't change these */
|
||||
} else {
|
||||
/* if any part of the format is invalid then fixing it up is
|
||||
impractical, so save just the modesetting bits and
|
||||
overwrite everything else */
|
||||
struct fb_videomode mode;
|
||||
fb_var_to_videomode(&mode, var);
|
||||
memcpy(var, &info->var, sizeof(*var));
|
||||
fb_videomode_to_var(var, &mode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_check_var);
|
||||
|
||||
/**
|
||||
* adf_fbdev_set_par - default implementation of fbdev set_par op
|
||||
*/
|
||||
int adf_fbdev_set_par(struct fb_info *info)
|
||||
{
|
||||
struct adf_fbdev *fbdev = info->par;
|
||||
struct adf_interface *intf = fbdev->intf;
|
||||
struct fb_videomode vmode;
|
||||
struct drm_mode_modeinfo mode;
|
||||
int ret;
|
||||
u32 format = drm_fourcc_from_fb_var(&info->var);
|
||||
|
||||
fb_var_to_videomode(&vmode, &info->var);
|
||||
adf_modeinfo_from_fb_videomode(&vmode, &mode);
|
||||
ret = adf_interface_set_mode(intf, &mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = adf_fbdev_post(fbdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (format != fbdev->format)
|
||||
adf_fbdev_set_format(fbdev, format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_set_par);
|
||||
|
||||
/**
|
||||
* adf_fbdev_blank - default implementation of fbdev blank op
|
||||
*/
|
||||
int adf_fbdev_blank(int blank, struct fb_info *info)
|
||||
{
|
||||
struct adf_fbdev *fbdev = info->par;
|
||||
struct adf_interface *intf = fbdev->intf;
|
||||
u8 dpms_state;
|
||||
|
||||
switch (blank) {
|
||||
case FB_BLANK_UNBLANK:
|
||||
dpms_state = DRM_MODE_DPMS_ON;
|
||||
break;
|
||||
case FB_BLANK_NORMAL:
|
||||
dpms_state = DRM_MODE_DPMS_STANDBY;
|
||||
break;
|
||||
case FB_BLANK_VSYNC_SUSPEND:
|
||||
dpms_state = DRM_MODE_DPMS_STANDBY;
|
||||
break;
|
||||
case FB_BLANK_HSYNC_SUSPEND:
|
||||
dpms_state = DRM_MODE_DPMS_SUSPEND;
|
||||
break;
|
||||
case FB_BLANK_POWERDOWN:
|
||||
dpms_state = DRM_MODE_DPMS_OFF;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return adf_interface_blank(intf, dpms_state);
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_blank);
|
||||
|
||||
/**
|
||||
* adf_fbdev_pan_display - default implementation of fbdev pan_display op
|
||||
*/
|
||||
int adf_fbdev_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
|
||||
{
|
||||
struct adf_fbdev *fbdev = info->par;
|
||||
return adf_fbdev_post(fbdev);
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_pan_display);
|
||||
|
||||
/**
|
||||
* adf_fbdev_mmap - default implementation of fbdev mmap op
|
||||
*/
|
||||
int adf_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
||||
{
|
||||
struct adf_fbdev *fbdev = info->par;
|
||||
|
||||
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
|
||||
return dma_buf_mmap(fbdev->dma_buf, vma, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_mmap);
|
||||
|
||||
/**
|
||||
* adf_fbdev_init - initialize helper to wrap ADF device in fbdev API
|
||||
*
|
||||
* @fbdev: the fbdev helper
|
||||
* @interface: the ADF interface that will display the framebuffer
|
||||
* @eng: the ADF overlay engine that will scan out the framebuffer
|
||||
* @xres_virtual: the virtual width of the framebuffer
|
||||
* @yres_virtual: the virtual height of the framebuffer
|
||||
* @format: the format of the framebuffer
|
||||
* @fbops: the device's fbdev ops
|
||||
* @fmt: formatting for the framebuffer identification string
|
||||
* @...: variable arguments
|
||||
*
|
||||
* @format must be a standard, non-indexed RGB format, i.e.,
|
||||
* adf_format_is_rgb(@format) && @format != @DRM_FORMAT_C8.
|
||||
*
|
||||
* Returns 0 on success or -errno on failure.
|
||||
*/
|
||||
int adf_fbdev_init(struct adf_fbdev *fbdev, struct adf_interface *interface,
|
||||
struct adf_overlay_engine *eng,
|
||||
u16 xres_virtual, u16 yres_virtual, u32 format,
|
||||
struct fb_ops *fbops, const char *fmt, ...)
|
||||
{
|
||||
struct adf_device *parent = adf_interface_parent(interface);
|
||||
struct device *dev = &parent->base.dev;
|
||||
u16 width_mm, height_mm;
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
if (!adf_format_is_rgb(format) ||
|
||||
format == DRM_FORMAT_C8) {
|
||||
dev_err(dev, "fbdev helper does not support format %u\n",
|
||||
format);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(fbdev, 0, sizeof(*fbdev));
|
||||
fbdev->intf = interface;
|
||||
fbdev->eng = eng;
|
||||
fbdev->info = framebuffer_alloc(0, dev);
|
||||
if (!fbdev->info) {
|
||||
dev_err(dev, "allocating framebuffer device failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
fbdev->default_xres_virtual = xres_virtual;
|
||||
fbdev->default_yres_virtual = yres_virtual;
|
||||
fbdev->default_format = format;
|
||||
|
||||
fbdev->info->flags = FBINFO_FLAG_DEFAULT;
|
||||
ret = adf_interface_get_screen_size(interface, &width_mm, &height_mm);
|
||||
if (ret < 0) {
|
||||
width_mm = 0;
|
||||
height_mm = 0;
|
||||
}
|
||||
fbdev->info->var.width = width_mm;
|
||||
fbdev->info->var.height = height_mm;
|
||||
fbdev->info->var.activate = FB_ACTIVATE_VBL;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(fbdev->info->fix.id, sizeof(fbdev->info->fix.id), fmt, args);
|
||||
va_end(args);
|
||||
fbdev->info->fix.type = FB_TYPE_PACKED_PIXELS;
|
||||
fbdev->info->fix.visual = FB_VISUAL_TRUECOLOR;
|
||||
fbdev->info->fix.xpanstep = 1;
|
||||
fbdev->info->fix.ypanstep = 1;
|
||||
INIT_LIST_HEAD(&fbdev->info->modelist);
|
||||
fbdev->info->fbops = fbops;
|
||||
fbdev->info->pseudo_palette = fbdev->pseudo_palette;
|
||||
fbdev->info->par = fbdev;
|
||||
|
||||
ret = register_framebuffer(fbdev->info);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "registering framebuffer failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_init);
|
||||
|
||||
/**
|
||||
* adf_fbdev_destroy - destroy helper to wrap ADF device in fbdev API
|
||||
*
|
||||
* @fbdev: the fbdev helper
|
||||
*/
|
||||
void adf_fbdev_destroy(struct adf_fbdev *fbdev)
|
||||
{
|
||||
unregister_framebuffer(fbdev->info);
|
||||
if (WARN_ON(fbdev->open))
|
||||
adf_fb_destroy(fbdev);
|
||||
framebuffer_release(fbdev->info);
|
||||
}
|
||||
EXPORT_SYMBOL(adf_fbdev_destroy);
|
||||
957
drivers/video/adf/adf_fops.c
Normal file
957
drivers/video/adf/adf_fops.c
Normal file
@@ -0,0 +1,957 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <video/adf_client.h>
|
||||
#include <video/adf_format.h>
|
||||
|
||||
#include "sw_sync.h"
|
||||
#include "sync.h"
|
||||
|
||||
#include "adf.h"
|
||||
#include "adf_fops.h"
|
||||
#include "adf_sysfs.h"
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
#include "adf_fops32.h"
|
||||
#endif
|
||||
|
||||
static int adf_obj_set_event(struct adf_obj *obj, struct adf_file *file,
|
||||
struct adf_set_event __user *arg)
|
||||
{
|
||||
struct adf_set_event data;
|
||||
bool enabled;
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
if (copy_from_user(&data, arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
|
||||
err = adf_obj_check_supports_event(obj, data.type);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
spin_lock_irqsave(&obj->file_lock, flags);
|
||||
if (data.enabled)
|
||||
enabled = test_and_set_bit(data.type,
|
||||
file->event_subscriptions);
|
||||
else
|
||||
enabled = test_and_clear_bit(data.type,
|
||||
file->event_subscriptions);
|
||||
spin_unlock_irqrestore(&obj->file_lock, flags);
|
||||
|
||||
if (data.enabled == enabled)
|
||||
return -EALREADY;
|
||||
|
||||
if (data.enabled)
|
||||
adf_event_get(obj, data.type);
|
||||
else
|
||||
adf_event_put(obj, data.type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adf_obj_copy_custom_data_to_user(struct adf_obj *obj,
|
||||
void __user *dst, size_t *dst_size)
|
||||
{
|
||||
void *custom_data;
|
||||
size_t custom_data_size;
|
||||
int ret;
|
||||
|
||||
if (!obj->ops || !obj->ops->custom_data) {
|
||||
dev_dbg(&obj->dev, "%s: no custom_data op\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL);
|
||||
if (!custom_data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = obj->ops->custom_data(obj, custom_data, &custom_data_size);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
if (copy_to_user(dst, custom_data, min(*dst_size, custom_data_size))) {
|
||||
ret = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
*dst_size = custom_data_size;
|
||||
|
||||
done:
|
||||
kfree(custom_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_eng_get_data(struct adf_overlay_engine *eng,
|
||||
struct adf_overlay_engine_data __user *arg)
|
||||
{
|
||||
struct adf_device *dev = adf_overlay_engine_parent(eng);
|
||||
struct adf_overlay_engine_data data;
|
||||
size_t n_supported_formats;
|
||||
u32 *supported_formats = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (copy_from_user(&data, arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
|
||||
strlcpy(data.name, eng->base.name, sizeof(data.name));
|
||||
|
||||
if (data.n_supported_formats > ADF_MAX_SUPPORTED_FORMATS)
|
||||
return -EINVAL;
|
||||
|
||||
n_supported_formats = data.n_supported_formats;
|
||||
data.n_supported_formats = eng->ops->n_supported_formats;
|
||||
|
||||
if (n_supported_formats) {
|
||||
supported_formats = kzalloc(n_supported_formats *
|
||||
sizeof(supported_formats[0]), GFP_KERNEL);
|
||||
if (!supported_formats)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(supported_formats, eng->ops->supported_formats,
|
||||
sizeof(u32) * min(n_supported_formats,
|
||||
eng->ops->n_supported_formats));
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
ret = adf_obj_copy_custom_data_to_user(&eng->base, arg->custom_data,
|
||||
&data.custom_data_size);
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
if (copy_to_user(arg, &data, sizeof(data))) {
|
||||
ret = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (supported_formats && copy_to_user(arg->supported_formats,
|
||||
supported_formats,
|
||||
n_supported_formats * sizeof(supported_formats[0])))
|
||||
ret = -EFAULT;
|
||||
|
||||
done:
|
||||
kfree(supported_formats);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_buffer_import(struct adf_device *dev,
|
||||
struct adf_buffer_config __user *cfg, struct adf_buffer *buf)
|
||||
{
|
||||
struct adf_buffer_config user_buf;
|
||||
size_t i;
|
||||
int ret = 0;
|
||||
|
||||
if (copy_from_user(&user_buf, cfg, sizeof(user_buf)))
|
||||
return -EFAULT;
|
||||
|
||||
memset(buf, 0, sizeof(*buf));
|
||||
|
||||
if (user_buf.n_planes > ADF_MAX_PLANES) {
|
||||
dev_err(&dev->base.dev, "invalid plane count %u\n",
|
||||
user_buf.n_planes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
buf->overlay_engine = idr_find(&dev->overlay_engines,
|
||||
user_buf.overlay_engine);
|
||||
if (!buf->overlay_engine) {
|
||||
dev_err(&dev->base.dev, "invalid overlay engine id %u\n",
|
||||
user_buf.overlay_engine);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
buf->w = user_buf.w;
|
||||
buf->h = user_buf.h;
|
||||
buf->format = user_buf.format;
|
||||
for (i = 0; i < user_buf.n_planes; i++) {
|
||||
buf->dma_bufs[i] = dma_buf_get(user_buf.fd[i]);
|
||||
if (IS_ERR(buf->dma_bufs[i])) {
|
||||
ret = PTR_ERR(buf->dma_bufs[i]);
|
||||
dev_err(&dev->base.dev, "importing dma_buf fd %llu failed: %d\n",
|
||||
user_buf.fd[i], ret);
|
||||
buf->dma_bufs[i] = NULL;
|
||||
goto done;
|
||||
}
|
||||
buf->offset[i] = user_buf.offset[i];
|
||||
buf->pitch[i] = user_buf.pitch[i];
|
||||
}
|
||||
buf->n_planes = user_buf.n_planes;
|
||||
|
||||
if (user_buf.acquire_fence >= 0) {
|
||||
buf->acquire_fence = sync_fence_fdget(user_buf.acquire_fence);
|
||||
if (!buf->acquire_fence) {
|
||||
dev_err(&dev->base.dev, "getting fence fd %lld failed\n",
|
||||
user_buf.acquire_fence);
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (ret < 0)
|
||||
adf_buffer_cleanup(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_device_post_config(struct adf_device *dev,
|
||||
struct adf_post_config __user *arg)
|
||||
{
|
||||
struct sync_fence *complete_fence;
|
||||
int complete_fence_fd;
|
||||
struct adf_buffer *bufs = NULL;
|
||||
struct adf_interface **intfs = NULL;
|
||||
size_t n_intfs, n_bufs, i;
|
||||
void *custom_data = NULL;
|
||||
size_t custom_data_size;
|
||||
int ret = 0;
|
||||
|
||||
complete_fence_fd = get_unused_fd();
|
||||
if (complete_fence_fd < 0)
|
||||
return complete_fence_fd;
|
||||
|
||||
if (get_user(n_intfs, &arg->n_interfaces)) {
|
||||
ret = -EFAULT;
|
||||
goto err_get_user;
|
||||
}
|
||||
|
||||
if (n_intfs > ADF_MAX_INTERFACES) {
|
||||
ret = -EINVAL;
|
||||
goto err_get_user;
|
||||
}
|
||||
|
||||
if (get_user(n_bufs, &arg->n_bufs)) {
|
||||
ret = -EFAULT;
|
||||
goto err_get_user;
|
||||
}
|
||||
|
||||
if (n_bufs > ADF_MAX_BUFFERS) {
|
||||
ret = -EINVAL;
|
||||
goto err_get_user;
|
||||
}
|
||||
|
||||
if (get_user(custom_data_size, &arg->custom_data_size)) {
|
||||
ret = -EFAULT;
|
||||
goto err_get_user;
|
||||
}
|
||||
|
||||
if (custom_data_size > ADF_MAX_CUSTOM_DATA_SIZE) {
|
||||
ret = -EINVAL;
|
||||
goto err_get_user;
|
||||
}
|
||||
|
||||
if (n_intfs) {
|
||||
intfs = kmalloc(sizeof(intfs[0]) * n_intfs, GFP_KERNEL);
|
||||
if (!intfs) {
|
||||
ret = -ENOMEM;
|
||||
goto err_get_user;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < n_intfs; i++) {
|
||||
u32 intf_id;
|
||||
if (get_user(intf_id, &arg->interfaces[i])) {
|
||||
ret = -EFAULT;
|
||||
goto err_get_user;
|
||||
}
|
||||
|
||||
intfs[i] = idr_find(&dev->interfaces, intf_id);
|
||||
if (!intfs[i]) {
|
||||
ret = -EINVAL;
|
||||
goto err_get_user;
|
||||
}
|
||||
}
|
||||
|
||||
if (n_bufs) {
|
||||
bufs = kzalloc(sizeof(bufs[0]) * n_bufs, GFP_KERNEL);
|
||||
if (!bufs) {
|
||||
ret = -ENOMEM;
|
||||
goto err_get_user;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < n_bufs; i++) {
|
||||
ret = adf_buffer_import(dev, &arg->bufs[i], &bufs[i]);
|
||||
if (ret < 0) {
|
||||
memset(&bufs[i], 0, sizeof(bufs[i]));
|
||||
goto err_import;
|
||||
}
|
||||
}
|
||||
|
||||
if (custom_data_size) {
|
||||
custom_data = kzalloc(custom_data_size, GFP_KERNEL);
|
||||
if (!custom_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err_import;
|
||||
}
|
||||
|
||||
if (copy_from_user(custom_data, arg->custom_data,
|
||||
custom_data_size)) {
|
||||
ret = -EFAULT;
|
||||
goto err_import;
|
||||
}
|
||||
}
|
||||
|
||||
if (put_user(complete_fence_fd, &arg->complete_fence)) {
|
||||
ret = -EFAULT;
|
||||
goto err_import;
|
||||
}
|
||||
|
||||
complete_fence = adf_device_post_nocopy(dev, intfs, n_intfs, bufs,
|
||||
n_bufs, custom_data, custom_data_size);
|
||||
if (IS_ERR(complete_fence)) {
|
||||
ret = PTR_ERR(complete_fence);
|
||||
goto err_import;
|
||||
}
|
||||
|
||||
sync_fence_install(complete_fence, complete_fence_fd);
|
||||
return 0;
|
||||
|
||||
err_import:
|
||||
for (i = 0; i < n_bufs; i++)
|
||||
adf_buffer_cleanup(&bufs[i]);
|
||||
|
||||
err_get_user:
|
||||
kfree(custom_data);
|
||||
kfree(bufs);
|
||||
kfree(intfs);
|
||||
put_unused_fd(complete_fence_fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_intf_simple_post_config(struct adf_interface *intf,
|
||||
struct adf_simple_post_config __user *arg)
|
||||
{
|
||||
struct adf_device *dev = intf->base.parent;
|
||||
struct sync_fence *complete_fence;
|
||||
int complete_fence_fd;
|
||||
struct adf_buffer buf;
|
||||
int ret = 0;
|
||||
|
||||
complete_fence_fd = get_unused_fd();
|
||||
if (complete_fence_fd < 0)
|
||||
return complete_fence_fd;
|
||||
|
||||
ret = adf_buffer_import(dev, &arg->buf, &buf);
|
||||
if (ret < 0)
|
||||
goto err_import;
|
||||
|
||||
if (put_user(complete_fence_fd, &arg->complete_fence)) {
|
||||
ret = -EFAULT;
|
||||
goto err_put_user;
|
||||
}
|
||||
|
||||
complete_fence = adf_interface_simple_post(intf, &buf);
|
||||
if (IS_ERR(complete_fence)) {
|
||||
ret = PTR_ERR(complete_fence);
|
||||
goto err_put_user;
|
||||
}
|
||||
|
||||
sync_fence_install(complete_fence, complete_fence_fd);
|
||||
return 0;
|
||||
|
||||
err_put_user:
|
||||
adf_buffer_cleanup(&buf);
|
||||
err_import:
|
||||
put_unused_fd(complete_fence_fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_intf_simple_buffer_alloc(struct adf_interface *intf,
|
||||
struct adf_simple_buffer_alloc __user *arg)
|
||||
{
|
||||
struct adf_simple_buffer_alloc data;
|
||||
struct dma_buf *dma_buf;
|
||||
int ret = 0;
|
||||
|
||||
if (copy_from_user(&data, arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
|
||||
data.fd = get_unused_fd_flags(O_CLOEXEC);
|
||||
if (data.fd < 0)
|
||||
return data.fd;
|
||||
|
||||
ret = adf_interface_simple_buffer_alloc(intf, data.w, data.h,
|
||||
data.format, &dma_buf, &data.offset, &data.pitch);
|
||||
if (ret < 0)
|
||||
goto err_alloc;
|
||||
|
||||
if (copy_to_user(arg, &data, sizeof(*arg))) {
|
||||
ret = -EFAULT;
|
||||
goto err_copy;
|
||||
}
|
||||
|
||||
fd_install(data.fd, dma_buf->file);
|
||||
return 0;
|
||||
|
||||
err_copy:
|
||||
dma_buf_put(dma_buf);
|
||||
|
||||
err_alloc:
|
||||
put_unused_fd(data.fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_copy_attachment_list_to_user(
|
||||
struct adf_attachment_config __user *to, size_t n_to,
|
||||
struct adf_attachment *from, size_t n_from)
|
||||
{
|
||||
struct adf_attachment_config *temp;
|
||||
size_t n = min(n_to, n_from);
|
||||
size_t i;
|
||||
int ret = 0;
|
||||
|
||||
if (!n)
|
||||
return 0;
|
||||
|
||||
temp = kzalloc(n * sizeof(temp[0]), GFP_KERNEL);
|
||||
if (!temp)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
temp[i].interface = from[i].interface->base.id;
|
||||
temp[i].overlay_engine = from[i].overlay_engine->base.id;
|
||||
}
|
||||
|
||||
if (copy_to_user(to, temp, n * sizeof(to[0]))) {
|
||||
ret = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
kfree(temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_device_get_data(struct adf_device *dev,
|
||||
struct adf_device_data __user *arg)
|
||||
{
|
||||
struct adf_device_data data;
|
||||
size_t n_attach;
|
||||
struct adf_attachment *attach = NULL;
|
||||
size_t n_allowed_attach;
|
||||
struct adf_attachment *allowed_attach = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (copy_from_user(&data, arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
|
||||
if (data.n_attachments > ADF_MAX_ATTACHMENTS ||
|
||||
data.n_allowed_attachments > ADF_MAX_ATTACHMENTS)
|
||||
return -EINVAL;
|
||||
|
||||
strlcpy(data.name, dev->base.name, sizeof(data.name));
|
||||
|
||||
if (data.n_attachments) {
|
||||
attach = kzalloc(data.n_attachments * sizeof(attach[0]),
|
||||
GFP_KERNEL);
|
||||
if (!attach)
|
||||
return -ENOMEM;
|
||||
}
|
||||
n_attach = adf_device_attachments(dev, attach, data.n_attachments);
|
||||
|
||||
if (data.n_allowed_attachments) {
|
||||
allowed_attach = kzalloc(data.n_allowed_attachments *
|
||||
sizeof(allowed_attach[0]), GFP_KERNEL);
|
||||
if (!allowed_attach) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
n_allowed_attach = adf_device_attachments_allowed(dev, allowed_attach,
|
||||
data.n_allowed_attachments);
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
ret = adf_obj_copy_custom_data_to_user(&dev->base, arg->custom_data,
|
||||
&data.custom_data_size);
|
||||
mutex_unlock(&dev->client_lock);
|
||||
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
ret = adf_copy_attachment_list_to_user(arg->attachments,
|
||||
data.n_attachments, attach, n_attach);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
ret = adf_copy_attachment_list_to_user(arg->allowed_attachments,
|
||||
data.n_allowed_attachments, allowed_attach,
|
||||
n_allowed_attach);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
data.n_attachments = n_attach;
|
||||
data.n_allowed_attachments = n_allowed_attach;
|
||||
|
||||
if (copy_to_user(arg, &data, sizeof(data)))
|
||||
ret = -EFAULT;
|
||||
|
||||
done:
|
||||
kfree(allowed_attach);
|
||||
kfree(attach);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_device_handle_attachment(struct adf_device *dev,
|
||||
struct adf_attachment_config __user *arg, bool attach)
|
||||
{
|
||||
struct adf_attachment_config data;
|
||||
struct adf_overlay_engine *eng;
|
||||
struct adf_interface *intf;
|
||||
|
||||
if (copy_from_user(&data, arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
|
||||
eng = idr_find(&dev->overlay_engines, data.overlay_engine);
|
||||
if (!eng) {
|
||||
dev_err(&dev->base.dev, "invalid overlay engine id %u\n",
|
||||
data.overlay_engine);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
intf = idr_find(&dev->interfaces, data.interface);
|
||||
if (!intf) {
|
||||
dev_err(&dev->base.dev, "invalid interface id %u\n",
|
||||
data.interface);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (attach)
|
||||
return adf_device_attach(dev, eng, intf);
|
||||
else
|
||||
return adf_device_detach(dev, eng, intf);
|
||||
}
|
||||
|
||||
static int adf_intf_set_mode(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo __user *arg)
|
||||
{
|
||||
struct drm_mode_modeinfo mode;
|
||||
|
||||
if (copy_from_user(&mode, arg, sizeof(mode)))
|
||||
return -EFAULT;
|
||||
|
||||
return adf_interface_set_mode(intf, &mode);
|
||||
}
|
||||
|
||||
static int adf_intf_get_data(struct adf_interface *intf,
|
||||
struct adf_interface_data __user *arg)
|
||||
{
|
||||
struct adf_device *dev = adf_interface_parent(intf);
|
||||
struct adf_interface_data data;
|
||||
struct drm_mode_modeinfo *modelist;
|
||||
size_t modelist_size;
|
||||
int err;
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
|
||||
if (copy_from_user(&data, arg, sizeof(data)))
|
||||
return -EFAULT;
|
||||
|
||||
strlcpy(data.name, intf->base.name, sizeof(data.name));
|
||||
|
||||
data.type = intf->type;
|
||||
data.id = intf->idx;
|
||||
data.flags = intf->flags;
|
||||
|
||||
err = adf_interface_get_screen_size(intf, &data.width_mm,
|
||||
&data.height_mm);
|
||||
if (err < 0) {
|
||||
data.width_mm = 0;
|
||||
data.height_mm = 0;
|
||||
}
|
||||
|
||||
modelist = kmalloc(sizeof(modelist[0]) * ADF_MAX_MODES, GFP_KERNEL);
|
||||
if (!modelist)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&dev->client_lock);
|
||||
read_lock_irqsave(&intf->hotplug_modelist_lock, flags);
|
||||
data.hotplug_detect = intf->hotplug_detect;
|
||||
modelist_size = min(data.n_available_modes, intf->n_modes) *
|
||||
sizeof(intf->modelist[0]);
|
||||
memcpy(modelist, intf->modelist, modelist_size);
|
||||
data.n_available_modes = intf->n_modes;
|
||||
read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags);
|
||||
|
||||
if (copy_to_user(arg->available_modes, modelist, modelist_size)) {
|
||||
ret = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
|
||||
data.dpms_state = intf->dpms_state;
|
||||
memcpy(&data.current_mode, &intf->current_mode,
|
||||
sizeof(intf->current_mode));
|
||||
|
||||
ret = adf_obj_copy_custom_data_to_user(&intf->base, arg->custom_data,
|
||||
&data.custom_data_size);
|
||||
done:
|
||||
mutex_unlock(&dev->client_lock);
|
||||
kfree(modelist);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (copy_to_user(arg, &data, sizeof(data)))
|
||||
ret = -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long adf_obj_custom_ioctl(struct adf_obj *obj, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
if (obj->ops && obj->ops->ioctl)
|
||||
return obj->ops->ioctl(obj, cmd, arg);
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
static long adf_overlay_engine_ioctl(struct adf_overlay_engine *eng,
|
||||
struct adf_file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case ADF_SET_EVENT:
|
||||
return adf_obj_set_event(&eng->base, file,
|
||||
(struct adf_set_event __user *)arg);
|
||||
|
||||
case ADF_GET_OVERLAY_ENGINE_DATA:
|
||||
return adf_eng_get_data(eng,
|
||||
(struct adf_overlay_engine_data __user *)arg);
|
||||
|
||||
case ADF_BLANK:
|
||||
case ADF_POST_CONFIG:
|
||||
case ADF_SET_MODE:
|
||||
case ADF_GET_DEVICE_DATA:
|
||||
case ADF_GET_INTERFACE_DATA:
|
||||
case ADF_SIMPLE_POST_CONFIG:
|
||||
case ADF_SIMPLE_BUFFER_ALLOC:
|
||||
case ADF_ATTACH:
|
||||
case ADF_DETACH:
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
return adf_obj_custom_ioctl(&eng->base, cmd, arg);
|
||||
}
|
||||
}
|
||||
|
||||
static long adf_interface_ioctl(struct adf_interface *intf,
|
||||
struct adf_file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case ADF_SET_EVENT:
|
||||
return adf_obj_set_event(&intf->base, file,
|
||||
(struct adf_set_event __user *)arg);
|
||||
|
||||
case ADF_BLANK:
|
||||
return adf_interface_blank(intf, arg);
|
||||
|
||||
case ADF_SET_MODE:
|
||||
return adf_intf_set_mode(intf,
|
||||
(struct drm_mode_modeinfo __user *)arg);
|
||||
|
||||
case ADF_GET_INTERFACE_DATA:
|
||||
return adf_intf_get_data(intf,
|
||||
(struct adf_interface_data __user *)arg);
|
||||
|
||||
case ADF_SIMPLE_POST_CONFIG:
|
||||
return adf_intf_simple_post_config(intf,
|
||||
(struct adf_simple_post_config __user *)arg);
|
||||
|
||||
case ADF_SIMPLE_BUFFER_ALLOC:
|
||||
return adf_intf_simple_buffer_alloc(intf,
|
||||
(struct adf_simple_buffer_alloc __user *)arg);
|
||||
|
||||
case ADF_POST_CONFIG:
|
||||
case ADF_GET_DEVICE_DATA:
|
||||
case ADF_GET_OVERLAY_ENGINE_DATA:
|
||||
case ADF_ATTACH:
|
||||
case ADF_DETACH:
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
return adf_obj_custom_ioctl(&intf->base, cmd, arg);
|
||||
}
|
||||
}
|
||||
|
||||
static long adf_device_ioctl(struct adf_device *dev, struct adf_file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case ADF_SET_EVENT:
|
||||
return adf_obj_set_event(&dev->base, file,
|
||||
(struct adf_set_event __user *)arg);
|
||||
|
||||
case ADF_POST_CONFIG:
|
||||
return adf_device_post_config(dev,
|
||||
(struct adf_post_config __user *)arg);
|
||||
|
||||
case ADF_GET_DEVICE_DATA:
|
||||
return adf_device_get_data(dev,
|
||||
(struct adf_device_data __user *)arg);
|
||||
|
||||
case ADF_ATTACH:
|
||||
return adf_device_handle_attachment(dev,
|
||||
(struct adf_attachment_config __user *)arg,
|
||||
true);
|
||||
|
||||
case ADF_DETACH:
|
||||
return adf_device_handle_attachment(dev,
|
||||
(struct adf_attachment_config __user *)arg,
|
||||
false);
|
||||
|
||||
case ADF_BLANK:
|
||||
case ADF_SET_MODE:
|
||||
case ADF_GET_INTERFACE_DATA:
|
||||
case ADF_GET_OVERLAY_ENGINE_DATA:
|
||||
case ADF_SIMPLE_POST_CONFIG:
|
||||
case ADF_SIMPLE_BUFFER_ALLOC:
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
return adf_obj_custom_ioctl(&dev->base, cmd, arg);
|
||||
}
|
||||
}
|
||||
|
||||
static int adf_file_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct adf_obj *obj;
|
||||
struct adf_file *fpriv = NULL;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
obj = adf_obj_sysfs_find(iminor(inode));
|
||||
if (!obj)
|
||||
return -ENODEV;
|
||||
|
||||
dev_dbg(&obj->dev, "opening %s\n", dev_name(&obj->dev));
|
||||
|
||||
if (!try_module_get(obj->parent->ops->owner)) {
|
||||
dev_err(&obj->dev, "getting owner module failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL);
|
||||
if (!fpriv) {
|
||||
ret = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&fpriv->head);
|
||||
fpriv->obj = obj;
|
||||
init_waitqueue_head(&fpriv->event_wait);
|
||||
|
||||
file->private_data = fpriv;
|
||||
|
||||
if (obj->ops && obj->ops->open) {
|
||||
ret = obj->ops->open(obj, inode, file);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&obj->file_lock, flags);
|
||||
list_add_tail(&fpriv->head, &obj->file_list);
|
||||
spin_unlock_irqrestore(&obj->file_lock, flags);
|
||||
|
||||
done:
|
||||
if (ret < 0) {
|
||||
kfree(fpriv);
|
||||
module_put(obj->parent->ops->owner);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adf_file_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct adf_file *fpriv = file->private_data;
|
||||
struct adf_obj *obj = fpriv->obj;
|
||||
enum adf_event_type event_type;
|
||||
unsigned long flags;
|
||||
|
||||
if (obj->ops && obj->ops->release)
|
||||
obj->ops->release(obj, inode, file);
|
||||
|
||||
spin_lock_irqsave(&obj->file_lock, flags);
|
||||
list_del(&fpriv->head);
|
||||
spin_unlock_irqrestore(&obj->file_lock, flags);
|
||||
|
||||
for_each_set_bit(event_type, fpriv->event_subscriptions,
|
||||
ADF_EVENT_TYPE_MAX) {
|
||||
adf_event_put(obj, event_type);
|
||||
}
|
||||
|
||||
kfree(fpriv);
|
||||
module_put(obj->parent->ops->owner);
|
||||
|
||||
dev_dbg(&obj->dev, "released %s\n", dev_name(&obj->dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
long adf_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct adf_file *fpriv = file->private_data;
|
||||
struct adf_obj *obj = fpriv->obj;
|
||||
long ret = -EINVAL;
|
||||
|
||||
dev_dbg(&obj->dev, "%s ioctl %u\n", dev_name(&obj->dev), _IOC_NR(cmd));
|
||||
|
||||
switch (obj->type) {
|
||||
case ADF_OBJ_OVERLAY_ENGINE:
|
||||
ret = adf_overlay_engine_ioctl(adf_obj_to_overlay_engine(obj),
|
||||
fpriv, cmd, arg);
|
||||
break;
|
||||
|
||||
case ADF_OBJ_INTERFACE:
|
||||
ret = adf_interface_ioctl(adf_obj_to_interface(obj), fpriv, cmd,
|
||||
arg);
|
||||
break;
|
||||
|
||||
case ADF_OBJ_DEVICE:
|
||||
ret = adf_device_ioctl(adf_obj_to_device(obj), fpriv, cmd, arg);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool adf_file_event_available(struct adf_file *fpriv)
|
||||
{
|
||||
int head = fpriv->event_head;
|
||||
int tail = fpriv->event_tail;
|
||||
return CIRC_CNT(head, tail, sizeof(fpriv->event_buf)) != 0;
|
||||
}
|
||||
|
||||
void adf_file_queue_event(struct adf_file *fpriv, struct adf_event *event)
|
||||
{
|
||||
int head = fpriv->event_head;
|
||||
int tail = fpriv->event_tail;
|
||||
size_t space = CIRC_SPACE(head, tail, sizeof(fpriv->event_buf));
|
||||
size_t space_to_end =
|
||||
CIRC_SPACE_TO_END(head, tail, sizeof(fpriv->event_buf));
|
||||
|
||||
if (space < event->length) {
|
||||
dev_dbg(&fpriv->obj->dev,
|
||||
"insufficient buffer space for event %u\n",
|
||||
event->type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (space_to_end >= event->length) {
|
||||
memcpy(fpriv->event_buf + head, event, event->length);
|
||||
} else {
|
||||
memcpy(fpriv->event_buf + head, event, space_to_end);
|
||||
memcpy(fpriv->event_buf, (u8 *)event + space_to_end,
|
||||
event->length - space_to_end);
|
||||
}
|
||||
|
||||
smp_wmb();
|
||||
fpriv->event_head = (fpriv->event_head + event->length) &
|
||||
(sizeof(fpriv->event_buf) - 1);
|
||||
wake_up_interruptible_all(&fpriv->event_wait);
|
||||
}
|
||||
|
||||
static ssize_t adf_file_copy_to_user(struct adf_file *fpriv,
|
||||
char __user *buffer, size_t buffer_size)
|
||||
{
|
||||
int head, tail;
|
||||
u8 *event_buf;
|
||||
size_t cnt, cnt_to_end, copy_size = 0;
|
||||
ssize_t ret = 0;
|
||||
unsigned long flags;
|
||||
|
||||
event_buf = kmalloc(min(buffer_size, sizeof(fpriv->event_buf)),
|
||||
GFP_KERNEL);
|
||||
if (!event_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irqsave(&fpriv->obj->file_lock, flags);
|
||||
|
||||
if (!adf_file_event_available(fpriv))
|
||||
goto out;
|
||||
|
||||
head = fpriv->event_head;
|
||||
tail = fpriv->event_tail;
|
||||
|
||||
cnt = CIRC_CNT(head, tail, sizeof(fpriv->event_buf));
|
||||
cnt_to_end = CIRC_CNT_TO_END(head, tail, sizeof(fpriv->event_buf));
|
||||
copy_size = min(buffer_size, cnt);
|
||||
|
||||
if (cnt_to_end >= copy_size) {
|
||||
memcpy(event_buf, fpriv->event_buf + tail, copy_size);
|
||||
} else {
|
||||
memcpy(event_buf, fpriv->event_buf + tail, cnt_to_end);
|
||||
memcpy(event_buf + cnt_to_end, fpriv->event_buf,
|
||||
copy_size - cnt_to_end);
|
||||
}
|
||||
|
||||
fpriv->event_tail = (fpriv->event_tail + copy_size) &
|
||||
(sizeof(fpriv->event_buf) - 1);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&fpriv->obj->file_lock, flags);
|
||||
if (copy_size) {
|
||||
if (copy_to_user(buffer, event_buf, copy_size))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = copy_size;
|
||||
}
|
||||
kfree(event_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t adf_file_read(struct file *filp, char __user *buffer,
|
||||
size_t count, loff_t *offset)
|
||||
{
|
||||
struct adf_file *fpriv = filp->private_data;
|
||||
int err;
|
||||
|
||||
err = wait_event_interruptible(fpriv->event_wait,
|
||||
adf_file_event_available(fpriv));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return adf_file_copy_to_user(fpriv, buffer, count);
|
||||
}
|
||||
|
||||
unsigned int adf_file_poll(struct file *filp, struct poll_table_struct *wait)
|
||||
{
|
||||
struct adf_file *fpriv = filp->private_data;
|
||||
unsigned int mask = 0;
|
||||
|
||||
poll_wait(filp, &fpriv->event_wait, wait);
|
||||
|
||||
if (adf_file_event_available(fpriv))
|
||||
mask |= POLLIN | POLLRDNORM;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
const struct file_operations adf_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = adf_file_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = adf_file_compat_ioctl,
|
||||
#endif
|
||||
.open = adf_file_open,
|
||||
.release = adf_file_release,
|
||||
.llseek = default_llseek,
|
||||
.read = adf_file_read,
|
||||
.poll = adf_file_poll,
|
||||
};
|
||||
37
drivers/video/adf/adf_fops.h
Normal file
37
drivers/video/adf/adf_fops.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __VIDEO_ADF_ADF_FOPS_H
|
||||
#define __VIDEO_ADF_ADF_FOPS_H
|
||||
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
extern const struct file_operations adf_fops;
|
||||
|
||||
struct adf_file {
|
||||
struct list_head head;
|
||||
struct adf_obj *obj;
|
||||
|
||||
DECLARE_BITMAP(event_subscriptions, ADF_EVENT_TYPE_MAX);
|
||||
u8 event_buf[4096];
|
||||
int event_head;
|
||||
int event_tail;
|
||||
wait_queue_head_t event_wait;
|
||||
};
|
||||
|
||||
void adf_file_queue_event(struct adf_file *file, struct adf_event *event);
|
||||
long adf_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
||||
|
||||
#endif /* __VIDEO_ADF_ADF_FOPS_H */
|
||||
217
drivers/video/adf/adf_fops32.c
Normal file
217
drivers/video/adf/adf_fops32.c
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
#include <video/adf.h>
|
||||
|
||||
#include "adf_fops.h"
|
||||
#include "adf_fops32.h"
|
||||
|
||||
long adf_compat_post_config(struct file *file,
|
||||
struct adf_post_config32 __user *arg)
|
||||
{
|
||||
struct adf_post_config32 cfg32;
|
||||
struct adf_post_config __user *cfg;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&cfg32, arg, sizeof(cfg32)))
|
||||
return -EFAULT;
|
||||
|
||||
cfg = compat_alloc_user_space(sizeof(*cfg));
|
||||
if (!access_ok(VERIFY_WRITE, cfg, sizeof(*cfg)))
|
||||
return -EFAULT;
|
||||
|
||||
if (put_user(cfg32.n_interfaces, &cfg->n_interfaces) ||
|
||||
put_user(compat_ptr(cfg32.interfaces),
|
||||
&cfg->interfaces) ||
|
||||
put_user(cfg32.n_bufs, &cfg->n_bufs) ||
|
||||
put_user(compat_ptr(cfg32.bufs), &cfg->bufs) ||
|
||||
put_user(cfg32.custom_data_size,
|
||||
&cfg->custom_data_size) ||
|
||||
put_user(compat_ptr(cfg32.custom_data),
|
||||
&cfg->custom_data))
|
||||
return -EFAULT;
|
||||
|
||||
ret = adf_file_ioctl(file, ADF_POST_CONFIG, (unsigned long)cfg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (copy_in_user(&arg->complete_fence, &cfg->complete_fence,
|
||||
sizeof(cfg->complete_fence)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long adf_compat_get_device_data(struct file *file,
|
||||
struct adf_device_data32 __user *arg)
|
||||
{
|
||||
struct adf_device_data32 data32;
|
||||
struct adf_device_data __user *data;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&data32, arg, sizeof(data32)))
|
||||
return -EFAULT;
|
||||
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (!access_ok(VERIFY_WRITE, data, sizeof(*data)))
|
||||
return -EFAULT;
|
||||
|
||||
if (put_user(data32.n_attachments, &data->n_attachments) ||
|
||||
put_user(compat_ptr(data32.attachments),
|
||||
&data->attachments) ||
|
||||
put_user(data32.n_allowed_attachments,
|
||||
&data->n_allowed_attachments) ||
|
||||
put_user(compat_ptr(data32.allowed_attachments),
|
||||
&data->allowed_attachments) ||
|
||||
put_user(data32.custom_data_size,
|
||||
&data->custom_data_size) ||
|
||||
put_user(compat_ptr(data32.custom_data),
|
||||
&data->custom_data))
|
||||
return -EFAULT;
|
||||
|
||||
ret = adf_file_ioctl(file, ADF_GET_DEVICE_DATA32, (unsigned long)data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (copy_in_user(arg->name, data->name, sizeof(arg->name)) ||
|
||||
copy_in_user(&arg->n_attachments, &data->n_attachments,
|
||||
sizeof(arg->n_attachments)) ||
|
||||
copy_in_user(&arg->n_allowed_attachments,
|
||||
&data->n_allowed_attachments,
|
||||
sizeof(arg->n_allowed_attachments)) ||
|
||||
copy_in_user(&arg->custom_data_size,
|
||||
&data->custom_data_size,
|
||||
sizeof(arg->custom_data_size)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long adf_compat_get_interface_data(struct file *file,
|
||||
struct adf_interface_data32 __user *arg)
|
||||
{
|
||||
struct adf_interface_data32 data32;
|
||||
struct adf_interface_data __user *data;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&data32, arg, sizeof(data32)))
|
||||
return -EFAULT;
|
||||
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (!access_ok(VERIFY_WRITE, data, sizeof(*data)))
|
||||
return -EFAULT;
|
||||
|
||||
if (put_user(data32.n_available_modes, &data->n_available_modes) ||
|
||||
put_user(compat_ptr(data32.available_modes),
|
||||
&data->available_modes) ||
|
||||
put_user(data32.custom_data_size,
|
||||
&data->custom_data_size) ||
|
||||
put_user(compat_ptr(data32.custom_data),
|
||||
&data->custom_data))
|
||||
return -EFAULT;
|
||||
|
||||
ret = adf_file_ioctl(file, ADF_GET_DEVICE_DATA32, (unsigned long)data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (copy_in_user(arg->name, data->name, sizeof(arg->name)) ||
|
||||
copy_in_user(&arg->type, &data->type,
|
||||
sizeof(arg->type)) ||
|
||||
copy_in_user(&arg->id, &data->id, sizeof(arg->id)) ||
|
||||
copy_in_user(&arg->flags, &data->flags,
|
||||
sizeof(arg->flags)) ||
|
||||
copy_in_user(&arg->dpms_state, &data->dpms_state,
|
||||
sizeof(arg->dpms_state)) ||
|
||||
copy_in_user(&arg->hotplug_detect,
|
||||
&data->hotplug_detect,
|
||||
sizeof(arg->hotplug_detect)) ||
|
||||
copy_in_user(&arg->width_mm, &data->width_mm,
|
||||
sizeof(arg->width_mm)) ||
|
||||
copy_in_user(&arg->height_mm, &data->height_mm,
|
||||
sizeof(arg->height_mm)) ||
|
||||
copy_in_user(&arg->current_mode, &data->current_mode,
|
||||
sizeof(arg->current_mode)) ||
|
||||
copy_in_user(&arg->n_available_modes,
|
||||
&data->n_available_modes,
|
||||
sizeof(arg->n_available_modes)) ||
|
||||
copy_in_user(&arg->custom_data_size,
|
||||
&data->custom_data_size,
|
||||
sizeof(arg->custom_data_size)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long adf_compat_get_overlay_engine_data(struct file *file,
|
||||
struct adf_overlay_engine_data32 __user *arg)
|
||||
{
|
||||
struct adf_overlay_engine_data32 data32;
|
||||
struct adf_overlay_engine_data __user *data;
|
||||
int ret;
|
||||
|
||||
if (copy_from_user(&data32, arg, sizeof(data32)))
|
||||
return -EFAULT;
|
||||
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (!access_ok(VERIFY_WRITE, data, sizeof(*data)))
|
||||
return -EFAULT;
|
||||
|
||||
if (put_user(data32.n_supported_formats, &data->n_supported_formats) ||
|
||||
put_user(compat_ptr(data32.supported_formats),
|
||||
&data->supported_formats) ||
|
||||
put_user(data32.custom_data_size,
|
||||
&data->custom_data_size) ||
|
||||
put_user(compat_ptr(data32.custom_data),
|
||||
&data->custom_data))
|
||||
return -EFAULT;
|
||||
|
||||
ret = adf_file_ioctl(file, ADF_GET_OVERLAY_ENGINE_DATA,
|
||||
(unsigned long)data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (copy_in_user(arg->name, data->name, sizeof(arg->name)) ||
|
||||
copy_in_user(&arg->n_supported_formats,
|
||||
&data->n_supported_formats,
|
||||
sizeof(arg->n_supported_formats)) ||
|
||||
copy_in_user(&arg->custom_data_size,
|
||||
&data->custom_data_size,
|
||||
sizeof(arg->custom_data_size)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long adf_file_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case ADF_POST_CONFIG32:
|
||||
return adf_compat_post_config(file, compat_ptr(arg));
|
||||
|
||||
case ADF_GET_DEVICE_DATA32:
|
||||
return adf_compat_get_device_data(file, compat_ptr(arg));
|
||||
|
||||
case ADF_GET_INTERFACE_DATA32:
|
||||
return adf_compat_get_interface_data(file, compat_ptr(arg));
|
||||
|
||||
case ADF_GET_OVERLAY_ENGINE_DATA32:
|
||||
return adf_compat_get_overlay_engine_data(file,
|
||||
compat_ptr(arg));
|
||||
|
||||
default:
|
||||
return adf_file_ioctl(file, cmd, arg);
|
||||
}
|
||||
}
|
||||
78
drivers/video/adf/adf_fops32.h
Normal file
78
drivers/video/adf/adf_fops32.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef __VIDEO_ADF_ADF_FOPS32_H
|
||||
#define __VIDEO_ADF_ADF_FOPS32_H
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#include <video/adf.h>
|
||||
|
||||
#define ADF_POST_CONFIG32 \
|
||||
_IOW('D', 2, struct adf_post_config32)
|
||||
#define ADF_GET_DEVICE_DATA32 \
|
||||
_IOR('D', 4, struct adf_device_data32)
|
||||
#define ADF_GET_INTERFACE_DATA32 \
|
||||
_IOR('D', 5, struct adf_interface_data32)
|
||||
#define ADF_GET_OVERLAY_ENGINE_DATA32 \
|
||||
_IOR('D', 6, struct adf_overlay_engine_data32)
|
||||
|
||||
struct adf_post_config32 {
|
||||
compat_size_t n_interfaces;
|
||||
compat_uptr_t interfaces;
|
||||
|
||||
compat_size_t n_bufs;
|
||||
compat_uptr_t bufs;
|
||||
|
||||
compat_size_t custom_data_size;
|
||||
compat_uptr_t custom_data;
|
||||
|
||||
__s64 complete_fence;
|
||||
};
|
||||
|
||||
struct adf_device_data32 {
|
||||
char name[ADF_NAME_LEN];
|
||||
|
||||
compat_size_t n_attachments;
|
||||
compat_uptr_t attachments;
|
||||
|
||||
compat_size_t n_allowed_attachments;
|
||||
compat_uptr_t allowed_attachments;
|
||||
|
||||
compat_size_t custom_data_size;
|
||||
compat_uptr_t custom_data;
|
||||
};
|
||||
|
||||
struct adf_interface_data32 {
|
||||
char name[ADF_NAME_LEN];
|
||||
|
||||
__u8 type;
|
||||
__u32 id;
|
||||
/* e.g. type=ADF_INTF_TYPE_DSI, id=1 => DSI.1 */
|
||||
__u32 flags;
|
||||
|
||||
__u8 dpms_state;
|
||||
__u8 hotplug_detect;
|
||||
__u16 width_mm;
|
||||
__u16 height_mm;
|
||||
|
||||
struct drm_mode_modeinfo current_mode;
|
||||
compat_size_t n_available_modes;
|
||||
compat_uptr_t available_modes;
|
||||
|
||||
compat_size_t custom_data_size;
|
||||
compat_uptr_t custom_data;
|
||||
};
|
||||
|
||||
struct adf_overlay_engine_data32 {
|
||||
char name[ADF_NAME_LEN];
|
||||
|
||||
compat_size_t n_supported_formats;
|
||||
compat_uptr_t supported_formats;
|
||||
|
||||
compat_size_t custom_data_size;
|
||||
compat_uptr_t custom_data;
|
||||
};
|
||||
|
||||
long adf_file_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg);
|
||||
|
||||
#endif /* __VIDEO_ADF_ADF_FOPS32_H */
|
||||
280
drivers/video/adf/adf_format.c
Normal file
280
drivers/video/adf/adf_format.c
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
* modified from drivers/gpu/drm/drm_crtc.c
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <drm/drm_fourcc.h>
|
||||
#include <video/adf_format.h>
|
||||
|
||||
bool adf_format_is_standard(u32 format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_C8:
|
||||
case DRM_FORMAT_RGB332:
|
||||
case DRM_FORMAT_BGR233:
|
||||
case DRM_FORMAT_XRGB4444:
|
||||
case DRM_FORMAT_XBGR4444:
|
||||
case DRM_FORMAT_RGBX4444:
|
||||
case DRM_FORMAT_BGRX4444:
|
||||
case DRM_FORMAT_ARGB4444:
|
||||
case DRM_FORMAT_ABGR4444:
|
||||
case DRM_FORMAT_RGBA4444:
|
||||
case DRM_FORMAT_BGRA4444:
|
||||
case DRM_FORMAT_XRGB1555:
|
||||
case DRM_FORMAT_XBGR1555:
|
||||
case DRM_FORMAT_RGBX5551:
|
||||
case DRM_FORMAT_BGRX5551:
|
||||
case DRM_FORMAT_ARGB1555:
|
||||
case DRM_FORMAT_ABGR1555:
|
||||
case DRM_FORMAT_RGBA5551:
|
||||
case DRM_FORMAT_BGRA5551:
|
||||
case DRM_FORMAT_RGB565:
|
||||
case DRM_FORMAT_BGR565:
|
||||
case DRM_FORMAT_RGB888:
|
||||
case DRM_FORMAT_BGR888:
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_XBGR8888:
|
||||
case DRM_FORMAT_RGBX8888:
|
||||
case DRM_FORMAT_BGRX8888:
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
case DRM_FORMAT_ABGR8888:
|
||||
case DRM_FORMAT_RGBA8888:
|
||||
case DRM_FORMAT_BGRA8888:
|
||||
case DRM_FORMAT_XRGB2101010:
|
||||
case DRM_FORMAT_XBGR2101010:
|
||||
case DRM_FORMAT_RGBX1010102:
|
||||
case DRM_FORMAT_BGRX1010102:
|
||||
case DRM_FORMAT_ARGB2101010:
|
||||
case DRM_FORMAT_ABGR2101010:
|
||||
case DRM_FORMAT_RGBA1010102:
|
||||
case DRM_FORMAT_BGRA1010102:
|
||||
case DRM_FORMAT_YUYV:
|
||||
case DRM_FORMAT_YVYU:
|
||||
case DRM_FORMAT_UYVY:
|
||||
case DRM_FORMAT_VYUY:
|
||||
case DRM_FORMAT_AYUV:
|
||||
case DRM_FORMAT_NV12:
|
||||
case DRM_FORMAT_NV21:
|
||||
case DRM_FORMAT_NV16:
|
||||
case DRM_FORMAT_NV61:
|
||||
case DRM_FORMAT_YUV410:
|
||||
case DRM_FORMAT_YVU410:
|
||||
case DRM_FORMAT_YUV411:
|
||||
case DRM_FORMAT_YVU411:
|
||||
case DRM_FORMAT_YUV420:
|
||||
case DRM_FORMAT_YVU420:
|
||||
case DRM_FORMAT_YUV422:
|
||||
case DRM_FORMAT_YVU422:
|
||||
case DRM_FORMAT_YUV444:
|
||||
case DRM_FORMAT_YVU444:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(adf_format_is_standard);
|
||||
|
||||
bool adf_format_is_rgb(u32 format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_C8:
|
||||
case DRM_FORMAT_RGB332:
|
||||
case DRM_FORMAT_BGR233:
|
||||
case DRM_FORMAT_XRGB1555:
|
||||
case DRM_FORMAT_XBGR1555:
|
||||
case DRM_FORMAT_RGBX5551:
|
||||
case DRM_FORMAT_BGRX5551:
|
||||
case DRM_FORMAT_ARGB1555:
|
||||
case DRM_FORMAT_ABGR1555:
|
||||
case DRM_FORMAT_RGBA5551:
|
||||
case DRM_FORMAT_BGRA5551:
|
||||
case DRM_FORMAT_RGB565:
|
||||
case DRM_FORMAT_BGR565:
|
||||
case DRM_FORMAT_RGB888:
|
||||
case DRM_FORMAT_BGR888:
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_XBGR8888:
|
||||
case DRM_FORMAT_RGBX8888:
|
||||
case DRM_FORMAT_BGRX8888:
|
||||
case DRM_FORMAT_XRGB2101010:
|
||||
case DRM_FORMAT_XBGR2101010:
|
||||
case DRM_FORMAT_RGBX1010102:
|
||||
case DRM_FORMAT_BGRX1010102:
|
||||
case DRM_FORMAT_ARGB2101010:
|
||||
case DRM_FORMAT_ABGR2101010:
|
||||
case DRM_FORMAT_RGBA1010102:
|
||||
case DRM_FORMAT_BGRA1010102:
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
case DRM_FORMAT_ABGR8888:
|
||||
case DRM_FORMAT_RGBA8888:
|
||||
case DRM_FORMAT_BGRA8888:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(adf_format_is_rgb);
|
||||
|
||||
u8 adf_format_num_planes(u32 format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_YUV410:
|
||||
case DRM_FORMAT_YVU410:
|
||||
case DRM_FORMAT_YUV411:
|
||||
case DRM_FORMAT_YVU411:
|
||||
case DRM_FORMAT_YUV420:
|
||||
case DRM_FORMAT_YVU420:
|
||||
case DRM_FORMAT_YUV422:
|
||||
case DRM_FORMAT_YVU422:
|
||||
case DRM_FORMAT_YUV444:
|
||||
case DRM_FORMAT_YVU444:
|
||||
return 3;
|
||||
case DRM_FORMAT_NV12:
|
||||
case DRM_FORMAT_NV21:
|
||||
case DRM_FORMAT_NV16:
|
||||
case DRM_FORMAT_NV61:
|
||||
return 2;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(adf_format_num_planes);
|
||||
|
||||
u8 adf_format_bpp(u32 format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_C8:
|
||||
case DRM_FORMAT_RGB332:
|
||||
case DRM_FORMAT_BGR233:
|
||||
return 8;
|
||||
|
||||
case DRM_FORMAT_XRGB1555:
|
||||
case DRM_FORMAT_XBGR1555:
|
||||
case DRM_FORMAT_RGBX5551:
|
||||
case DRM_FORMAT_BGRX5551:
|
||||
case DRM_FORMAT_ARGB1555:
|
||||
case DRM_FORMAT_ABGR1555:
|
||||
case DRM_FORMAT_RGBA5551:
|
||||
case DRM_FORMAT_BGRA5551:
|
||||
case DRM_FORMAT_RGB565:
|
||||
case DRM_FORMAT_BGR565:
|
||||
return 16;
|
||||
|
||||
case DRM_FORMAT_RGB888:
|
||||
case DRM_FORMAT_BGR888:
|
||||
return 24;
|
||||
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_XBGR8888:
|
||||
case DRM_FORMAT_RGBX8888:
|
||||
case DRM_FORMAT_BGRX8888:
|
||||
case DRM_FORMAT_XRGB2101010:
|
||||
case DRM_FORMAT_XBGR2101010:
|
||||
case DRM_FORMAT_RGBX1010102:
|
||||
case DRM_FORMAT_BGRX1010102:
|
||||
case DRM_FORMAT_ARGB2101010:
|
||||
case DRM_FORMAT_ABGR2101010:
|
||||
case DRM_FORMAT_RGBA1010102:
|
||||
case DRM_FORMAT_BGRA1010102:
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
case DRM_FORMAT_ABGR8888:
|
||||
case DRM_FORMAT_RGBA8888:
|
||||
case DRM_FORMAT_BGRA8888:
|
||||
return 32;
|
||||
|
||||
default:
|
||||
pr_debug("%s: unsupported pixel format %u\n", __func__, format);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(adf_format_bpp);
|
||||
|
||||
u8 adf_format_plane_cpp(u32 format, int plane)
|
||||
{
|
||||
if (plane >= adf_format_num_planes(format))
|
||||
return 0;
|
||||
|
||||
switch (format) {
|
||||
case DRM_FORMAT_YUYV:
|
||||
case DRM_FORMAT_YVYU:
|
||||
case DRM_FORMAT_UYVY:
|
||||
case DRM_FORMAT_VYUY:
|
||||
return 2;
|
||||
case DRM_FORMAT_NV12:
|
||||
case DRM_FORMAT_NV21:
|
||||
case DRM_FORMAT_NV16:
|
||||
case DRM_FORMAT_NV61:
|
||||
return plane ? 2 : 1;
|
||||
case DRM_FORMAT_YUV410:
|
||||
case DRM_FORMAT_YVU410:
|
||||
case DRM_FORMAT_YUV411:
|
||||
case DRM_FORMAT_YVU411:
|
||||
case DRM_FORMAT_YUV420:
|
||||
case DRM_FORMAT_YVU420:
|
||||
case DRM_FORMAT_YUV422:
|
||||
case DRM_FORMAT_YVU422:
|
||||
case DRM_FORMAT_YUV444:
|
||||
case DRM_FORMAT_YVU444:
|
||||
return 1;
|
||||
default:
|
||||
return adf_format_bpp(format) / 8;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(adf_format_plane_cpp);
|
||||
|
||||
u8 adf_format_horz_chroma_subsampling(u32 format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_YUV411:
|
||||
case DRM_FORMAT_YVU411:
|
||||
case DRM_FORMAT_YUV410:
|
||||
case DRM_FORMAT_YVU410:
|
||||
return 4;
|
||||
case DRM_FORMAT_YUYV:
|
||||
case DRM_FORMAT_YVYU:
|
||||
case DRM_FORMAT_UYVY:
|
||||
case DRM_FORMAT_VYUY:
|
||||
case DRM_FORMAT_NV12:
|
||||
case DRM_FORMAT_NV21:
|
||||
case DRM_FORMAT_NV16:
|
||||
case DRM_FORMAT_NV61:
|
||||
case DRM_FORMAT_YUV422:
|
||||
case DRM_FORMAT_YVU422:
|
||||
case DRM_FORMAT_YUV420:
|
||||
case DRM_FORMAT_YVU420:
|
||||
return 2;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(adf_format_horz_chroma_subsampling);
|
||||
|
||||
u8 adf_format_vert_chroma_subsampling(u32 format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_YUV410:
|
||||
case DRM_FORMAT_YVU410:
|
||||
return 4;
|
||||
case DRM_FORMAT_YUV420:
|
||||
case DRM_FORMAT_YVU420:
|
||||
case DRM_FORMAT_NV12:
|
||||
case DRM_FORMAT_NV21:
|
||||
return 2;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(adf_format_vert_chroma_subsampling);
|
||||
149
drivers/video/adf/adf_memblock.c
Normal file
149
drivers/video/adf/adf_memblock.c
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct adf_memblock_pdata {
|
||||
phys_addr_t base;
|
||||
};
|
||||
|
||||
static struct sg_table *adf_memblock_map(struct dma_buf_attachment *attach,
|
||||
enum dma_data_direction direction)
|
||||
{
|
||||
struct adf_memblock_pdata *pdata = attach->dmabuf->priv;
|
||||
unsigned long pfn = PFN_DOWN(pdata->base);
|
||||
struct page *page = pfn_to_page(pfn);
|
||||
struct sg_table *table;
|
||||
int ret;
|
||||
|
||||
table = kzalloc(sizeof(*table), GFP_KERNEL);
|
||||
if (!table)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = sg_alloc_table(table, 1, GFP_KERNEL);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
sg_set_page(table->sgl, page, attach->dmabuf->size, 0);
|
||||
return table;
|
||||
|
||||
err:
|
||||
kfree(table);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static void adf_memblock_unmap(struct dma_buf_attachment *attach,
|
||||
struct sg_table *table, enum dma_data_direction direction)
|
||||
{
|
||||
sg_free_table(table);
|
||||
}
|
||||
|
||||
static void __init_memblock adf_memblock_release(struct dma_buf *buf)
|
||||
{
|
||||
struct adf_memblock_pdata *pdata = buf->priv;
|
||||
int err = memblock_free(pdata->base, buf->size);
|
||||
|
||||
if (err < 0)
|
||||
pr_warn("%s: freeing memblock failed: %d\n", __func__, err);
|
||||
kfree(pdata);
|
||||
}
|
||||
|
||||
static void *adf_memblock_do_kmap(struct dma_buf *buf, unsigned long pgoffset,
|
||||
bool atomic)
|
||||
{
|
||||
struct adf_memblock_pdata *pdata = buf->priv;
|
||||
unsigned long pfn = PFN_DOWN(pdata->base) + pgoffset;
|
||||
struct page *page = pfn_to_page(pfn);
|
||||
|
||||
if (atomic)
|
||||
return kmap_atomic(page);
|
||||
else
|
||||
return kmap(page);
|
||||
}
|
||||
|
||||
static void *adf_memblock_kmap_atomic(struct dma_buf *buf,
|
||||
unsigned long pgoffset)
|
||||
{
|
||||
return adf_memblock_do_kmap(buf, pgoffset, true);
|
||||
}
|
||||
|
||||
static void adf_memblock_kunmap_atomic(struct dma_buf *buf,
|
||||
unsigned long pgoffset, void *vaddr)
|
||||
{
|
||||
kunmap_atomic(vaddr);
|
||||
}
|
||||
|
||||
static void *adf_memblock_kmap(struct dma_buf *buf, unsigned long pgoffset)
|
||||
{
|
||||
return adf_memblock_do_kmap(buf, pgoffset, false);
|
||||
}
|
||||
|
||||
static void adf_memblock_kunmap(struct dma_buf *buf, unsigned long pgoffset,
|
||||
void *vaddr)
|
||||
{
|
||||
kunmap(vaddr);
|
||||
}
|
||||
|
||||
static int adf_memblock_mmap(struct dma_buf *buf, struct vm_area_struct *vma)
|
||||
{
|
||||
struct adf_memblock_pdata *pdata = buf->priv;
|
||||
|
||||
return remap_pfn_range(vma, vma->vm_start, PFN_DOWN(pdata->base),
|
||||
vma->vm_end - vma->vm_start, vma->vm_page_prot);
|
||||
}
|
||||
|
||||
struct dma_buf_ops adf_memblock_ops = {
|
||||
.map_dma_buf = adf_memblock_map,
|
||||
.unmap_dma_buf = adf_memblock_unmap,
|
||||
.release = adf_memblock_release,
|
||||
.kmap_atomic = adf_memblock_kmap_atomic,
|
||||
.kunmap_atomic = adf_memblock_kunmap_atomic,
|
||||
.kmap = adf_memblock_kmap,
|
||||
.kunmap = adf_memblock_kunmap,
|
||||
.mmap = adf_memblock_mmap,
|
||||
};
|
||||
|
||||
/**
|
||||
* adf_memblock_export - export a memblock reserved area as a dma-buf
|
||||
*
|
||||
* @base: base physical address
|
||||
* @size: memblock size
|
||||
* @flags: mode flags for the dma-buf's file
|
||||
*
|
||||
* @base and @size must be page-aligned.
|
||||
*
|
||||
* Returns a dma-buf on success or ERR_PTR(-errno) on failure.
|
||||
*/
|
||||
struct dma_buf *adf_memblock_export(phys_addr_t base, size_t size, int flags)
|
||||
{
|
||||
struct adf_memblock_pdata *pdata;
|
||||
struct dma_buf *buf;
|
||||
|
||||
if (PAGE_ALIGN(base) != base || PAGE_ALIGN(size) != size)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pdata->base = base;
|
||||
buf = dma_buf_export(pdata, &adf_memblock_ops, size, flags);
|
||||
if (IS_ERR(buf))
|
||||
kfree(pdata);
|
||||
|
||||
return buf;
|
||||
}
|
||||
302
drivers/video/adf/adf_sysfs.c
Normal file
302
drivers/video/adf/adf_sysfs.c
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <video/adf_client.h>
|
||||
|
||||
#include "adf.h"
|
||||
#include "adf_fops.h"
|
||||
#include "adf_sysfs.h"
|
||||
|
||||
static struct class *adf_class;
|
||||
static int adf_major;
|
||||
static DEFINE_IDR(adf_minors);
|
||||
|
||||
#define dev_to_adf_interface(p) \
|
||||
adf_obj_to_interface(container_of(p, struct adf_obj, dev))
|
||||
|
||||
static ssize_t dpms_state_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adf_interface *intf = dev_to_adf_interface(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n",
|
||||
adf_interface_dpms_state(intf));
|
||||
}
|
||||
|
||||
static ssize_t dpms_state_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct adf_interface *intf = dev_to_adf_interface(dev);
|
||||
u8 dpms_state;
|
||||
int err;
|
||||
|
||||
err = kstrtou8(buf, 0, &dpms_state);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = adf_interface_blank(intf, dpms_state);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t current_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adf_interface *intf = dev_to_adf_interface(dev);
|
||||
struct drm_mode_modeinfo mode;
|
||||
|
||||
adf_interface_current_mode(intf, &mode);
|
||||
|
||||
if (mode.name[0]) {
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", mode.name);
|
||||
} else {
|
||||
bool interlaced = !!(mode.flags & DRM_MODE_FLAG_INTERLACE);
|
||||
return scnprintf(buf, PAGE_SIZE, "%ux%u%s\n", mode.hdisplay,
|
||||
mode.vdisplay, interlaced ? "i" : "");
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t type_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct adf_interface *intf = dev_to_adf_interface(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n",
|
||||
adf_interface_type_str(intf));
|
||||
}
|
||||
|
||||
static ssize_t vsync_timestamp_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adf_interface *intf = dev_to_adf_interface(dev);
|
||||
ktime_t timestamp;
|
||||
unsigned long flags;
|
||||
|
||||
read_lock_irqsave(&intf->vsync_lock, flags);
|
||||
memcpy(×tamp, &intf->vsync_timestamp, sizeof(timestamp));
|
||||
read_unlock_irqrestore(&intf->vsync_lock, flags);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%llu\n", ktime_to_ns(timestamp));
|
||||
}
|
||||
|
||||
static ssize_t hotplug_detect_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct adf_interface *intf = dev_to_adf_interface(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", intf->hotplug_detect);
|
||||
}
|
||||
|
||||
static struct device_attribute adf_interface_attrs[] = {
|
||||
__ATTR(dpms_state, S_IRUGO|S_IWUSR, dpms_state_show, dpms_state_store),
|
||||
__ATTR_RO(current_mode),
|
||||
__ATTR_RO(hotplug_detect),
|
||||
__ATTR_RO(type),
|
||||
__ATTR_RO(vsync_timestamp),
|
||||
};
|
||||
|
||||
static char *adf_devnode(struct device *dev, umode_t *mode)
|
||||
{
|
||||
return kasprintf(GFP_KERNEL, "adf/%s", dev_name(dev));
|
||||
}
|
||||
|
||||
int adf_obj_sysfs_init(struct adf_obj *obj, struct device *parent)
|
||||
{
|
||||
int ret = idr_alloc(&adf_minors, obj, 0, 0, GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: allocating adf minor failed: %d\n", __func__,
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
obj->minor = ret;
|
||||
obj->dev.parent = parent;
|
||||
obj->dev.class = adf_class;
|
||||
obj->dev.devt = MKDEV(adf_major, obj->minor);
|
||||
|
||||
ret = device_register(&obj->dev);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: registering adf object failed: %d\n", __func__,
|
||||
ret);
|
||||
goto err_device_register;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_device_register:
|
||||
idr_remove(&adf_minors, obj->minor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *adf_device_devnode(struct device *dev, umode_t *mode,
|
||||
kuid_t *uid, kgid_t *gid)
|
||||
{
|
||||
struct adf_obj *obj = container_of(dev, struct adf_obj, dev);
|
||||
return kasprintf(GFP_KERNEL, "adf/%s/device", obj->name);
|
||||
}
|
||||
|
||||
static char *adf_interface_devnode(struct device *dev, umode_t *mode,
|
||||
kuid_t *uid, kgid_t *gid)
|
||||
{
|
||||
struct adf_obj *obj = container_of(dev, struct adf_obj, dev);
|
||||
struct adf_interface *intf = adf_obj_to_interface(obj);
|
||||
struct adf_device *parent = adf_interface_parent(intf);
|
||||
return kasprintf(GFP_KERNEL, "adf/%s/interface%d",
|
||||
parent->base.name, intf->base.id);
|
||||
}
|
||||
|
||||
static char *adf_overlay_engine_devnode(struct device *dev, umode_t *mode,
|
||||
kuid_t *uid, kgid_t *gid)
|
||||
{
|
||||
struct adf_obj *obj = container_of(dev, struct adf_obj, dev);
|
||||
struct adf_overlay_engine *eng = adf_obj_to_overlay_engine(obj);
|
||||
struct adf_device *parent = adf_overlay_engine_parent(eng);
|
||||
return kasprintf(GFP_KERNEL, "adf/%s/overlay-engine%d",
|
||||
parent->base.name, eng->base.id);
|
||||
}
|
||||
|
||||
static void adf_noop_release(struct device *dev)
|
||||
{
|
||||
}
|
||||
|
||||
static struct device_type adf_device_type = {
|
||||
.name = "adf_device",
|
||||
.devnode = adf_device_devnode,
|
||||
.release = adf_noop_release,
|
||||
};
|
||||
|
||||
static struct device_type adf_interface_type = {
|
||||
.name = "adf_interface",
|
||||
.devnode = adf_interface_devnode,
|
||||
.release = adf_noop_release,
|
||||
};
|
||||
|
||||
static struct device_type adf_overlay_engine_type = {
|
||||
.name = "adf_overlay_engine",
|
||||
.devnode = adf_overlay_engine_devnode,
|
||||
.release = adf_noop_release,
|
||||
};
|
||||
|
||||
int adf_device_sysfs_init(struct adf_device *dev)
|
||||
{
|
||||
dev->base.dev.type = &adf_device_type;
|
||||
dev_set_name(&dev->base.dev, "%s", dev->base.name);
|
||||
return adf_obj_sysfs_init(&dev->base, dev->dev);
|
||||
}
|
||||
|
||||
int adf_interface_sysfs_init(struct adf_interface *intf)
|
||||
{
|
||||
struct adf_device *parent = adf_interface_parent(intf);
|
||||
size_t i, j;
|
||||
int ret;
|
||||
|
||||
intf->base.dev.type = &adf_interface_type;
|
||||
dev_set_name(&intf->base.dev, "%s-interface%d", parent->base.name,
|
||||
intf->base.id);
|
||||
|
||||
ret = adf_obj_sysfs_init(&intf->base, &parent->base.dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++) {
|
||||
ret = device_create_file(&intf->base.dev,
|
||||
&adf_interface_attrs[i]);
|
||||
if (ret < 0) {
|
||||
dev_err(&intf->base.dev, "creating sysfs attribute %s failed: %d\n",
|
||||
adf_interface_attrs[i].attr.name, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
for (j = 0; j < i; j++)
|
||||
device_remove_file(&intf->base.dev, &adf_interface_attrs[j]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int adf_overlay_engine_sysfs_init(struct adf_overlay_engine *eng)
|
||||
{
|
||||
struct adf_device *parent = adf_overlay_engine_parent(eng);
|
||||
|
||||
eng->base.dev.type = &adf_overlay_engine_type;
|
||||
dev_set_name(&eng->base.dev, "%s-overlay-engine%d", parent->base.name,
|
||||
eng->base.id);
|
||||
|
||||
return adf_obj_sysfs_init(&eng->base, &parent->base.dev);
|
||||
}
|
||||
|
||||
struct adf_obj *adf_obj_sysfs_find(int minor)
|
||||
{
|
||||
return idr_find(&adf_minors, minor);
|
||||
}
|
||||
|
||||
void adf_obj_sysfs_destroy(struct adf_obj *obj)
|
||||
{
|
||||
idr_remove(&adf_minors, obj->minor);
|
||||
device_unregister(&obj->dev);
|
||||
}
|
||||
|
||||
void adf_device_sysfs_destroy(struct adf_device *dev)
|
||||
{
|
||||
adf_obj_sysfs_destroy(&dev->base);
|
||||
}
|
||||
|
||||
void adf_interface_sysfs_destroy(struct adf_interface *intf)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++)
|
||||
device_remove_file(&intf->base.dev, &adf_interface_attrs[i]);
|
||||
adf_obj_sysfs_destroy(&intf->base);
|
||||
}
|
||||
|
||||
void adf_overlay_engine_sysfs_destroy(struct adf_overlay_engine *eng)
|
||||
{
|
||||
adf_obj_sysfs_destroy(&eng->base);
|
||||
}
|
||||
|
||||
int adf_sysfs_init(void)
|
||||
{
|
||||
struct class *class;
|
||||
int ret;
|
||||
|
||||
class = class_create(THIS_MODULE, "adf");
|
||||
if (IS_ERR(class)) {
|
||||
ret = PTR_ERR(class);
|
||||
pr_err("%s: creating class failed: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = register_chrdev(0, "adf", &adf_fops);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: registering device failed: %d\n", __func__, ret);
|
||||
goto err_chrdev;
|
||||
}
|
||||
|
||||
class->devnode = adf_devnode;
|
||||
adf_class = class;
|
||||
adf_major = ret;
|
||||
return 0;
|
||||
|
||||
err_chrdev:
|
||||
class_destroy(adf_class);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void adf_sysfs_destroy(void)
|
||||
{
|
||||
idr_destroy(&adf_minors);
|
||||
class_destroy(adf_class);
|
||||
}
|
||||
33
drivers/video/adf/adf_sysfs.h
Normal file
33
drivers/video/adf/adf_sysfs.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __VIDEO_ADF_ADF_SYSFS_H
|
||||
#define __VIDEO_ADF_ADF_SYSFS_H
|
||||
|
||||
struct adf_device;
|
||||
struct adf_interface;
|
||||
struct adf_overlay_engine;
|
||||
|
||||
int adf_device_sysfs_init(struct adf_device *dev);
|
||||
void adf_device_sysfs_destroy(struct adf_device *dev);
|
||||
int adf_interface_sysfs_init(struct adf_interface *intf);
|
||||
void adf_interface_sysfs_destroy(struct adf_interface *intf);
|
||||
int adf_overlay_engine_sysfs_init(struct adf_overlay_engine *eng);
|
||||
void adf_overlay_engine_sysfs_destroy(struct adf_overlay_engine *eng);
|
||||
struct adf_obj *adf_obj_sysfs_find(int minor);
|
||||
|
||||
int adf_sysfs_init(void);
|
||||
void adf_sysfs_destroy(void);
|
||||
|
||||
#endif /* __VIDEO_ADF_ADF_SYSFS_H */
|
||||
93
drivers/video/adf/adf_trace.h
Normal file
93
drivers/video/adf/adf_trace.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM adf
|
||||
|
||||
#if !defined(__VIDEO_ADF_ADF_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define __VIDEO_ADF_ADF_TRACE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
#include <video/adf.h>
|
||||
|
||||
TRACE_EVENT(adf_event,
|
||||
TP_PROTO(struct adf_obj *obj, enum adf_event_type type),
|
||||
TP_ARGS(obj, type),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__string(name, obj->name)
|
||||
__field(enum adf_event_type, type)
|
||||
__array(char, type_str, 32)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name, obj->name);
|
||||
__entry->type = type;
|
||||
strlcpy(__entry->type_str, adf_event_type_str(obj, type),
|
||||
sizeof(__entry->type_str));
|
||||
),
|
||||
TP_printk("obj=%s type=%u (%s)",
|
||||
__get_str(name),
|
||||
__entry->type,
|
||||
__entry->type_str)
|
||||
);
|
||||
|
||||
TRACE_EVENT(adf_event_enable,
|
||||
TP_PROTO(struct adf_obj *obj, enum adf_event_type type),
|
||||
TP_ARGS(obj, type),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__string(name, obj->name)
|
||||
__field(enum adf_event_type, type)
|
||||
__array(char, type_str, 32)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name, obj->name);
|
||||
__entry->type = type;
|
||||
strlcpy(__entry->type_str, adf_event_type_str(obj, type),
|
||||
sizeof(__entry->type_str));
|
||||
),
|
||||
TP_printk("obj=%s type=%u (%s)",
|
||||
__get_str(name),
|
||||
__entry->type,
|
||||
__entry->type_str)
|
||||
);
|
||||
|
||||
TRACE_EVENT(adf_event_disable,
|
||||
TP_PROTO(struct adf_obj *obj, enum adf_event_type type),
|
||||
TP_ARGS(obj, type),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__string(name, obj->name)
|
||||
__field(enum adf_event_type, type)
|
||||
__array(char, type_str, 32)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name, obj->name);
|
||||
__entry->type = type;
|
||||
strlcpy(__entry->type_str, adf_event_type_str(obj, type),
|
||||
sizeof(__entry->type_str));
|
||||
),
|
||||
TP_printk("obj=%s type=%u (%s)",
|
||||
__get_str(name),
|
||||
__entry->type,
|
||||
__entry->type_str)
|
||||
);
|
||||
|
||||
#endif /* __VIDEO_ADF_ADF_TRACE_H */
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#define TRACE_INCLUDE_FILE adf_trace
|
||||
#include <trace/define_trace.h>
|
||||
@@ -134,6 +134,56 @@ static void release_task_mempolicy(struct proc_maps_private *priv)
|
||||
}
|
||||
#endif
|
||||
|
||||
static void seq_print_vma_name(struct seq_file *m, struct vm_area_struct *vma)
|
||||
{
|
||||
const char __user *name = vma_get_anon_name(vma);
|
||||
struct mm_struct *mm = vma->vm_mm;
|
||||
|
||||
unsigned long page_start_vaddr;
|
||||
unsigned long page_offset;
|
||||
unsigned long num_pages;
|
||||
unsigned long max_len = NAME_MAX;
|
||||
int i;
|
||||
|
||||
page_start_vaddr = (unsigned long)name & PAGE_MASK;
|
||||
page_offset = (unsigned long)name - page_start_vaddr;
|
||||
num_pages = DIV_ROUND_UP(page_offset + max_len, PAGE_SIZE);
|
||||
|
||||
seq_puts(m, "[anon:");
|
||||
|
||||
for (i = 0; i < num_pages; i++) {
|
||||
int len;
|
||||
int write_len;
|
||||
const char *kaddr;
|
||||
long pages_pinned;
|
||||
struct page *page;
|
||||
|
||||
pages_pinned = get_user_pages(current, mm, page_start_vaddr,
|
||||
1, 0, 0, &page, NULL);
|
||||
if (pages_pinned < 1) {
|
||||
seq_puts(m, "<fault>]");
|
||||
return;
|
||||
}
|
||||
|
||||
kaddr = (const char *)kmap(page);
|
||||
len = min(max_len, PAGE_SIZE - page_offset);
|
||||
write_len = strnlen(kaddr + page_offset, len);
|
||||
seq_write(m, kaddr + page_offset, write_len);
|
||||
kunmap(page);
|
||||
put_page(page);
|
||||
|
||||
/* if strnlen hit a null terminator then we're done */
|
||||
if (write_len != len)
|
||||
break;
|
||||
|
||||
max_len -= len;
|
||||
page_offset = 0;
|
||||
page_start_vaddr += PAGE_SIZE;
|
||||
}
|
||||
|
||||
seq_putc(m, ']');
|
||||
}
|
||||
|
||||
static void vma_stop(struct proc_maps_private *priv, struct vm_area_struct *vma)
|
||||
{
|
||||
if (vma && vma != priv->tail_vma) {
|
||||
@@ -335,6 +385,12 @@ show_map_vma(struct seq_file *m, struct vm_area_struct *vma, int is_pid)
|
||||
pad_len_spaces(m, len);
|
||||
seq_printf(m, "[stack:%d]", tid);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (vma_get_anon_name(vma)) {
|
||||
pad_len_spaces(m, len);
|
||||
seq_print_vma_name(m, vma);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,6 +690,12 @@ static int show_smap(struct seq_file *m, void *v, int is_pid)
|
||||
|
||||
show_smap_vma_flags(m, vma);
|
||||
|
||||
if (vma_get_anon_name(vma)) {
|
||||
seq_puts(m, "Name: ");
|
||||
seq_print_vma_name(m, vma);
|
||||
seq_putc(m, '\n');
|
||||
}
|
||||
|
||||
if (m->count < m->size) /* vma is copied successfully */
|
||||
m->version = (vma != get_gate_vma(task->mm))
|
||||
? vma->vm_start : 0;
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct ion_handle;
|
||||
typedef int ion_user_handle_t;
|
||||
|
||||
/**
|
||||
* enum ion_heap_types - list of all possible types of heaps
|
||||
* @ION_HEAP_TYPE_SYSTEM: memory allocated via vmalloc
|
||||
@@ -27,6 +28,7 @@ struct ion_handle;
|
||||
* @ION_HEAP_TYPE_CARVEOUT: memory allocated from a prereserved
|
||||
* carveout heap, allocations are physically
|
||||
* contiguous
|
||||
* @ION_HEAP_TYPE_DMA: memory allocated via DMA API
|
||||
* @ION_NUM_HEAPS: helper for iterating over heaps, a bit mask
|
||||
* is used to identify the heaps, so only 32
|
||||
* total heap types are supported
|
||||
@@ -36,6 +38,7 @@ enum ion_heap_type {
|
||||
ION_HEAP_TYPE_SYSTEM_CONTIG,
|
||||
ION_HEAP_TYPE_CARVEOUT,
|
||||
ION_HEAP_TYPE_CHUNK,
|
||||
ION_HEAP_TYPE_DMA,
|
||||
ION_HEAP_TYPE_CUSTOM, /* must be last so device specific heaps always
|
||||
are at the end of this enum */
|
||||
ION_NUM_HEAPS = 16,
|
||||
@@ -44,6 +47,7 @@ enum ion_heap_type {
|
||||
#define ION_HEAP_SYSTEM_MASK (1 << ION_HEAP_TYPE_SYSTEM)
|
||||
#define ION_HEAP_SYSTEM_CONTIG_MASK (1 << ION_HEAP_TYPE_SYSTEM_CONTIG)
|
||||
#define ION_HEAP_CARVEOUT_MASK (1 << ION_HEAP_TYPE_CARVEOUT)
|
||||
#define ION_HEAP_TYPE_DMA_MASK (1 << ION_HEAP_TYPE_DMA)
|
||||
|
||||
#define ION_NUM_HEAP_IDS sizeof(unsigned int) * 8
|
||||
|
||||
@@ -60,6 +64,7 @@ enum ion_heap_type {
|
||||
caches must be managed manually */
|
||||
|
||||
#ifdef __KERNEL__
|
||||
struct ion_handle;
|
||||
struct ion_device;
|
||||
struct ion_heap;
|
||||
struct ion_mapper;
|
||||
@@ -105,7 +110,7 @@ struct ion_platform_heap {
|
||||
*/
|
||||
struct ion_platform_data {
|
||||
int nr;
|
||||
struct ion_platform_heap heaps[];
|
||||
struct ion_platform_heap *heaps;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -265,7 +270,7 @@ struct ion_allocation_data {
|
||||
size_t align;
|
||||
unsigned int heap_id_mask;
|
||||
unsigned int flags;
|
||||
struct ion_handle *handle;
|
||||
ion_user_handle_t handle;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -279,7 +284,7 @@ struct ion_allocation_data {
|
||||
* provides the file descriptor and the kernel returns the handle.
|
||||
*/
|
||||
struct ion_fd_data {
|
||||
struct ion_handle *handle;
|
||||
ion_user_handle_t handle;
|
||||
int fd;
|
||||
};
|
||||
|
||||
@@ -288,7 +293,7 @@ struct ion_fd_data {
|
||||
* @handle: a handle
|
||||
*/
|
||||
struct ion_handle_data {
|
||||
struct ion_handle *handle;
|
||||
ion_user_handle_t handle;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1486,7 +1486,7 @@ extern int vma_adjust(struct vm_area_struct *vma, unsigned long start,
|
||||
extern struct vm_area_struct *vma_merge(struct mm_struct *,
|
||||
struct vm_area_struct *prev, unsigned long addr, unsigned long end,
|
||||
unsigned long vm_flags, struct anon_vma *, struct file *, pgoff_t,
|
||||
struct mempolicy *);
|
||||
struct mempolicy *, const char __user *);
|
||||
extern struct anon_vma *find_mergeable_anon_vma(struct vm_area_struct *);
|
||||
extern int split_vma(struct mm_struct *,
|
||||
struct vm_area_struct *, unsigned long addr, int new_below);
|
||||
|
||||
@@ -255,6 +255,10 @@ struct vm_area_struct {
|
||||
* For areas with an address space and backing store,
|
||||
* linkage into the address_space->i_mmap interval tree, or
|
||||
* linkage of vma in the address_space->i_mmap_nonlinear list.
|
||||
*
|
||||
* For private anonymous mappings, a pointer to a null terminated string
|
||||
* in the user process containing the name given to the vma, or NULL
|
||||
* if unnamed.
|
||||
*/
|
||||
union {
|
||||
struct {
|
||||
@@ -262,6 +266,7 @@ struct vm_area_struct {
|
||||
unsigned long rb_subtree_last;
|
||||
} linear;
|
||||
struct list_head nonlinear;
|
||||
const char __user *anon_name;
|
||||
} shared;
|
||||
|
||||
/*
|
||||
@@ -457,4 +462,14 @@ static inline cpumask_t *mm_cpumask(struct mm_struct *mm)
|
||||
return mm->cpu_vm_mask_var;
|
||||
}
|
||||
|
||||
|
||||
/* Return the name for an anonymous mapping or NULL for a file-backed mapping */
|
||||
static inline const char __user *vma_get_anon_name(struct vm_area_struct *vma)
|
||||
{
|
||||
if (vma->vm_file)
|
||||
return NULL;
|
||||
|
||||
return vma->shared.anon_name;
|
||||
}
|
||||
|
||||
#endif /* _LINUX_MM_TYPES_H */
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* android_battery.h
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_ANDROID_BATTERY_H
|
||||
#define _LINUX_ANDROID_BATTERY_H
|
||||
|
||||
enum {
|
||||
CHARGE_SOURCE_NONE = 0,
|
||||
CHARGE_SOURCE_AC,
|
||||
CHARGE_SOURCE_USB,
|
||||
};
|
||||
|
||||
struct android_bat_callbacks {
|
||||
void (*charge_source_changed)
|
||||
(struct android_bat_callbacks *, int);
|
||||
void (*battery_set_full)(struct android_bat_callbacks *);
|
||||
};
|
||||
|
||||
struct android_bat_platform_data {
|
||||
void (*register_callbacks)(struct android_bat_callbacks *);
|
||||
void (*unregister_callbacks)(void);
|
||||
void (*set_charging_current) (int);
|
||||
void (*set_charging_enable) (int);
|
||||
int (*poll_charge_source) (void);
|
||||
int (*get_capacity) (void);
|
||||
int (*get_temperature) (int *);
|
||||
int (*get_voltage_now)(void);
|
||||
int (*get_current_now)(int *);
|
||||
|
||||
int temp_high_threshold;
|
||||
int temp_high_recovery;
|
||||
int temp_low_recovery;
|
||||
int temp_low_threshold;
|
||||
|
||||
unsigned long full_charging_time;
|
||||
unsigned long recharging_time;
|
||||
unsigned int recharging_voltage;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -149,4 +149,7 @@
|
||||
|
||||
#define PR_GET_TID_ADDRESS 40
|
||||
|
||||
#define PR_SET_VMA 0x53564d41
|
||||
# define PR_SET_VMA_ANON_NAME 0
|
||||
|
||||
#endif /* _LINUX_PRCTL_H */
|
||||
|
||||
311
include/uapi/video/adf.h
Normal file
311
include/uapi/video/adf.h
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_VIDEO_ADF_H_
|
||||
#define _UAPI_VIDEO_ADF_H_
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <drm/drm_fourcc.h>
|
||||
#include <drm/drm_mode.h>
|
||||
|
||||
#define ADF_NAME_LEN 32
|
||||
#define ADF_MAX_CUSTOM_DATA_SIZE PAGE_SIZE
|
||||
|
||||
enum adf_interface_type {
|
||||
ADF_INTF_DSI = 0,
|
||||
ADF_INTF_eDP = 1,
|
||||
ADF_INTF_DPI = 2,
|
||||
ADF_INTF_VGA = 3,
|
||||
ADF_INTF_DVI = 4,
|
||||
ADF_INTF_HDMI = 5,
|
||||
ADF_INTF_MEMORY = 6,
|
||||
ADF_INTF_TYPE_DEVICE_CUSTOM = 128,
|
||||
ADF_INTF_TYPE_MAX = (~(__u32)0),
|
||||
};
|
||||
|
||||
#define ADF_INTF_FLAG_PRIMARY (1 << 0)
|
||||
#define ADF_INTF_FLAG_EXTERNAL (1 << 1)
|
||||
|
||||
enum adf_event_type {
|
||||
ADF_EVENT_VSYNC = 0,
|
||||
ADF_EVENT_HOTPLUG = 1,
|
||||
ADF_EVENT_DEVICE_CUSTOM = 128,
|
||||
ADF_EVENT_TYPE_MAX = 255,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_set_event - start or stop subscribing to ADF events
|
||||
*
|
||||
* @type: the type of event to (un)subscribe
|
||||
* @enabled: subscribe or unsubscribe
|
||||
*
|
||||
* After subscribing to an event, userspace may poll() the ADF object's fd
|
||||
* to wait for events or read() to consume the event's data.
|
||||
*
|
||||
* ADF reserves event types 0 to %ADF_EVENT_DEVICE_CUSTOM-1 for its own events.
|
||||
* Devices may use event types %ADF_EVENT_DEVICE_CUSTOM to %ADF_EVENT_TYPE_MAX-1
|
||||
* for driver-private events.
|
||||
*/
|
||||
struct adf_set_event {
|
||||
__u8 type;
|
||||
__u8 enabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_event - common header for ADF event data
|
||||
*
|
||||
* @type: event type
|
||||
* @length: total size of event data, header inclusive
|
||||
*/
|
||||
struct adf_event {
|
||||
__u8 type;
|
||||
__u32 length;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_vsync_event - ADF vsync event
|
||||
*
|
||||
* @base: event header (see &struct adf_event)
|
||||
* @timestamp: time of vsync event, in nanoseconds
|
||||
*/
|
||||
struct adf_vsync_event {
|
||||
struct adf_event base;
|
||||
__u64 timestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_vsync_event - ADF display hotplug event
|
||||
*
|
||||
* @base: event header (see &struct adf_event)
|
||||
* @connected: whether a display is now connected to the interface
|
||||
*/
|
||||
struct adf_hotplug_event {
|
||||
struct adf_event base;
|
||||
__u8 connected;
|
||||
};
|
||||
|
||||
#define ADF_MAX_PLANES 4
|
||||
/**
|
||||
* struct adf_buffer_config - description of buffer displayed by adf_post_config
|
||||
*
|
||||
* @overlay_engine: id of the target overlay engine
|
||||
* @w: width of display region in pixels
|
||||
* @h: height of display region in pixels
|
||||
* @format: DRM-style fourcc, see drm_fourcc.h for standard formats
|
||||
* @fd: dma_buf fd for each plane
|
||||
* @offset: location of first pixel to scan out, in bytes
|
||||
* @pitch: stride (i.e. length of a scanline including padding) in bytes
|
||||
* @n_planes: number of planes in buffer
|
||||
* @acquire_fence: sync_fence fd which will clear when the buffer is
|
||||
* ready for display, or <0 if the buffer is already ready
|
||||
*/
|
||||
struct adf_buffer_config {
|
||||
__u32 overlay_engine;
|
||||
|
||||
__u32 w;
|
||||
__u32 h;
|
||||
__u32 format;
|
||||
|
||||
__s64 fd[ADF_MAX_PLANES];
|
||||
__u32 offset[ADF_MAX_PLANES];
|
||||
__u32 pitch[ADF_MAX_PLANES];
|
||||
__u8 n_planes;
|
||||
|
||||
__s64 acquire_fence;
|
||||
};
|
||||
#define ADF_MAX_BUFFERS (PAGE_SIZE / sizeof(struct adf_buffer_config))
|
||||
|
||||
/**
|
||||
* struct adf_post_config - request to flip to a new set of buffers
|
||||
*
|
||||
* @n_interfaces: number of interfaces targeted by the flip (input)
|
||||
* @interfaces: ids of interfaces targeted by the flip (input)
|
||||
* @n_bufs: number of buffers displayed (input)
|
||||
* @bufs: description of buffers displayed (input)
|
||||
* @custom_data_size: size of driver-private data (input)
|
||||
* @custom_data: driver-private data (input)
|
||||
* @complete_fence: sync_fence fd which will clear when this
|
||||
* configuration has left the screen (output)
|
||||
*/
|
||||
struct adf_post_config {
|
||||
size_t n_interfaces;
|
||||
__u32 __user *interfaces;
|
||||
|
||||
size_t n_bufs;
|
||||
struct adf_buffer_config __user *bufs;
|
||||
|
||||
size_t custom_data_size;
|
||||
void __user *custom_data;
|
||||
|
||||
__s64 complete_fence;
|
||||
};
|
||||
#define ADF_MAX_INTERFACES (PAGE_SIZE / sizeof(__u32))
|
||||
|
||||
/**
|
||||
* struct adf_simple_buffer_allocate - request to allocate a "simple" buffer
|
||||
*
|
||||
* @w: width of buffer in pixels (input)
|
||||
* @h: height of buffer in pixels (input)
|
||||
* @format: DRM-style fourcc (input)
|
||||
*
|
||||
* @fd: dma_buf fd (output)
|
||||
* @offset: location of first pixel, in bytes (output)
|
||||
* @pitch: length of a scanline including padding, in bytes (output)
|
||||
*
|
||||
* Simple buffers are analogous to DRM's "dumb" buffers. They have a single
|
||||
* plane of linear RGB data which can be allocated and scanned out without
|
||||
* any driver-private ioctls or data.
|
||||
*
|
||||
* @format must be a standard RGB format defined in drm_fourcc.h.
|
||||
*
|
||||
* ADF clients must NOT assume that an interface can scan out a simple buffer
|
||||
* allocated by a different ADF interface, even if the two interfaces belong to
|
||||
* the same ADF device.
|
||||
*/
|
||||
struct adf_simple_buffer_alloc {
|
||||
__u16 w;
|
||||
__u16 h;
|
||||
__u32 format;
|
||||
|
||||
__s64 fd;
|
||||
__u32 offset;
|
||||
__u32 pitch;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_simple_post_config - request to flip to a single buffer without
|
||||
* driver-private data
|
||||
*
|
||||
* @buf: description of buffer displayed (input)
|
||||
* @complete_fence: sync_fence fd which will clear when this buffer has left the
|
||||
* screen (output)
|
||||
*/
|
||||
struct adf_simple_post_config {
|
||||
struct adf_buffer_config buf;
|
||||
__s64 complete_fence;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_attachment_config - description of attachment between an overlay
|
||||
* engine and an interface
|
||||
*
|
||||
* @overlay_engine: id of the overlay engine
|
||||
* @interface: id of the interface
|
||||
*/
|
||||
struct adf_attachment_config {
|
||||
__u32 overlay_engine;
|
||||
__u32 interface;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_device_data - describes a display device
|
||||
*
|
||||
* @name: display device's name
|
||||
* @n_attachments: the number of current attachments
|
||||
* @attachments: list of current attachments
|
||||
* @n_allowed_attachments: the number of allowed attachments
|
||||
* @allowed_attachments: list of allowed attachments
|
||||
* @custom_data_size: size of driver-private data
|
||||
* @custom_data: driver-private data
|
||||
*/
|
||||
struct adf_device_data {
|
||||
char name[ADF_NAME_LEN];
|
||||
|
||||
size_t n_attachments;
|
||||
struct adf_attachment_config __user *attachments;
|
||||
|
||||
size_t n_allowed_attachments;
|
||||
struct adf_attachment_config __user *allowed_attachments;
|
||||
|
||||
size_t custom_data_size;
|
||||
void __user *custom_data;
|
||||
};
|
||||
#define ADF_MAX_ATTACHMENTS (PAGE_SIZE / sizeof(struct adf_attachment))
|
||||
|
||||
/**
|
||||
* struct adf_device_data - describes a display interface
|
||||
*
|
||||
* @name: display interface's name
|
||||
* @type: interface type (see enum @adf_interface_type)
|
||||
* @id: which interface of type @type;
|
||||
* e.g. interface DSI.1 -> @type=@ADF_INTF_TYPE_DSI, @id=1
|
||||
* @flags: informational flags (bitmask of %ADF_INTF_FLAG_* values)
|
||||
* @dpms_state: DPMS state (one of @DRM_MODE_DPMS_* defined in drm_mode.h)
|
||||
* @hotplug_detect: whether a display is plugged in
|
||||
* @width_mm: screen width in millimeters, or 0 if unknown
|
||||
* @height_mm: screen height in millimeters, or 0 if unknown
|
||||
* @current_mode: current display mode
|
||||
* @n_available_modes: the number of hardware display modes
|
||||
* @available_modes: list of hardware display modes
|
||||
* @custom_data_size: size of driver-private data
|
||||
* @custom_data: driver-private data
|
||||
*/
|
||||
struct adf_interface_data {
|
||||
char name[ADF_NAME_LEN];
|
||||
|
||||
__u32 type;
|
||||
__u32 id;
|
||||
/* e.g. type=ADF_INTF_TYPE_DSI, id=1 => DSI.1 */
|
||||
__u32 flags;
|
||||
|
||||
__u8 dpms_state;
|
||||
__u8 hotplug_detect;
|
||||
__u16 width_mm;
|
||||
__u16 height_mm;
|
||||
|
||||
struct drm_mode_modeinfo current_mode;
|
||||
size_t n_available_modes;
|
||||
struct drm_mode_modeinfo __user *available_modes;
|
||||
|
||||
size_t custom_data_size;
|
||||
void __user *custom_data;
|
||||
};
|
||||
#define ADF_MAX_MODES (PAGE_SIZE / sizeof(struct drm_mode_modeinfo))
|
||||
|
||||
/**
|
||||
* struct adf_overlay_engine_data - describes an overlay engine
|
||||
*
|
||||
* @name: overlay engine's name
|
||||
* @n_supported_formats: number of supported formats
|
||||
* @supported_formats: list of supported formats
|
||||
* @custom_data_size: size of driver-private data
|
||||
* @custom_data: driver-private data
|
||||
*/
|
||||
struct adf_overlay_engine_data {
|
||||
char name[ADF_NAME_LEN];
|
||||
|
||||
size_t n_supported_formats;
|
||||
__u32 __user *supported_formats;
|
||||
|
||||
size_t custom_data_size;
|
||||
void __user *custom_data;
|
||||
};
|
||||
#define ADF_MAX_SUPPORTED_FORMATS (PAGE_SIZE / sizeof(__u32))
|
||||
|
||||
#define ADF_SET_EVENT _IOW('D', 0, struct adf_set_event)
|
||||
#define ADF_BLANK _IOW('D', 1, __u8)
|
||||
#define ADF_POST_CONFIG _IOW('D', 2, struct adf_post_config)
|
||||
#define ADF_SET_MODE _IOW('D', 3, struct drm_mode_modeinfo)
|
||||
#define ADF_GET_DEVICE_DATA _IOR('D', 4, struct adf_device_data)
|
||||
#define ADF_GET_INTERFACE_DATA _IOR('D', 5, struct adf_interface_data)
|
||||
#define ADF_GET_OVERLAY_ENGINE_DATA \
|
||||
_IOR('D', 6, struct adf_overlay_engine_data)
|
||||
#define ADF_SIMPLE_POST_CONFIG _IOW('D', 7, struct adf_simple_post_config)
|
||||
#define ADF_SIMPLE_BUFFER_ALLOC _IOW('D', 8, struct adf_simple_buffer_alloc)
|
||||
#define ADF_ATTACH _IOW('D', 9, struct adf_attachment_config)
|
||||
#define ADF_DETACH _IOW('D', 10, struct adf_attachment_config)
|
||||
|
||||
#endif /* _UAPI_VIDEO_ADF_H_ */
|
||||
484
include/video/adf.h
Normal file
484
include/video/adf.h
Normal file
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _VIDEO_ADF_H
|
||||
#define _VIDEO_ADF_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <uapi/video/adf.h>
|
||||
#include "sync.h"
|
||||
|
||||
struct adf_obj;
|
||||
struct adf_obj_ops;
|
||||
struct adf_device;
|
||||
struct adf_device_ops;
|
||||
struct adf_interface;
|
||||
struct adf_interface_ops;
|
||||
struct adf_overlay_engine;
|
||||
struct adf_overlay_engine_ops;
|
||||
|
||||
/**
|
||||
* struct adf_buffer - buffer displayed by adf_post
|
||||
*
|
||||
* @overlay_engine: target overlay engine
|
||||
* @w: width of display region in pixels
|
||||
* @h: height of display region in pixels
|
||||
* @format: DRM-style fourcc, see drm_fourcc.h for standard formats
|
||||
* @dma_bufs: dma_buf for each plane
|
||||
* @offset: location of first pixel to scan out, in bytes
|
||||
* @pitch: length of a scanline including padding, in bytes
|
||||
* @n_planes: number of planes in buffer
|
||||
* @acquire_fence: sync_fence which will clear when the buffer is
|
||||
* ready for display
|
||||
*
|
||||
* &struct adf_buffer is the in-kernel counterpart to the userspace-facing
|
||||
* &struct adf_buffer_config.
|
||||
*/
|
||||
struct adf_buffer {
|
||||
struct adf_overlay_engine *overlay_engine;
|
||||
|
||||
u32 w;
|
||||
u32 h;
|
||||
u32 format;
|
||||
|
||||
struct dma_buf *dma_bufs[ADF_MAX_PLANES];
|
||||
u32 offset[ADF_MAX_PLANES];
|
||||
u32 pitch[ADF_MAX_PLANES];
|
||||
u8 n_planes;
|
||||
|
||||
struct sync_fence *acquire_fence;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_buffer_mapping - state for mapping a &struct adf_buffer into the
|
||||
* display device
|
||||
*
|
||||
* @attachments: dma-buf attachment for each plane
|
||||
* @sg_tables: SG tables for each plane
|
||||
*/
|
||||
struct adf_buffer_mapping {
|
||||
struct dma_buf_attachment *attachments[ADF_MAX_PLANES];
|
||||
struct sg_table *sg_tables[ADF_MAX_PLANES];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_post - request to flip to a new set of buffers
|
||||
*
|
||||
* @n_bufs: number of buffers displayed
|
||||
* @bufs: buffers displayed
|
||||
* @mappings: in-device mapping state for each buffer
|
||||
* @custom_data_size: size of driver-private data
|
||||
* @custom_data: driver-private data
|
||||
*
|
||||
* &struct adf_post is the in-kernel counterpart to the userspace-facing
|
||||
* &struct adf_post_config.
|
||||
*/
|
||||
struct adf_post {
|
||||
size_t n_bufs;
|
||||
struct adf_buffer *bufs;
|
||||
struct adf_buffer_mapping *mappings;
|
||||
|
||||
size_t custom_data_size;
|
||||
void *custom_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_attachment - description of attachment between an overlay engine
|
||||
* and an interface
|
||||
*
|
||||
* @overlay_engine: the overlay engine
|
||||
* @interface: the interface
|
||||
*
|
||||
* &struct adf_attachment is the in-kernel counterpart to the userspace-facing
|
||||
* &struct adf_attachment_config.
|
||||
*/
|
||||
struct adf_attachment {
|
||||
struct adf_overlay_engine *overlay_engine;
|
||||
struct adf_interface *interface;
|
||||
};
|
||||
|
||||
struct adf_pending_post {
|
||||
struct list_head head;
|
||||
struct adf_post config;
|
||||
void *state;
|
||||
};
|
||||
|
||||
enum adf_obj_type {
|
||||
ADF_OBJ_OVERLAY_ENGINE = 0,
|
||||
ADF_OBJ_INTERFACE = 1,
|
||||
ADF_OBJ_DEVICE = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_obj_ops - common ADF object implementation ops
|
||||
*
|
||||
* @open: handle opening the object's device node
|
||||
* @release: handle releasing an open file
|
||||
* @ioctl: handle custom ioctls
|
||||
*
|
||||
* @supports_event: return whether the object supports generating events of type
|
||||
* @type
|
||||
* @set_event: enable or disable events of type @type
|
||||
* @event_type_str: return a string representation of custom event @type
|
||||
* (@type >= %ADF_EVENT_DEVICE_CUSTOM).
|
||||
*
|
||||
* @custom_data: copy up to %ADF_MAX_CUSTOM_DATA_SIZE bytes of driver-private
|
||||
* data into @data (allocated by ADF) and return the number of copied bytes
|
||||
* in @size. Return 0 on success or an error code (<0) on failure.
|
||||
*/
|
||||
struct adf_obj_ops {
|
||||
/* optional */
|
||||
int (*open)(struct adf_obj *obj, struct inode *inode,
|
||||
struct file *file);
|
||||
/* optional */
|
||||
void (*release)(struct adf_obj *obj, struct inode *inode,
|
||||
struct file *file);
|
||||
/* optional */
|
||||
long (*ioctl)(struct adf_obj *obj, unsigned int cmd, unsigned long arg);
|
||||
|
||||
/* optional */
|
||||
bool (*supports_event)(struct adf_obj *obj, enum adf_event_type type);
|
||||
/* required if supports_event is implemented */
|
||||
void (*set_event)(struct adf_obj *obj, enum adf_event_type type,
|
||||
bool enabled);
|
||||
/* optional */
|
||||
const char *(*event_type_str)(struct adf_obj *obj,
|
||||
enum adf_event_type type);
|
||||
|
||||
/* optional */
|
||||
int (*custom_data)(struct adf_obj *obj, void *data, size_t *size);
|
||||
};
|
||||
|
||||
struct adf_obj {
|
||||
enum adf_obj_type type;
|
||||
char name[ADF_NAME_LEN];
|
||||
struct adf_device *parent;
|
||||
|
||||
const struct adf_obj_ops *ops;
|
||||
|
||||
struct device dev;
|
||||
|
||||
struct spinlock file_lock;
|
||||
struct list_head file_list;
|
||||
|
||||
struct mutex event_lock;
|
||||
struct rb_root event_refcount;
|
||||
|
||||
int id;
|
||||
int minor;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_device_ops - display device implementation ops
|
||||
*
|
||||
* @owner: device's module
|
||||
* @base: common operations (see &struct adf_obj_ops)
|
||||
*
|
||||
* @attach: attach overlay engine @eng to interface @intf. Return 0 on success
|
||||
* or error code (<0) on failure.
|
||||
* @detach: detach overlay engine @eng from interface @intf. Return 0 on
|
||||
* success or error code (<0) on failure.
|
||||
*
|
||||
* @validate_custom_format: validate the number and size of planes
|
||||
* in buffers with a custom format (i.e., not one of the @DRM_FORMAT_*
|
||||
* types defined in drm/drm_fourcc.h). Return 0 if the buffer is valid or
|
||||
* an error code (<0) otherwise.
|
||||
*
|
||||
* @validate: validate that the proposed configuration @cfg is legal. The
|
||||
* driver may optionally allocate and return some driver-private state in
|
||||
* @driver_state, which will be passed to the corresponding post(). The
|
||||
* driver may NOT commit any changes to hardware. Return 0 if @cfg is
|
||||
* valid or an error code (<0) otherwise.
|
||||
* @complete_fence: create a hardware-backed sync fence to be signaled when
|
||||
* @cfg is removed from the screen. If unimplemented, ADF automatically
|
||||
* creates an sw_sync fence. Return the sync fence on success or a
|
||||
* PTR_ERR() on failure.
|
||||
* @post: flip @cfg onto the screen. Wait for the display to begin scanning out
|
||||
* @cfg before returning.
|
||||
* @advance_timeline: signal the sync fence for the last configuration to leave
|
||||
* the display. If unimplemented, ADF automatically advances an sw_sync
|
||||
* timeline.
|
||||
* @state_free: free driver-private state allocated during validate()
|
||||
*/
|
||||
struct adf_device_ops {
|
||||
/* required */
|
||||
struct module *owner;
|
||||
const struct adf_obj_ops base;
|
||||
|
||||
/* optional */
|
||||
int (*attach)(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf);
|
||||
/* optional */
|
||||
int (*detach)(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf);
|
||||
|
||||
/* required if any of the device's overlay engines supports at least one
|
||||
custom format */
|
||||
int (*validate_custom_format)(struct adf_device *dev,
|
||||
struct adf_buffer *buf);
|
||||
|
||||
/* required */
|
||||
int (*validate)(struct adf_device *dev, struct adf_post *cfg,
|
||||
void **driver_state);
|
||||
/* optional */
|
||||
struct sync_fence *(*complete_fence)(struct adf_device *dev,
|
||||
struct adf_post *cfg, void *driver_state);
|
||||
/* required */
|
||||
void (*post)(struct adf_device *dev, struct adf_post *cfg,
|
||||
void *driver_state);
|
||||
/* required if complete_fence is implemented */
|
||||
void (*advance_timeline)(struct adf_device *dev,
|
||||
struct adf_post *cfg, void *driver_state);
|
||||
/* required if validate allocates driver state */
|
||||
void (*state_free)(struct adf_device *dev, void *driver_state);
|
||||
};
|
||||
|
||||
struct adf_attachment_list {
|
||||
struct adf_attachment attachment;
|
||||
struct list_head head;
|
||||
};
|
||||
|
||||
struct adf_device {
|
||||
struct adf_obj base;
|
||||
struct device *dev;
|
||||
|
||||
const struct adf_device_ops *ops;
|
||||
|
||||
struct mutex client_lock;
|
||||
|
||||
struct idr interfaces;
|
||||
size_t n_interfaces;
|
||||
struct idr overlay_engines;
|
||||
|
||||
struct list_head post_list;
|
||||
struct mutex post_lock;
|
||||
struct kthread_worker post_worker;
|
||||
struct task_struct *post_thread;
|
||||
struct kthread_work post_work;
|
||||
|
||||
struct list_head attached;
|
||||
size_t n_attached;
|
||||
struct list_head attach_allowed;
|
||||
size_t n_attach_allowed;
|
||||
|
||||
struct adf_pending_post *onscreen;
|
||||
|
||||
struct sw_sync_timeline *timeline;
|
||||
int timeline_max;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_interface_ops - display interface implementation ops
|
||||
*
|
||||
* @base: common operations (see &struct adf_obj_ops)
|
||||
*
|
||||
* @blank: change the display's DPMS state. Return 0 on success or error
|
||||
* code (<0) on failure.
|
||||
*
|
||||
* @alloc_simple_buffer: allocate a buffer with the specified @w, @h, and
|
||||
* @format. @format will be a standard RGB format (i.e.,
|
||||
* adf_format_is_rgb(@format) == true). Return 0 on success or error code
|
||||
* (<0) on failure. On success, return the buffer, offset, and pitch in
|
||||
* @dma_buf, @offset, and @pitch respectively.
|
||||
* @describe_simple_post: provide driver-private data needed to post a single
|
||||
* buffer @buf. Copy up to ADF_MAX_CUSTOM_DATA_SIZE bytes into @data
|
||||
* (allocated by ADF) and return the number of bytes in @size. Return 0 on
|
||||
* success or error code (<0) on failure.
|
||||
*
|
||||
* @modeset: change the interface's mode. @mode is not necessarily part of the
|
||||
* modelist passed to adf_hotplug_notify_connected(); the driver may
|
||||
* accept or reject custom modes at its discretion. Return 0 on success or
|
||||
* error code (<0) if the mode could not be set.
|
||||
*
|
||||
* @screen_size: copy the screen dimensions in millimeters into @width_mm
|
||||
* and @height_mm. Return 0 on success or error code (<0) if the display
|
||||
* dimensions are unknown.
|
||||
*
|
||||
* @type_str: return a string representation of custom @intf->type
|
||||
* (@intf->type >= @ADF_INTF_TYPE_DEVICE_CUSTOM).
|
||||
*/
|
||||
struct adf_interface_ops {
|
||||
const struct adf_obj_ops base;
|
||||
|
||||
/* optional */
|
||||
int (*blank)(struct adf_interface *intf, u8 state);
|
||||
|
||||
/* optional */
|
||||
int (*alloc_simple_buffer)(struct adf_interface *intf,
|
||||
u16 w, u16 h, u32 format,
|
||||
struct dma_buf **dma_buf, u32 *offset, u32 *pitch);
|
||||
/* optional */
|
||||
int (*describe_simple_post)(struct adf_interface *intf,
|
||||
struct adf_buffer *fb, void *data, size_t *size);
|
||||
|
||||
/* optional */
|
||||
int (*modeset)(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *mode);
|
||||
|
||||
/* optional */
|
||||
int (*screen_size)(struct adf_interface *intf, u16 *width_mm,
|
||||
u16 *height_mm);
|
||||
|
||||
/* optional */
|
||||
const char *(*type_str)(struct adf_interface *intf);
|
||||
};
|
||||
|
||||
struct adf_interface {
|
||||
struct adf_obj base;
|
||||
const struct adf_interface_ops *ops;
|
||||
|
||||
struct drm_mode_modeinfo current_mode;
|
||||
|
||||
enum adf_interface_type type;
|
||||
u32 idx;
|
||||
u32 flags;
|
||||
|
||||
wait_queue_head_t vsync_wait;
|
||||
ktime_t vsync_timestamp;
|
||||
rwlock_t vsync_lock;
|
||||
|
||||
u8 dpms_state;
|
||||
|
||||
bool hotplug_detect;
|
||||
struct drm_mode_modeinfo *modelist;
|
||||
size_t n_modes;
|
||||
rwlock_t hotplug_modelist_lock;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct adf_interface_ops - overlay engine implementation ops
|
||||
*
|
||||
* @base: common operations (see &struct adf_obj_ops)
|
||||
*
|
||||
* @supported_formats: list of fourccs the overlay engine can scan out
|
||||
* @n_supported_formats: length of supported_formats, up to
|
||||
* ADF_MAX_SUPPORTED_FORMATS
|
||||
*/
|
||||
struct adf_overlay_engine_ops {
|
||||
const struct adf_obj_ops base;
|
||||
|
||||
/* required */
|
||||
const u32 *supported_formats;
|
||||
/* required */
|
||||
const size_t n_supported_formats;
|
||||
};
|
||||
|
||||
struct adf_overlay_engine {
|
||||
struct adf_obj base;
|
||||
|
||||
const struct adf_overlay_engine_ops *ops;
|
||||
};
|
||||
|
||||
#define adf_obj_to_device(ptr) \
|
||||
container_of((ptr), struct adf_device, base)
|
||||
|
||||
#define adf_obj_to_interface(ptr) \
|
||||
container_of((ptr), struct adf_interface, base)
|
||||
|
||||
#define adf_obj_to_overlay_engine(ptr) \
|
||||
container_of((ptr), struct adf_overlay_engine, base)
|
||||
|
||||
int __printf(4, 5) adf_device_init(struct adf_device *dev,
|
||||
struct device *parent, const struct adf_device_ops *ops,
|
||||
const char *fmt, ...);
|
||||
void adf_device_destroy(struct adf_device *dev);
|
||||
int __printf(7, 8) adf_interface_init(struct adf_interface *intf,
|
||||
struct adf_device *dev, enum adf_interface_type type, u32 idx,
|
||||
u32 flags, const struct adf_interface_ops *ops, const char *fmt,
|
||||
...);
|
||||
void adf_interface_destroy(struct adf_interface *intf);
|
||||
static inline struct adf_device *adf_interface_parent(
|
||||
struct adf_interface *intf)
|
||||
{
|
||||
return intf->base.parent;
|
||||
}
|
||||
int __printf(4, 5) adf_overlay_engine_init(struct adf_overlay_engine *eng,
|
||||
struct adf_device *dev,
|
||||
const struct adf_overlay_engine_ops *ops, const char *fmt, ...);
|
||||
void adf_overlay_engine_destroy(struct adf_overlay_engine *eng);
|
||||
static inline struct adf_device *adf_overlay_engine_parent(
|
||||
struct adf_overlay_engine *eng)
|
||||
{
|
||||
return eng->base.parent;
|
||||
}
|
||||
|
||||
int adf_attachment_allow(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf);
|
||||
|
||||
const char *adf_obj_type_str(enum adf_obj_type type);
|
||||
const char *adf_interface_type_str(struct adf_interface *intf);
|
||||
const char *adf_event_type_str(struct adf_obj *obj, enum adf_event_type type);
|
||||
|
||||
#define ADF_FORMAT_STR_SIZE 5
|
||||
void adf_format_str(u32 format, char buf[ADF_FORMAT_STR_SIZE]);
|
||||
int adf_format_validate_yuv(struct adf_device *dev, struct adf_buffer *buf,
|
||||
u8 num_planes, u8 hsub, u8 vsub, u8 cpp[]);
|
||||
/**
|
||||
* adf_format_validate_rgb - validate the number and size of planes in buffers
|
||||
* with a custom RGB format.
|
||||
*
|
||||
* @dev: ADF device performing the validation
|
||||
* @buf: buffer to validate
|
||||
* @cpp: expected bytes per pixel
|
||||
*
|
||||
* adf_format_validate_rgb() is intended to be called as a helper from @dev's
|
||||
* validate_custom_format() op. @buf must have a single RGB plane.
|
||||
*
|
||||
* Returns 0 if @buf has a single plane with sufficient size, or -EINVAL
|
||||
* otherwise.
|
||||
*/
|
||||
static inline int adf_format_validate_rgb(struct adf_device *dev,
|
||||
struct adf_buffer *buf, u8 cpp)
|
||||
{
|
||||
return adf_format_validate_yuv(dev, buf, 1, 1, 1, &cpp);
|
||||
}
|
||||
|
||||
int adf_event_get(struct adf_obj *obj, enum adf_event_type type);
|
||||
int adf_event_put(struct adf_obj *obj, enum adf_event_type type);
|
||||
int adf_event_notify(struct adf_obj *obj, struct adf_event *event);
|
||||
|
||||
static inline void adf_vsync_get(struct adf_interface *intf)
|
||||
{
|
||||
adf_event_get(&intf->base, ADF_EVENT_VSYNC);
|
||||
}
|
||||
|
||||
static inline void adf_vsync_put(struct adf_interface *intf)
|
||||
{
|
||||
adf_event_put(&intf->base, ADF_EVENT_VSYNC);
|
||||
}
|
||||
|
||||
int adf_vsync_wait(struct adf_interface *intf, long timeout);
|
||||
void adf_vsync_notify(struct adf_interface *intf, ktime_t timestamp);
|
||||
|
||||
int adf_hotplug_notify_connected(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *modelist, size_t n_modes);
|
||||
void adf_hotplug_notify_disconnected(struct adf_interface *intf);
|
||||
|
||||
void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode);
|
||||
void adf_modeinfo_set_vrefresh(struct drm_mode_modeinfo *mode);
|
||||
|
||||
#endif /* _VIDEO_ADF_H */
|
||||
61
include/video/adf_client.h
Normal file
61
include/video/adf_client.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _VIDEO_ADF_CLIENT_H_
|
||||
#define _VIDEO_ADF_CLIENT_H_
|
||||
|
||||
#include <video/adf.h>
|
||||
|
||||
int adf_interface_blank(struct adf_interface *intf, u8 state);
|
||||
u8 adf_interface_dpms_state(struct adf_interface *intf);
|
||||
|
||||
void adf_interface_current_mode(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *mode);
|
||||
size_t adf_interface_modelist(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *modelist, size_t n_modes);
|
||||
int adf_interface_set_mode(struct adf_interface *intf,
|
||||
struct drm_mode_modeinfo *mode);
|
||||
int adf_interface_get_screen_size(struct adf_interface *intf, u16 *width,
|
||||
u16 *height);
|
||||
int adf_interface_simple_buffer_alloc(struct adf_interface *intf, u16 w, u16 h,
|
||||
u32 format, struct dma_buf **dma_buf, u32 *offset, u32 *pitch);
|
||||
struct sync_fence *adf_interface_simple_post(struct adf_interface *intf,
|
||||
struct adf_buffer *buf);
|
||||
|
||||
bool adf_overlay_engine_supports_format(struct adf_overlay_engine *eng,
|
||||
u32 format);
|
||||
|
||||
size_t adf_device_attachments(struct adf_device *dev,
|
||||
struct adf_attachment *attachments, size_t n_attachments);
|
||||
size_t adf_device_attachments_allowed(struct adf_device *dev,
|
||||
struct adf_attachment *attachments, size_t n_attachments);
|
||||
bool adf_device_attached(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf);
|
||||
bool adf_device_attach_allowed(struct adf_device *dev,
|
||||
struct adf_overlay_engine *eng, struct adf_interface *intf);
|
||||
int adf_device_attach(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf);
|
||||
int adf_device_detach(struct adf_device *dev, struct adf_overlay_engine *eng,
|
||||
struct adf_interface *intf);
|
||||
|
||||
struct sync_fence *adf_device_post(struct adf_device *dev,
|
||||
struct adf_interface **intfs, size_t n_intfs,
|
||||
struct adf_buffer *bufs, size_t n_bufs, void *custom_data,
|
||||
size_t custom_data_size);
|
||||
struct sync_fence *adf_device_post_nocopy(struct adf_device *dev,
|
||||
struct adf_interface **intfs, size_t n_intfs,
|
||||
struct adf_buffer *bufs, size_t n_bufs, void *custom_data,
|
||||
size_t custom_data_size);
|
||||
|
||||
#endif /* _VIDEO_ADF_CLIENT_H_ */
|
||||
59
include/video/adf_fbdev.h
Normal file
59
include/video/adf_fbdev.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _VIDEO_ADF_FBDEV_H_
|
||||
#define _VIDEO_ADF_FBDEV_H_
|
||||
|
||||
#include <linux/fb.h>
|
||||
#include <video/adf.h>
|
||||
|
||||
struct adf_fbdev {
|
||||
struct adf_interface *intf;
|
||||
struct adf_overlay_engine *eng;
|
||||
struct fb_info *info;
|
||||
u32 pseudo_palette[16];
|
||||
|
||||
bool open;
|
||||
|
||||
struct dma_buf *dma_buf;
|
||||
u32 offset;
|
||||
u32 pitch;
|
||||
void *vaddr;
|
||||
u32 format;
|
||||
|
||||
u16 default_xres_virtual;
|
||||
u16 default_yres_virtual;
|
||||
u32 default_format;
|
||||
};
|
||||
|
||||
void adf_modeinfo_to_fb_videomode(const struct drm_mode_modeinfo *mode,
|
||||
struct fb_videomode *vmode);
|
||||
void adf_modeinfo_from_fb_videomode(const struct fb_videomode *vmode,
|
||||
struct drm_mode_modeinfo *mode);
|
||||
|
||||
int adf_fbdev_init(struct adf_fbdev *fbdev, struct adf_interface *interface,
|
||||
struct adf_overlay_engine *eng,
|
||||
u16 xres_virtual, u16 yres_virtual, u32 format,
|
||||
struct fb_ops *fbops, const char *fmt, ...);
|
||||
void adf_fbdev_destroy(struct adf_fbdev *fbdev);
|
||||
|
||||
int adf_fbdev_open(struct fb_info *info, int user);
|
||||
int adf_fbdev_release(struct fb_info *info, int user);
|
||||
int adf_fbdev_check_var(struct fb_var_screeninfo *var, struct fb_info *info);
|
||||
int adf_fbdev_set_par(struct fb_info *info);
|
||||
int adf_fbdev_blank(int blank, struct fb_info *info);
|
||||
int adf_fbdev_pan_display(struct fb_var_screeninfo *var, struct fb_info *info);
|
||||
int adf_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma);
|
||||
|
||||
#endif /* _VIDEO_ADF_FBDEV_H_ */
|
||||
26
include/video/adf_format.h
Normal file
26
include/video/adf_format.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _VIDEO_ADF_FORMAT_H
|
||||
#define _VIDEO_ADF_FORMAT_H
|
||||
|
||||
bool adf_format_is_standard(u32 format);
|
||||
bool adf_format_is_rgb(u32 format);
|
||||
u8 adf_format_num_planes(u32 format);
|
||||
u8 adf_format_bpp(u32 format);
|
||||
u8 adf_format_plane_cpp(u32 format, int plane);
|
||||
u8 adf_format_horz_chroma_subsampling(u32 format);
|
||||
u8 adf_format_vert_chroma_subsampling(u32 format);
|
||||
|
||||
#endif /* _VIDEO_ADF_FORMAT_H */
|
||||
20
include/video/adf_memblock.h
Normal file
20
include/video/adf_memblock.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _VIDEO_ADF_MEMBLOCK_H_
|
||||
#define _VIDEO_ADF_MEMBLOCK_H_
|
||||
|
||||
struct dma_buf *adf_memblock_export(phys_addr_t base, size_t size, int flags);
|
||||
|
||||
#endif /* _VIDEO_ADF_MEMBLOCK_H_ */
|
||||
145
kernel/sys.c
145
kernel/sys.c
@@ -42,6 +42,8 @@
|
||||
#include <linux/syscore_ops.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mempolicy.h>
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/syscalls.h>
|
||||
@@ -2099,6 +2101,146 @@ static int prctl_get_tid_address(struct task_struct *me, int __user **tid_addr)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static int prctl_update_vma_anon_name(struct vm_area_struct *vma,
|
||||
struct vm_area_struct **prev,
|
||||
unsigned long start, unsigned long end,
|
||||
const char __user *name_addr)
|
||||
{
|
||||
struct mm_struct * mm = vma->vm_mm;
|
||||
int error = 0;
|
||||
pgoff_t pgoff;
|
||||
|
||||
if (name_addr == vma_get_anon_name(vma)) {
|
||||
*prev = vma;
|
||||
goto out;
|
||||
}
|
||||
|
||||
pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
|
||||
*prev = vma_merge(mm, *prev, start, end, vma->vm_flags, vma->anon_vma,
|
||||
vma->vm_file, pgoff, vma_policy(vma),
|
||||
name_addr);
|
||||
if (*prev) {
|
||||
vma = *prev;
|
||||
goto success;
|
||||
}
|
||||
|
||||
*prev = vma;
|
||||
|
||||
if (start != vma->vm_start) {
|
||||
error = split_vma(mm, vma, start, 1);
|
||||
if (error)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (end != vma->vm_end) {
|
||||
error = split_vma(mm, vma, end, 0);
|
||||
if (error)
|
||||
goto out;
|
||||
}
|
||||
|
||||
success:
|
||||
if (!vma->vm_file)
|
||||
vma->shared.anon_name = name_addr;
|
||||
|
||||
out:
|
||||
if (error == -ENOMEM)
|
||||
error = -EAGAIN;
|
||||
return error;
|
||||
}
|
||||
|
||||
static int prctl_set_vma_anon_name(unsigned long start, unsigned long end,
|
||||
unsigned long arg)
|
||||
{
|
||||
unsigned long tmp;
|
||||
struct vm_area_struct * vma, *prev;
|
||||
int unmapped_error = 0;
|
||||
int error = -EINVAL;
|
||||
|
||||
/*
|
||||
* If the interval [start,end) covers some unmapped address
|
||||
* ranges, just ignore them, but return -ENOMEM at the end.
|
||||
* - this matches the handling in madvise.
|
||||
*/
|
||||
vma = find_vma_prev(current->mm, start, &prev);
|
||||
if (vma && start > vma->vm_start)
|
||||
prev = vma;
|
||||
|
||||
for (;;) {
|
||||
/* Still start < end. */
|
||||
error = -ENOMEM;
|
||||
if (!vma)
|
||||
return error;
|
||||
|
||||
/* Here start < (end|vma->vm_end). */
|
||||
if (start < vma->vm_start) {
|
||||
unmapped_error = -ENOMEM;
|
||||
start = vma->vm_start;
|
||||
if (start >= end)
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Here vma->vm_start <= start < (end|vma->vm_end) */
|
||||
tmp = vma->vm_end;
|
||||
if (end < tmp)
|
||||
tmp = end;
|
||||
|
||||
/* Here vma->vm_start <= start < tmp <= (end|vma->vm_end). */
|
||||
error = prctl_update_vma_anon_name(vma, &prev, start, end,
|
||||
(const char __user *)arg);
|
||||
if (error)
|
||||
return error;
|
||||
start = tmp;
|
||||
if (prev && start < prev->vm_end)
|
||||
start = prev->vm_end;
|
||||
error = unmapped_error;
|
||||
if (start >= end)
|
||||
return error;
|
||||
if (prev)
|
||||
vma = prev->vm_next;
|
||||
else /* madvise_remove dropped mmap_sem */
|
||||
vma = find_vma(current->mm, start);
|
||||
}
|
||||
}
|
||||
|
||||
static int prctl_set_vma(unsigned long opt, unsigned long start,
|
||||
unsigned long len_in, unsigned long arg)
|
||||
{
|
||||
struct mm_struct *mm = current->mm;
|
||||
int error;
|
||||
unsigned long len;
|
||||
unsigned long end;
|
||||
|
||||
if (start & ~PAGE_MASK)
|
||||
return -EINVAL;
|
||||
len = (len_in + ~PAGE_MASK) & PAGE_MASK;
|
||||
|
||||
/* Check to see whether len was rounded up from small -ve to zero */
|
||||
if (len_in && !len)
|
||||
return -EINVAL;
|
||||
|
||||
end = start + len;
|
||||
if (end < start)
|
||||
return -EINVAL;
|
||||
|
||||
if (end == start)
|
||||
return 0;
|
||||
|
||||
down_write(&mm->mmap_sem);
|
||||
|
||||
switch (opt) {
|
||||
case PR_SET_VMA_ANON_NAME:
|
||||
error = prctl_set_vma_anon_name(start, end, arg);
|
||||
break;
|
||||
default:
|
||||
error = -EINVAL;
|
||||
}
|
||||
|
||||
up_write(&mm->mmap_sem);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
|
||||
unsigned long, arg4, unsigned long, arg5)
|
||||
{
|
||||
@@ -2226,6 +2368,9 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
|
||||
else
|
||||
return -EINVAL;
|
||||
break;
|
||||
case PR_SET_VMA:
|
||||
error = prctl_set_vma(arg2, arg3, arg4, arg5);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ extern char core_pattern[];
|
||||
extern unsigned int core_pipe_limit;
|
||||
#endif
|
||||
extern int pid_max;
|
||||
extern int extra_free_kbytes;
|
||||
extern int min_free_order_shift;
|
||||
extern int pid_max_min, pid_max_max;
|
||||
extern int percpu_pagelist_fraction;
|
||||
@@ -1266,6 +1267,14 @@ static struct ctl_table vm_table[] = {
|
||||
.proc_handler = min_free_kbytes_sysctl_handler,
|
||||
.extra1 = &zero,
|
||||
},
|
||||
{
|
||||
.procname = "extra_free_kbytes",
|
||||
.data = &extra_free_kbytes,
|
||||
.maxlen = sizeof(extra_free_kbytes),
|
||||
.mode = 0644,
|
||||
.proc_handler = min_free_kbytes_sysctl_handler,
|
||||
.extra1 = &zero,
|
||||
},
|
||||
{
|
||||
.procname = "min_free_order_shift",
|
||||
.data = &min_free_order_shift,
|
||||
|
||||
@@ -102,7 +102,8 @@ static long madvise_behavior(struct vm_area_struct * vma,
|
||||
|
||||
pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
|
||||
*prev = vma_merge(mm, *prev, start, end, new_flags, vma->anon_vma,
|
||||
vma->vm_file, pgoff, vma_policy(vma));
|
||||
vma->vm_file, pgoff, vma_policy(vma),
|
||||
vma_get_anon_name(vma));
|
||||
if (*prev) {
|
||||
vma = *prev;
|
||||
goto success;
|
||||
|
||||
@@ -728,7 +728,7 @@ static int mbind_range(struct mm_struct *mm, unsigned long start,
|
||||
((vmstart - vma->vm_start) >> PAGE_SHIFT);
|
||||
prev = vma_merge(mm, prev, vmstart, vmend, vma->vm_flags,
|
||||
vma->anon_vma, vma->vm_file, pgoff,
|
||||
new_pol);
|
||||
new_pol, vma_get_anon_name(name));
|
||||
if (prev) {
|
||||
vma = prev;
|
||||
next = vma->vm_next;
|
||||
|
||||
@@ -287,7 +287,8 @@ static int mlock_fixup(struct vm_area_struct *vma, struct vm_area_struct **prev,
|
||||
|
||||
pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
|
||||
*prev = vma_merge(mm, *prev, start, end, newflags, vma->anon_vma,
|
||||
vma->vm_file, pgoff, vma_policy(vma));
|
||||
vma->vm_file, pgoff, vma_policy(vma),
|
||||
vma_get_anon_name(vma));
|
||||
if (*prev) {
|
||||
vma = *prev;
|
||||
goto success;
|
||||
|
||||
44
mm/mmap.c
44
mm/mmap.c
@@ -893,7 +893,8 @@ again: remove_next = 1 + (end > next->vm_end);
|
||||
* per-vma resources, so we don't attempt to merge those.
|
||||
*/
|
||||
static inline int is_mergeable_vma(struct vm_area_struct *vma,
|
||||
struct file *file, unsigned long vm_flags)
|
||||
struct file *file, unsigned long vm_flags,
|
||||
const char __user *anon_name)
|
||||
{
|
||||
if (vma->vm_flags ^ vm_flags)
|
||||
return 0;
|
||||
@@ -901,6 +902,8 @@ static inline int is_mergeable_vma(struct vm_area_struct *vma,
|
||||
return 0;
|
||||
if (vma->vm_ops && vma->vm_ops->close)
|
||||
return 0;
|
||||
if (vma_get_anon_name(vma) != anon_name)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -931,9 +934,10 @@ static inline int is_mergeable_anon_vma(struct anon_vma *anon_vma1,
|
||||
*/
|
||||
static int
|
||||
can_vma_merge_before(struct vm_area_struct *vma, unsigned long vm_flags,
|
||||
struct anon_vma *anon_vma, struct file *file, pgoff_t vm_pgoff)
|
||||
struct anon_vma *anon_vma, struct file *file, pgoff_t vm_pgoff,
|
||||
const char __user *anon_name)
|
||||
{
|
||||
if (is_mergeable_vma(vma, file, vm_flags) &&
|
||||
if (is_mergeable_vma(vma, file, vm_flags, anon_name) &&
|
||||
is_mergeable_anon_vma(anon_vma, vma->anon_vma, vma)) {
|
||||
if (vma->vm_pgoff == vm_pgoff)
|
||||
return 1;
|
||||
@@ -950,9 +954,10 @@ can_vma_merge_before(struct vm_area_struct *vma, unsigned long vm_flags,
|
||||
*/
|
||||
static int
|
||||
can_vma_merge_after(struct vm_area_struct *vma, unsigned long vm_flags,
|
||||
struct anon_vma *anon_vma, struct file *file, pgoff_t vm_pgoff)
|
||||
struct anon_vma *anon_vma, struct file *file, pgoff_t vm_pgoff,
|
||||
const char __user *anon_name)
|
||||
{
|
||||
if (is_mergeable_vma(vma, file, vm_flags) &&
|
||||
if (is_mergeable_vma(vma, file, vm_flags, anon_name) &&
|
||||
is_mergeable_anon_vma(anon_vma, vma->anon_vma, vma)) {
|
||||
pgoff_t vm_pglen;
|
||||
vm_pglen = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
|
||||
@@ -963,9 +968,9 @@ can_vma_merge_after(struct vm_area_struct *vma, unsigned long vm_flags,
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a mapping request (addr,end,vm_flags,file,pgoff), figure out
|
||||
* whether that can be merged with its predecessor or its successor.
|
||||
* Or both (it neatly fills a hole).
|
||||
* Given a mapping request (addr,end,vm_flags,file,pgoff,anon_name),
|
||||
* figure out whether that can be merged with its predecessor or its
|
||||
* successor. Or both (it neatly fills a hole).
|
||||
*
|
||||
* In most cases - when called for mmap, brk or mremap - [addr,end) is
|
||||
* certain not to be mapped by the time vma_merge is called; but when
|
||||
@@ -995,7 +1000,8 @@ struct vm_area_struct *vma_merge(struct mm_struct *mm,
|
||||
struct vm_area_struct *prev, unsigned long addr,
|
||||
unsigned long end, unsigned long vm_flags,
|
||||
struct anon_vma *anon_vma, struct file *file,
|
||||
pgoff_t pgoff, struct mempolicy *policy)
|
||||
pgoff_t pgoff, struct mempolicy *policy,
|
||||
const char __user *anon_name)
|
||||
{
|
||||
pgoff_t pglen = (end - addr) >> PAGE_SHIFT;
|
||||
struct vm_area_struct *area, *next;
|
||||
@@ -1021,15 +1027,15 @@ struct vm_area_struct *vma_merge(struct mm_struct *mm,
|
||||
*/
|
||||
if (prev && prev->vm_end == addr &&
|
||||
mpol_equal(vma_policy(prev), policy) &&
|
||||
can_vma_merge_after(prev, vm_flags,
|
||||
anon_vma, file, pgoff)) {
|
||||
can_vma_merge_after(prev, vm_flags, anon_vma,
|
||||
file, pgoff, anon_name)) {
|
||||
/*
|
||||
* OK, it can. Can we now merge in the successor as well?
|
||||
*/
|
||||
if (next && end == next->vm_start &&
|
||||
mpol_equal(policy, vma_policy(next)) &&
|
||||
can_vma_merge_before(next, vm_flags,
|
||||
anon_vma, file, pgoff+pglen) &&
|
||||
can_vma_merge_before(next, vm_flags, anon_vma,
|
||||
file, pgoff+pglen, anon_name) &&
|
||||
is_mergeable_anon_vma(prev->anon_vma,
|
||||
next->anon_vma, NULL)) {
|
||||
/* cases 1, 6 */
|
||||
@@ -1049,8 +1055,8 @@ struct vm_area_struct *vma_merge(struct mm_struct *mm,
|
||||
*/
|
||||
if (next && end == next->vm_start &&
|
||||
mpol_equal(policy, vma_policy(next)) &&
|
||||
can_vma_merge_before(next, vm_flags,
|
||||
anon_vma, file, pgoff+pglen)) {
|
||||
can_vma_merge_before(next, vm_flags, anon_vma,
|
||||
file, pgoff+pglen, anon_name)) {
|
||||
if (prev && addr < prev->vm_end) /* case 4 */
|
||||
err = vma_adjust(prev, prev->vm_start,
|
||||
addr, prev->vm_pgoff, NULL);
|
||||
@@ -1519,7 +1525,8 @@ munmap_back:
|
||||
/*
|
||||
* Can we just expand an old mapping?
|
||||
*/
|
||||
vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL);
|
||||
vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff,
|
||||
NULL, NULL);
|
||||
if (vma)
|
||||
goto out;
|
||||
|
||||
@@ -2663,7 +2670,7 @@ static unsigned long do_brk(unsigned long addr, unsigned long len)
|
||||
|
||||
/* Can we just expand an old private anonymous mapping? */
|
||||
vma = vma_merge(mm, prev, addr, addr + len, flags,
|
||||
NULL, NULL, pgoff, NULL);
|
||||
NULL, NULL, pgoff, NULL, NULL);
|
||||
if (vma)
|
||||
goto out;
|
||||
|
||||
@@ -2821,7 +2828,8 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,
|
||||
if (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent))
|
||||
return NULL; /* should never get here */
|
||||
new_vma = vma_merge(mm, prev, addr, addr + len, vma->vm_flags,
|
||||
vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma));
|
||||
vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma),
|
||||
vma_get_anon_name(vma));
|
||||
if (new_vma) {
|
||||
/*
|
||||
* Source vma may have been merged into new_vma
|
||||
|
||||
@@ -271,7 +271,8 @@ mprotect_fixup(struct vm_area_struct *vma, struct vm_area_struct **pprev,
|
||||
*/
|
||||
pgoff = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT);
|
||||
*pprev = vma_merge(mm, *pprev, start, end, newflags,
|
||||
vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma));
|
||||
vma->anon_vma, vma->vm_file, pgoff, vma_policy(vma),
|
||||
vma_get_anon_name(vma));
|
||||
if (*pprev) {
|
||||
vma = *pprev;
|
||||
goto success;
|
||||
|
||||
@@ -196,9 +196,21 @@ static char * const zone_names[MAX_NR_ZONES] = {
|
||||
"Movable",
|
||||
};
|
||||
|
||||
/*
|
||||
* Try to keep at least this much lowmem free. Do not allow normal
|
||||
* allocations below this point, only high priority ones. Automatically
|
||||
* tuned according to the amount of memory in the system.
|
||||
*/
|
||||
int min_free_kbytes = 1024;
|
||||
int min_free_order_shift = 1;
|
||||
|
||||
/*
|
||||
* Extra memory for the system to try freeing. Used to temporarily
|
||||
* free memory, to make space for new workloads. Anyone can allocate
|
||||
* down to the min watermarks controlled by min_free_kbytes above.
|
||||
*/
|
||||
int extra_free_kbytes = 0;
|
||||
|
||||
static unsigned long __meminitdata nr_kernel_pages;
|
||||
static unsigned long __meminitdata nr_all_pages;
|
||||
static unsigned long __meminitdata dma_reserve;
|
||||
@@ -5321,6 +5333,7 @@ static void setup_per_zone_lowmem_reserve(void)
|
||||
static void __setup_per_zone_wmarks(void)
|
||||
{
|
||||
unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
|
||||
unsigned long pages_low = extra_free_kbytes >> (PAGE_SHIFT - 10);
|
||||
unsigned long lowmem_pages = 0;
|
||||
struct zone *zone;
|
||||
unsigned long flags;
|
||||
@@ -5332,11 +5345,14 @@ static void __setup_per_zone_wmarks(void)
|
||||
}
|
||||
|
||||
for_each_zone(zone) {
|
||||
u64 tmp;
|
||||
u64 min, low;
|
||||
|
||||
spin_lock_irqsave(&zone->lock, flags);
|
||||
tmp = (u64)pages_min * zone->managed_pages;
|
||||
do_div(tmp, lowmem_pages);
|
||||
min = (u64)pages_min * zone->managed_pages;
|
||||
do_div(min, lowmem_pages);
|
||||
low = (u64)pages_low * zone->managed_pages;
|
||||
do_div(low, vm_total_pages);
|
||||
|
||||
if (is_highmem(zone)) {
|
||||
/*
|
||||
* __GFP_HIGH and PF_MEMALLOC allocations usually don't
|
||||
@@ -5357,11 +5373,13 @@ static void __setup_per_zone_wmarks(void)
|
||||
* If it's a lowmem zone, reserve a number of pages
|
||||
* proportionate to the zone's size.
|
||||
*/
|
||||
zone->watermark[WMARK_MIN] = tmp;
|
||||
zone->watermark[WMARK_MIN] = min;
|
||||
}
|
||||
|
||||
zone->watermark[WMARK_LOW] = min_wmark_pages(zone) + (tmp >> 2);
|
||||
zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);
|
||||
zone->watermark[WMARK_LOW] = min_wmark_pages(zone) +
|
||||
low + (min >> 2);
|
||||
zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) +
|
||||
low + (min >> 1);
|
||||
|
||||
setup_zone_migrate_reserve(zone);
|
||||
spin_unlock_irqrestore(&zone->lock, flags);
|
||||
@@ -5474,7 +5492,7 @@ module_init(init_per_zone_wmark_min)
|
||||
/*
|
||||
* min_free_kbytes_sysctl_handler - just a wrapper around proc_dointvec() so
|
||||
* that we can call two helper functions whenever min_free_kbytes
|
||||
* changes.
|
||||
* or extra_free_kbytes changes.
|
||||
*/
|
||||
int min_free_kbytes_sysctl_handler(ctl_table *table, int write,
|
||||
void __user *buffer, size_t *length, loff_t *ppos)
|
||||
|
||||
50
mm/page_io.c
50
mm/page_io.c
@@ -21,6 +21,7 @@
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/frontswap.h>
|
||||
#include <linux/aio.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <asm/pgtable.h>
|
||||
|
||||
static struct bio *get_swap_bio(gfp_t gfp_flags,
|
||||
@@ -80,9 +81,54 @@ void end_swap_bio_read(struct bio *bio, int err)
|
||||
imajor(bio->bi_bdev->bd_inode),
|
||||
iminor(bio->bi_bdev->bd_inode),
|
||||
(unsigned long long)bio->bi_sector);
|
||||
} else {
|
||||
SetPageUptodate(page);
|
||||
goto out;
|
||||
}
|
||||
|
||||
SetPageUptodate(page);
|
||||
|
||||
/*
|
||||
* There is no guarantee that the page is in swap cache - the software
|
||||
* suspend code (at least) uses end_swap_bio_read() against a non-
|
||||
* swapcache page. So we must check PG_swapcache before proceeding with
|
||||
* this optimization.
|
||||
*/
|
||||
if (likely(PageSwapCache(page))) {
|
||||
struct swap_info_struct *sis;
|
||||
|
||||
sis = page_swap_info(page);
|
||||
if (sis->flags & SWP_BLKDEV) {
|
||||
/*
|
||||
* The swap subsystem performs lazy swap slot freeing,
|
||||
* expecting that the page will be swapped out again.
|
||||
* So we can avoid an unnecessary write if the page
|
||||
* isn't redirtied.
|
||||
* This is good for real swap storage because we can
|
||||
* reduce unnecessary I/O and enhance wear-leveling
|
||||
* if an SSD is used as the as swap device.
|
||||
* But if in-memory swap device (eg zram) is used,
|
||||
* this causes a duplicated copy between uncompressed
|
||||
* data in VM-owned memory and compressed data in
|
||||
* zram-owned memory. So let's free zram-owned memory
|
||||
* and make the VM-owned decompressed page *dirty*,
|
||||
* so the page should be swapped out somewhere again if
|
||||
* we again wish to reclaim it.
|
||||
*/
|
||||
struct gendisk *disk = sis->bdev->bd_disk;
|
||||
if (disk->fops->swap_slot_free_notify) {
|
||||
swp_entry_t entry;
|
||||
unsigned long offset;
|
||||
|
||||
entry.val = page_private(page);
|
||||
offset = swp_offset(entry);
|
||||
|
||||
SetPageDirty(page);
|
||||
disk->fops->swap_slot_free_notify(sis->bdev,
|
||||
offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
unlock_page(page);
|
||||
bio_put(bio);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user