blob: 58b360cbdbdc6e7570c419542cb0191f996adfb1 [file] [log] [blame]
/*
* 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;
}