diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/video/fbdev/msm/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/Makefile | 1 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/msm_dba/Kconfig | 15 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/msm_dba/Makefile | 3 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/msm_dba/msm_dba.c | 146 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/msm_dba/msm_dba_debug.c | 299 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c | 445 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/msm_dba/msm_dba_init.c | 131 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/msm_dba/msm_dba_internal.h | 317 |
9 files changed, 1358 insertions, 0 deletions
diff --git a/drivers/video/fbdev/msm/Kconfig b/drivers/video/fbdev/msm/Kconfig index a4d5d5e0d637..c49ce06430be 100644 --- a/drivers/video/fbdev/msm/Kconfig +++ b/drivers/video/fbdev/msm/Kconfig @@ -1,3 +1,4 @@ +source "drivers/video/fbdev/msm/msm_dba/Kconfig" if FB_MSM diff --git a/drivers/video/fbdev/msm/Makefile b/drivers/video/fbdev/msm/Makefile index eadf8c7191ee..ac83251cbfe6 100644 --- a/drivers/video/fbdev/msm/Makefile +++ b/drivers/video/fbdev/msm/Makefile @@ -1,6 +1,7 @@ ccflags-y += -I$(src) -Idrivers/staging/android obj-$(CONFIG_FB_MSM_MDSS_MHL3) += mhl3/ +obj-$(CONFIG_MSM_DBA) += msm_dba/ mdss-mdp3-objs = mdp3.o mdp3_dma.o mdp3_ctrl.o dsi_status_v2.o mdss-mdp3-objs += mdp3_ppp.o mdp3_ppp_hwio.o mdp3_ppp_data.o diff --git a/drivers/video/fbdev/msm/msm_dba/Kconfig b/drivers/video/fbdev/msm/msm_dba/Kconfig new file mode 100644 index 000000000000..a20d59c16a0d --- /dev/null +++ b/drivers/video/fbdev/msm/msm_dba/Kconfig @@ -0,0 +1,15 @@ +# +# MSM DBA +# + +config MSM_DBA + bool "MSM Display Bridge Abstraction support" + depends on ARM + ---help--- + Support for MSM display bridge abstraction interface. MSM display + drivers can use the same interface to interact with different third + party bridge chips. Drivers implemented for third party bridge chips + should support this interface to allow display driver to control the + bridge chip. The MSM DBA driver maintains a list of devices supported + on the platform and allow clients to register and access these + devices. diff --git a/drivers/video/fbdev/msm/msm_dba/Makefile b/drivers/video/fbdev/msm/msm_dba/Makefile new file mode 100644 index 000000000000..68a1adf3f88e --- /dev/null +++ b/drivers/video/fbdev/msm/msm_dba/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MSM_DBA) += msm_dba.o msm_dba_init.o msm_dba_helpers.o msm_dba_debug.o +clean: + rm *.o diff --git a/drivers/video/fbdev/msm/msm_dba/msm_dba.c b/drivers/video/fbdev/msm/msm_dba/msm_dba.c new file mode 100644 index 000000000000..7a5c9d9d873a --- /dev/null +++ b/drivers/video/fbdev/msm/msm_dba/msm_dba.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/err.h> + +#include <video/msm_dba.h> +#include <msm_dba_internal.h> + +static DEFINE_MUTEX(register_mutex); + +void *msm_dba_register_client(struct msm_dba_reg_info *info, + struct msm_dba_ops *ops) +{ + int rc = 0; + struct msm_dba_device_info *device = NULL; + struct msm_dba_client_info *client = NULL; + + pr_debug("%s: ENTER\n", __func__); + + if (!info || !ops) { + pr_err("%s: Invalid params\n", __func__); + return ERR_PTR(-EINVAL); + } + + mutex_lock(®ister_mutex); + + pr_debug("%s: Client(%s) Chip(%s) Instance(%d)\n", __func__, + info->client_name, info->chip_name, info->instance_id); + + rc = msm_dba_get_probed_device(info, &device); + if (rc) { + pr_err("%s: Device not found (%s, %d)\n", __func__, + info->chip_name, + info->instance_id); + mutex_unlock(®ister_mutex); + return ERR_PTR(rc); + } + + pr_debug("%s: Client(%s) device found\n", __func__, info->client_name); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) { + mutex_unlock(®ister_mutex); + return ERR_PTR(-ENOMEM); + } + + memset(client, 0x0, sizeof(*client)); + client->dev = device; + strlcpy(client->client_name, info->client_name, + MSM_DBA_CLIENT_NAME_LEN); + + client->cb = info->cb; + client->cb_data = info->cb_data; + + mutex_lock_nested(&device->dev_mutex, SINGLE_DEPTH_NESTING); + list_add(&client->list, &device->client_list); + *ops = device->client_ops; + mutex_unlock(&device->dev_mutex); + + if (device->reg_fxn) { + rc = device->reg_fxn(client); + if (rc) { + pr_err("%s: Client register failed (%s, %d)\n", + __func__, info->chip_name, info->instance_id); + kfree(client); + mutex_unlock(®ister_mutex); + return ERR_PTR(rc); + } + } + + mutex_unlock(®ister_mutex); + + pr_debug("%s: EXIT\n", __func__); + return client; +} +EXPORT_SYMBOL(msm_dba_register_client); + +int msm_dba_deregister_client(void *client) +{ + int rc = 0; + struct msm_dba_client_info *handle = client; + struct msm_dba_client_info *node = NULL; + struct list_head *tmp = NULL; + struct list_head *position = NULL; + + pr_debug("%s: ENTER\n", __func__); + + if (!handle) { + pr_err("%s: Invalid Params\n", __func__); + return -EINVAL; + } + + mutex_lock(®ister_mutex); + + pr_debug("%s: Client(%s) Chip(%s) Instance(%d)\n", __func__, + handle->client_name, handle->dev->chip_name, + handle->dev->instance_id); + + if (handle->dev->dereg_fxn) { + rc = handle->dev->dereg_fxn(handle); + if (rc) { + pr_err("%s: Client deregister failed (%s)\n", + __func__, handle->client_name); + } + } + + mutex_lock_nested(&handle->dev->dev_mutex, SINGLE_DEPTH_NESTING); + + list_for_each_safe(position, tmp, &handle->dev->client_list) { + + node = list_entry(position, struct msm_dba_client_info, list); + + if (node == handle) { + list_del(&node->list); + break; + } + } + + mutex_unlock(&handle->dev->dev_mutex); + + kfree(handle); + + mutex_unlock(®ister_mutex); + + pr_debug("%s: EXIT (%d)\n", __func__, rc); + return rc; +} +EXPORT_SYMBOL(msm_dba_deregister_client); diff --git a/drivers/video/fbdev/msm/msm_dba/msm_dba_debug.c b/drivers/video/fbdev/msm/msm_dba/msm_dba_debug.c new file mode 100644 index 000000000000..b59dd583b98d --- /dev/null +++ b/drivers/video/fbdev/msm/msm_dba/msm_dba_debug.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/i2c.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/stat.h> + +#include <video/msm_dba.h> +#include "msm_dba_internal.h" + +static inline struct msm_dba_device_info *to_dba_dev(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static ssize_t device_name_rda_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + + return snprintf(buf, PAGE_SIZE, "%s:%d\n", device->chip_name, + device->instance_id); +} + +static ssize_t client_list_rda_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + struct msm_dba_client_info *c; + struct list_head *pos = NULL; + ssize_t bytes = 0; + + mutex_lock(&device->dev_mutex); + + list_for_each(pos, &device->client_list) { + c = list_entry(pos, struct msm_dba_client_info, list); + bytes += snprintf(buf + bytes, (PAGE_SIZE - bytes), "%s\n", + c->client_name); + } + + mutex_unlock(&device->dev_mutex); + + return bytes; +} + +static ssize_t power_status_rda_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + struct msm_dba_client_info *c; + struct list_head *pos = NULL; + ssize_t bytes = 0; + + mutex_lock(&device->dev_mutex); + bytes = snprintf(buf, PAGE_SIZE, "power_status:%d\n", + device->power_status); + + list_for_each(pos, &device->client_list) { + c = list_entry(pos, struct msm_dba_client_info, list); + bytes += snprintf(buf + bytes, (PAGE_SIZE - bytes), + "client: %s, status = %d\n", + c->client_name, c->power_on); + } + + mutex_unlock(&device->dev_mutex); + return bytes; +} + +static ssize_t video_status_rda_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + struct msm_dba_client_info *c; + struct list_head *pos = NULL; + ssize_t bytes = 0; + + mutex_lock(&device->dev_mutex); + bytes = snprintf(buf, PAGE_SIZE, "video_status:%d\n", + device->video_status); + + list_for_each(pos, &device->client_list) { + c = list_entry(pos, struct msm_dba_client_info, list); + bytes += snprintf(buf + bytes, (PAGE_SIZE - bytes), + "client: %s, status = %d\n", + c->client_name, c->video_on); + } + + mutex_unlock(&device->dev_mutex); + return bytes; +} + +static ssize_t audio_status_rda_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + struct msm_dba_client_info *c; + struct list_head *pos = NULL; + ssize_t bytes = 0; + + mutex_lock(&device->dev_mutex); + bytes = snprintf(buf, PAGE_SIZE, "audio_status:%d\n", + device->audio_status); + + list_for_each(pos, &device->client_list) { + c = list_entry(pos, struct msm_dba_client_info, list); + bytes += snprintf(buf + bytes, (PAGE_SIZE - bytes), + "client: %s, status = %d\n", + c->client_name, c->audio_on); + } + + mutex_unlock(&device->dev_mutex); + return bytes; +} + +static ssize_t write_reg_wta_attr(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + char *regstr, *valstr, *ptr; + char str[20]; + long reg = 0; + long val = 0; + int rc = 0; + int len; + + len = strlen(buf); + strlcpy(str, buf, 20); + if (len < 20) + str[len] = '\0'; + else + str[19] = '\0'; + + ptr = str; + regstr = strsep(&ptr, ":"); + valstr = strsep(&ptr, ":"); + + rc = kstrtol(regstr, 0, ®); + if (rc) { + pr_err("%s: kstrol error %d\n", __func__, rc); + } else { + rc = kstrtol(valstr, 0, &val); + if (rc) + pr_err("%s: kstrol error for val %d\n", __func__, rc); + } + + if (!rc) { + mutex_lock(&device->dev_mutex); + + if (device->dev_ops.write_reg) { + rc = device->dev_ops.write_reg(device, + (u32)reg, + (u32)val); + + if (rc) { + pr_err("%s: failed to write reg %d", __func__, + rc); + } + } else { + pr_err("%s: not supported\n", __func__); + } + + mutex_unlock(&device->dev_mutex); + } + + return count; +} + +static ssize_t read_reg_rda_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + ssize_t bytes; + + mutex_lock(&device->dev_mutex); + + bytes = snprintf(buf, PAGE_SIZE, "0x%x\n", device->register_val); + + mutex_unlock(&device->dev_mutex); + + return bytes; +} + +static ssize_t read_reg_wta_attr(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + long reg = 0; + int rc = 0; + u32 val = 0; + + rc = kstrtol(buf, 0, ®); + if (rc) { + pr_err("%s: kstrol error %d\n", __func__, rc); + } else { + mutex_lock(&device->dev_mutex); + + if (device->dev_ops.read_reg) { + rc = device->dev_ops.read_reg(device, + (u32)reg, + &val); + + if (rc) { + pr_err("%s: failed to write reg %d", __func__, + rc); + } else { + device->register_val = val; + } + } else { + pr_err("%s: not supported\n", __func__); + } + + mutex_unlock(&device->dev_mutex); + } + + return count; +} + +static ssize_t dump_info_wta_attr(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct msm_dba_device_info *device = to_dba_dev(dev); + int rc; + + rc = device->dev_ops.dump_debug_info(device, 0x00); + if (rc) + pr_err("%s: failed to dump debug data\n", __func__); + + return count; +} + +static DEVICE_ATTR(device_name, S_IRUGO, device_name_rda_attr, NULL); +static DEVICE_ATTR(client_list, S_IRUGO, client_list_rda_attr, NULL); +static DEVICE_ATTR(power_status, S_IRUGO, power_status_rda_attr, NULL); +static DEVICE_ATTR(video_status, S_IRUGO, video_status_rda_attr, NULL); +static DEVICE_ATTR(audio_status, S_IRUGO, audio_status_rda_attr, NULL); +static DEVICE_ATTR(write_reg, S_IWUSR, NULL, write_reg_wta_attr); +static DEVICE_ATTR(read_reg, S_IRUGO | S_IWUSR, read_reg_rda_attr, + read_reg_wta_attr); +static DEVICE_ATTR(dump_info, S_IWUSR, NULL, dump_info_wta_attr); + +static struct attribute *msm_dba_sysfs_attrs[] = { + &dev_attr_device_name.attr, + &dev_attr_client_list.attr, + &dev_attr_power_status.attr, + &dev_attr_video_status.attr, + &dev_attr_audio_status.attr, + &dev_attr_write_reg.attr, + &dev_attr_read_reg.attr, + &dev_attr_dump_info.attr, + NULL, +}; + +static struct attribute_group msm_dba_sysfs_attr_grp = { + .attrs = msm_dba_sysfs_attrs, +}; + +int msm_dba_helper_sysfs_init(struct device *dev) +{ + int rc = 0; + + if (!dev) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + rc = sysfs_create_group(&dev->kobj, &msm_dba_sysfs_attr_grp); + if (rc) + pr_err("%s: sysfs group creation failed %d\n", __func__, rc); + + return rc; +} diff --git a/drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c b/drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c new file mode 100644 index 000000000000..f6128ae01a75 --- /dev/null +++ b/drivers/video/fbdev/msm/msm_dba/msm_dba_helpers.c @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/i2c.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/delay.h> + +#include <video/msm_dba.h> +#include "msm_dba_internal.h" + +static void msm_dba_helper_hdcp_handler(struct work_struct *work) +{ + struct msm_dba_device_info *dev; + int rc = 0; + + if (!work) { + pr_err("%s: Invalid params\n", __func__); + return; + } + + dev = container_of(work, struct msm_dba_device_info, hdcp_work); + + mutex_lock(&dev->dev_mutex); + if (dev->hdcp_status) { + pr_debug("%s[%s:%d] HDCP is authenticated\n", __func__, + dev->chip_name, dev->instance_id); + mutex_unlock(&dev->dev_mutex); + return; + } + + if (dev->dev_ops.hdcp_reset) { + rc = dev->dev_ops.hdcp_reset(dev); + if (rc) + pr_err("%s[%s:%d] HDCP reset failed\n", __func__, + dev->chip_name, dev->instance_id); + } + + if (dev->dev_ops.hdcp_retry) { + rc = dev->dev_ops.hdcp_retry(dev, MSM_DBA_ASYNC_FLAG); + if (rc) + pr_err("%s[%s:%d] HDCP retry failed\n", __func__, + dev->chip_name, dev->instance_id); + } + mutex_unlock(&dev->dev_mutex); +} + +static void msm_dba_helper_issue_cb(struct msm_dba_device_info *dev, + struct msm_dba_client_info *client, + enum msm_dba_callback_event event) +{ + struct msm_dba_client_info *c; + struct list_head *pos = NULL; + u32 user_mask = 0; + + list_for_each(pos, &dev->client_list) { + c = list_entry(pos, struct msm_dba_client_info, list); + if (client && client == c) + continue; + + user_mask = c->event_mask & event; + if (c->cb && user_mask) + c->cb(c->cb_data, user_mask); + } +} + +static irqreturn_t msm_dba_helper_irq_handler(int irq, void *dev) +{ + struct msm_dba_device_info *device = dev; + u32 mask = 0; + int rc = 0; + bool ret; + + mutex_lock(&device->dev_mutex); + if (device->dev_ops.handle_interrupts) { + rc = device->dev_ops.handle_interrupts(device, &mask); + if (rc) + pr_err("%s: interrupt handler failed\n", __func__); + } + + pr_debug("%s(%s:%d): Eventmask = 0x%x\n", __func__, device->chip_name, + device->instance_id, mask); + if (mask) + msm_dba_helper_issue_cb(device, NULL, mask); + + if ((mask & MSM_DBA_CB_HDCP_LINK_UNAUTHENTICATED) && + device->hdcp_monitor_on) { + ret = queue_work(device->hdcp_wq, &device->hdcp_work); + if (!ret) + pr_err("%s: queue_work failed %d\n", __func__, rc); + } + + if (device->dev_ops.unmask_interrupts) + rc = device->dev_ops.unmask_interrupts(device, mask); + + mutex_unlock(&device->dev_mutex); + return IRQ_HANDLED; +} + +int msm_dba_helper_i2c_write_byte(struct i2c_client *client, + u8 addr, + u8 reg, + u8 val) +{ + int rc = 0; + struct i2c_msg msg; + u8 buf[2] = {reg, val}; + + if (!client) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + pr_debug("%s: [%s:0x02%x] : W[0x%02x, 0x%02x]\n", __func__, + client->name, addr, reg, val); + client->addr = addr; + + msg.addr = addr; + msg.flags = 0; + msg.len = 2; + msg.buf = buf; + + if (i2c_transfer(client->adapter, &msg, 1) < 1) { + pr_err("%s: i2c write failed\n", __func__); + rc = -EIO; + } + + return rc; +} + +int msm_dba_helper_i2c_write_buffer(struct i2c_client *client, + u8 addr, + u8 *buf, + u32 size) +{ + int rc = 0; + struct i2c_msg msg; + + if (!client) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + pr_debug("%s: [%s:0x02%x] : W %d bytes\n", __func__, + client->name, addr, size); + + client->addr = addr; + + msg.addr = addr; + msg.flags = 0; + msg.len = size; + msg.buf = buf; + + if (i2c_transfer(client->adapter, &msg, 1) != 1) { + pr_err("%s: i2c write failed\n", __func__); + rc = -EIO; + } + + return rc; +} +int msm_dba_helper_i2c_read(struct i2c_client *client, + u8 addr, + u8 reg, + char *buf, + u32 size) +{ + int rc = 0; + struct i2c_msg msg[2]; + + if (!client || !buf) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + client->addr = addr; + + msg[0].addr = addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = ® + + msg[1].addr = addr; + msg[1].flags = I2C_M_RD; + msg[1].len = size; + msg[1].buf = buf; + + if (i2c_transfer(client->adapter, msg, 2) != 2) { + pr_err("%s: i2c read failed\n", __func__); + rc = -EIO; + } + + pr_debug("%s: [%s:0x02%x] : R[0x%02x, 0x%02x]\n", __func__, + client->name, addr, reg, *buf); + return rc; +} + +int msm_dba_helper_power_on(void *client, bool on, u32 flags) +{ + int rc = 0; + struct msm_dba_client_info *c = client; + struct msm_dba_device_info *device; + struct msm_dba_client_info *node; + struct list_head *pos = NULL; + bool power_on = false; + + if (!c) { + pr_err("%s: Invalid Params\n", __func__); + return -EINVAL; + } + + device = c->dev; + mutex_lock(&device->dev_mutex); + + /* + * Power on the device if atleast one client powers on the device. But + * power off will be done only after all the clients have called power + * off + */ + if (on == device->power_status) { + c->power_on = on; + } else if (on) { + rc = device->dev_ops.dev_power_on(device, on); + if (rc) + pr_err("%s:%s: power on failed\n", device->chip_name, + __func__); + else + c->power_on = on; + } else { + c->power_on = false; + + list_for_each(pos, &device->client_list) { + node = list_entry(pos, struct msm_dba_client_info, + list); + if (c->power_on) { + power_on = true; + break; + } + } + + if (!power_on) { + rc = device->dev_ops.dev_power_on(device, false); + if (rc) { + pr_err("%s:%s: power off failed\n", + device->chip_name, __func__); + c->power_on = true; + } + } + } + + mutex_unlock(&device->dev_mutex); + return rc; +} + +int msm_dba_helper_video_on(void *client, bool on, + struct msm_dba_video_cfg *cfg, u32 flags) +{ + int rc = 0; + struct msm_dba_client_info *c = client; + struct msm_dba_device_info *device; + struct msm_dba_client_info *node; + struct list_head *pos = NULL; + bool video_on = false; + + if (!c) { + pr_err("%s: Invalid Params\n", __func__); + return -EINVAL; + } + + device = c->dev; + mutex_lock(&device->dev_mutex); + + /* + * Video will be turned on if at least one client turns on video. But + * video off will be done only after all the clients have called video + * off + */ + if (on == device->video_status) { + c->video_on = on; + } else if (on) { + rc = device->dev_ops.dev_video_on(device, cfg, on); + if (rc) + pr_err("%s:%s: video on failed\n", device->chip_name, + __func__); + else + c->video_on = on; + } else { + c->video_on = false; + + list_for_each(pos, &device->client_list) { + node = list_entry(pos, struct msm_dba_client_info, + list); + if (c->video_on) { + video_on = true; + break; + } + } + + if (!video_on) { + rc = device->dev_ops.dev_video_on(device, cfg, false); + if (rc) { + pr_err("%s:%s: video off failed\n", + device->chip_name, __func__); + c->video_on = true; + } + } + } + + mutex_unlock(&device->dev_mutex); + return rc; +} + +int msm_dba_helper_interrupts_enable(void *client, bool on, + u32 event_mask, u32 flags) +{ + struct msm_dba_client_info *c = client; + struct msm_dba_device_info *device; + + if (!c) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + device = c->dev; + mutex_lock(&device->dev_mutex); + + if (on) + c->event_mask = event_mask; + else + c->event_mask = 0; + + mutex_unlock(&device->dev_mutex); + return 0; +} + +int msm_dba_helper_register_irq(struct msm_dba_device_info *dev, + u32 irq, u32 irq_flags) +{ + int rc; + + if (!dev) { + pr_err("%s: invalid params\n", __func__); + return -EINVAL; + } + + mutex_lock(&dev->dev_mutex); + + rc = request_threaded_irq(irq, NULL, msm_dba_helper_irq_handler, + irq_flags, dev->chip_name, dev); + + if (rc) + pr_err("%s:%s: Failed to register irq\n", dev->chip_name, + __func__); + + mutex_unlock(&dev->dev_mutex); + return rc; +} + +int msm_dba_helper_get_caps(void *client, struct msm_dba_capabilities *caps) +{ + struct msm_dba_client_info *c = client; + struct msm_dba_device_info *device; + + if (!c || !caps) { + pr_err("%s: invalid params\n", __func__); + return -EINVAL; + } + + device = c->dev; + mutex_lock(&device->dev_mutex); + + memcpy(caps, &device->caps, sizeof(*caps)); + + mutex_unlock(&device->dev_mutex); + return 0; +} + +int msm_dba_register_hdcp_monitor(struct msm_dba_device_info *dev, bool enable) +{ + int rc = 0; + + if (!dev) { + pr_err("%s: invalid params\n", __func__); + return -EINVAL; + } + + if (enable) { + dev->hdcp_wq = alloc_workqueue("hdcp_monitor(%s:%d)", 0, 0, + dev->chip_name, + dev->instance_id); + if (!dev->hdcp_wq) { + pr_err("%s: failed to allocate wq\n", __func__); + rc = -ENOMEM; + goto fail; + } + + INIT_WORK(&dev->hdcp_work, msm_dba_helper_hdcp_handler); + dev->hdcp_monitor_on = true; + } else if (!enable && dev->hdcp_wq) { + destroy_workqueue(dev->hdcp_wq); + dev->hdcp_wq = NULL; + dev->hdcp_monitor_on = false; + } + +fail: + return rc; +} + +int msm_dba_helper_force_reset(void *client, u32 flags) +{ + struct msm_dba_client_info *c = client; + struct msm_dba_device_info *device; + int rc = 0; + + if (!c) { + pr_err("%s: invalid params\n", __func__); + return -EINVAL; + } + + device = c->dev; + mutex_lock(&device->dev_mutex); + + msm_dba_helper_issue_cb(device, c, MSM_DBA_CB_PRE_RESET); + + if (device->dev_ops.force_reset) + rc = device->dev_ops.force_reset(device, flags); + + if (rc) + pr_err("%s: Force reset failed\n", __func__); + + msm_dba_helper_issue_cb(device, c, MSM_DBA_CB_POST_RESET); + + mutex_unlock(&device->dev_mutex); + return rc; +} diff --git a/drivers/video/fbdev/msm/msm_dba/msm_dba_init.c b/drivers/video/fbdev/msm/msm_dba/msm_dba_init.c new file mode 100644 index 000000000000..96d0d70bccf8 --- /dev/null +++ b/drivers/video/fbdev/msm/msm_dba/msm_dba_init.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/err.h> + +#include <video/msm_dba.h> +#include "msm_dba_internal.h" + +struct msm_dba_device_list { + struct msm_dba_device_info *dev; + struct list_head list; +}; + +static LIST_HEAD(device_list); +static DEFINE_MUTEX(init_mutex); + +int msm_dba_add_probed_device(struct msm_dba_device_info *dev) +{ + struct msm_dba_device_list *node; + + if (!dev) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + mutex_lock(&init_mutex); + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + mutex_unlock(&init_mutex); + return -ENOMEM; + } + + memset(node, 0x0, sizeof(*node)); + node->dev = dev; + list_add(&node->list, &device_list); + + pr_debug("%s: Added new device (%s, %d)", __func__, dev->chip_name, + dev->instance_id); + + mutex_unlock(&init_mutex); + + return 0; +} + +int msm_dba_get_probed_device(struct msm_dba_reg_info *reg, + struct msm_dba_device_info **dev) +{ + int rc = 0; + struct msm_dba_device_list *node; + struct list_head *position = NULL; + + if (!reg || !dev) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + mutex_lock(&init_mutex); + + *dev = NULL; + list_for_each(position, &device_list) { + node = list_entry(position, struct msm_dba_device_list, list); + if (!strcmp(reg->chip_name, node->dev->chip_name) && + reg->instance_id == node->dev->instance_id) { + pr_debug("%s: Found device (%s, %d)\n", __func__, + reg->chip_name, + reg->instance_id); + *dev = node->dev; + break; + } + } + + if (!*dev) { + pr_err("%s: Device not found (%s, %d)\n", __func__, + reg->chip_name, + reg->instance_id); + rc = -ENODEV; + } + + mutex_unlock(&init_mutex); + + return rc; +} + +int msm_dba_remove_probed_device(struct msm_dba_device_info *dev) +{ + struct msm_dba_device_list *node; + struct list_head *position = NULL; + struct list_head *temp = NULL; + + if (!dev) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + mutex_lock(&init_mutex); + + list_for_each_safe(position, temp, &device_list) { + node = list_entry(position, struct msm_dba_device_list, list); + if (node->dev == dev) { + list_del(&node->list); + pr_debug("%s: Removed device (%s, %d)\n", __func__, + dev->chip_name, + dev->instance_id); + kfree(node); + break; + } + } + + mutex_unlock(&init_mutex); + + return 0; +} diff --git a/drivers/video/fbdev/msm/msm_dba/msm_dba_internal.h b/drivers/video/fbdev/msm/msm_dba/msm_dba_internal.h new file mode 100644 index 000000000000..5da673bfcffb --- /dev/null +++ b/drivers/video/fbdev/msm/msm_dba/msm_dba_internal.h @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MSM_DBA_INTERNAL_H +#define _MSM_DBA_INTERNAL_H + +#include <video/msm_dba.h> + +struct msm_dba_client_info; +struct msm_dba_device_info; + +/** + * struct msm_dba_device_ops - Function pointers to device specific operations + * @dev_power_on: Power on operation called by msm_dba_helper_power_on. Mutex + * protection is handled by the caller. + * @dev_video_on: Video on operation called by msm_dba_helper_video_on. Mutex + * protection is handled by the caller. + * @handle_interrupts: Function pointer called when an interrupt is fired. If + * the bridge driver uses msm_dba_helper_register_irq + * for handling interrupts, irq handler will call + * handle_interrupts to figure out the event mask. + * @unmask_interrupts: Function pointer called by irq handler for unmasking + * interrupts. + * @hdcp_reset: Function pointer to reset the HDCP block. This needs to be valid + * if HDCP monitor is used. + * @hdcp_retry: Function pointer to retry HDCP authentication. This needs to be + * valid if HDCP monitor is used. + * @write_reg: Function pointer to write to device specific register. + * @read_reg: Function pointer to read device specific register. + * @force_reset: Function pointer to force reset the device. + * @dump_debug_info: Function pointer to trigger a dump to dmesg. + * + * The device operation function pointers are used if bridge driver uses helper + * functions in place of some client operations. If used, the helper functions + * will call the device function pointers to perform device specific + * programming. + */ +struct msm_dba_device_ops { + int (*dev_power_on)(struct msm_dba_device_info *dev, bool on); + int (*dev_video_on)(struct msm_dba_device_info *dev, + struct msm_dba_video_cfg *cfg, bool on); + int (*handle_interrupts)(struct msm_dba_device_info *dev, u32 *mask); + int (*unmask_interrupts)(struct msm_dba_device_info *dev, u32 mask); + int (*hdcp_reset)(struct msm_dba_device_info *dev); + int (*hdcp_retry)(struct msm_dba_device_info *dev, u32 flags); + int (*write_reg)(struct msm_dba_device_info *dev, u32 reg, u32 val); + int (*read_reg)(struct msm_dba_device_info *dev, u32 reg, u32 *val); + int (*force_reset)(struct msm_dba_device_info *dev, u32 flags); + int (*dump_debug_info)(struct msm_dba_device_info *dev, u32 flags); +}; + +/** + * struct msm_dba_device_info - Device specific information + * @chip_name: chip name + * @instance_id: Instance id + * @caps: Capabilities of the bridge chip + * @dev_ops: function pointers to device specific operations + * @client_ops: function pointers to client operations + * @dev_mutex: mutex for protecting device access + * @hdcp_wq: HDCP workqueue for handling failures. + * @client_list: list head for client list + * @reg_fxn: Function pointer called when a client registers with dba driver + * @dereg_fxn: Function pointer called when a client deregisters. + * @power_status: current power status of device + * @video_status: current video status of device + * @audio_status: current audio status of device + * @hdcp_on: hdcp enable status. + * @enc-on: encryption enable status. + * @hdcp_status: hdcp link status. + * @hdcp_monitor_on: hdcp monitor status + * @register_val: debug field used to support read register. + * + * Structure containing device specific information. This structure is allocated + * by the bridge driver. This structure should be unique to each device. + * + */ +struct msm_dba_device_info { + char chip_name[MSM_DBA_CHIP_NAME_MAX_LEN]; + u32 instance_id; + struct msm_dba_capabilities caps; + struct msm_dba_device_ops dev_ops; + struct msm_dba_ops client_ops; + struct mutex dev_mutex; + struct workqueue_struct *hdcp_wq; + struct work_struct hdcp_work; + struct list_head client_list; + int (*reg_fxn)(struct msm_dba_client_info *client); + int (*dereg_fxn)(struct msm_dba_client_info *client); + + bool power_status; + bool video_status; + bool audio_status; + bool hdcp_on; + bool enc_on; + bool hdcp_status; + bool hdcp_monitor_on; + + /* Debug info */ + u32 register_val; +}; + +/** + * struct msm_dba_client_info - Client specific information + * @dev: pointer to device information + * @client_name: client name + * @power_on: client power on status + * @video_on: client video on status + * @audio_on: client audio on status + * @event_mask: client event mask for callbacks. + * @cb: callback function for the client + * @cb_data: callback data pointer. + * @list: list pointer + * + * This structure is used to uniquely identify a client for a bridge chip. The + * pointer to this structure is returned as a handle from + * msm_dba_register_client. + */ +struct msm_dba_client_info { + struct msm_dba_device_info *dev; + char client_name[MSM_DBA_CLIENT_NAME_LEN]; + bool power_on; + bool video_on; + bool audio_on; + u32 event_mask; + msm_dba_cb cb; + void *cb_data; + struct list_head list; +}; + +/** + * msm_dba_add_probed_device() - Add a new device to the probed devices list. + * @info: Pointer to structure containing the device information. This should be + * allocated by the specific bridge driver and kept until + * msm_dba_remove_probed_device() is called. + * + * Once a bridge chip is initialized and probed, it should add its device to the + * existing list of all probed display bridge chips. This list is maintained by + * the MSM DBA driver and is checked whenever there is a client register + * request. + */ +int msm_dba_add_probed_device(struct msm_dba_device_info *info); + +/** + * msm_dba_remove_probed_device() - Remove a device from the probed devices list + * @info: Pointer to structure containing the device info. This should be the + * same pointer used for msm_dba_add_probed_device(). + * + * Bridge chip driver should call this to remove device from probed list. + */ +int msm_dba_remove_probed_device(struct msm_dba_device_info *info); + +/** + * msm_dba_get_probed_device() - Check if a device is present in the device list + * @reg: Pointer to structure containing the chip info received from the client + * driver + * @info: Pointer to the device info pointer that will be returned if the device + * has been found in the device list + * + * When clients of the MSM DBA driver call msm_dba_register_client(), the MSM + * DBA driver will use this function to check if the specific device requested + * by the client has been probed. If probed, function will return a pointer to + * the device information structure. + */ +int msm_dba_get_probed_device(struct msm_dba_reg_info *reg, + struct msm_dba_device_info **info); + +/** + * msm_dba_helper_i2c_read() - perform an i2c read transaction + * @client: i2c client pointer + * @addr: i2c slave address + * @reg: register where the data should be read from + * @buf: buffer where the read data is stored. + * @size: bytes to read from slave. buffer should be atleast size bytes. + * + * Helper function to perform a read from an i2c slave. Internally this calls + * i2c_transfer(). + */ +int msm_dba_helper_i2c_read(struct i2c_client *client, + u8 addr, + u8 reg, + char *buf, + u32 size); + +/** + * msm_dba_helper_i2c_write_buffer() - write buffer to i2c slave. + * @client: i2c client pointer + * @addr: i2c slave address + * @buf: buffer where the data will be read from. + * @size: bytes to write. + * + * Helper function to perform a write to an i2c slave. Internally this calls + * i2c_transfer(). + */ +int msm_dba_helper_i2c_write_buffer(struct i2c_client *client, + u8 addr, + u8 *buf, + u32 size); + +/** + * msm_dba_helper_i2c_write_byte() - write to a register on an i2c slave. + * @client: i2c client pointer + * @addr: i2c slave address + * @reg: slave register to write to + * @val: data to write. + * + * Helper function to perform a write to an i2c slave. Internally this calls + * i2c_transfer(). + */ +int msm_dba_helper_i2c_write_byte(struct i2c_client *client, + u8 addr, + u8 reg, + u8 val); + +/** + * msm_dba_helper_power_on() - power on bridge chip + * @client: client handle + * @on: on/off + * @flags: flags + * + * This helper function can be used as power_on() function defined in struct + * msm_dba_ops. Internally, this function does some bookkeeping to figure out + * when to actually power on/off the device. If used, bridge driver should + * provide a dev_power_on to do the device specific power change. + */ +int msm_dba_helper_power_on(void *client, bool on, u32 flags); + +/** + * msm_dba_helper_video_on() - video on bridge chip + * @client: client handle + * @on: on/off + * @flags: flags + * + * This helper function can be used as video_on() function defined in struct + * msm_dba_ops. Internally, this function does some bookkeeping to figure out + * when to actually video on/off the device. If used, bridge driver should + * provide a dev_video_on to do the device specific video change. + */ +int msm_dba_helper_video_on(void *client, bool on, + struct msm_dba_video_cfg *cfg, u32 flags); + +/** + * msm_dba_helper_interrupts_enable() - manage interrupt callbacks + * @client: client handle + * @on: on/off + * @events_mask: events on which callbacks are required. + * @flags: flags + * + * This helper function provides the functionality needed for interrupts_enable + * function pointer in struct msm_dba_ops. + */ +int msm_dba_helper_interrupts_enable(void *client, bool on, + u32 events_mask, u32 flags); + +/** + * msm_dba_helper_get_caps() - return device capabilities + * @client: client handle + * @flags: flags + * + * Helper function to replace get_caps function pointer in struct msm_dba_ops + * structure. + */ +int msm_dba_helper_get_caps(void *client, struct msm_dba_capabilities *caps); + +/** + * msm_dba_helper_register_irq() - register irq and handle interrupts. + * @dev: pointer to device structure + * @irq: irq number + * @irq_flags: irq_flags. + * + * Helper function register an irq and handling interrupts. This will attach a + * threaded interrupt handler to the irq provided as input. When the irq + * handler is triggered, handler will call handle_interrupts in the device + * specific functions pointers so that bridge driver can parse the interrupt + * status registers and return the event mask. IRQ handler will use this event + * mask to provide callbacks to the clients. Once the callbacks are done, + * handler will call unmask_interrupts() before returning, + */ +int msm_dba_helper_register_irq(struct msm_dba_device_info *dev, + u32 irq, u32 irq_flags); + +/** + * msm_dba_register_hdcp_monitor() - kicks off monitoring for hdcp failures + * @dev: pointer to device structure. + * @enable: enable/disable + * + * Helper function to enable HDCP monitoring. This should be called only if irq + * is handled through msm dba helper functions. + */ +int msm_dba_register_hdcp_monitor(struct msm_dba_device_info *dev, bool enable); + +/** + * msm_dba_helper_sysfs_init() - create sysfs attributes for debugging + * @dev: pointer to struct device structure. + * + */ +int msm_dba_helper_sysfs_init(struct device *dev); + +/** + * msm_dba_helper_force_reset() - force reset bridge chip + * @client: client handle + * @flags: flags + * + * Helper function to replace force_reset function pointer in struct msm_dba_ops + * structure. Driver should set dev_ops.force_reset to a valid function. + */ +int msm_dba_helper_force_reset(void *client, u32 flags); +#endif /* _MSM_DBA_INTERNAL_H */ |
