Jetpack/kernel/nvidia/drivers/extcon/extcon-disp-state.c

179 lines
4.8 KiB
C

/*
* extcon-disp-state - extcon driver for display accessory detection
* compatible with switch-mid
*
* Copyright (c) 2018-2019, NVIDIA CORPORATION, All rights reserved.
*
* 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/module.h>
#include <linux/version.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/extcon/extcon-disp.h>
#include <linux/extcon.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#if KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE
#include "extcon.h"
#endif
#define EXTCON_DISP_AUX_BASE 2
#define EXTCON_DISP_MAX_AUX_CONNECTORS 4
struct disp_state_extcon_info {
struct device *dev;
struct extcon_dev *edev;
};
static const unsigned int disp_state_extcon_cables[] = {
EXTCON_DISP_HDMI,
EXTCON_DISP_DP,
EXTCON_DISP_AUDIO_AUX0,
EXTCON_DISP_AUDIO_AUX1,
EXTCON_DISP_AUDIO_AUX2,
EXTCON_DISP_AUDIO_AUX3,
EXTCON_DISP_DSIHPD,
EXTCON_DISP_HDMI2,
EXTCON_NONE,
};
static struct disp_state_extcon_info *disp_extcon_info;
static struct class_compat *switch_class;
void disp_state_extcon_switch_report(const unsigned int cable, bool state)
{
if ((!disp_extcon_info) || (!disp_extcon_info->edev) ||
(!disp_extcon_info->dev))
return;
#if KERNEL_VERSION(4, 9, 0) > LINUX_VERSION_CODE
if (extcon_get_cable_state_(disp_extcon_info->edev, cable) == state) {
#else
if (extcon_get_state(disp_extcon_info->edev, cable) == state) {
#endif
dev_info(disp_extcon_info->dev, "cable %d state %d already set.\n",
cable, state);
return;
}
#if KERNEL_VERSION(4, 9, 0) > LINUX_VERSION_CODE
extcon_set_cable_state_(disp_extcon_info->edev, cable, state);
#else
extcon_set_state_sync(disp_extcon_info->edev, cable, state);
#endif
dev_info(disp_extcon_info->dev, "cable %d state %d\n", cable, state);
}
void disp_state_extcon_aux_report(const unsigned int aux_idx, bool state)
{
if ((!disp_extcon_info) || (!disp_extcon_info->edev) ||
(!disp_extcon_info->dev))
return;
if (aux_idx < EXTCON_DISP_MAX_AUX_CONNECTORS) {
disp_state_extcon_switch_report(
disp_state_extcon_cables[EXTCON_DISP_AUX_BASE +
aux_idx], state);
} else {
dev_info(disp_extcon_info->dev, "AUX%d is invalid\n", aux_idx);
return;
}
}
static int disp_state_extcon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct disp_state_extcon_info *info;
int retval = 0;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = dev;
info->edev = devm_extcon_dev_allocate(dev, disp_state_extcon_cables);
if (IS_ERR(info->edev)) {
dev_err(dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
switch_class = class_compat_register("switch_disp_state");
if (WARN(!switch_class, "cannot allocate"))
return -ENOMEM;
info->edev->name = "disp_state_ext";
retval = devm_extcon_dev_register(dev, info->edev);
if (retval < 0) {
class_compat_unregister(switch_class);
dev_err(dev, "failed to register extcon device\n");
return retval;
}
retval = class_compat_create_link(switch_class, dev, NULL);
if (retval)
dev_warn(dev, "Create compatibility class link failed\n");
platform_set_drvdata(pdev, info);
disp_extcon_info = info;
return 0;
}
static int disp_state_extcon_remove(struct platform_device *pdev)
{
struct disp_state_extcon_info *extcon_data = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
class_compat_unregister(switch_class);
class_compat_remove_link(switch_class, dev, NULL);
extcon_dev_unregister(extcon_data->edev);
disp_extcon_info = NULL;
return 0;
}
static const struct of_device_id of_extcon_disp_state_tbl[] = {
{ .compatible = "extcon-disp-state", },
{ /* end */ }
};
MODULE_DEVICE_TABLE(of, of_extcon_disp_state_tbl);
static struct platform_driver disp_state_extcon_driver = {
.probe = disp_state_extcon_probe,
.remove = disp_state_extcon_remove,
.driver = {
.name = "extcon-disp-state",
.owner = THIS_MODULE,
.of_match_table = of_extcon_disp_state_tbl,
},
};
static int __init disp_state_extcon_driver_init(void)
{
return platform_driver_register(&disp_state_extcon_driver);
}
subsys_initcall_sync(disp_state_extcon_driver_init);
static void __exit disp_state_extcon_driver_exit(void)
{
platform_driver_unregister(&disp_state_extcon_driver);
}
module_exit(disp_state_extcon_driver_exit);
MODULE_AUTHOR("Dara Ramesh (dramesh@nvidia.com)");
MODULE_DESCRIPTION("EXTCON Extcon driver");
MODULE_LICENSE("GPL v2");