// SPDX-License-Identifier: GPL-2.0 /* * * Copyright (C) 2011-2022 Samsung, Inc. * Author: Dongrak Shin * */ /* usb notify layer v3.7 */ #include #include #include #include #include #include #include #include #if defined(CONFIG_USB_HW_PARAM) #include #endif struct notify_data { struct class *host_notify_class; atomic_t device_count; struct mutex host_notify_lock; }; static struct notify_data host_notify; static ssize_t mode_show( struct device *dev, struct device_attribute *attr, char *buf) { struct host_notify_dev *ndev = (struct host_notify_dev *) dev_get_drvdata(dev); char *mode; switch (ndev->mode) { case NOTIFY_HOST_MODE: mode = "HOST"; break; case NOTIFY_PERIPHERAL_MODE: mode = "PERIPHERAL"; break; case NOTIFY_TEST_MODE: mode = "TEST"; break; case NOTIFY_NONE_MODE: default: mode = "NONE"; break; } return snprintf(buf, sizeof(mode)+1, "%s\n", mode); } static ssize_t mode_store( struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct host_notify_dev *ndev = (struct host_notify_dev *) dev_get_drvdata(dev); char *mode; size_t ret = -ENOMEM; int sret = 0; if (size < strlen(buf)) goto error; mode = kzalloc(size+1, GFP_KERNEL); if (!mode) goto error; sret = sscanf(buf, "%s", mode); if (sret != 1) goto error1; if (ndev->set_mode) { pr_info("host_notify: set mode %s\n", mode); if (!strncmp(mode, "HOST", 4)) ndev->set_mode(NOTIFY_SET_ON); else if (!strncmp(mode, "NONE", 4)) ndev->set_mode(NOTIFY_SET_OFF); } ret = size; error1: kfree(mode); error: return ret; } static ssize_t booster_show(struct device *dev, struct device_attribute *attr, char *buf) { struct host_notify_dev *ndev = (struct host_notify_dev *) dev_get_drvdata(dev); char *booster; switch (ndev->booster) { case NOTIFY_POWER_ON: booster = "ON"; break; case NOTIFY_POWER_OFF: default: booster = "OFF"; break; } pr_info("host_notify: read booster %s\n", booster); return snprintf(buf, sizeof(booster)+1, "%s\n", booster); } static ssize_t booster_store( struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct host_notify_dev *ndev = (struct host_notify_dev *) dev_get_drvdata(dev); char *booster; size_t ret = -ENOMEM; int sret = 0; if (size < strlen(buf)) goto error; booster = kzalloc(size+1, GFP_KERNEL); if (!booster) goto error; sret = sscanf(buf, "%s", booster); if (sret != 1) goto error1; if (ndev->set_booster) { pr_info("host_notify: set booster %s\n", booster); if (!strncmp(booster, "ON", 2)) { ndev->set_booster(NOTIFY_SET_ON); ndev->mode = NOTIFY_TEST_MODE; } else if (!strncmp(booster, "OFF", 3)) { ndev->set_booster(NOTIFY_SET_OFF); ndev->mode = NOTIFY_NONE_MODE; } } ret = size; error1: kfree(booster); error: return ret; } static DEVICE_ATTR_RW(mode); static DEVICE_ATTR_RW(booster); static struct attribute *host_notify_attrs[] = { &dev_attr_mode.attr, &dev_attr_booster.attr, NULL, }; static struct attribute_group host_notify_attr_grp = { .attrs = host_notify_attrs, }; char *host_state_string(int type) { switch (type) { case NOTIFY_HOST_NONE: return "none"; case NOTIFY_HOST_ADD: return "add"; case NOTIFY_HOST_REMOVE: return "remove"; case NOTIFY_HOST_OVERCURRENT: return "overcurrent"; case NOTIFY_HOST_LOWBATT: return "lowbatt"; case NOTIFY_HOST_BLOCK: return "block"; case NOTIFY_HOST_SOURCE: return "source"; case NOTIFY_HOST_SINK: return "sink"; case NOTIFY_HOST_UNKNOWN: default: return "unknown"; } } static int check_state_type(int state) { int ret = 0; switch (state) { case NOTIFY_HOST_ADD: case NOTIFY_HOST_REMOVE: case NOTIFY_HOST_BLOCK: ret = NOTIFY_HOST_STATE; break; case NOTIFY_HOST_OVERCURRENT: case NOTIFY_HOST_LOWBATT: case NOTIFY_HOST_SOURCE: case NOTIFY_HOST_SINK: ret = NOTIFY_POWER_STATE; break; case NOTIFY_HOST_NONE: case NOTIFY_HOST_UNKNOWN: default: ret = NOTIFY_UNKNOWN_STATE; break; } return ret; } int host_state_notify(struct host_notify_dev *ndev, int state) { int type = 0; if (!ndev->dev) { pr_err("host_notify: %s ndev->dev is NULL\n", __func__); return -ENXIO; } mutex_lock(&host_notify.host_notify_lock); pr_info("host_notify: ndev name=%s: state=%s\n", ndev->name, host_state_string(state)); type = check_state_type(state); if (type == NOTIFY_HOST_STATE) { if (ndev->host_state != state) { pr_info("host_notify: host_state (%s->%s)\n", host_state_string(ndev->host_state), host_state_string(state)); ndev->host_state = state; ndev->host_change = 1; kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE); ndev->host_change = 0; #if defined(CONFIG_USB_HW_PARAM) if (state == NOTIFY_HOST_ADD) inc_hw_param_host(ndev, USB_CCIC_OTG_USE_COUNT); #endif } } else if (type == NOTIFY_POWER_STATE) { if (ndev->power_state != state) { pr_info("host_notify: power_state (%s->%s)\n", host_state_string(ndev->power_state), host_state_string(state)); ndev->power_state = state; ndev->power_change = 1; kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE); ndev->power_change = 0; #if defined(CONFIG_USB_HW_PARAM) if (state == NOTIFY_HOST_OVERCURRENT) inc_hw_param_host(ndev, USB_CCIC_OVC_COUNT); #endif } } else { ndev->host_state = state; ndev->power_state = state; } mutex_unlock(&host_notify.host_notify_lock); return 0; } EXPORT_SYMBOL_GPL(host_state_notify); static int host_notify_uevent(struct device *dev, struct kobj_uevent_env *env) { struct host_notify_dev *ndev = (struct host_notify_dev *) dev_get_drvdata(dev); char *state; int state_type; if (!ndev) { /* this happens when the device is first created */ return 0; } if (ndev->host_change) state_type = ndev->host_state; else if (ndev->power_change) state_type = ndev->power_state; else state_type = NOTIFY_HOST_NONE; switch (state_type) { case NOTIFY_HOST_ADD: state = "ADD"; break; case NOTIFY_HOST_REMOVE: state = "REMOVE"; break; case NOTIFY_HOST_OVERCURRENT: state = "OVERCURRENT"; break; case NOTIFY_HOST_LOWBATT: state = "LOWBATT"; break; case NOTIFY_HOST_BLOCK: state = "BLOCK"; break; case NOTIFY_HOST_SOURCE: state = "SOURCE"; break; case NOTIFY_HOST_SINK: state = "SINK"; break; case NOTIFY_HOST_UNKNOWN: state = "UNKNOWN"; break; case NOTIFY_HOST_NONE: default: return 0; } if (add_uevent_var(env, "DEVNAME=%s", ndev->dev->kobj.name)) return -ENOMEM; if (add_uevent_var(env, "STATE=%s", state)) return -ENOMEM; return 0; } static int create_notify_class(void) { if (!host_notify.host_notify_class) { host_notify.host_notify_class = class_create(THIS_MODULE, "host_notify"); if (IS_ERR(host_notify.host_notify_class)) return PTR_ERR(host_notify.host_notify_class); atomic_set(&host_notify.device_count, 0); mutex_init(&host_notify.host_notify_lock); host_notify.host_notify_class->dev_uevent = host_notify_uevent; } return 0; } int host_notify_dev_register(struct host_notify_dev *ndev) { int ret; if (!host_notify.host_notify_class) { ret = create_notify_class(); if (ret < 0) return ret; } ndev->index = atomic_inc_return(&host_notify.device_count); ndev->dev = device_create(host_notify.host_notify_class, NULL, MKDEV(0, ndev->index), NULL, "%s", ndev->name); if (IS_ERR(ndev->dev)) return PTR_ERR(ndev->dev); ret = sysfs_create_group(&ndev->dev->kobj, &host_notify_attr_grp); if (ret < 0) { device_destroy(host_notify.host_notify_class, MKDEV(0, ndev->index)); return ret; } dev_set_drvdata(ndev->dev, ndev); ndev->host_state = NOTIFY_HOST_NONE; ndev->power_state = NOTIFY_HOST_SINK; return 0; } EXPORT_SYMBOL_GPL(host_notify_dev_register); void host_notify_dev_unregister(struct host_notify_dev *ndev) { ndev->host_state = NOTIFY_HOST_NONE; ndev->power_state = NOTIFY_HOST_SINK; sysfs_remove_group(&ndev->dev->kobj, &host_notify_attr_grp); dev_set_drvdata(ndev->dev, NULL); device_destroy(host_notify.host_notify_class, MKDEV(0, ndev->index)); ndev->dev = NULL; } EXPORT_SYMBOL_GPL(host_notify_dev_unregister); int notify_class_init(void) { return create_notify_class(); } void notify_class_exit(void) { class_destroy(host_notify.host_notify_class); }