162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * An hwmon driver for the Analog Devices AD7414 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2006 Stefan Roese <sr at denx.de>, DENX Software Engineering 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (c) 2008 PIKA Technologies 862306a36Sopenharmony_ci * Sean MacLennan <smaclennan@pikatech.com> 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Copyright (c) 2008 Spansion Inc. 1162306a36Sopenharmony_ci * Frank Edelhaeuser <frank.edelhaeuser at spansion.com> 1262306a36Sopenharmony_ci * (converted to "new style" I2C driver model, removed checkpatch.pl warnings) 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * Based on ad7418.c 1562306a36Sopenharmony_ci * Copyright 2006 Tower Technologies, Alessandro Zummo <a.zummo at towertech.it> 1662306a36Sopenharmony_ci */ 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <linux/module.h> 1962306a36Sopenharmony_ci#include <linux/jiffies.h> 2062306a36Sopenharmony_ci#include <linux/i2c.h> 2162306a36Sopenharmony_ci#include <linux/hwmon.h> 2262306a36Sopenharmony_ci#include <linux/hwmon-sysfs.h> 2362306a36Sopenharmony_ci#include <linux/err.h> 2462306a36Sopenharmony_ci#include <linux/mutex.h> 2562306a36Sopenharmony_ci#include <linux/sysfs.h> 2662306a36Sopenharmony_ci#include <linux/slab.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* AD7414 registers */ 3062306a36Sopenharmony_ci#define AD7414_REG_TEMP 0x00 3162306a36Sopenharmony_ci#define AD7414_REG_CONF 0x01 3262306a36Sopenharmony_ci#define AD7414_REG_T_HIGH 0x02 3362306a36Sopenharmony_ci#define AD7414_REG_T_LOW 0x03 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic u8 AD7414_REG_LIMIT[] = { AD7414_REG_T_HIGH, AD7414_REG_T_LOW }; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistruct ad7414_data { 3862306a36Sopenharmony_ci struct i2c_client *client; 3962306a36Sopenharmony_ci struct mutex lock; /* atomic read data updates */ 4062306a36Sopenharmony_ci bool valid; /* true if following fields are valid */ 4162306a36Sopenharmony_ci unsigned long next_update; /* In jiffies */ 4262306a36Sopenharmony_ci s16 temp_input; /* Register values */ 4362306a36Sopenharmony_ci s8 temps[ARRAY_SIZE(AD7414_REG_LIMIT)]; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* REG: (0.25C/bit, two's complement) << 6 */ 4762306a36Sopenharmony_cistatic inline int ad7414_temp_from_reg(s16 reg) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci /* 5062306a36Sopenharmony_ci * use integer division instead of equivalent right shift to 5162306a36Sopenharmony_ci * guarantee arithmetic shift and preserve the sign 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci return ((int)reg / 64) * 250; 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic inline int ad7414_read(struct i2c_client *client, u8 reg) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci if (reg == AD7414_REG_TEMP) 5962306a36Sopenharmony_ci return i2c_smbus_read_word_swapped(client, reg); 6062306a36Sopenharmony_ci else 6162306a36Sopenharmony_ci return i2c_smbus_read_byte_data(client, reg); 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic inline int ad7414_write(struct i2c_client *client, u8 reg, u8 value) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci return i2c_smbus_write_byte_data(client, reg, value); 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic struct ad7414_data *ad7414_update_device(struct device *dev) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci struct ad7414_data *data = dev_get_drvdata(dev); 7262306a36Sopenharmony_ci struct i2c_client *client = data->client; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci mutex_lock(&data->lock); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (time_after(jiffies, data->next_update) || !data->valid) { 7762306a36Sopenharmony_ci int value, i; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci dev_dbg(&client->dev, "starting ad7414 update\n"); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci value = ad7414_read(client, AD7414_REG_TEMP); 8262306a36Sopenharmony_ci if (value < 0) 8362306a36Sopenharmony_ci dev_dbg(&client->dev, "AD7414_REG_TEMP err %d\n", 8462306a36Sopenharmony_ci value); 8562306a36Sopenharmony_ci else 8662306a36Sopenharmony_ci data->temp_input = value; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(AD7414_REG_LIMIT); ++i) { 8962306a36Sopenharmony_ci value = ad7414_read(client, AD7414_REG_LIMIT[i]); 9062306a36Sopenharmony_ci if (value < 0) 9162306a36Sopenharmony_ci dev_dbg(&client->dev, "AD7414 reg %d err %d\n", 9262306a36Sopenharmony_ci AD7414_REG_LIMIT[i], value); 9362306a36Sopenharmony_ci else 9462306a36Sopenharmony_ci data->temps[i] = value; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci data->next_update = jiffies + HZ + HZ / 2; 9862306a36Sopenharmony_ci data->valid = true; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci mutex_unlock(&data->lock); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return data; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic ssize_t temp_input_show(struct device *dev, 10762306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct ad7414_data *data = ad7414_update_device(dev); 11062306a36Sopenharmony_ci return sprintf(buf, "%d\n", ad7414_temp_from_reg(data->temp_input)); 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic ssize_t max_min_show(struct device *dev, struct device_attribute *attr, 11562306a36Sopenharmony_ci char *buf) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci int index = to_sensor_dev_attr(attr)->index; 11862306a36Sopenharmony_ci struct ad7414_data *data = ad7414_update_device(dev); 11962306a36Sopenharmony_ci return sprintf(buf, "%d\n", data->temps[index] * 1000); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic ssize_t max_min_store(struct device *dev, 12362306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, 12462306a36Sopenharmony_ci size_t count) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct ad7414_data *data = dev_get_drvdata(dev); 12762306a36Sopenharmony_ci struct i2c_client *client = data->client; 12862306a36Sopenharmony_ci int index = to_sensor_dev_attr(attr)->index; 12962306a36Sopenharmony_ci u8 reg = AD7414_REG_LIMIT[index]; 13062306a36Sopenharmony_ci long temp; 13162306a36Sopenharmony_ci int ret = kstrtol(buf, 10, &temp); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (ret < 0) 13462306a36Sopenharmony_ci return ret; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci temp = clamp_val(temp, -40000, 85000); 13762306a36Sopenharmony_ci temp = (temp + (temp < 0 ? -500 : 500)) / 1000; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci mutex_lock(&data->lock); 14062306a36Sopenharmony_ci data->temps[index] = temp; 14162306a36Sopenharmony_ci ad7414_write(client, reg, temp); 14262306a36Sopenharmony_ci mutex_unlock(&data->lock); 14362306a36Sopenharmony_ci return count; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(temp1_max, max_min, 0); 14762306a36Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(temp1_min, max_min, 1); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic ssize_t alarm_show(struct device *dev, struct device_attribute *attr, 15062306a36Sopenharmony_ci char *buf) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci int bitnr = to_sensor_dev_attr(attr)->index; 15362306a36Sopenharmony_ci struct ad7414_data *data = ad7414_update_device(dev); 15462306a36Sopenharmony_ci int value = (data->temp_input >> bitnr) & 1; 15562306a36Sopenharmony_ci return sprintf(buf, "%d\n", value); 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, 3); 15962306a36Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 4); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic struct attribute *ad7414_attrs[] = { 16262306a36Sopenharmony_ci &sensor_dev_attr_temp1_input.dev_attr.attr, 16362306a36Sopenharmony_ci &sensor_dev_attr_temp1_max.dev_attr.attr, 16462306a36Sopenharmony_ci &sensor_dev_attr_temp1_min.dev_attr.attr, 16562306a36Sopenharmony_ci &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, 16662306a36Sopenharmony_ci &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, 16762306a36Sopenharmony_ci NULL 16862306a36Sopenharmony_ci}; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ciATTRIBUTE_GROUPS(ad7414); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic int ad7414_probe(struct i2c_client *client) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci struct device *dev = &client->dev; 17562306a36Sopenharmony_ci struct ad7414_data *data; 17662306a36Sopenharmony_ci struct device *hwmon_dev; 17762306a36Sopenharmony_ci int conf; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | 18062306a36Sopenharmony_ci I2C_FUNC_SMBUS_READ_WORD_DATA)) 18162306a36Sopenharmony_ci return -EOPNOTSUPP; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci data = devm_kzalloc(dev, sizeof(struct ad7414_data), GFP_KERNEL); 18462306a36Sopenharmony_ci if (!data) 18562306a36Sopenharmony_ci return -ENOMEM; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci data->client = client; 18862306a36Sopenharmony_ci mutex_init(&data->lock); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci dev_info(&client->dev, "chip found\n"); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* Make sure the chip is powered up. */ 19362306a36Sopenharmony_ci conf = i2c_smbus_read_byte_data(client, AD7414_REG_CONF); 19462306a36Sopenharmony_ci if (conf < 0) 19562306a36Sopenharmony_ci dev_warn(dev, "ad7414_probe unable to read config register.\n"); 19662306a36Sopenharmony_ci else { 19762306a36Sopenharmony_ci conf &= ~(1 << 7); 19862306a36Sopenharmony_ci i2c_smbus_write_byte_data(client, AD7414_REG_CONF, conf); 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci hwmon_dev = devm_hwmon_device_register_with_groups(dev, 20262306a36Sopenharmony_ci client->name, 20362306a36Sopenharmony_ci data, ad7414_groups); 20462306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(hwmon_dev); 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic const struct i2c_device_id ad7414_id[] = { 20862306a36Sopenharmony_ci { "ad7414", 0 }, 20962306a36Sopenharmony_ci {} 21062306a36Sopenharmony_ci}; 21162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ad7414_id); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic const struct of_device_id __maybe_unused ad7414_of_match[] = { 21462306a36Sopenharmony_ci { .compatible = "ad,ad7414" }, 21562306a36Sopenharmony_ci { }, 21662306a36Sopenharmony_ci}; 21762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ad7414_of_match); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic struct i2c_driver ad7414_driver = { 22062306a36Sopenharmony_ci .driver = { 22162306a36Sopenharmony_ci .name = "ad7414", 22262306a36Sopenharmony_ci .of_match_table = of_match_ptr(ad7414_of_match), 22362306a36Sopenharmony_ci }, 22462306a36Sopenharmony_ci .probe = ad7414_probe, 22562306a36Sopenharmony_ci .id_table = ad7414_id, 22662306a36Sopenharmony_ci}; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cimodule_i2c_driver(ad7414_driver); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ciMODULE_AUTHOR("Stefan Roese <sr at denx.de>, " 23162306a36Sopenharmony_ci "Frank Edelhaeuser <frank.edelhaeuser at spansion.com>"); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ciMODULE_DESCRIPTION("AD7414 driver"); 23462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 235