| /* |
| * tps23880-ops.c |
| * |
| * TPS23880 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 <linux/delay.h> |
| #include "../pse-port.h" |
| #include "tps23880.h" |
| #include "tps23880-regdefs.h" |
| |
| |
| static int tps23880_voltage(struct device *dev); |
| static int tps23880_current(struct device *dev); |
| static int tps23880_update_detection(struct device *dev); |
| static int tps23880_apply_power(struct device *dev, unsigned enabled); |
| static int tps23880_pd_powered(struct device *dev); |
| static int tps23880_configure_port(struct device *dev, struct pse_port_config *config); |
| static int tps23880_get_config(struct device *dev, struct pse_port_config *config); |
| static int tps23880_port_reset(struct device *dev); |
| static int tps23880_port_set_power_limit(struct device *dev, int current_limit); |
| static int tps23880_port_get_power_limit(struct device *dev); |
| static int tps23880_port_enable_classify(struct device *dev, int enable); |
| static int tps23880_port_enable_detect(struct device *dev, int enable); |
| |
| /* Caller will lock */ |
| struct pse_port_ops tps23880_port_ops = { |
| .pd_voltage = tps23880_voltage, |
| .pd_current = tps23880_current, |
| .last_classification = NULL, |
| .last_detection = NULL, |
| .update_detection = tps23880_update_detection, |
| .update_classification = tps23880_update_detection, |
| .apply_power = tps23880_apply_power, |
| .pd_powered = tps23880_pd_powered, |
| .configure = tps23880_configure_port, |
| .status = NULL, |
| .get_config = tps23880_get_config, |
| .reset = tps23880_port_reset, |
| .set_power_limit = tps23880_port_set_power_limit, |
| .get_power_limit = tps23880_port_get_power_limit, |
| .enable_classify = tps23880_port_enable_classify, |
| .enable_detect = tps23880_port_enable_detect, |
| }; |
| |
| static int tps23880_voltage(struct device *dev) |
| { |
| struct pse_port_device *port = to_ppd(dev); |
| uint32_t voltage = 0; |
| uint32_t channel_voltage = 0; |
| int res = 0; |
| int i = 0; |
| |
| /* Since each port is composed of two channels, each channel can have it's |
| * own voltage and is independent of the other channel. The only product we've |
| * got with a BT PSE is Ironman, and on that product, every port has two |
| * channels; therefore we'll simply take the maximum of either channel and |
| * report that as the port voltage |
| */ |
| |
| for (i = 0; i < 2; i++) { |
| res = tps23880_read_channel_register(port->channel[i], TPS23880_VOLTAGE_BASE, 4, tps23880_read_register_word); |
| if (res < 0) { |
| dev_warn(dev, "Failed to read channel register in %s, res=%d\n", __func__, res); |
| res = 0; |
| } |
| channel_voltage = ((uint32_t)(res & TPS23880_VOLTAGE_MASK) * 3662); /* uV */ |
| if (channel_voltage > voltage) { |
| voltage = channel_voltage; |
| } |
| } |
| |
| return (int) (voltage/1000); /* Scale from uV to mV */ |
| } |
| static int tps23880_current(struct device *dev) |
| { |
| struct pse_port_device *port = to_ppd(dev); |
| uint32_t c = 0; |
| int res = 0; |
| int i = 0; |
| |
| for (i = 0; i < 2; i++) { |
| res = tps23880_read_channel_register(port->channel[i], TPS23880_CURRENT_BASE, 4, tps23880_read_register_word); |
| if (res < 0) { |
| dev_info(dev, "Failed to read channel register in %s, res=%d\n", __func__, res); |
| res = 0; |
| } |
| c += ((uint32_t)(res & TPS23880_CURRENT_MASK) * 7019); /* 70.19 uA/bit */ |
| } |
| |
| /* We don't average this because the current is the sum of both channels. |
| * We do /100 because 70.19uA/bit but we multiplied by 7019 |
| */ |
| |
| return (int)(c/100); |
| } |
| |
| |
| static int tps23880_update_detection(struct device *dev) |
| { |
| struct pse_port_device *port = to_ppd(dev); |
| |
| tps23880_read_discovery_results(port); |
| return 0; |
| } |
| |
| static int tps23880_apply_power(struct device *dev, unsigned enabled) |
| { |
| struct pse_port_device *port = to_ppd(dev); |
| struct tps23880_device *tdev = port_tps23880_device(port); |
| uint8_t data = 0; |
| uint8_t i = 0; |
| struct power_channel *channel; |
| |
| dev_dbg(&port->dev, " in %s\n", __func__); |
| if (enabled) { |
| for (i = 0 ; i < 2; i++) { |
| channel = port->channel[i]; |
| if (channel->detect_result == DETECT_POWERED_DEVICE && channel->class_result != CLASS_INVALID) { |
| data |= TPS23880_PWRPB_ON(channel_index(channel)); |
| dev_info(&port->dev, "Setting the pwron bit for channel %d\n", channel_index(channel)); |
| } |
| } |
| } else { |
| data = TPS23880_PWRPB_OFF(channel_index(port->channel[0])) | TPS23880_PWRPB_OFF(channel_index(port->channel[1])); |
| } |
| dev_dbg(&port->dev, "writing 0x%02X to %s port %d\n", data, enabled ? "turn on" : "turn off", port->id); |
| return tps23880_write_register(tdev, TPS23880_PWRPB, data); |
| } |
| |
| static int tps23880_pd_powered(struct device *dev) |
| { |
| struct pse_port_device *port = to_pse_port_device(dev); |
| struct tps23880_device *tdev = port_tps23880_device(port); |
| int res = 0; |
| uint8_t pwr_gd; |
| |
| res = tps23880_read_register(tdev, TPS23880_STATPWR); |
| if (res < 0) { |
| dev_err(&port->dev, "Failed to read register 0x10 in %s, i2c_smbus_read_byte_data returned %d\n", __func__, res); |
| return res; |
| } |
| pwr_gd = (res & (TPS23880_STATPWR_PG(channel_index(port->channel[0])) | TPS23880_STATPWR_PG(channel_index(port->channel[1])))); |
| if (port->dual_signature) |
| return (pwr_gd == (TPS23880_STATPWR_PG(channel_index(port->channel[0])) | TPS23880_STATPWR_PG(channel_index(port->channel[1])))); |
| |
| return !!(pwr_gd); |
| } |
| |
| static int tps23880_configure_port(struct device *dev, struct pse_port_config *config) |
| { |
| dev_dbg(dev, " in %s\n", __func__); |
| return 0; |
| } |
| static int tps23880_port_enable_classify(struct device *dev, int enable) |
| { |
| struct pse_port_device *port = to_pse_port_device(dev); |
| struct tps23880_device *tdev = port_tps23880_device(port); |
| uint8_t data = 0x00; |
| |
| data = TPS23880_DETENA_CLS((channel_index(port->channel[0]))) | TPS23880_DETENA_CLS(channel_index(port->channel[1])); |
| dev_dbg(&port->dev, "in %s, operating on port->port-id=%d (port->id=%d) using data=0x%02X\n", __func__, port->port_id, port->id, data); |
| if (enable) { |
| dev_dbg(&port->dev, " Enabling classification for port %d\n", port->port_id); |
| return tps23880_write_register(tdev, TPS23880_DETENA_PB, data); |
| } |
| |
| return tps23880_read_modify_write_byte(tdev, TPS23880_DETENA, data, 0); /*Data is acting as the "mask" here because we want to set zeros */ |
| } |
| static int tps23880_port_enable_detect(struct device *dev, int enable) |
| { |
| struct pse_port_device *port = to_ppd(dev); |
| struct i2c_client *client = container_of(dev->parent, struct i2c_client, dev); |
| struct tps23880_device *tdev = i2c_get_clientdata(client); |
| uint8_t data = 0x00; |
| |
| data = TPS23880_DETENA_DET((channel_index(port->channel[0]))) | TPS23880_DETENA_DET(channel_index(port->channel[1])); |
| |
| if (enable) { |
| dev_dbg(&port->dev, " Enabling detection for port%d by writing 0x%02X to 0x%02X\n", port->id, data, TPS23880_DETENA_PB); |
| return tps23880_write_register(tdev, TPS23880_DETENA_PB, data); |
| } |
| |
| dev_dbg(&port->dev, " Disabling detection for port%d by writing 0x%02X to 0x%02X\n", port->id, data, TPS23880_DETENA); |
| return tps23880_read_modify_write_byte(tdev, TPS23880_DETENA, data, 0); |
| } |
| |
| int tps23880_get_config(struct device *dev, struct pse_port_config *config) |
| { |
| struct pse_port_device *port = to_pse_port_device(dev); |
| |
| dev_dbg(dev, " in %s\n", __func__); |
| |
| config->enabled = port->enabled; |
| config->managed = 1; |
| config->disconnect_event = (tps23880_read_register(port_tps23880_device(port), TPS23880_DISENA) & (TPS23880_DISENA_DC(channel_index(port->channel[0])) | TPS23880_DISENA_DC(channel_index(port->channel[1])))) ? 1 : 0; |
| |
| config->detect_enable = (tps23880_read_register(port_tps23880_device(port), TPS23880_DETENA) & (TPS23880_DETENA_DET(channel_index(port->channel[0])) | TPS23880_DETENA_DET(channel_index(port->channel[1])))) ? 1 : 0; |
| config->classify_enable = (tps23880_read_register(port_tps23880_device(port), TPS23880_DETENA) & (TPS23880_DETENA_CLS(channel_index(port->channel[0])) | TPS23880_DETENA_CLS(channel_index(port->channel[1])))) ? 1 : 0; |
| config->legacy_enable = 0; |
| config->current_limit = 0; |
| return 0; |
| |
| } |
| static int tps23880_port_get_power_limit(struct device *dev) |
| { |
| dev_dbg(dev, " in %s\n", __func__); |
| struct pse_port_device * port = to_ppd(dev); |
| int power_limit = 0; |
| |
| power_limit = tps23880_read_register(port_tps23880_device(port), TPS23880_4P_POLICE(channel_index(port->channel[0]))); |
| |
| dev_info(dev, "in %s, read 0x%02X = %d mW\n", __func__, power_limit, (power_limit*1000)/2); |
| return (1000*power_limit)/2; |
| } |
| |
| static int tps23880_port_set_power_limit(struct device *dev, int power_limit) |
| { |
| struct pse_port_device * port = to_ppd(dev); |
| uint8_t police_limit = (uint8_t) ((power_limit * 2)/1000); |
| dev_info(dev, "Setting the power limit to %d mW, writing 0x%02X\n", power_limit, police_limit); |
| tps23880_write_register(port_tps23880_device(port), TPS23880_2P_POLICE(channel_index(port->channel[0])), police_limit); |
| |
| tps23880_write_register(port_tps23880_device(port), TPS23880_4P_POLICE(channel_index(port->channel[0])), police_limit); |
| return 0; |
| } |
| |
| |
| static int tps23880_port_reset(struct device *dev) |
| { |
| struct pse_port_device *port = to_ppd(dev); |
| int ret = 0; |
| |
| /* Tickle the reset push-button register for this port*/ |
| /* And then re-init the port */ |
| |
| pse_port_reset(port); |
| ret = tps23880_port_init(port); |
| |
| if (ret < 0) { |
| dev_warn(dev, "Port init has failed\n"); |
| goto reset_unlock_ret; |
| } |
| |
| ret = port->ops->enable_detect(dev, 1); |
| if (ret < 0) { |
| dev_warn(dev, "Failed to enable detection\n"); |
| goto reset_unlock_ret; |
| } |
| |
| ret = port->ops->enable_classify(dev, 1); |
| if (ret < 0) { |
| dev_warn(dev, "Failed to enable classification\n"); |
| goto reset_unlock_ret; |
| } |
| |
| reset_unlock_ret: |
| return ret; |
| } |