| /* |
| * 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); |
| } |