[devices]: add celstica haliburton
Signed-off-by: Guohan Lu <gulv@microsoft.com>
This commit is contained in:
parent
7d13479aed
commit
3021e4efd6
12
device/celestica/x86_64-cel_e1031-r0/fancontrol
Normal file
12
device/celestica/x86_64-cel_e1031-r0/fancontrol
Normal file
@ -0,0 +1,12 @@
|
||||
# Configuration file generated by pwmconfig, changes will be lost
|
||||
INTERVAL=2
|
||||
DEVPATH=hwmon3=devices/pci0000:00/0000:00:13.0/i2c-0/i2c-8/i2c-23/23-004d/ hwmon2=devices/pci0000:00/0000:00:13.0/i2c-0/i2c-8/i2c-11/11-001a/hwmon/hwmon2
|
||||
DEVNAME=hwmon3=emc2305 hwmon2=max6697
|
||||
FCTEMPS=hwmon3/pwm1=hwmon2/temp2_input hwmon3/pwm2=hwmon2/temp2_input hwmon3/pwm4=hwmon2/temp2_input
|
||||
FCFANS=hwmon3/device/pwm1=hwmon3/device/fan1_input hwmon3/device/pwm2=hwmon3/device/fan2_input hwmon3/device/pwm4=hwmon3/device/fan4_input
|
||||
MINTEMP=hwmon3/device/pwm1=29 hwmon3/device/pwm2=29 hwmon3/device/pwm4=29
|
||||
MAXTEMP=hwmon3/device/pwm1=44 hwmon3/device/pwm2=44 hwmon3/device/pwm4=44
|
||||
MINSTART=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102
|
||||
MINSTOP=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102
|
||||
MINPWM=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102
|
||||
MAXPWM=hwmon3/device/pwm1=255 hwmon3/device/pwm2=255 hwmon3/device/pwm4=255
|
23
device/celestica/x86_64-cel_e1031-r0/plugins/eeprom.py
Normal file
23
device/celestica/x86_64-cel_e1031-r0/plugins/eeprom.py
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#############################################################################
|
||||
# Celestica Haliburton
|
||||
#
|
||||
# Platform and model specific eeprom subclass, inherits from the base class,
|
||||
# and provides the followings:
|
||||
# - the eeprom format definition
|
||||
# - specific encoder/decoder if there is special need
|
||||
#############################################################################
|
||||
|
||||
try:
|
||||
from sonic_eeprom import eeprom_tlvinfo
|
||||
except ImportError, e:
|
||||
raise ImportError (str(e) + "- required module not found")
|
||||
|
||||
|
||||
class board(eeprom_tlvinfo.TlvInfoDecoder):
|
||||
|
||||
def __init__(self, name, path, cpld_root, ro):
|
||||
self.eeprom_path = "/sys/class/i2c-adapter/i2c-2/2-0050/eeprom"
|
||||
super(board, self).__init__(self.eeprom_path, 0, '', True)
|
||||
|
12
device/celestica/x86_64-cel_e1031-r0/sensors.conf
Normal file
12
device/celestica/x86_64-cel_e1031-r0/sensors.conf
Normal file
@ -0,0 +1,12 @@
|
||||
# libsensors configuration file for Celestica DX010.
|
||||
# The i2c bus portion is omit because adapter name
|
||||
# changes every time when system boot up.
|
||||
|
||||
chip "max6697-i2c-*-1a"
|
||||
label temp1 "temp sensor 1"
|
||||
label temp2 "temp sensor 2"
|
||||
label temp3 "temp sensor 3"
|
||||
label temp4 "temp sensor 4"
|
||||
label temp5 "temp sensor 5"
|
||||
ignore temp6
|
||||
ignore temp7
|
@ -23,6 +23,7 @@ $(SONIC_ONE_IMAGE)_LAZY_INSTALLS += $(DELL_S6000_PLATFORM_MODULE) \
|
||||
$(INVENTEC_D7032Q28B_PLATFORM_MODULE) \
|
||||
$(INVENTEC_D7054Q28B_PLATFORM_MODULE) \
|
||||
$(CEL_DX010_PLATFORM_MODULE) \
|
||||
$(CEL_HALIBURTON_PLATFORM_MODULE) \
|
||||
$(DELTA_AG9032V1_PLATFORM_MODULE) \
|
||||
$(DELTA_AG9064_PLATFORM_MODULE) \
|
||||
$(DELTA_AG5648_PLATFORM_MODULE) \
|
||||
|
@ -1,8 +1,10 @@
|
||||
# Celestica DX010 Platform modules
|
||||
# Celestica DX010 and Haliburton Platform modules
|
||||
|
||||
CEL_DX010_PLATFORM_MODULE_VERSION = 0.7
|
||||
CEL_DX010_PLATFORM_MODULE_VERSION = 0.8
|
||||
CEL_HALIBURTON_PLATFORM_MODULE_VERSION = 0.8
|
||||
|
||||
export CEL_DX010_PLATFORM_MODULE_VERSION
|
||||
export CEL_HALIBURTON_PLATFORM_MODULE_VERSION
|
||||
|
||||
CEL_DX010_PLATFORM_MODULE = platform-modules-dx010_$(CEL_DX010_PLATFORM_MODULE_VERSION)_amd64.deb
|
||||
$(CEL_DX010_PLATFORM_MODULE)_SRC_PATH = $(PLATFORM_PATH)/sonic-platform-modules-cel
|
||||
@ -10,4 +12,8 @@ $(CEL_DX010_PLATFORM_MODULE)_DEPENDS += $(LINUX_HEADERS) $(LINUX_HEADERS_COMMON)
|
||||
$(CEL_DX010_PLATFORM_MODULE)_PLATFORM = x86_64-cel_seastone-r0
|
||||
SONIC_DPKG_DEBS += $(CEL_DX010_PLATFORM_MODULE)
|
||||
|
||||
CEL_HALIBURTON_PLATFORM_MODULE = platform-modules-haliburton_$(CEL_HALIBURTON_PLATFORM_MODULE_VERSION)_amd64.deb
|
||||
$(CEL_HALIBURTON_PLATFORM_MODULE)_PLATFORM = x86_64-cel_e1031-r0
|
||||
$(eval $(call add_extra_package,$(CEL_DX010_PLATFORM_MODULE),$(CEL_HALIBURTON_PLATFORM_MODULE)))
|
||||
|
||||
SONIC_STRETCH_DEBS += $(CEL_DX010_PLATFORM_MODULE)
|
||||
|
@ -1 +1 @@
|
||||
platform drivers for Celestica DX010 for the SONiC project
|
||||
platform drivers for Celestica DX010 and Haliburton for the SONiC project
|
||||
|
@ -1,3 +1,9 @@
|
||||
sonic-cel-platform-modules (0.8) unstable; urgency=low
|
||||
|
||||
* Add haliburton platform
|
||||
|
||||
-- Supakit Fuangkaew <sfuangk@celestica.com> Thu, 5 Apr 2018 09:09:09 +0700
|
||||
|
||||
sonic-cel-platform-modules (0.7) unstable; urgency=low
|
||||
|
||||
* Add dx010 plaform gpio sysfs exported when module load.
|
||||
|
@ -10,3 +10,8 @@ Architecture: amd64
|
||||
Depends: linux-image-4.9.0-5-amd64
|
||||
Description: kernel modules for platform devices such as fan, led, sfp
|
||||
|
||||
|
||||
Package: platform-modules-haliburton
|
||||
Architecture: amd64
|
||||
Depends: linux-image-4.9.0-5-amd64
|
||||
Description: kernel modules for platform devices such as fan, led, sfp
|
||||
|
@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: setup-board
|
||||
# Required-Start: $portmap
|
||||
# Required-Stop:
|
||||
# Should-Start:
|
||||
# Should-Stop:
|
||||
# Default-Start: S
|
||||
# Default-Stop: 0 6
|
||||
# Short-Description: Setup Haliburton board.
|
||||
### END INIT INFO
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo -n "Setting up board... "
|
||||
|
||||
depmod -a
|
||||
modprobe i2c-dev
|
||||
modprobe i2c-mux-pca954x
|
||||
|
||||
found=0
|
||||
for devnum in 0 1; do
|
||||
devname=`cat /sys/bus/i2c/devices/i2c-${devnum}/name`
|
||||
# iSMT adapter can be at either dffd0000 or dfff0000
|
||||
if [[ $devname == 'SMBus iSMT adapter at '* ]]; then
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
[ $found -eq 0 ] && echo "cannot find iSMT" && exit 1
|
||||
|
||||
i2cset -y ${devnum} 0x73 0x10 0x00 0x01 i
|
||||
|
||||
# Attach PCA9548 0x73 Channel Extender for CPU Board
|
||||
echo pca9548 0x73 > /sys/bus/i2c/devices/i2c-${devnum}/new_device
|
||||
sleep 1
|
||||
|
||||
# Attach PCA9541 Ox71 Master Selector
|
||||
chmod 755 /sys/bus/i2c/devices/i2c-${devnum}/new_device
|
||||
echo pca9548 0x71 > /sys/bus/i2c/devices/i2c-8/new_device
|
||||
sleep 1
|
||||
|
||||
# Attach PCA9548 0x72 Channel Extender for Main Board
|
||||
echo pca9548 0x72 > /sys/bus/i2c/devices/i2c-8/new_device
|
||||
sleep 1
|
||||
|
||||
# Attach syseeprom
|
||||
echo 24lc64t 0x50 > /sys/bus/i2c/devices/i2c-2/new_device
|
||||
|
||||
echo max6697 0x1a > /sys/bus/i2c/devices/i2c-3/new_device
|
||||
echo max6697 0x1a > /sys/bus/i2c/devices/i2c-11/new_device
|
||||
|
||||
|
||||
# Attach fans
|
||||
echo emc2305 0x4d > /sys/bus/i2c/devices/i2c-23/new_device
|
||||
|
||||
# Attach PSUs
|
||||
echo dps200 0x5a > /sys/bus/i2c/devices/i2c-12/new_device
|
||||
echo dps200 0x5b > /sys/bus/i2c/devices/i2c-13/new_device
|
||||
|
||||
# Attach 4 SFP+ Uplink
|
||||
echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-14/new_device
|
||||
echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-15/new_device
|
||||
echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-16/new_device
|
||||
echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-17/new_device
|
||||
|
||||
echo "done."
|
||||
;;
|
||||
|
||||
stop)
|
||||
echo "done."
|
||||
;;
|
||||
|
||||
force-reload|restart)
|
||||
echo "Not supported"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: /etc/init.d/platform-modules-haliburton.init {start|stop}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
@ -0,0 +1 @@
|
||||
haliburton/cfg/haliburton-modules.conf etc/modules-load.d
|
@ -5,7 +5,7 @@ export INSTALL_MOD_DIR:=extra
|
||||
KVERSION ?= $(shell uname -r)
|
||||
KERNEL_SRC := /lib/modules/$(KVERSION)
|
||||
MOD_SRC_DIR:= $(shell pwd)
|
||||
MODULE_DIRS:= dx010
|
||||
MODULE_DIRS:= dx010 haliburton
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
@ -0,0 +1,15 @@
|
||||
# /etc/modules: kernel modules to load at boot time.
|
||||
#
|
||||
# This file contains the names of kernel modules that should be loaded
|
||||
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||
|
||||
i2c-i801
|
||||
i2c-isch
|
||||
i2c-ismt
|
||||
i2c-dev
|
||||
i2c-mux
|
||||
i2c-smbus
|
||||
|
||||
i2c-mux-gpio
|
||||
i2c-mux-pca954x
|
||||
|
@ -0,0 +1 @@
|
||||
obj-m := mc24lc64t.o emc2305.o
|
@ -0,0 +1,877 @@
|
||||
/*
|
||||
* emc2305.c - hwmon driver for SMSC EMC2305 fan controller
|
||||
* (C) Copyright 2013
|
||||
* Reinhard Pfau, Guntermann & Drunck GmbH <pfau@gdsys.de>
|
||||
*
|
||||
* Based on emc2103 driver by SMSC.
|
||||
*
|
||||
* Datasheet available at:
|
||||
* http://www.smsc.com/Downloads/SMSC/Downloads_Public/Data_Sheets/2305.pdf
|
||||
*
|
||||
* Also supports the EMC2303 fan controller which has the same functionality
|
||||
* and register layout as EMC2305, but supports only up to 3 fans instead of 5.
|
||||
*
|
||||
* Also supports EMC2302 (up to 2 fans) and EMC2301 (1 fan) fan controller.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO / IDEAS:
|
||||
* - expose more of the configuration and features
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.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/of.h>
|
||||
|
||||
/*
|
||||
* Addresses scanned.
|
||||
* Listed in the same order as they appear in the EMC2305, EMC2303 data sheets.
|
||||
*
|
||||
* Note: these are the I2C adresses which are possible for EMC2305 and EMC2303
|
||||
* chips.
|
||||
* The EMC2302 supports only 0x2e (EMC2302-1) and 0x2f (EMC2302-2).
|
||||
* The EMC2301 supports only 0x2f.
|
||||
*/
|
||||
static const unsigned short i2c_adresses[] = {
|
||||
0x2E,
|
||||
0x2F,
|
||||
0x2C,
|
||||
0x2D,
|
||||
0x4C,
|
||||
0x4D,
|
||||
I2C_CLIENT_END
|
||||
};
|
||||
|
||||
/*
|
||||
* global registers
|
||||
*/
|
||||
enum {
|
||||
REG_CONFIGURATION = 0x20,
|
||||
REG_FAN_STATUS = 0x24,
|
||||
REG_FAN_STALL_STATUS = 0x25,
|
||||
REG_FAN_SPIN_STATUS = 0x26,
|
||||
REG_DRIVE_FAIL_STATUS = 0x27,
|
||||
REG_FAN_INTERRUPT_ENABLE = 0x29,
|
||||
REG_PWM_POLARITY_CONFIG = 0x2a,
|
||||
REG_PWM_OUTPUT_CONFIG = 0x2b,
|
||||
REG_PWM_BASE_FREQ_1 = 0x2c,
|
||||
REG_PWM_BASE_FREQ_2 = 0x2d,
|
||||
REG_SOFTWARE_LOCK = 0xef,
|
||||
REG_PRODUCT_FEATURES = 0xfc,
|
||||
REG_PRODUCT_ID = 0xfd,
|
||||
REG_MANUFACTURER_ID = 0xfe,
|
||||
REG_REVISION = 0xff
|
||||
};
|
||||
|
||||
/*
|
||||
* fan specific registers
|
||||
*/
|
||||
enum {
|
||||
REG_FAN_SETTING = 0x30,
|
||||
REG_PWM_DIVIDE = 0x31,
|
||||
REG_FAN_CONFIGURATION_1 = 0x32,
|
||||
REG_FAN_CONFIGURATION_2 = 0x33,
|
||||
REG_GAIN = 0x35,
|
||||
REG_FAN_SPIN_UP_CONFIG = 0x36,
|
||||
REG_FAN_MAX_STEP = 0x37,
|
||||
REG_FAN_MINIMUM_DRIVE = 0x38,
|
||||
REG_FAN_VALID_TACH_COUNT = 0x39,
|
||||
REG_FAN_DRIVE_FAIL_BAND_LOW = 0x3a,
|
||||
REG_FAN_DRIVE_FAIL_BAND_HIGH = 0x3b,
|
||||
REG_TACH_TARGET_LOW = 0x3c,
|
||||
REG_TACH_TARGET_HIGH = 0x3d,
|
||||
REG_TACH_READ_HIGH = 0x3e,
|
||||
REG_TACH_READ_LOW = 0x3f,
|
||||
};
|
||||
|
||||
#define SEL_FAN(fan, reg) (reg + fan * 0x10)
|
||||
|
||||
/*
|
||||
* Factor by equations [2] and [3] from data sheet; valid for fans where the
|
||||
* number of edges equals (poles * 2 + 1).
|
||||
*/
|
||||
#define FAN_RPM_FACTOR 3932160
|
||||
|
||||
|
||||
struct emc2305_fan_data {
|
||||
bool enabled;
|
||||
bool valid;
|
||||
unsigned long last_updated;
|
||||
bool rpm_control;
|
||||
u8 multiplier;
|
||||
u8 poles;
|
||||
u16 target;
|
||||
u16 tach;
|
||||
u16 rpm_factor;
|
||||
u8 pwm;
|
||||
};
|
||||
|
||||
struct emc2305_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
int fans;
|
||||
struct emc2305_fan_data fan[5];
|
||||
};
|
||||
|
||||
static int read_u8_from_i2c(struct i2c_client *client, u8 i2c_reg, u8 *output)
|
||||
{
|
||||
int status = i2c_smbus_read_byte_data(client, i2c_reg);
|
||||
if (status < 0) {
|
||||
dev_warn(&client->dev, "reg 0x%02x, err %d\n",
|
||||
i2c_reg, status);
|
||||
} else {
|
||||
*output = status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static void read_fan_from_i2c(struct i2c_client *client, u16 *output,
|
||||
u8 hi_addr, u8 lo_addr)
|
||||
{
|
||||
u8 high_byte, lo_byte;
|
||||
|
||||
if (read_u8_from_i2c(client, hi_addr, &high_byte) < 0)
|
||||
return;
|
||||
|
||||
if (read_u8_from_i2c(client, lo_addr, &lo_byte) < 0)
|
||||
return;
|
||||
|
||||
*output = ((u16)high_byte << 5) | (lo_byte >> 3);
|
||||
}
|
||||
|
||||
static void write_fan_target_to_i2c(struct i2c_client *client, int fan,
|
||||
u16 new_target)
|
||||
{
|
||||
const u8 lo_reg = SEL_FAN(fan, REG_TACH_TARGET_LOW);
|
||||
const u8 hi_reg = SEL_FAN(fan, REG_TACH_TARGET_HIGH);
|
||||
u8 high_byte = (new_target & 0x1fe0) >> 5;
|
||||
u8 low_byte = (new_target & 0x001f) << 3;
|
||||
i2c_smbus_write_byte_data(client, lo_reg, low_byte);
|
||||
i2c_smbus_write_byte_data(client, hi_reg, high_byte);
|
||||
}
|
||||
|
||||
static void read_fan_config_from_i2c(struct i2c_client *client, int fan)
|
||||
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
u8 conf1;
|
||||
|
||||
if (read_u8_from_i2c(client, SEL_FAN(fan, REG_FAN_CONFIGURATION_1),
|
||||
&conf1) < 0)
|
||||
return;
|
||||
|
||||
data->fan[fan].rpm_control = (conf1 & 0x80) != 0;
|
||||
data->fan[fan].multiplier = 1 << ((conf1 & 0x60) >> 5);
|
||||
data->fan[fan].poles = ((conf1 & 0x18) >> 3) + 1;
|
||||
}
|
||||
|
||||
static void read_fan_setting(struct i2c_client *client, int fan)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
u8 setting;
|
||||
|
||||
if (read_u8_from_i2c(client, SEL_FAN(fan, REG_FAN_SETTING),
|
||||
&setting) < 0)
|
||||
return;
|
||||
|
||||
data->fan[fan].pwm = setting;
|
||||
}
|
||||
|
||||
static void read_fan_data(struct i2c_client *client, int fan_idx)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
|
||||
read_fan_from_i2c(client, &data->fan[fan_idx].target,
|
||||
SEL_FAN(fan_idx, REG_TACH_TARGET_HIGH),
|
||||
SEL_FAN(fan_idx, REG_TACH_TARGET_LOW));
|
||||
read_fan_from_i2c(client, &data->fan[fan_idx].tach,
|
||||
SEL_FAN(fan_idx, REG_TACH_READ_HIGH),
|
||||
SEL_FAN(fan_idx, REG_TACH_READ_LOW));
|
||||
}
|
||||
|
||||
static struct emc2305_fan_data *
|
||||
emc2305_update_fan(struct i2c_client *client, int fan_idx)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
struct emc2305_fan_data *fan_data = &data->fan[fan_idx];
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, fan_data->last_updated + HZ + HZ / 2)
|
||||
|| !fan_data->valid) {
|
||||
read_fan_config_from_i2c(client, fan_idx);
|
||||
read_fan_data(client, fan_idx);
|
||||
read_fan_setting(client, fan_idx);
|
||||
fan_data->valid = true;
|
||||
fan_data->last_updated = jiffies;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return fan_data;
|
||||
}
|
||||
|
||||
static struct emc2305_fan_data *
|
||||
emc2305_update_device_fan(struct device *dev, struct device_attribute *da)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
int fan_idx = to_sensor_dev_attr(da)->index;
|
||||
|
||||
return emc2305_update_fan(client, fan_idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* set/ config functions
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note: we also update the fan target here, because its value is
|
||||
* determined in part by the fan clock divider. This follows the principle
|
||||
* of least surprise; the user doesn't expect the fan target to change just
|
||||
* because the divider changed.
|
||||
*/
|
||||
static int
|
||||
emc2305_set_fan_div(struct i2c_client *client, int fan_idx, long new_div)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
|
||||
const u8 reg_conf1 = SEL_FAN(fan_idx, REG_FAN_CONFIGURATION_1);
|
||||
int new_range_bits, old_div = 8 / fan->multiplier;
|
||||
int status = 0;
|
||||
|
||||
if (new_div == old_div) /* No change */
|
||||
return 0;
|
||||
|
||||
switch (new_div) {
|
||||
case 1:
|
||||
new_range_bits = 3;
|
||||
break;
|
||||
case 2:
|
||||
new_range_bits = 2;
|
||||
break;
|
||||
case 4:
|
||||
new_range_bits = 1;
|
||||
break;
|
||||
case 8:
|
||||
new_range_bits = 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
status = i2c_smbus_read_byte_data(client, reg_conf1);
|
||||
if (status < 0) {
|
||||
dev_dbg(&client->dev, "reg 0x%02x, err %d\n",
|
||||
reg_conf1, status);
|
||||
status = -EIO;
|
||||
goto exit_unlock;
|
||||
}
|
||||
status &= 0x9F;
|
||||
status |= (new_range_bits << 5);
|
||||
status = i2c_smbus_write_byte_data(client, reg_conf1, status);
|
||||
if (status < 0) {
|
||||
status = -EIO;
|
||||
goto exit_invalidate;
|
||||
}
|
||||
|
||||
fan->multiplier = 8 / new_div;
|
||||
|
||||
/* update fan target if high byte is not disabled */
|
||||
if ((fan->target & 0x1fe0) != 0x1fe0) {
|
||||
u16 new_target = (fan->target * old_div) / new_div;
|
||||
fan->target = min_t(u16, new_target, 0x1fff);
|
||||
write_fan_target_to_i2c(client, fan_idx, fan->target);
|
||||
}
|
||||
|
||||
exit_invalidate:
|
||||
/* invalidate fan data to force re-read from hardware */
|
||||
fan->valid = false;
|
||||
exit_unlock:
|
||||
mutex_unlock(&data->update_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
emc2305_set_fan_target(struct i2c_client *client, int fan_idx, long rpm_target)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
|
||||
|
||||
/*
|
||||
* Datasheet states 16000 as maximum RPM target
|
||||
* (table 2.2 and section 4.3)
|
||||
*/
|
||||
if ((rpm_target < 0) || (rpm_target > 16000))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (rpm_target == 0)
|
||||
fan->target = 0x1fff;
|
||||
else
|
||||
fan->target = clamp_val(
|
||||
(FAN_RPM_FACTOR * fan->multiplier) / rpm_target,
|
||||
0, 0x1fff);
|
||||
|
||||
write_fan_target_to_i2c(client, fan_idx, fan->target);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
emc2305_set_pwm_enable(struct i2c_client *client, int fan_idx, long enable)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
|
||||
const u8 reg_fan_conf1 = SEL_FAN(fan_idx, REG_FAN_CONFIGURATION_1);
|
||||
int status = 0;
|
||||
u8 conf_reg;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
switch (enable) {
|
||||
case 0:
|
||||
fan->rpm_control = false;
|
||||
break;
|
||||
case 3:
|
||||
fan->rpm_control = true;
|
||||
break;
|
||||
default:
|
||||
status = -EINVAL;
|
||||
goto exit_unlock;
|
||||
}
|
||||
|
||||
status = read_u8_from_i2c(client, reg_fan_conf1, &conf_reg);
|
||||
if (status < 0) {
|
||||
status = -EIO;
|
||||
goto exit_unlock;
|
||||
}
|
||||
|
||||
if (fan->rpm_control)
|
||||
conf_reg |= 0x80;
|
||||
else
|
||||
conf_reg &= ~0x80;
|
||||
|
||||
status = i2c_smbus_write_byte_data(client, reg_fan_conf1, conf_reg);
|
||||
if (status < 0)
|
||||
status = -EIO;
|
||||
|
||||
exit_unlock:
|
||||
mutex_unlock(&data->update_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
emc2305_set_pwm(struct i2c_client *client, int fan_idx, long pwm)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
struct emc2305_fan_data *fan = emc2305_update_fan(client, fan_idx);
|
||||
const u8 reg_fan_setting = SEL_FAN(fan_idx, REG_FAN_SETTING);
|
||||
int status = 0;
|
||||
|
||||
/*
|
||||
* Datasheet states 255 as maximum PWM
|
||||
* (section 5.7)
|
||||
*/
|
||||
if ((pwm < 0) || (pwm > 255))
|
||||
return -EINVAL;
|
||||
|
||||
fan->pwm = pwm;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
status = i2c_smbus_write_byte_data(client, reg_fan_setting, fan->pwm);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return status;
|
||||
}
|
||||
/*
|
||||
* sysfs callback functions
|
||||
*
|
||||
* Note:
|
||||
* Naming of the funcs is modelled after the naming scheme described in
|
||||
* Documentation/hwmon/sysfs-interface:
|
||||
*
|
||||
* For a sysfs file <type><number>_<item> the functions are named like this:
|
||||
* the show function: show_<type>_<item>
|
||||
* the store function: set_<type>_<item>
|
||||
* For read only (RO) attributes of course only the show func is required.
|
||||
*
|
||||
* This convention allows us to define the sysfs attributes by using macros.
|
||||
*/
|
||||
|
||||
static ssize_t
|
||||
show_fan_input(struct device *dev, struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
|
||||
int rpm = 0;
|
||||
if (fan->tach != 0)
|
||||
rpm = (FAN_RPM_FACTOR * fan->multiplier) / fan->tach;
|
||||
return sprintf(buf, "%d\n", rpm);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
show_fan_fault(struct device *dev, struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
|
||||
bool fault = ((fan->tach & 0x1fe0) == 0x1fe0);
|
||||
return sprintf(buf, "%d\n", fault ? 1 : 0);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
show_fan_div(struct device *dev, struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
|
||||
int fan_div = 8 / fan->multiplier;
|
||||
return sprintf(buf, "%d\n", fan_div);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
set_fan_div(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
int fan_idx = to_sensor_dev_attr(da)->index;
|
||||
long new_div;
|
||||
int status;
|
||||
|
||||
status = kstrtol(buf, 10, &new_div);
|
||||
if (status < 0)
|
||||
return -EINVAL;
|
||||
|
||||
status = emc2305_set_fan_div(client, fan_idx, new_div);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
show_fan_target(struct device *dev, struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
|
||||
int rpm = 0;
|
||||
|
||||
/* high byte of 0xff indicates disabled so return 0 */
|
||||
if ((fan->target != 0) && ((fan->target & 0x1fe0) != 0x1fe0))
|
||||
rpm = (FAN_RPM_FACTOR * fan->multiplier)
|
||||
/ fan->target;
|
||||
|
||||
return sprintf(buf, "%d\n", rpm);
|
||||
}
|
||||
|
||||
static ssize_t set_fan_target(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
int fan_idx = to_sensor_dev_attr(da)->index;
|
||||
long rpm_target;
|
||||
int status;
|
||||
|
||||
status = kstrtol(buf, 10, &rpm_target);
|
||||
if (status < 0)
|
||||
return -EINVAL;
|
||||
|
||||
status = emc2305_set_fan_target(client, fan_idx, rpm_target);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
show_pwm_enable(struct device *dev, struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
|
||||
return sprintf(buf, "%d\n", fan->rpm_control ? 3 : 0);
|
||||
}
|
||||
|
||||
static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
int fan_idx = to_sensor_dev_attr(da)->index;
|
||||
long new_value;
|
||||
int status;
|
||||
|
||||
status = kstrtol(buf, 10, &new_value);
|
||||
if (status < 0)
|
||||
return -EINVAL;
|
||||
status = emc2305_set_pwm_enable(client, fan_idx, new_value);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_pwm(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct emc2305_fan_data *fan = emc2305_update_device_fan(dev, da);
|
||||
return sprintf(buf, "%d\n", fan->pwm);
|
||||
}
|
||||
|
||||
static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
int fan_idx = to_sensor_dev_attr(da)->index;
|
||||
unsigned long val;
|
||||
int ret;
|
||||
int status;
|
||||
|
||||
ret = kstrtoul(buf, 10, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (val > 255)
|
||||
return -EINVAL;
|
||||
|
||||
status = emc2305_set_pwm(client, fan_idx, val);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* define a read only attribute */
|
||||
#define EMC2305_ATTR_RO(_type, _item, _num) \
|
||||
SENSOR_ATTR(_type ## _num ## _ ## _item, S_IRUGO, \
|
||||
show_## _type ## _ ## _item, NULL, _num - 1)
|
||||
|
||||
/* define a read/write attribute */
|
||||
#define EMC2305_ATTR_RW(_type, _item, _num) \
|
||||
SENSOR_ATTR(_type ## _num ## _ ## _item, S_IRUGO | S_IWUSR, \
|
||||
show_## _type ##_ ## _item, \
|
||||
set_## _type ## _ ## _item, _num - 1)
|
||||
|
||||
/*
|
||||
* TODO: Ugly hack, but temporary as this whole logic needs
|
||||
* to be rewritten as per standard HWMON sysfs registration
|
||||
*/
|
||||
|
||||
/* define a read/write attribute */
|
||||
#define EMC2305_ATTR_RW2(_type, _num) \
|
||||
SENSOR_ATTR(_type ## _num, S_IRUGO | S_IWUSR, \
|
||||
show_## _type, set_## _type, _num - 1)
|
||||
|
||||
/* defines the attributes for a single fan */
|
||||
#define EMC2305_DEFINE_FAN_ATTRS(_num) \
|
||||
static const \
|
||||
struct sensor_device_attribute emc2305_attr_fan ## _num[] = { \
|
||||
EMC2305_ATTR_RO(fan, input, _num), \
|
||||
EMC2305_ATTR_RO(fan, fault, _num), \
|
||||
EMC2305_ATTR_RW(fan, div, _num), \
|
||||
EMC2305_ATTR_RW(fan, target, _num), \
|
||||
EMC2305_ATTR_RW(pwm, enable, _num), \
|
||||
EMC2305_ATTR_RW2(pwm, _num) \
|
||||
}
|
||||
|
||||
#define EMC2305_NUM_FAN_ATTRS ARRAY_SIZE(emc2305_attr_fan1)
|
||||
|
||||
/* common attributes for EMC2303 and EMC2305 */
|
||||
static const struct sensor_device_attribute emc2305_attr_common[] = {
|
||||
};
|
||||
|
||||
/* fan attributes for the single fans */
|
||||
EMC2305_DEFINE_FAN_ATTRS(1);
|
||||
EMC2305_DEFINE_FAN_ATTRS(2);
|
||||
EMC2305_DEFINE_FAN_ATTRS(3);
|
||||
EMC2305_DEFINE_FAN_ATTRS(4);
|
||||
EMC2305_DEFINE_FAN_ATTRS(5);
|
||||
EMC2305_DEFINE_FAN_ATTRS(6);
|
||||
|
||||
/* fan attributes */
|
||||
static const struct sensor_device_attribute *emc2305_fan_attrs[] = {
|
||||
emc2305_attr_fan1,
|
||||
emc2305_attr_fan2,
|
||||
emc2305_attr_fan3,
|
||||
emc2305_attr_fan4,
|
||||
emc2305_attr_fan5,
|
||||
};
|
||||
|
||||
/*
|
||||
* driver interface
|
||||
*/
|
||||
|
||||
static int emc2305_remove(struct i2c_client *client)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
int fan_idx, i;
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
|
||||
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
|
||||
for (i = 0; i < EMC2305_NUM_FAN_ATTRS; ++i)
|
||||
device_remove_file(
|
||||
&client->dev,
|
||||
&emc2305_fan_attrs[fan_idx][i].dev_attr);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(emc2305_attr_common); ++i)
|
||||
device_remove_file(&client->dev,
|
||||
&emc2305_attr_common[i].dev_attr);
|
||||
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
/*
|
||||
* device tree support
|
||||
*/
|
||||
|
||||
struct of_fan_attribute {
|
||||
const char *name;
|
||||
int (*set)(struct i2c_client*, int, long);
|
||||
};
|
||||
|
||||
struct of_fan_attribute of_fan_attributes[] = {
|
||||
{"fan-div", emc2305_set_fan_div},
|
||||
{"fan-target", emc2305_set_fan_target},
|
||||
{"pwm-enable", emc2305_set_pwm_enable},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
static int emc2305_config_of(struct i2c_client *client)
|
||||
{
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
struct device_node *node;
|
||||
unsigned int fan_idx;
|
||||
|
||||
if (!client->dev.of_node)
|
||||
return -EINVAL;
|
||||
if (!of_get_next_child(client->dev.of_node, NULL))
|
||||
return 0;
|
||||
|
||||
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
|
||||
data->fan[fan_idx].enabled = false;
|
||||
|
||||
for_each_child_of_node(client->dev.of_node, node) {
|
||||
const __be32 *property;
|
||||
int len;
|
||||
struct of_fan_attribute *attr;
|
||||
|
||||
property = of_get_property(node, "reg", &len);
|
||||
if (!property || len != sizeof(int)) {
|
||||
dev_err(&client->dev, "invalid reg on %s\n",
|
||||
node->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
fan_idx = be32_to_cpup(property);
|
||||
if (fan_idx >= data->fans) {
|
||||
dev_err(&client->dev,
|
||||
"invalid fan index %d on %s\n",
|
||||
fan_idx, node->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
data->fan[fan_idx].enabled = true;
|
||||
|
||||
for (attr = of_fan_attributes; attr->name; ++attr) {
|
||||
int status = 0;
|
||||
long value;
|
||||
property = of_get_property(node, attr->name, &len);
|
||||
if (!property)
|
||||
continue;
|
||||
if (len != sizeof(int)) {
|
||||
dev_err(&client->dev, "invalid %s on %s\n",
|
||||
attr->name, node->full_name);
|
||||
continue;
|
||||
}
|
||||
value = be32_to_cpup(property);
|
||||
status = attr->set(client, fan_idx, value);
|
||||
if (status == -EINVAL) {
|
||||
dev_err(&client->dev,
|
||||
"invalid value for %s on %s\n",
|
||||
attr->name, node->full_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void emc2305_get_config(struct i2c_client *client)
|
||||
{
|
||||
int i;
|
||||
struct emc2305_data *data = i2c_get_clientdata(client);
|
||||
|
||||
for (i = 0; i < data->fans; ++i) {
|
||||
data->fan[i].enabled = true;
|
||||
emc2305_update_fan(client, i);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
emc2305_config_of(client);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
static int
|
||||
emc2305_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct emc2305_data *data;
|
||||
int status;
|
||||
int i;
|
||||
int fan_idx;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -EIO;
|
||||
|
||||
data = kzalloc(sizeof(struct emc2305_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
status = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID);
|
||||
switch (status) {
|
||||
case 0x34: /* EMC2305 */
|
||||
data->fans = 5;
|
||||
break;
|
||||
case 0x35: /* EMC2303 */
|
||||
data->fans = 3;
|
||||
break;
|
||||
case 0x36: /* EMC2302 */
|
||||
data->fans = 2;
|
||||
break;
|
||||
case 0x37: /* EMC2301 */
|
||||
data->fans = 1;
|
||||
break;
|
||||
default:
|
||||
if (status >= 0)
|
||||
status = -EINVAL;
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
emc2305_get_config(client);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(emc2305_attr_common); ++i) {
|
||||
status = device_create_file(&client->dev,
|
||||
&emc2305_attr_common[i].dev_attr);
|
||||
if (status)
|
||||
goto exit_remove;
|
||||
}
|
||||
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
|
||||
for (i = 0; i < EMC2305_NUM_FAN_ATTRS; ++i) {
|
||||
if (!data->fan[fan_idx].enabled)
|
||||
continue;
|
||||
status = device_create_file(
|
||||
&client->dev,
|
||||
&emc2305_fan_attrs[fan_idx][i].dev_attr);
|
||||
if (status)
|
||||
goto exit_remove_fans;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&client->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
status = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove_fans;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "%s: sensor '%s'\n",
|
||||
dev_name(data->hwmon_dev), client->name);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove_fans:
|
||||
for (fan_idx = 0; fan_idx < data->fans; ++fan_idx)
|
||||
for (i = 0; i < EMC2305_NUM_FAN_ATTRS; ++i)
|
||||
device_remove_file(
|
||||
&client->dev,
|
||||
&emc2305_fan_attrs[fan_idx][i].dev_attr);
|
||||
|
||||
exit_remove:
|
||||
for (i = 0; i < ARRAY_SIZE(emc2305_attr_common); ++i)
|
||||
device_remove_file(&client->dev,
|
||||
&emc2305_attr_common[i].dev_attr);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
return status;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id emc2305_id[] = {
|
||||
{ "emc2305", 0 },
|
||||
{ "emc2303", 0 },
|
||||
{ "emc2302", 0 },
|
||||
{ "emc2301", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, emc2305_id);
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int
|
||||
emc2305_detect(struct i2c_client *new_client, struct i2c_board_info *info)
|
||||
{
|
||||
struct i2c_adapter *adapter = new_client->adapter;
|
||||
int manufacturer, product;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
manufacturer =
|
||||
i2c_smbus_read_byte_data(new_client, REG_MANUFACTURER_ID);
|
||||
if (manufacturer != 0x5D)
|
||||
return -ENODEV;
|
||||
|
||||
product = i2c_smbus_read_byte_data(new_client, REG_PRODUCT_ID);
|
||||
|
||||
switch (product) {
|
||||
case 0x34:
|
||||
strlcpy(info->type, "emc2305", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x35:
|
||||
strlcpy(info->type, "emc2303", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x36:
|
||||
strlcpy(info->type, "emc2302", I2C_NAME_SIZE);
|
||||
break;
|
||||
case 0x37:
|
||||
strlcpy(info->type, "emc2301", I2C_NAME_SIZE);
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver emc2305_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "emc2305",
|
||||
},
|
||||
.probe = emc2305_probe,
|
||||
.remove = emc2305_remove,
|
||||
.id_table = emc2305_id,
|
||||
/*
|
||||
.detect = emc2305_detect,
|
||||
.address_list = i2c_adresses,
|
||||
*/
|
||||
};
|
||||
|
||||
module_i2c_driver(emc2305_driver);
|
||||
|
||||
MODULE_AUTHOR("Reinhard Pfau <pfau@gdsys.de>");
|
||||
MODULE_DESCRIPTION("SMSC EMC2305 hwmon driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* mc24lc64t.c - driver for Microchip 24LC64T
|
||||
*
|
||||
* Copyright (C) 2017 Celestica Corp.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
struct mc24lc64t_data {
|
||||
struct i2c_client *fake_client;
|
||||
struct mutex update_lock;
|
||||
};
|
||||
|
||||
static ssize_t mc24lc64t_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct i2c_client *client = kobj_to_i2c_client(kobj);
|
||||
struct mc24lc64t_data *drvdata = i2c_get_clientdata(client);
|
||||
unsigned long timeout, read_time, i = 0;
|
||||
int status;
|
||||
|
||||
mutex_lock(&drvdata->update_lock);
|
||||
|
||||
if (i2c_smbus_write_byte_data(client, off>>8, off))
|
||||
{
|
||||
status = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
msleep(1);
|
||||
|
||||
begin:
|
||||
|
||||
if (i < count)
|
||||
{
|
||||
timeout = jiffies + msecs_to_jiffies(25); /* 25 mS timeout*/
|
||||
do {
|
||||
read_time = jiffies;
|
||||
|
||||
status = i2c_smbus_read_byte(client);
|
||||
if (status >= 0)
|
||||
{
|
||||
buf[i++] = status;
|
||||
goto begin;
|
||||
}
|
||||
} while (time_before(read_time, timeout));
|
||||
|
||||
status = -ETIMEDOUT;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
status = count;
|
||||
|
||||
exit:
|
||||
mutex_unlock(&drvdata->update_lock);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct bin_attribute mc24lc64t_bit_attr = {
|
||||
.attr = {
|
||||
.name = "eeprom",
|
||||
.mode = S_IRUGO,
|
||||
},
|
||||
.size = 65536,
|
||||
.read = mc24lc64t_read,
|
||||
};
|
||||
|
||||
static int mc24lc64t_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
struct mc24lc64t_data *drvdata;
|
||||
int err;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA
|
||||
| I2C_FUNC_SMBUS_READ_BYTE))
|
||||
return -EPFNOSUPPORT;
|
||||
|
||||
if (!(drvdata = devm_kzalloc(&client->dev,
|
||||
sizeof(struct mc24lc64t_data), GFP_KERNEL)))
|
||||
return -ENOMEM;
|
||||
|
||||
drvdata->fake_client = i2c_new_dummy(client->adapter, client->addr + 1);
|
||||
if (!drvdata->fake_client)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, drvdata);
|
||||
mutex_init(&drvdata->update_lock);
|
||||
|
||||
err = sysfs_create_bin_file(&client->dev.kobj, &mc24lc64t_bit_attr);
|
||||
if (err)
|
||||
i2c_unregister_device(drvdata->fake_client);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int mc24lc64t_remove(struct i2c_client *client)
|
||||
{
|
||||
struct mc24lc64t_data *drvdata = i2c_get_clientdata(client);
|
||||
|
||||
i2c_unregister_device(drvdata->fake_client);
|
||||
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &mc24lc64t_bit_attr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mc24lc64t_id[] = {
|
||||
{ "24lc64t", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mc24lc64t_id);
|
||||
|
||||
static struct i2c_driver mc24lc64t_driver = {
|
||||
.driver = {
|
||||
.name = "mc24lc64t",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = mc24lc64t_probe,
|
||||
.remove = mc24lc64t_remove,
|
||||
.id_table = mc24lc64t_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mc24lc64t_driver);
|
||||
|
||||
MODULE_AUTHOR("Abhisit Sangjan <asang@celestica.com>");
|
||||
MODULE_DESCRIPTION("Microchip 24LC64T Driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user