// SPDX-License-Identifier: GPL-2.0-or-later
/*
* g762 - Driver for the Global Mixed-mode Technology Inc. fan speed
* PWM controller chips from G762 family, i.e. G762 and G763
*
* Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
*
* This work is based on a basic version for 2.6.31 kernel developed
* by Olivier Mouchet for LaCie. Updates and correction have been
* performed to run on recent kernels. Additional features, like the
* ability to configure various characteristics via .dts file or
* board init file have been added. Detailed datasheet on which this
* development is based is available here:
*
* http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf
*
* Headers from previous developments have been kept below:
*
* Copyright (c) 2009 LaCie
*
* Author: Olivier Mouchet <olivier.mouchet@gmail.com>
*
* based on g760a code written by Herbert Valerio Riedel <hvr@gnu.org>
* Copyright (C) 2007 Herbert Valerio Riedel <hvr@gnu.org>
*
* g762: minimal datasheet available at:
* http://www.gmt.com.tw/product/datasheet/EDS-762_3.pdf
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/platform_data/g762.h>
#define DRVNAME "g762"
static const struct i2c_device_id g762_id[] = {
{ "g761" },
{ "g762" },
{ "g763" },
{ }
};
MODULE_DEVICE_TABLE(i2c, g762_id);
enum g762_regs {
G762_REG_SET_CNT = 0x00,
G762_REG_ACT_CNT = 0x01,
G762_REG_FAN_STA = 0x02,
G762_REG_SET_OUT = 0x03,
G762_REG_FAN_CMD1 = 0x04,
G762_REG_FAN_CMD2 = 0x05,
};
/* Config register bits */
#define G762_REG_FAN_CMD1_DET_FAN_FAIL 0x80 /* enable fan_fail signal */
#define G762_REG_FAN_CMD1_DET_FAN_OOC 0x40 /* enable fan_out_of_control */
#define G762_REG_FAN_CMD1_OUT_MODE 0x20 /* out mode: PWM or DC */
#define G762_REG_FAN_CMD1_FAN_MODE 0x10 /* fan mode: closed/open-loop */
#define G762_REG_FAN_CMD1_CLK_DIV_ID1 0x08 /* clock divisor value */
#define G762_REG_FAN_CMD1_CLK_DIV_ID0 0x04
#define G762_REG_FAN_CMD1_PWM_POLARITY 0x02 /* PWM polarity */
#define G762_REG_FAN_CMD1_PULSE_PER_REV 0x01 /* pulse per fan revolution */
#define G761_REG_FAN_CMD2_FAN_CLOCK 0x20 /* choose internal clock*/
#define G762_REG_FAN_CMD2_GEAR_MODE_1 0x08 /* fan gear mode */
#define G762_REG_FAN_CMD2_GEAR_MODE_0 0x04
#define G762_REG_FAN_CMD2_FAN_STARTV_1 0x02 /* fan startup voltage */
#define G762_REG_FAN_CMD2_FAN_STARTV_0 0x01
#define G762_REG_FAN_STA_FAIL 0x02 /* fan fail */
#define G762_REG_FAN_STA_OOC 0x01 /* fan out of control */
/* Config register values */
#define G762_OUT_MODE_PWM 1
#define G762_OUT_MODE_DC 0
#define G762_FAN_MODE_CLOSED_LOOP 2
#define G762_FAN_MODE_OPEN_LOOP 1
#define G762_PWM_POLARITY_NEGATIVE 1
#define G762_PWM_POLARITY_POSITIVE 0
/* Register data is read (and cached) at most once per second. */
#define G762_UPDATE_INTERVAL HZ
/*
* Extract pulse count per fan revolution value (2 or 4) from given
* FAN_CMD1 register value.
*/
#define G762_PULSE_FROM_REG(reg) \
((((reg) & G762_REG_FAN_CMD1_PULSE_PER_REV) + 1) << 1)
/*
* Extract fan clock divisor (1, 2, 4 or 8) from given FAN_CMD1
* register value.
*/
#define G762_CLKDIV_FROM_REG(reg) \
(1 << (((reg) & (G762_REG_FAN_CMD1_CLK_DIV_ID0 | \
G762_REG_FAN_CMD1_CLK_DIV_ID1)) >> 2))
/*
* Extract fan gear mode multiplier value (0, 2 or 4) from given
* FAN_CMD2 register value.
*/
#define G762_GEARMULT_FROM_REG(reg) \
(1 << (((reg) & (G762_REG_FAN_CMD2_GEAR_MODE_0 | \
G762_REG_FAN_CMD2_GEAR_MODE_1)) >> 2))
struct g762_data {
struct i2c_client *client;
bool internal_clock;
struct clk *clk;
/* update mutex */
struct mutex update_lock;
/* board specific parameters. */
u32 clk_freq;
/* g762 register cache */
bool valid;
unsigned long last_updated; /* in jiffies */
u8 set_cnt; /* controls fan rotation speed in closed-loop mode */
u8 act_cnt; /* provides access to current fan RPM value */
u8 fan_sta; /* bit 0: set when actual fan speed is more than
* 25% outside requested fan speed
* bit 1: set when no transition occurs on fan
* pin for 0.7s
*/
u8 set_out; /* controls fan rotation speed in open-loop mode */
u8 fan_cmd1; /* 0: FG_PLS_ID0 FG pulses count per revolution
* 0: 2 counts per revolution
* 1: 4 counts per revolution
* 1: PWM_POLARITY 1: negative_duty
* 0: positive_duty
* 2,3: [FG_CLOCK_ID0, FG_CLK_ID1]
* 00: Divide fan clock by 1
* 01: Divide fan clock by 2
* 10: Divide fan clock by 4
* 11: Divide fan clock by 8
* 4: FAN_MODE 1:closed-loop, 0:open-loop
* 5: OUT_MODE 1:PWM, 0:DC
* 6: DET_FAN_OOC enable "fan ooc" status
* 7: DET_FAN_FAIL enable "fan fail" status
*/
u8 fan_cmd2; /* 0,1: FAN_STARTV 0,1,2,3 -> 0,32,64,96 dac_code
* 2,3: FG_GEAR_MODE
* 00: multiplier = 1
* 01: multiplier = 2
* 10: multiplier = 4
* 4: Mask ALERT# (g763 only)
*/
};
/*
* Convert count value from fan controller register (FAN_SET_CNT) into fan
* speed RPM value. Note that the datasheet documents a basic formula;
* influence of additional parameters (fan clock divisor, fan gear mode)
* have been infered from examples in the datasheet and tests.
*/
static inline unsigned int rpm_from_cnt(u8 cnt, u32 clk_freq, u16 p,
u8 clk_div, u8 gear_mult)
{
if (cnt == 0xff) /* setting cnt to 255 stops the fan */
return 0;
return (clk_freq * 30 * gear_mult) / ((cnt ? cnt : 1) * p * clk_div);
}
/*
* Convert fan RPM value from sysfs into count value for fan controller
* register (FAN_SET_CNT).
*/
static inline unsigned char cnt_from_rpm(unsigned long rpm, u32 clk_freq, u16 p,
u8 clk_div, u8 gear_mult)
{
unsigned