| /* |
| * ltc4266-ops.c |
| * |
| * LTC4266 PSE port implementation |
| * |
| * Author: Cradlepoint Technology, Inc. <source@cradlepoint.com> |
| * Kyle Swenson <kswenson@cradlepoint.com> |
| * |
| * Copyright 2017 Cradlepoint Technology, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License 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/kernel.h> |
| #include <linux/mutex.h> |
| #include <linux/interrupt.h> |
| #include <linux/errno.h> |
| #include <linux/i2c.h> |
| #include <linux/device.h> |
| #include <linux/workqueue.h> |
| #include "../pse-port.h" |
| #include "ltc4266.h" |
| #include "ltc4266-regdefs.h" |
| |
| static int ltc4266_voltage(struct device *dev); |
| static int ltc4266_current(struct device *dev); |
| static int ltc4266_last_classification(struct device *dev); |
| static int ltc4266_last_detection(struct device *dev); |
| static int ltc4266_apply_power(struct device *dev, unsigned enabled); |
| static int ltc4266_pd_powered(struct device *dev); |
| static int ltc4266_configure_port(struct device *dev, struct pse_port_config *config); |
| static int ltc4266_get_config(struct device *dev, struct pse_port_config *config); |
| static int ltc4266_port_reset(struct device *dev); |
| static int ltc4266_port_set_current_limit(struct device *dev, int current_limit); |
| static int ltc4266_port_get_current_limit(struct device *dev); |
| static int ltc4266_port_enable_classify(struct device *dev, int enable); |
| static int ltc4266_port_enable_detect(struct device *dev, int enable); |
| |
| |
| struct pse_port_ops ltc4266_port_ops = { |
| .pd_voltage = ltc4266_voltage, |
| .pd_current = ltc4266_current, |
| .last_classification = ltc4266_last_classification, |
| .last_detection = ltc4266_last_detection, |
| .apply_power = ltc4266_apply_power, |
| .pd_powered = ltc4266_pd_powered, |
| .configure = ltc4266_configure_port, |
| .status = NULL, |
| .get_config = ltc4266_get_config, |
| .reset = ltc4266_port_reset, |
| .set_current_limit = ltc4266_port_set_current_limit, |
| .get_current_limit = ltc4266_port_get_current_limit, |
| .enable_classify = ltc4266_port_enable_classify, |
| .enable_detect = ltc4266_port_enable_detect, |
| }; |
| |
| static int ltc4266_voltage(struct device *dev) |
| { |
| return ltc4266_read_iv(to_pse_port_device(dev), READ_VOLTAGE); |
| } |
| static int ltc4266_current(struct device *dev) |
| { |
| return ltc4266_read_iv(to_pse_port_device(dev), READ_CURRENT); |
| } |
| |
| static int ltc4266_last_classification(struct device *dev) |
| { |
| uint8_t classv; |
| |
| classv = LTC4266_PORT_CLASS(ltc4266_port_stat(to_pse_port_device(dev))); |
| return class_map(classv); |
| } |
| |
| static int ltc4266_last_detection(struct device *dev) |
| { |
| uint8_t detv; |
| |
| detv = LTC4266_PORT_DETECT(ltc4266_port_stat(to_pse_port_device(dev))); |
| return detect_map(detv); |
| } |
| |
| static int ltc4266_apply_power(struct device *dev, unsigned enabled) |
| { |
| return ltc4266_port_power(to_pse_port_device(dev), enabled); |
| } |
| static int ltc4266_pd_powered(struct device *dev) |
| { |
| return ltc4266_port_powered(to_pse_port_device(dev)); |
| } |
| |
| static int ltc4266_configure_port(struct device *dev, struct pse_port_config *config) |
| { |
| struct pse_port_device *port = to_pse_port_device(dev); |
| int status = 0; |
| |
| if (!config->enabled) { |
| status = ltc4266_port_mode(port, OPMD_SHUTDOWN); |
| return 0; |
| } |
| if (!config->managed) |
| ltc4266_port_mode(port, OPMD_AUTO); |
| else |
| ltc4266_port_mode(port, OPMD_SEMI); |
| |
| ltc4266_port_highpower(port, 1); |
| ltc4266_port_disconnect_detect_enable(port, config->disconnect_event); |
| ltc4266_port_detect_enable(port, config->detect_enable); |
| ltc4266_port_class_enable(port, config->classify_enable); |
| ltc4266_port_legacy_enable(port, config->legacy_enable); |
| return 0; |
| } |
| |
| static int ltc4266_port_enable_classify(struct device *dev, int enable) |
| { |
| return ltc4266_port_class_enable(to_pse_port_device(dev), enable); |
| } |
| |
| static int ltc4266_port_enable_detect(struct device *dev, int enable) |
| { |
| return ltc4266_port_detect_enable(to_pse_port_device(dev), enable); |
| } |
| |
| int ltc4266_get_config(struct device *dev, struct pse_port_config *config) |
| { |
| uint8_t rt; |
| struct pse_port_device *port = to_pse_port_device(dev); |
| uint8_t pid = port->port_id; |
| struct ltc4266_device *ltc4266 = pse_port_get_clientdata(port); |
| |
| rt = LTC4266_OPMD_VAL(i2c_smbus_read_byte_data(ltc4266->client, LTC4266_OPMD_REG), pid); |
| |
| config->enabled = !(rt == OPMD_SHUTDOWN); |
| config->managed = (rt == OPMD_SEMI); |
| |
| rt = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_DISENA); |
| config->disconnect_event = !!(rt & LTC4266_DISENA_DC(pid)); |
| |
| |
| rt = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_DETENA); |
| config->classify_enable = !!(rt & LTC4266_DETENA_CLS(pid)); |
| config->detect_enable = !!(rt & LTC4266_DETENA_DET(pid)); |
| |
| rt = i2c_smbus_read_byte_data(ltc4266->client, LTC4266_HPMD_REG(pid)); |
| config->legacy_enable = !!(rt & LTC4266_HPMD_LEGEN); |
| config->ping_pong_enable = !!(rt & LTC4266_HPMD_PONGEN); |
| |
| return 0; |
| |
| } |
| static int ltc4266_port_get_current_limit(struct device *dev) |
| { |
| return ltc4266_port_get_icut(to_pse_port_device(dev)); |
| } |
| static int ltc4266_port_set_current_limit(struct device *dev, int current_limit) |
| { |
| int icut; |
| struct pse_port_device *p = to_pse_port_device(dev); |
| |
| icut = ltc4266_port_set_icut(p, current_limit); |
| if (current_limit < 0) |
| return -EINVAL; |
| |
| if (current_limit >= 638) { |
| /* requested current limit is above af levels, so we must assume |
| * userland wants at+ levels |
| */ |
| ltc4266_port_set_ilim(p, 850); |
| } else { |
| ltc4266_port_set_ilim(p, 425); |
| } |
| return 0; |
| } |
| |
| static int ltc4266_port_reset(struct device *dev) |
| { |
| int res = 0; |
| struct pse_port_device *port = to_pse_port_device(dev); |
| struct ltc4266_device *ltc4266 = pse_port_get_clientdata(port); |
| |
| mutex_lock(&port->lock); |
| port->last_class_result = CLASS_INVALID; |
| port->last_detect_result = DETECT_NO_DEVICE; |
| port->power_allocation = 0; |
| |
| res = i2c_smbus_write_byte_data(ltc4266->client, LTC4266_RSTPB, LN(port->port_id)); |
| if (!res) |
| ltc4266_port_init(port); |
| |
| mutex_unlock(&port->lock); |
| return res; |
| } |