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