drivers/hwmon/net_asic.c (183 lines of code) (raw):

/* * net_asic.c - The i2c driver for net_asic * * Copyright 2019-present Facebook. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <linux/errno.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/delay.h> #include <linux/jiffies.h> #define BUILD_U64(_high, _low) (((u_int64_t)(_high) << 32) | (_low)) #define NET_ASIC_DELAY 10 #define NET_NCSI_MSG_LEN 4 #define NET_ASIC_REG_BASE 0x80000 #define NET_ASIC_TEMP_REG_BASE NET_ASIC_REG_BASE #define HEARTBEAT_REFRESH_INTERVAL (HZ / 10) #define NET_ASIC_HEARTBEAT_LSB_REG (NET_ASIC_REG_BASE + 0x108) #define NET_ASIC_HEARTBEAT_MSB_REG (NET_ASIC_REG_BASE + 0x10C) /* * This is because net_asic sometimes return random temperatures. * after discussion, let's return -1 if temperature is bigger than 150 digree. */ #define NET_ASIC_IMPOSSIBLE_TEMP (150 * 1000) enum sdk_status { NOT_RUNNING, RUNNING, }; struct heart_beat { /* * When SDK is running, the heart beat value should keep increasing * every 100ms, or it should be keep the same. */ u_int64_t curr; u_int64_t last; }; struct net_asic_data { struct i2c_client *client; struct mutex update_lock; /* mutex protect updates */ unsigned long last_updated; /* In jiffies */ struct heart_beat heartbeat; unsigned int sdk_status; }; /* * Check sdk is running or not * return value: 1-running, 0-not running * When SDK is running, the heart beat value should keep increasing * every 100ms, or it should be keep the same. */ static inline int sdk_is_running(struct heart_beat heartbeat) { return heartbeat.curr > heartbeat.last ? RUNNING : NOT_RUNNING; } static int net_asic_i2c_read(struct net_asic_data *data, unsigned int reg, unsigned int *value) { struct i2c_client *client = data->client; char send_buf[NET_NCSI_MSG_LEN], read_buf[NET_NCSI_MSG_LEN]; int index, bit_shift; for (index = 0; index < NET_NCSI_MSG_LEN; index++) { // Convert 32-bits address to buffer bit_shift = (NET_NCSI_MSG_LEN - 1 - index) * 8; send_buf[index] = (reg & (0xff << bit_shift)) >> bit_shift; } mutex_lock(&data->update_lock); if(i2c_master_send(client, send_buf, NET_NCSI_MSG_LEN) != NET_NCSI_MSG_LEN) { goto err_exit; } if(i2c_master_recv(client, read_buf, NET_NCSI_MSG_LEN) != NET_NCSI_MSG_LEN) { goto err_exit; } mutex_unlock(&data->update_lock); *value = 0; for (index = 0; index < NET_NCSI_MSG_LEN; index++) { // Convert read buffer to 32-bits value bit_shift = (NET_NCSI_MSG_LEN - 1 - index) * 8; *value |= (read_buf[index] << bit_shift); } return 0; err_exit: mutex_unlock(&data->update_lock); return -1; } static int net_asic_heartbeat_update(struct net_asic_data *data) { u_int32_t lsb, msb; if(time_after(jiffies, data->last_updated + HEARTBEAT_REFRESH_INTERVAL)) { if(net_asic_i2c_read(data, NET_ASIC_HEARTBEAT_LSB_REG, &lsb)) goto err_exit; if(net_asic_i2c_read(data, NET_ASIC_HEARTBEAT_MSB_REG, &msb)) goto err_exit; data->last_updated = jiffies; data->heartbeat.curr = BUILD_U64(msb, lsb); if(sdk_is_running(data->heartbeat) == RUNNING) { data->sdk_status = RUNNING; } else { data->sdk_status = NOT_RUNNING; } data->heartbeat.last = data->heartbeat.curr; } return 0; err_exit: return -1; } static int net_asic_sdk_status_show(struct device *dev, struct device_attribute *dev_attr, char *buf) { struct net_asic_data *data = dev_get_drvdata(dev); if(net_asic_heartbeat_update(data)) return -1; return sprintf(buf, "%d\n", data->sdk_status == RUNNING ? 1 : 0); } /* sysfs callback function */ static ssize_t net_asic_temp_show(struct device *dev, struct device_attribute *dev_attr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); struct net_asic_data *data = dev_get_drvdata(dev); int value = -1; int file_index = attr->index - 1; if(file_index < 0) return -1; int reg = file_index * NET_NCSI_MSG_LEN + NET_ASIC_TEMP_REG_BASE; /* * Check sdk is running or not * return value: 1-running, 0-not running * When SDK is running, the heart beat value should keep increasing * every 100ms, or it should be keep the same. * Only when SDK is running, the temperature values are valid. */ if (net_asic_heartbeat_update(data)) return -1; /* if SDK is running in the last 100ms, return the temperature */ if (data->sdk_status == RUNNING) { /* * This is because net_asic sometimes return random temperatures. * After discussion, let's return -1 if temperature is bigger than * NET_ASIC_IMPOSSIBLE_TEMP digree. */ if (net_asic_i2c_read(data, reg, &value) || value > NET_ASIC_IMPOSSIBLE_TEMP) goto err_exit; return sprintf(buf, "%d\n", value); } err_exit: return -1; } static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, net_asic_temp_show, NULL, 1); static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, net_asic_temp_show, NULL, 2); static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, net_asic_temp_show, NULL, 3); static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, net_asic_temp_show, NULL, 4); static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, net_asic_temp_show, NULL, 5); static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, net_asic_temp_show, NULL, 6); static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, net_asic_temp_show, NULL, 7); static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, net_asic_temp_show, NULL, 8); static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, net_asic_temp_show, NULL, 9); static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO, net_asic_temp_show, NULL, 10); static SENSOR_DEVICE_ATTR(temp11_input, S_IRUGO, net_asic_temp_show, NULL, 11); static SENSOR_DEVICE_ATTR(temp12_input, S_IRUGO, net_asic_temp_show, NULL, 12); static SENSOR_DEVICE_ATTR(sdk_status, S_IRUGO, net_asic_sdk_status_show, NULL, 0); static struct attribute *net_asic_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp2_input.dev_attr.attr, &sensor_dev_attr_temp3_input.dev_attr.attr, &sensor_dev_attr_temp4_input.dev_attr.attr, &sensor_dev_attr_temp5_input.dev_attr.attr, &sensor_dev_attr_temp6_input.dev_attr.attr, &sensor_dev_attr_temp7_input.dev_attr.attr, &sensor_dev_attr_temp8_input.dev_attr.attr, &sensor_dev_attr_temp9_input.dev_attr.attr, &sensor_dev_attr_temp10_input.dev_attr.attr, &sensor_dev_attr_temp11_input.dev_attr.attr, &sensor_dev_attr_temp12_input.dev_attr.attr, &sensor_dev_attr_sdk_status.dev_attr.attr, NULL, }; ATTRIBUTE_GROUPS(net_asic); static int net_asic_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct net_asic_data *data; struct device *dev = &client->dev; struct device *hwmon_dev; u_int32_t lsb = 0xffffffff; u_int32_t msb = 0xffffffff; data = devm_kzalloc(dev, sizeof(struct net_asic_data), GFP_KERNEL); if (!data) return -ENOMEM; if (!i2c_verify_client(dev)) { return -1; } data->client = client; mutex_init(&data->update_lock); net_asic_i2c_read(data, NET_ASIC_HEARTBEAT_LSB_REG, &lsb); net_asic_i2c_read(data, NET_ASIC_HEARTBEAT_MSB_REG, &msb); data->heartbeat.last = BUILD_U64(msb, lsb); data->last_updated = jiffies; data->sdk_status = NOT_RUNNING; hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, data, net_asic_groups); return PTR_ERR_OR_ZERO(hwmon_dev); } /* net_asic id */ static const struct i2c_device_id net_asic_id[] = { {"net_asic", 0}, { }, }; MODULE_DEVICE_TABLE(i2c, net_asic_id); static struct i2c_driver net_asic_driver = { .driver = { .name = "net_asic", }, .probe = net_asic_probe, .id_table = net_asic_id, }; module_i2c_driver(net_asic_driver); MODULE_AUTHOR("Tianhui Xu <tianhui@celestica.com>"); MODULE_DESCRIPTION("NET_ASIC Driver"); MODULE_LICENSE("GPL");