162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * g760a - Driver for the Global Mixed-mode Technology Inc. G760A
462306a36Sopenharmony_ci *	   fan speed PWM controller chip
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2007  Herbert Valerio Riedel <hvr@gnu.org>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Complete datasheet is available at GMT's website:
962306a36Sopenharmony_ci * http://www.gmt.com.tw/product/datasheet/EDS-760A.pdf
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci#include <linux/jiffies.h>
1662306a36Sopenharmony_ci#include <linux/i2c.h>
1762306a36Sopenharmony_ci#include <linux/hwmon.h>
1862306a36Sopenharmony_ci#include <linux/hwmon-sysfs.h>
1962306a36Sopenharmony_ci#include <linux/err.h>
2062306a36Sopenharmony_ci#include <linux/mutex.h>
2162306a36Sopenharmony_ci#include <linux/sysfs.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cienum g760a_regs {
2462306a36Sopenharmony_ci	G760A_REG_SET_CNT = 0x00,
2562306a36Sopenharmony_ci	G760A_REG_ACT_CNT = 0x01,
2662306a36Sopenharmony_ci	G760A_REG_FAN_STA = 0x02
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define G760A_REG_FAN_STA_RPM_OFF 0x1 /* +/-20% off */
3062306a36Sopenharmony_ci#define G760A_REG_FAN_STA_RPM_LOW 0x2 /* below 1920rpm */
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* register data is read (and cached) at most once per second */
3362306a36Sopenharmony_ci#define G760A_UPDATE_INTERVAL (HZ)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistruct g760a_data {
3662306a36Sopenharmony_ci	struct i2c_client *client;
3762306a36Sopenharmony_ci	struct mutex update_lock;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	/* board specific parameters */
4062306a36Sopenharmony_ci	u32 clk; /* default 32kHz */
4162306a36Sopenharmony_ci	u16 fan_div; /* default P=2 */
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	/* g760a register cache */
4462306a36Sopenharmony_ci	unsigned int valid:1;
4562306a36Sopenharmony_ci	unsigned long last_updated; /* In jiffies */
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	u8 set_cnt; /* PWM (period) count number; 0xff stops fan */
4862306a36Sopenharmony_ci	u8 act_cnt; /*   formula: cnt = (CLK * 30)/(rpm * P) */
4962306a36Sopenharmony_ci	u8 fan_sta; /* bit 0: set when actual fan speed more than 20%
5062306a36Sopenharmony_ci		     *   outside requested fan speed
5162306a36Sopenharmony_ci		     * bit 1: set when fan speed below 1920 rpm
5262306a36Sopenharmony_ci		     */
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci#define G760A_DEFAULT_CLK 32768
5662306a36Sopenharmony_ci#define G760A_DEFAULT_FAN_DIV 2
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#define PWM_FROM_CNT(cnt)	(0xff-(cnt))
5962306a36Sopenharmony_ci#define PWM_TO_CNT(pwm)		(0xff-(pwm))
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic inline unsigned int rpm_from_cnt(u8 val, u32 clk, u16 div)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	return ((val == 0x00) ? 0 : ((clk*30)/(val*div)));
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/* read/write wrappers */
6762306a36Sopenharmony_cistatic int g760a_read_value(struct i2c_client *client, enum g760a_regs reg)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	return i2c_smbus_read_byte_data(client, reg);
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int g760a_write_value(struct i2c_client *client, enum g760a_regs reg,
7362306a36Sopenharmony_ci			     u16 value)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	return i2c_smbus_write_byte_data(client, reg, value);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci/*
7962306a36Sopenharmony_ci * sysfs attributes
8062306a36Sopenharmony_ci */
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic struct g760a_data *g760a_update_client(struct device *dev)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	struct g760a_data *data = dev_get_drvdata(dev);
8562306a36Sopenharmony_ci	struct i2c_client *client = data->client;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	mutex_lock(&data->update_lock);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (time_after(jiffies, data->last_updated + G760A_UPDATE_INTERVAL)
9062306a36Sopenharmony_ci	    || !data->valid) {
9162306a36Sopenharmony_ci		dev_dbg(&client->dev, "Starting g760a update\n");
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		data->set_cnt = g760a_read_value(client, G760A_REG_SET_CNT);
9462306a36Sopenharmony_ci		data->act_cnt = g760a_read_value(client, G760A_REG_ACT_CNT);
9562306a36Sopenharmony_ci		data->fan_sta = g760a_read_value(client, G760A_REG_FAN_STA);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci		data->last_updated = jiffies;
9862306a36Sopenharmony_ci		data->valid = true;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	mutex_unlock(&data->update_lock);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	return data;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic ssize_t fan1_input_show(struct device *dev,
10762306a36Sopenharmony_ci			       struct device_attribute *da, char *buf)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	struct g760a_data *data = g760a_update_client(dev);
11062306a36Sopenharmony_ci	unsigned int rpm = 0;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	mutex_lock(&data->update_lock);
11362306a36Sopenharmony_ci	if (!(data->fan_sta & G760A_REG_FAN_STA_RPM_LOW))
11462306a36Sopenharmony_ci		rpm = rpm_from_cnt(data->act_cnt, data->clk, data->fan_div);
11562306a36Sopenharmony_ci	mutex_unlock(&data->update_lock);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return sprintf(buf, "%d\n", rpm);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic ssize_t fan1_alarm_show(struct device *dev,
12162306a36Sopenharmony_ci			       struct device_attribute *da, char *buf)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct g760a_data *data = g760a_update_client(dev);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	int fan_alarm = (data->fan_sta & G760A_REG_FAN_STA_RPM_OFF) ? 1 : 0;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return sprintf(buf, "%d\n", fan_alarm);
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic ssize_t pwm1_show(struct device *dev, struct device_attribute *da,
13162306a36Sopenharmony_ci			 char *buf)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	struct g760a_data *data = g760a_update_client(dev);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return sprintf(buf, "%d\n", PWM_FROM_CNT(data->set_cnt));
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic ssize_t pwm1_store(struct device *dev, struct device_attribute *da,
13962306a36Sopenharmony_ci			  const char *buf, size_t count)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct g760a_data *data = g760a_update_client(dev);
14262306a36Sopenharmony_ci	struct i2c_client *client = data->client;
14362306a36Sopenharmony_ci	unsigned long val;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (kstrtoul(buf, 10, &val))
14662306a36Sopenharmony_ci		return -EINVAL;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	mutex_lock(&data->update_lock);
14962306a36Sopenharmony_ci	data->set_cnt = PWM_TO_CNT(clamp_val(val, 0, 255));
15062306a36Sopenharmony_ci	g760a_write_value(client, G760A_REG_SET_CNT, data->set_cnt);
15162306a36Sopenharmony_ci	mutex_unlock(&data->update_lock);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	return count;
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(pwm1);
15762306a36Sopenharmony_cistatic DEVICE_ATTR_RO(fan1_input);
15862306a36Sopenharmony_cistatic DEVICE_ATTR_RO(fan1_alarm);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic struct attribute *g760a_attrs[] = {
16162306a36Sopenharmony_ci	&dev_attr_pwm1.attr,
16262306a36Sopenharmony_ci	&dev_attr_fan1_input.attr,
16362306a36Sopenharmony_ci	&dev_attr_fan1_alarm.attr,
16462306a36Sopenharmony_ci	NULL
16562306a36Sopenharmony_ci};
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ciATTRIBUTE_GROUPS(g760a);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci/*
17062306a36Sopenharmony_ci * new-style driver model code
17162306a36Sopenharmony_ci */
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic int g760a_probe(struct i2c_client *client)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	struct device *dev = &client->dev;
17662306a36Sopenharmony_ci	struct g760a_data *data;
17762306a36Sopenharmony_ci	struct device *hwmon_dev;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
18062306a36Sopenharmony_ci		return -EIO;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	data = devm_kzalloc(dev, sizeof(struct g760a_data), GFP_KERNEL);
18362306a36Sopenharmony_ci	if (!data)
18462306a36Sopenharmony_ci		return -ENOMEM;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	data->client = client;
18762306a36Sopenharmony_ci	mutex_init(&data->update_lock);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	/* setup default configuration for now */
19062306a36Sopenharmony_ci	data->fan_div = G760A_DEFAULT_FAN_DIV;
19162306a36Sopenharmony_ci	data->clk = G760A_DEFAULT_CLK;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
19462306a36Sopenharmony_ci							   data,
19562306a36Sopenharmony_ci							   g760a_groups);
19662306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(hwmon_dev);
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic const struct i2c_device_id g760a_id[] = {
20062306a36Sopenharmony_ci	{ "g760a", 0 },
20162306a36Sopenharmony_ci	{ }
20262306a36Sopenharmony_ci};
20362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, g760a_id);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic struct i2c_driver g760a_driver = {
20662306a36Sopenharmony_ci	.driver = {
20762306a36Sopenharmony_ci		.name	= "g760a",
20862306a36Sopenharmony_ci	},
20962306a36Sopenharmony_ci	.probe = g760a_probe,
21062306a36Sopenharmony_ci	.id_table = g760a_id,
21162306a36Sopenharmony_ci};
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cimodule_i2c_driver(g760a_driver);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ciMODULE_AUTHOR("Herbert Valerio Riedel <hvr@gnu.org>");
21662306a36Sopenharmony_ciMODULE_DESCRIPTION("GMT G760A driver");
21762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
218