You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
7.4 KiB
296 lines
7.4 KiB
/* |
|
* 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), |
|
}; |
|
|
|
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%d", obj->id); |
|
} |
|
|
|
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-interface%d.%d", |
|
parent->base.id, 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-overlay-engine%d.%d", |
|
parent->base.id, 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; |
|
} |
|
|
|
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); |
|
}
|
|
|