[platform]: incorporate sonic-platform-modules-cel into sonic buildimage repo
This commit is contained in:
parent
759edc93c9
commit
f489bea105
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -59,9 +59,6 @@
|
|||||||
[submodule "platform/broadcom/sonic-platform-modules-accton"]
|
[submodule "platform/broadcom/sonic-platform-modules-accton"]
|
||||||
path = platform/broadcom/sonic-platform-modules-accton
|
path = platform/broadcom/sonic-platform-modules-accton
|
||||||
url = https://github.com/edge-core/sonic-platform-modules-accton.git
|
url = https://github.com/edge-core/sonic-platform-modules-accton.git
|
||||||
[submodule "platform/broadcom/sonic-platform-modules-cel"]
|
|
||||||
path = platform/broadcom/sonic-platform-modules-cel
|
|
||||||
url = https://github.com/celestica-Inc/sonic-platform-modules-cel.git
|
|
||||||
[submodule "src/sonic-frr/frr"]
|
[submodule "src/sonic-frr/frr"]
|
||||||
path = src/sonic-frr/frr
|
path = src/sonic-frr/frr
|
||||||
url = https://github.com/FRRouting/frr.git
|
url = https://github.com/FRRouting/frr.git
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Subproject commit 5ec0c5e5d4fe8f2ec1ab5a6abefa7d168a81ca98
|
|
33
platform/broadcom/sonic-platform-modules-cel/.gitignore
vendored
Normal file
33
platform/broadcom/sonic-platform-modules-cel/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
15
platform/broadcom/sonic-platform-modules-cel/LICENSE
Normal file
15
platform/broadcom/sonic-platform-modules-cel/LICENSE
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Copyright (C) 2017 Celestica, Inc
|
||||||
|
|
||||||
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
1
platform/broadcom/sonic-platform-modules-cel/README.md
Normal file
1
platform/broadcom/sonic-platform-modules-cel/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
platform drivers for Celestica DX010 for the SONiC project
|
@ -0,0 +1,38 @@
|
|||||||
|
sonic-cel-platform-modules (0.6) unstable; urgency=low
|
||||||
|
|
||||||
|
* Remove unused port-mode switch script. This should be done by hwsku config script.
|
||||||
|
* Add script to turn off QSFP low power mode when boot up.
|
||||||
|
|
||||||
|
-- Pradchaya Phucharoen <pphuchar@celestica.com> Wed, 26 July 2017 10:43:00 +0700
|
||||||
|
|
||||||
|
sonic-cel-platform-modules (0.5) unstable; urgency=low
|
||||||
|
|
||||||
|
* Add port-mode switch script to support 100G 50G 10G_50G qsfp modes.
|
||||||
|
* Fix garbage data when using sfputil to read QSFP-transceiver's eeprom.
|
||||||
|
* Fix incorrect endian in eeprom read word data.
|
||||||
|
|
||||||
|
-- Pradchaya Phucharoen <pphuchar@celestica.com> Tue, 18 July 2017 11:30:00 +0700
|
||||||
|
|
||||||
|
sonic-cel-platform-modules (0.4) unstable; urgency=low
|
||||||
|
|
||||||
|
* Add support for DX010's fancontrol, automatic run-up and FIX bug lpmod
|
||||||
|
|
||||||
|
-- Pariwat Leamsumran <pleamsum@celestica.com> Thu, 14 June 2017 16:25:00 +0700
|
||||||
|
|
||||||
|
sonic-cel-platform-modules (0.3) unstable; urgency=low
|
||||||
|
|
||||||
|
* Add support for DX010's DPS800
|
||||||
|
|
||||||
|
-- Abhisit Sangjan <asang@celestica.com> Thu, 29 May 2017 19:23:00 +0700
|
||||||
|
|
||||||
|
sonic-cel-platform-modules (0.2) unstable; urgency=low
|
||||||
|
|
||||||
|
* Add support for DX010's LM75B, Watchdog and EMC2305
|
||||||
|
|
||||||
|
-- Abhisit Sangjan <asang@celestica.com> Thu, 25 May 2017 15:26:00 +0700
|
||||||
|
|
||||||
|
sonic-cel-platform-modules (0.1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Initial release
|
||||||
|
|
||||||
|
-- Abhisit Sangjan <asang@celestica.com> Mon, 2 May 2017 14:47:00 +0700
|
@ -0,0 +1 @@
|
|||||||
|
9
|
12
platform/broadcom/sonic-platform-modules-cel/debian/control
Normal file
12
platform/broadcom/sonic-platform-modules-cel/debian/control
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Source: sonic-cel-platform-modules
|
||||||
|
Section: main
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: Abhisit Sangjan <asang@celestica.com>
|
||||||
|
Build-Depends: debhelper (>= 8.0.0), bzip2
|
||||||
|
Standards-Version: 3.9.3
|
||||||
|
|
||||||
|
Package: platform-modules-dx010
|
||||||
|
Architecture: amd64
|
||||||
|
Depends: linux-image-3.16.0-5-amd64
|
||||||
|
Description: kernel modules for platform devices such as fan, led, sfp
|
||||||
|
|
@ -0,0 +1,106 @@
|
|||||||
|
#!/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 DX010 board.
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo -n "Setting up board... "
|
||||||
|
|
||||||
|
depmod -a
|
||||||
|
modprobe i2c-dev
|
||||||
|
modprobe i2c-mux-pca954x
|
||||||
|
modprobe dx010_wdt
|
||||||
|
modprobe leds-dx010
|
||||||
|
|
||||||
|
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} 0x70 0x10 0x00 0x01 i
|
||||||
|
|
||||||
|
# Attach PCA9541 Ox70 Master Selector
|
||||||
|
chmod 755 /sys/bus/i2c/devices/i2c-${devnum}/new_device
|
||||||
|
echo pca9541 0x70 > /sys/bus/i2c/devices/i2c-${devnum}/new_device
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Attach PCA9548 0x71 Channel Extender for Main Board
|
||||||
|
echo pca9548 0x71 > /sys/bus/i2c/devices/i2c-${devnum}/new_device
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Attach PCA9548 0x73 Channel Extender for CPU Board
|
||||||
|
echo pca9548 0x73 > /sys/bus/i2c/devices/i2c-${devnum}/new_device
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Attach PCA9548 0x77 Channel Extender for Fan's EEPROMs
|
||||||
|
echo pca9548 0x77 > /sys/bus/i2c/devices/i2c-${devnum}/new_device
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Attach syseeprom
|
||||||
|
echo 24lc64t 0x50 > /sys/bus/i2c/devices/i2c-12/new_device
|
||||||
|
|
||||||
|
# Attach temperature sensors
|
||||||
|
echo dx010_lm75b 0x48 > /sys/bus/i2c/devices/i2c-5/new_device
|
||||||
|
echo dx010_lm75b 0x49 > /sys/bus/i2c/devices/i2c-6/new_device
|
||||||
|
echo dx010_lm75b 0x4a > /sys/bus/i2c/devices/i2c-7/new_device
|
||||||
|
echo dx010_lm75b 0x48 > /sys/bus/i2c/devices/i2c-14/new_device
|
||||||
|
echo dx010_lm75b 0x4e > /sys/bus/i2c/devices/i2c-15/new_device
|
||||||
|
|
||||||
|
# Attach fans
|
||||||
|
echo emc2305 0x2e > /sys/bus/i2c/devices/i2c-13/new_device
|
||||||
|
echo emc2305 0x4d > /sys/bus/i2c/devices/i2c-13/new_device
|
||||||
|
|
||||||
|
# Attach PSUs
|
||||||
|
echo dps460 0x5a > /sys/bus/i2c/devices/i2c-10/new_device
|
||||||
|
echo dps460 0x5b > /sys/bus/i2c/devices/i2c-11/new_device
|
||||||
|
|
||||||
|
# Attach PCA9506 GPIO expander for 40 pins
|
||||||
|
echo pca9505 0x20 > /sys/bus/i2c/devices/i2c-17/new_device
|
||||||
|
|
||||||
|
modprobe dx010_cpld
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Turn off/down lpmod by defult (0 - Normal, 1 - Low Pow)
|
||||||
|
echo 0x00000000 > /sys/devices/platform/dx010_cpld/qsfp_lpmode
|
||||||
|
|
||||||
|
# Attach 32 instances of EEPROM driver QSFP ports
|
||||||
|
for ((n=26;n<=58;n++));
|
||||||
|
do
|
||||||
|
echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-$n/new_device
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "done."
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop)
|
||||||
|
echo "done."
|
||||||
|
;;
|
||||||
|
|
||||||
|
force-reload|restart)
|
||||||
|
echo "Not supported"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: /etc/init.d/platform-modules-dx010.init {start|stop}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
@ -0,0 +1,2 @@
|
|||||||
|
dx010/scripts/dx010_check_qsfp.sh usr/local/bin
|
||||||
|
dx010/cfg/dx010-modules.conf etc/modules-load.d
|
33
platform/broadcom/sonic-platform-modules-cel/debian/rules
Normal file
33
platform/broadcom/sonic-platform-modules-cel/debian/rules
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
export INSTALL_MOD_DIR:=extra
|
||||||
|
|
||||||
|
KVERSION ?= $(shell uname -r)
|
||||||
|
KERNEL_SRC := /lib/modules/$(KVERSION)
|
||||||
|
MOD_SRC_DIR:= $(shell pwd)
|
||||||
|
MODULE_DIRS:= dx010
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
(for mod in $(MODULE_DIRS); do \
|
||||||
|
make -C $(KERNEL_SRC)/build M=$(MOD_SRC_DIR)/$${mod}/modules; \
|
||||||
|
done)
|
||||||
|
|
||||||
|
override_dh_auto_install:
|
||||||
|
(for mod in $(MODULE_DIRS); do \
|
||||||
|
dh_installdirs -pplatform-modules-$${mod} \
|
||||||
|
$(KERNEL_SRC)/$(INSTALL_MOD_DIR); \
|
||||||
|
cp $(MOD_SRC_DIR)/$${mod}/modules/*.ko \
|
||||||
|
debian/platform-modules-$${mod}/$(KERNEL_SRC)/$(INSTALL_MOD_DIR); \
|
||||||
|
done)
|
||||||
|
|
||||||
|
override_dh_usrlocal:
|
||||||
|
|
||||||
|
override_dh_clean:
|
||||||
|
dh_clean
|
||||||
|
(for mod in $(MODULE_DIRS); do \
|
||||||
|
make -C $(KERNEL_SRC)/build M=$(MOD_SRC_DIR)/$${mod}/modules clean; \
|
||||||
|
done)
|
||||||
|
|
@ -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 := dx010_cpld.o mc24lc64t.o emc2305.o dx010_wdt.o leds-dx010.o lm75.o
|
@ -0,0 +1,506 @@
|
|||||||
|
/*
|
||||||
|
* dx010_cpld.c - driver for SeaStone's CPLD
|
||||||
|
*
|
||||||
|
* 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/interrupt.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/pci.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/stddef.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/ioport.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/dmi.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/wait.h>
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <uapi/linux/stat.h>
|
||||||
|
|
||||||
|
#define DRIVER_NAME "dx010_cpld"
|
||||||
|
|
||||||
|
#define LPMOD0108 0x252
|
||||||
|
#define LPMOD0910 0x253
|
||||||
|
#define LPMOD1118 0x2d2
|
||||||
|
#define LPMOD1921 0x2d3
|
||||||
|
#define LPMOD2229 0x3d2
|
||||||
|
#define LPMOD3032 0x3d3
|
||||||
|
|
||||||
|
#define ABS0108 0x254
|
||||||
|
#define ABS0910 0x255
|
||||||
|
#define ABS1118 0x2d4
|
||||||
|
#define ABS1921 0x2d5
|
||||||
|
#define ABS2229 0x3d4
|
||||||
|
#define ABS3032 0x3d5
|
||||||
|
|
||||||
|
#define INT0108 0x256
|
||||||
|
#define INT0910 0x257
|
||||||
|
#define INT1118 0x2d6
|
||||||
|
#define INT1921 0x2d7
|
||||||
|
#define INT2229 0x3d6
|
||||||
|
#define INT3032 0x3d7
|
||||||
|
|
||||||
|
#define LENGTH_PORT_CPLD 34
|
||||||
|
#define PORT_BANK1_START 1
|
||||||
|
#define PORT_BANK1_END 10
|
||||||
|
#define PORT_BANK2_START 11
|
||||||
|
#define PORT_BANK2_END 21
|
||||||
|
#define PORT_BANK3_START 22
|
||||||
|
#define PORT_BANK3_END 32
|
||||||
|
#define PORT_SFPP1 33
|
||||||
|
#define PORT_SFPP2 34
|
||||||
|
|
||||||
|
#define PORT_ID_BANK1 0x210
|
||||||
|
#define PORT_ID_BANK2 0x290
|
||||||
|
#define PORT_ID_BANK3 0x390
|
||||||
|
|
||||||
|
#define OPCODE_ID_BANK1 0x211
|
||||||
|
#define OPCODE_ID_BANK2 0x291
|
||||||
|
#define OPCODE_ID_BANK3 0x391
|
||||||
|
|
||||||
|
#define DEVADDR_ID_BANK1 0x212
|
||||||
|
#define DEVADDR_ID_BANK2 0x292
|
||||||
|
#define DEVADDR_ID_BANK3 0x392
|
||||||
|
|
||||||
|
#define CMDBYT_ID_BANK1 0x213
|
||||||
|
#define CMDBYT_ID_BANK2 0x293
|
||||||
|
#define CMDBYT_ID_BANK3 0x393
|
||||||
|
|
||||||
|
#define WRITE_ID_BANK1 0x220
|
||||||
|
#define WRITE_ID_BANK2 0x2A0
|
||||||
|
#define WRITE_ID_BANK3 0x3A0
|
||||||
|
|
||||||
|
#define READ_ID_BANK1 0x230
|
||||||
|
#define READ_ID_BANK2 0x2B0
|
||||||
|
#define READ_ID_BANK3 0x3B0
|
||||||
|
|
||||||
|
#define SSRR_ID_BANK1 0x216
|
||||||
|
#define SSRR_ID_BANK2 0x296
|
||||||
|
#define SSRR_ID_BANK3 0x396
|
||||||
|
|
||||||
|
#define HST_CNTL2_QUICK 0x00
|
||||||
|
#define HST_CNTL2_BYTE 0x01
|
||||||
|
#define HST_CNTL2_BYTE_DATA 0x02
|
||||||
|
#define HST_CNTL2_WORD_DATA 0x03
|
||||||
|
#define HST_CNTL2_BLOCK 0x05
|
||||||
|
|
||||||
|
struct dx010_i2c_data {
|
||||||
|
int portid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dx010_cpld_data {
|
||||||
|
struct i2c_adapter *i2c_adapter[LENGTH_PORT_CPLD];
|
||||||
|
struct mutex cpld_lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dx010_cpld_data *cpld_data;
|
||||||
|
|
||||||
|
static ssize_t get_lpmode(struct device *dev, struct device_attribute *devattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
unsigned long lpmod = 0;
|
||||||
|
|
||||||
|
mutex_lock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
lpmod =
|
||||||
|
(inb(LPMOD3032) & 0x07) << (24+5) |
|
||||||
|
inb(LPMOD2229) << (24-3) |
|
||||||
|
(inb(LPMOD1921) & 0x07) << (16 + 2) |
|
||||||
|
inb(LPMOD1118) << (16-6) |
|
||||||
|
(inb(LPMOD0910) & 0x03 ) << 8 |
|
||||||
|
inb(LPMOD0108);
|
||||||
|
|
||||||
|
mutex_unlock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
return sprintf(buf,"0x%8.8lx\n", lpmod & 0xffffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t set_lpmode(struct device *dev, struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
unsigned long lpmod;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
mutex_lock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
err = kstrtoul(buf, 16, &lpmod);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
mutex_unlock(&cpld_data->cpld_lock);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
outb( (lpmod >> 0) & 0xFF, LPMOD0108);
|
||||||
|
outb( (lpmod >> 8) & 0x03, LPMOD0910);
|
||||||
|
outb( (lpmod >> 10) & 0xFF, LPMOD1118);
|
||||||
|
outb( (lpmod >> 18) & 0x07, LPMOD1921);
|
||||||
|
outb( (lpmod >> 21) & 0xFF, LPMOD2229);
|
||||||
|
outb( (lpmod >> 29) & 0x07, LPMOD3032);
|
||||||
|
|
||||||
|
mutex_unlock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t get_modprs(struct device *dev, struct device_attribute *devattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
unsigned long present;
|
||||||
|
|
||||||
|
mutex_lock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
present =
|
||||||
|
(inb(ABS3032) & 0x07) << (24+5) |
|
||||||
|
inb(ABS2229) << (24-3) |
|
||||||
|
(inb(ABS1921) & 0x07) << (16 + 2) |
|
||||||
|
inb(ABS1118) << (16-6) |
|
||||||
|
(inb(ABS0910) & 0x03) << 8 |
|
||||||
|
inb(ABS0108);
|
||||||
|
|
||||||
|
mutex_unlock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
return sprintf(buf,"0x%8.8lx\n", present & 0xffffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t get_modirq(struct device *dev, struct device_attribute *devattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
unsigned long irq;
|
||||||
|
|
||||||
|
mutex_lock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
irq =
|
||||||
|
(inb(INT3032) & 0x07) << (24+5) |
|
||||||
|
inb(INT2229) << (24-3) |
|
||||||
|
(inb(INT1921) & 0x07) << (16 + 2) |
|
||||||
|
inb(INT1118) << (16-6) |
|
||||||
|
(inb(INT0910) & 0x03) << 8 |
|
||||||
|
inb(INT0108);
|
||||||
|
|
||||||
|
mutex_unlock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
return sprintf(buf,"0x%8.8lx\n", irq & 0xffffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(qsfp_lpmode, S_IRUGO | S_IWUSR, get_lpmode, set_lpmode);
|
||||||
|
static DEVICE_ATTR(qsfp_modprs, S_IRUGO, get_modprs, NULL);
|
||||||
|
static DEVICE_ATTR(qsfp_modirq, S_IRUGO, get_modirq, NULL);
|
||||||
|
|
||||||
|
static struct attribute *dx010_lpc_attrs[] = {
|
||||||
|
&dev_attr_qsfp_lpmode.attr,
|
||||||
|
&dev_attr_qsfp_modprs.attr,
|
||||||
|
&dev_attr_qsfp_modirq.attr,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute_group dx010_lpc_attr_grp = {
|
||||||
|
.attrs = dx010_lpc_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct resource cel_dx010_lpc_resources[] = {
|
||||||
|
{
|
||||||
|
.flags = IORESOURCE_IO,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void cel_dx010_lpc_dev_release( struct device * dev)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_device cel_dx010_lpc_dev = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
.id = -1,
|
||||||
|
.num_resources = ARRAY_SIZE(cel_dx010_lpc_resources),
|
||||||
|
.resource = cel_dx010_lpc_resources,
|
||||||
|
.dev = {
|
||||||
|
.release = cel_dx010_lpc_dev_release,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read eeprom of QSFP device.
|
||||||
|
* @param a i2c adapter.
|
||||||
|
* @param addr address to read.
|
||||||
|
* @param new_data QSFP port number struct.
|
||||||
|
* @param cmd i2c command.
|
||||||
|
* @return 0 if not error, else the error code.
|
||||||
|
*/
|
||||||
|
static int i2c_read_eeprom(struct i2c_adapter *a, u16 addr,
|
||||||
|
struct dx010_i2c_data *new_data, u8 cmd, union i2c_smbus_data *data){
|
||||||
|
|
||||||
|
u32 reg;
|
||||||
|
int ioBase=0;
|
||||||
|
char byte;
|
||||||
|
short temp;
|
||||||
|
short portid, opcode, devaddr, cmdbyte0, ssrr, writedata, readdata;
|
||||||
|
__u16 word_data;
|
||||||
|
char read_byte;
|
||||||
|
int error = -EIO;
|
||||||
|
|
||||||
|
mutex_lock(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
if (((new_data->portid >= PORT_BANK1_START)
|
||||||
|
&& (new_data->portid <= PORT_BANK1_END))
|
||||||
|
|| (new_data->portid == PORT_SFPP1)
|
||||||
|
|| (new_data->portid == PORT_SFPP2))
|
||||||
|
{
|
||||||
|
portid = PORT_ID_BANK1;
|
||||||
|
opcode = OPCODE_ID_BANK1;
|
||||||
|
devaddr = DEVADDR_ID_BANK1;
|
||||||
|
cmdbyte0 = CMDBYT_ID_BANK1;
|
||||||
|
ssrr = SSRR_ID_BANK1;
|
||||||
|
writedata = WRITE_ID_BANK1;
|
||||||
|
readdata = READ_ID_BANK1;
|
||||||
|
}else if ((new_data->portid >= PORT_BANK2_START) && (new_data->portid <= PORT_BANK2_END)){
|
||||||
|
portid = PORT_ID_BANK2;
|
||||||
|
opcode = OPCODE_ID_BANK2;
|
||||||
|
devaddr = DEVADDR_ID_BANK2;
|
||||||
|
cmdbyte0 = CMDBYT_ID_BANK2;
|
||||||
|
ssrr = SSRR_ID_BANK2;
|
||||||
|
writedata = WRITE_ID_BANK2;
|
||||||
|
readdata = READ_ID_BANK2;
|
||||||
|
}else if ((new_data->portid >= PORT_BANK3_START) && (new_data->portid <= PORT_BANK3_END)){
|
||||||
|
portid = PORT_ID_BANK3;
|
||||||
|
opcode = OPCODE_ID_BANK3;
|
||||||
|
devaddr = DEVADDR_ID_BANK3;
|
||||||
|
cmdbyte0 = CMDBYT_ID_BANK3;
|
||||||
|
ssrr = SSRR_ID_BANK3;
|
||||||
|
writedata = WRITE_ID_BANK3;
|
||||||
|
readdata = READ_ID_BANK3;
|
||||||
|
}else{
|
||||||
|
/* Invalid parameter! */
|
||||||
|
error = -EINVAL;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((inb(ioBase + ssrr) & 0x40));
|
||||||
|
if ((inb(ioBase + ssrr) & 0x80) == 0x80) {
|
||||||
|
error = -EIO;
|
||||||
|
/* Read error reset the port */
|
||||||
|
outb(0x00, ioBase + ssrr);
|
||||||
|
udelay(3000);
|
||||||
|
outb(0x01, ioBase + ssrr);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte = 0x40 +new_data->portid;
|
||||||
|
reg = cmd;
|
||||||
|
outb(byte, ioBase + portid);
|
||||||
|
outb(reg,ioBase + cmdbyte0);
|
||||||
|
byte = 33;
|
||||||
|
outb(byte, ioBase + opcode);
|
||||||
|
addr = addr << 1;
|
||||||
|
addr |= 0x01;
|
||||||
|
outb(addr, ioBase + devaddr);
|
||||||
|
while ((inb(ioBase + ssrr) & 0x40))
|
||||||
|
{
|
||||||
|
udelay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((inb(ioBase + ssrr) & 0x80) == 0x80) {
|
||||||
|
/* Read error reset the port */
|
||||||
|
error = -EIO;
|
||||||
|
outb(0x00, ioBase + ssrr);
|
||||||
|
udelay(3000);
|
||||||
|
outb(0x01, ioBase + ssrr);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = ioBase + readdata;
|
||||||
|
word_data = inb(temp);
|
||||||
|
word_data |= (inb(++temp) << 8);
|
||||||
|
|
||||||
|
mutex_unlock(&cpld_data->cpld_lock);
|
||||||
|
data->word = word_data;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
mutex_unlock(&cpld_data->cpld_lock);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dx010_i2c_access(struct i2c_adapter *a, u16 addr,
|
||||||
|
unsigned short flags, char rw, u8 cmd,
|
||||||
|
int size, union i2c_smbus_data *data)
|
||||||
|
{
|
||||||
|
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
struct dx010_i2c_data *new_data;
|
||||||
|
|
||||||
|
/* Write the command register */
|
||||||
|
new_data = i2c_get_adapdata(a);
|
||||||
|
|
||||||
|
/* Map the size to what the chip understands */
|
||||||
|
switch (size) {
|
||||||
|
case I2C_SMBUS_QUICK:
|
||||||
|
size = HST_CNTL2_QUICK;
|
||||||
|
break;
|
||||||
|
case I2C_SMBUS_BYTE:
|
||||||
|
size = HST_CNTL2_BYTE;
|
||||||
|
break;
|
||||||
|
case I2C_SMBUS_BYTE_DATA:
|
||||||
|
size = HST_CNTL2_BYTE_DATA;
|
||||||
|
break;
|
||||||
|
case I2C_SMBUS_WORD_DATA:
|
||||||
|
size = HST_CNTL2_WORD_DATA;
|
||||||
|
break;
|
||||||
|
case I2C_SMBUS_BLOCK_DATA:
|
||||||
|
size = HST_CNTL2_BLOCK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dev_warn(&a->dev, "Unsupported transaction %d\n", size);
|
||||||
|
error = -EOPNOTSUPP;
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case HST_CNTL2_BYTE: /* Result put in SMBHSTDAT0 */
|
||||||
|
break;
|
||||||
|
case HST_CNTL2_BYTE_DATA:
|
||||||
|
break;
|
||||||
|
case HST_CNTL2_WORD_DATA:
|
||||||
|
if( 0 == i2c_read_eeprom(a,addr,new_data,cmd,data)){
|
||||||
|
error = 0;
|
||||||
|
}else{
|
||||||
|
error = -EIO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Done:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 dx010_i2c_func(struct i2c_adapter *a)
|
||||||
|
{
|
||||||
|
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
||||||
|
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
|
||||||
|
I2C_FUNC_SMBUS_BLOCK_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct i2c_algorithm dx010_i2c_algorithm = {
|
||||||
|
.smbus_xfer = dx010_i2c_access,
|
||||||
|
.functionality = dx010_i2c_func,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct i2c_adapter * cel_dx010_i2c_init(struct platform_device *pdev, int portid)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
struct i2c_adapter *new_adapter;
|
||||||
|
struct dx010_i2c_data *new_data;
|
||||||
|
|
||||||
|
new_adapter = kzalloc(sizeof(*new_adapter), GFP_KERNEL);
|
||||||
|
if (!new_adapter)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
new_adapter->dev.parent = &pdev->dev;
|
||||||
|
new_adapter->owner = THIS_MODULE;
|
||||||
|
new_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
|
||||||
|
new_adapter->algo = &dx010_i2c_algorithm;
|
||||||
|
|
||||||
|
snprintf(new_adapter->name, sizeof(new_adapter->name),
|
||||||
|
"SMBus dx010 i2c Adapter portid@%04x", portid);
|
||||||
|
|
||||||
|
new_data = kzalloc(sizeof(*new_data), GFP_KERNEL);
|
||||||
|
if (!new_data)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
new_data->portid = portid;
|
||||||
|
|
||||||
|
i2c_set_adapdata(new_adapter,new_data);
|
||||||
|
|
||||||
|
error = i2c_add_adapter(new_adapter);
|
||||||
|
if(error)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return new_adapter;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int cel_dx010_lpc_drv_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct resource *res;
|
||||||
|
int ret =0;
|
||||||
|
int portid_count;
|
||||||
|
|
||||||
|
cpld_data = devm_kzalloc(&pdev->dev, sizeof(struct dx010_cpld_data),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!cpld_data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
mutex_init(&cpld_data->cpld_lock);
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||||
|
if (unlikely(!res)) {
|
||||||
|
printk(KERN_ERR " Specified Resource Not Available...\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sysfs_create_group(&pdev->dev.kobj, &dx010_lpc_attr_grp);
|
||||||
|
if (ret) {
|
||||||
|
printk(KERN_ERR "Cannot create sysfs\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(portid_count=1 ; portid_count<=LENGTH_PORT_CPLD ; portid_count++)
|
||||||
|
cpld_data->i2c_adapter[portid_count-1] =
|
||||||
|
cel_dx010_i2c_init(pdev, portid_count);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cel_dx010_lpc_drv_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
int portid_count;
|
||||||
|
struct dx010_i2c_data *new_data;
|
||||||
|
|
||||||
|
sysfs_remove_group(&pdev->dev.kobj, &dx010_lpc_attr_grp);
|
||||||
|
|
||||||
|
for (portid_count=1 ; portid_count<=LENGTH_PORT_CPLD ; portid_count++)
|
||||||
|
i2c_del_adapter(cpld_data->i2c_adapter[portid_count-1]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver cel_dx010_lpc_drv = {
|
||||||
|
.probe = cel_dx010_lpc_drv_probe,
|
||||||
|
.remove = __exit_p(cel_dx010_lpc_drv_remove),
|
||||||
|
.driver = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
int cel_dx010_lpc_init(void)
|
||||||
|
{
|
||||||
|
platform_device_register(&cel_dx010_lpc_dev);
|
||||||
|
platform_driver_register(&cel_dx010_lpc_drv);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cel_dx010_lpc_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&cel_dx010_lpc_drv);
|
||||||
|
platform_device_unregister(&cel_dx010_lpc_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(cel_dx010_lpc_init);
|
||||||
|
module_exit(cel_dx010_lpc_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Abhisit Sangjan <asang@celestica.com>");
|
||||||
|
MODULE_AUTHOR("Pariwat Leamsumran <pleamsum@celestica.com>");
|
||||||
|
MODULE_DESCRIPTION("Celestica SeaStone DX010 LPC Driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
* Watchdog driver for the Seastone DX010
|
||||||
|
*
|
||||||
|
* 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/module.h>
|
||||||
|
#include <linux/moduleparam.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/watchdog.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define DRIVER_NAME "dx010_wdt"
|
||||||
|
|
||||||
|
#define RESET_CTRL 0x102
|
||||||
|
#define WDT_MASK 0x04
|
||||||
|
#define WDI_GPIO_DIR 0x504
|
||||||
|
#define WDI_GPIO 0x508
|
||||||
|
|
||||||
|
static bool nowayout = WATCHDOG_NOWAYOUT;
|
||||||
|
|
||||||
|
struct dx010_wdt_drvdata {
|
||||||
|
struct watchdog_device wdt;
|
||||||
|
struct mutex lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct resource dx010_wdt_resources[] = {
|
||||||
|
{
|
||||||
|
.flags = IORESOURCE_IO,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void dx010_wdt_dev_release( struct device * dev)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_device dx010_wdt_dev = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
.id = -1,
|
||||||
|
.num_resources = ARRAY_SIZE(dx010_wdt_resources),
|
||||||
|
.resource = dx010_wdt_resources,
|
||||||
|
.dev = {
|
||||||
|
.release = dx010_wdt_dev_release,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int dx010_wdt_start(struct watchdog_device *wdt_dev)
|
||||||
|
{
|
||||||
|
struct dx010_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
|
||||||
|
unsigned char reset_ctrl = 0x00;
|
||||||
|
unsigned long gpio ,dir;
|
||||||
|
|
||||||
|
mutex_lock(&drvdata->lock);
|
||||||
|
|
||||||
|
gpio = inl(WDI_GPIO);
|
||||||
|
gpio |= 1 << 15;
|
||||||
|
outl(gpio, WDI_GPIO);
|
||||||
|
|
||||||
|
outl((inl(WDI_GPIO_DIR) & (~(1 << 15))), WDI_GPIO_DIR);
|
||||||
|
|
||||||
|
reset_ctrl = inb(RESET_CTRL);
|
||||||
|
|
||||||
|
gpio = inl(WDI_GPIO);
|
||||||
|
gpio &= ~(1 << 15);
|
||||||
|
outl_p( gpio, WDI_GPIO );
|
||||||
|
|
||||||
|
mdelay(10);
|
||||||
|
|
||||||
|
gpio = inl(WDI_GPIO);
|
||||||
|
gpio |= (1 << 15);
|
||||||
|
outl_p( gpio, WDI_GPIO );
|
||||||
|
|
||||||
|
reset_ctrl |= WDT_MASK;
|
||||||
|
outb(reset_ctrl, RESET_CTRL);
|
||||||
|
|
||||||
|
mutex_unlock(&drvdata->lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dx010_wdt_stop(struct watchdog_device *wdt_dev)
|
||||||
|
{
|
||||||
|
struct dx010_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
|
||||||
|
unsigned long reset_ctrl;
|
||||||
|
|
||||||
|
mutex_lock(&drvdata->lock);
|
||||||
|
|
||||||
|
reset_ctrl = inb(RESET_CTRL);
|
||||||
|
reset_ctrl &= ~WDT_MASK;
|
||||||
|
outb(reset_ctrl, RESET_CTRL);
|
||||||
|
|
||||||
|
mutex_unlock(&drvdata->lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dx010_wdt_ping(struct watchdog_device *wdt_dev)
|
||||||
|
{
|
||||||
|
struct dx010_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
|
||||||
|
unsigned long gpio;
|
||||||
|
|
||||||
|
mutex_lock(&drvdata->lock);
|
||||||
|
|
||||||
|
gpio = inl(WDI_GPIO);
|
||||||
|
gpio &= ~(1 << 15);
|
||||||
|
outl_p( gpio, WDI_GPIO );
|
||||||
|
|
||||||
|
mdelay(10);
|
||||||
|
|
||||||
|
gpio = inl(WDI_GPIO);
|
||||||
|
gpio |= (1 << 15);
|
||||||
|
outl_p( gpio, WDI_GPIO );
|
||||||
|
|
||||||
|
mutex_unlock(&drvdata->lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct watchdog_info dx010_wdt_info = {
|
||||||
|
.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
||||||
|
.identity = "DX010 Watchdog",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct watchdog_ops dx010_wdt_ops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.start = dx010_wdt_start,
|
||||||
|
.stop = dx010_wdt_stop,
|
||||||
|
.ping = dx010_wdt_ping,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int dx010_wdt_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct dx010_wdt_drvdata *drvdata;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!drvdata) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_init(&drvdata->lock);
|
||||||
|
|
||||||
|
drvdata->wdt.info = &dx010_wdt_info;
|
||||||
|
drvdata->wdt.ops = &dx010_wdt_ops;
|
||||||
|
|
||||||
|
watchdog_set_nowayout(&drvdata->wdt, nowayout);
|
||||||
|
watchdog_set_drvdata(&drvdata->wdt, drvdata);
|
||||||
|
|
||||||
|
ret = watchdog_register_device(&drvdata->wdt);
|
||||||
|
if (ret != 0) {
|
||||||
|
dev_err(&pdev->dev, "watchdog_register_device() failed: %d\n",
|
||||||
|
ret);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, drvdata);
|
||||||
|
|
||||||
|
err:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dx010_wdt_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct dx010_wdt_drvdata *drvdata = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
watchdog_unregister_device(&drvdata->wdt);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver dx010_wdt_drv = {
|
||||||
|
.probe = dx010_wdt_probe,
|
||||||
|
.remove = dx010_wdt_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
int dx010_wdt_init(void)
|
||||||
|
{
|
||||||
|
platform_device_register(&dx010_wdt_dev);
|
||||||
|
platform_driver_register(&dx010_wdt_drv);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dx010_wdt_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&dx010_wdt_drv);
|
||||||
|
platform_device_unregister(&dx010_wdt_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(dx010_wdt_init);
|
||||||
|
module_exit(dx010_wdt_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Abhisit Sangjan <asang@celestica.com>");
|
||||||
|
MODULE_AUTHOR("Pariwat Leamsumran <pleamsum@celestica.com>");
|
||||||
|
MODULE_DESCRIPTION("Seastone DX010 Watchdog");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_ALIAS("platform:dx010-watchdog");
|
@ -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,289 @@
|
|||||||
|
/*
|
||||||
|
* leds-dx010-status.c - Driver for Seastone DX010 front panel LEDs
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/leds.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#define DRIVER_NAME "leds_dx010"
|
||||||
|
#define FRONT_LED_STAT 0x303
|
||||||
|
|
||||||
|
static int dx010_led_blink_stat(struct led_classdev *led_cdev,
|
||||||
|
unsigned long *delay_on,
|
||||||
|
unsigned long *delay_off)
|
||||||
|
{
|
||||||
|
unsigned char led;
|
||||||
|
|
||||||
|
if (!(*delay_on == 0 && *delay_off == 0) &&
|
||||||
|
!(*delay_on == 250 && *delay_off == 250) &&
|
||||||
|
!(*delay_on == 500 && *delay_off == 500))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
led &= 0xfc;
|
||||||
|
|
||||||
|
if ((*delay_on == 250) && (*delay_off == 250))
|
||||||
|
led |= 0x02;
|
||||||
|
else if ((*delay_on == 500) && (*delay_off == 500))
|
||||||
|
led |= 0x01;
|
||||||
|
|
||||||
|
outb(led, FRONT_LED_STAT);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t dx010_led_blink_show_stat(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct led_classdev *leddev = dev_get_drvdata(dev);
|
||||||
|
unsigned char led;
|
||||||
|
const char *msg;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
led &= 0x03;
|
||||||
|
|
||||||
|
switch (led)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
msg = "No blinking, turn on";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
msg = "1 Hz is blinking";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
msg = "4 Hz is blinking";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
msg = "No blinking, turn off";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msg = "Unknown error";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(buf, "%s\n", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t dx010_led_blink_store_stat(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf, size_t size)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct led_classdev *leddev = dev_get_drvdata(dev);
|
||||||
|
unsigned long blink_state;
|
||||||
|
unsigned char led;
|
||||||
|
|
||||||
|
ret = kstrtoul(buf, 10, &blink_state);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
led &= 0xfc;
|
||||||
|
|
||||||
|
switch (blink_state)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
led |= 0x03;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
break;
|
||||||
|
case 250:
|
||||||
|
led |= 0x02;
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
led |= 0x01;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
outb(led, FRONT_LED_STAT);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
static DEVICE_ATTR(blink, 0644, dx010_led_blink_show_stat, dx010_led_blink_store_stat);
|
||||||
|
|
||||||
|
static void dx010_led_brightness_set_stat(struct led_classdev *led_cdev,
|
||||||
|
enum led_brightness brightness)
|
||||||
|
{
|
||||||
|
unsigned char led;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
led &= 0xfc;
|
||||||
|
|
||||||
|
if (!brightness)
|
||||||
|
led |= 0x03;
|
||||||
|
|
||||||
|
outb( led, FRONT_LED_STAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum led_brightness dx010_led_brightness_get_p2(struct led_classdev *led_cdev)
|
||||||
|
{
|
||||||
|
unsigned char led;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
|
||||||
|
return (led & 0x08) ? LED_OFF : LED_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dx010_led_brightness_set_p2(struct led_classdev *led_cdev,
|
||||||
|
enum led_brightness brightness)
|
||||||
|
{
|
||||||
|
unsigned char led;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
led &= 0xf7;
|
||||||
|
|
||||||
|
if (!brightness)
|
||||||
|
led |= 0x08;
|
||||||
|
|
||||||
|
outb( led, FRONT_LED_STAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum led_brightness dx010_led_brightness_get_p1(struct led_classdev *led_cdev)
|
||||||
|
{
|
||||||
|
unsigned char led;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
|
||||||
|
return (led & 0x04) ? LED_OFF : LED_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dx010_led_brightness_set_p1(struct led_classdev *led_cdev,
|
||||||
|
enum led_brightness brightness)
|
||||||
|
{
|
||||||
|
unsigned char led;
|
||||||
|
|
||||||
|
led = inb(FRONT_LED_STAT);
|
||||||
|
led &= 0xfb;
|
||||||
|
|
||||||
|
if (!brightness)
|
||||||
|
led |= 0x04;
|
||||||
|
|
||||||
|
outb( led, FRONT_LED_STAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct led_classdev dx010_leds[] = {
|
||||||
|
{
|
||||||
|
.name = "dx010:green:p-1",
|
||||||
|
.brightness = LED_OFF,
|
||||||
|
.max_brightness = 1,
|
||||||
|
.brightness_get = dx010_led_brightness_get_p1,
|
||||||
|
.brightness_set = dx010_led_brightness_set_p1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "dx010:green:p-2",
|
||||||
|
.brightness = LED_OFF,
|
||||||
|
.max_brightness = 1,
|
||||||
|
.brightness_get = dx010_led_brightness_get_p2,
|
||||||
|
.brightness_set = dx010_led_brightness_set_p2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "dx010:green:stat",
|
||||||
|
.brightness = LED_OFF,
|
||||||
|
.max_brightness = 1,
|
||||||
|
.brightness_set = dx010_led_brightness_set_stat,
|
||||||
|
.blink_set = dx010_led_blink_stat,
|
||||||
|
.flags = LED_CORE_SUSPENDRESUME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct resource dx010_led_resources[] = {
|
||||||
|
{
|
||||||
|
.flags = IORESOURCE_IO,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void dx010_led_dev_release( struct device * dev)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_device dx010_lpc_dev = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
.id = -1,
|
||||||
|
.num_resources = ARRAY_SIZE(dx010_led_resources),
|
||||||
|
.resource = dx010_led_resources,
|
||||||
|
.dev = {
|
||||||
|
.release = dx010_led_dev_release,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int dx010_led_drv_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(dx010_leds); i++) {
|
||||||
|
ret = led_classdev_register(&pdev->dev, &dx010_leds[i]);
|
||||||
|
if (ret < 0)
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = device_create_file(&pdev->dev, &dev_attr_blink);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
for (i = 0; i < ARRAY_SIZE(dx010_leds); i++)
|
||||||
|
led_classdev_unregister(&dx010_leds[i]);
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dx010_led_drv_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(dx010_leds); i++)
|
||||||
|
led_classdev_unregister(&dx010_leds[i]);
|
||||||
|
|
||||||
|
device_remove_file(&pdev->dev, &dev_attr_blink);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver dx010_led_drv = {
|
||||||
|
.probe = dx010_led_drv_probe,
|
||||||
|
.remove = __exit_p(dx010_led_drv_remove),
|
||||||
|
.driver = {
|
||||||
|
.name = DRIVER_NAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
int dx010_led_init(void)
|
||||||
|
{
|
||||||
|
platform_device_register(&dx010_lpc_dev);
|
||||||
|
platform_driver_register(&dx010_led_drv);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dx010_led_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&dx010_led_drv);
|
||||||
|
platform_device_unregister(&dx010_lpc_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(dx010_led_init);
|
||||||
|
module_exit(dx010_led_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Abhisit Sangjan <asang@celestica.com>");
|
||||||
|
MODULE_DESCRIPTION("Celestica SeaStone DX010 LEDs Front Panel Status Driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
@ -0,0 +1,553 @@
|
|||||||
|
/*
|
||||||
|
* lm75.c - Part of lm_sensors, Linux kernel modules for hardware
|
||||||
|
* monitoring
|
||||||
|
* Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
|
||||||
|
*
|
||||||
|
* 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/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>
|
||||||
|
#include <linux/thermal.h>
|
||||||
|
#include "lm75.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This driver handles the LM75 and compatible digital temperature sensors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum lm75_type { /* keep sorted in alphabetical order */
|
||||||
|
adt75,
|
||||||
|
ds1775,
|
||||||
|
ds75,
|
||||||
|
ds7505,
|
||||||
|
g751,
|
||||||
|
lm75,
|
||||||
|
lm75a,
|
||||||
|
lm75b,
|
||||||
|
max6625,
|
||||||
|
max6626,
|
||||||
|
mcp980x,
|
||||||
|
stds75,
|
||||||
|
tcn75,
|
||||||
|
tmp100,
|
||||||
|
tmp101,
|
||||||
|
tmp105,
|
||||||
|
tmp112,
|
||||||
|
tmp175,
|
||||||
|
tmp275,
|
||||||
|
tmp75,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Addresses scanned */
|
||||||
|
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
|
||||||
|
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
||||||
|
|
||||||
|
|
||||||
|
/* The LM75 registers */
|
||||||
|
#define LM75_REG_CONF 0x01
|
||||||
|
static const u8 LM75_REG_TEMP[3] = {
|
||||||
|
0x00, /* input */
|
||||||
|
0x03, /* max */
|
||||||
|
0x02, /* hyst */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Each client has this additional data */
|
||||||
|
struct lm75_data {
|
||||||
|
struct i2c_client *client;
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
struct thermal_zone_device *tz;
|
||||||
|
struct mutex update_lock;
|
||||||
|
u8 orig_conf;
|
||||||
|
u8 resolution; /* In bits, between 9 and 12 */
|
||||||
|
u8 resolution_limits;
|
||||||
|
char valid; /* !=0 if registers are valid */
|
||||||
|
unsigned long last_updated; /* In jiffies */
|
||||||
|
unsigned long sample_time; /* In jiffies */
|
||||||
|
s16 temp[3]; /* Register values,
|
||||||
|
0 = input
|
||||||
|
1 = max
|
||||||
|
2 = hyst */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int lm75_read_value(struct i2c_client *client, u8 reg);
|
||||||
|
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value);
|
||||||
|
static struct lm75_data *lm75_update_device(struct device *dev);
|
||||||
|
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
|
||||||
|
{
|
||||||
|
return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sysfs attributes for hwmon */
|
||||||
|
|
||||||
|
static int lm75_read_temp(void *dev, long *temp)
|
||||||
|
{
|
||||||
|
struct lm75_data *data = lm75_update_device(dev);
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
|
*temp = lm75_reg_to_mc(data->temp[0], data->resolution);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_temp(struct device *dev, struct device_attribute *da,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
|
struct lm75_data *data = lm75_update_device(dev);
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
|
return sprintf(buf, "%ld\n", lm75_reg_to_mc(data->temp[attr->index],
|
||||||
|
data->resolution));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
int nr = attr->index;
|
||||||
|
long temp;
|
||||||
|
int error;
|
||||||
|
u8 resolution;
|
||||||
|
|
||||||
|
error = kstrtol(buf, 10, &temp);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resolution of limit registers is assumed to be the same as the
|
||||||
|
* temperature input register resolution unless given explicitly.
|
||||||
|
*/
|
||||||
|
if (attr->index && data->resolution_limits)
|
||||||
|
resolution = data->resolution_limits;
|
||||||
|
else
|
||||||
|
resolution = data->resolution;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||||
|
data->temp[nr] = DIV_ROUND_CLOSEST(temp << (resolution - 8),
|
||||||
|
1000) << (16 - resolution);
|
||||||
|
lm75_write_value(client, LM75_REG_TEMP[nr], data->temp[nr]);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
||||||
|
show_temp, set_temp, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
||||||
|
show_temp, set_temp, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
||||||
|
|
||||||
|
static struct attribute *lm75_attrs[] = {
|
||||||
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||||
|
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
ATTRIBUTE_GROUPS(lm75);
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* device probe and removal */
|
||||||
|
|
||||||
|
static int
|
||||||
|
lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
struct device *dev = &client->dev;
|
||||||
|
struct lm75_data *data;
|
||||||
|
int status;
|
||||||
|
u8 set_mask, clr_mask;
|
||||||
|
int new;
|
||||||
|
enum lm75_type kind = id->driver_data;
|
||||||
|
|
||||||
|
if (!i2c_check_functionality(client->adapter,
|
||||||
|
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
|
data = devm_kzalloc(dev, sizeof(struct lm75_data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
data->client = client;
|
||||||
|
i2c_set_clientdata(client, data);
|
||||||
|
mutex_init(&data->update_lock);
|
||||||
|
|
||||||
|
/* Set to LM75 resolution (9 bits, 1/2 degree C) and range.
|
||||||
|
* Then tweak to be more precise when appropriate.
|
||||||
|
*/
|
||||||
|
set_mask = 0;
|
||||||
|
clr_mask = LM75_SHUTDOWN; /* continuous conversions */
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
case adt75:
|
||||||
|
clr_mask |= 1 << 5; /* not one-shot mode */
|
||||||
|
data->resolution = 12;
|
||||||
|
data->sample_time = HZ / 8;
|
||||||
|
break;
|
||||||
|
case ds1775:
|
||||||
|
case ds75:
|
||||||
|
case stds75:
|
||||||
|
clr_mask |= 3 << 5;
|
||||||
|
set_mask |= 2 << 5; /* 11-bit mode */
|
||||||
|
data->resolution = 11;
|
||||||
|
data->sample_time = HZ;
|
||||||
|
break;
|
||||||
|
case ds7505:
|
||||||
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
|
data->resolution = 12;
|
||||||
|
data->sample_time = HZ / 4;
|
||||||
|
break;
|
||||||
|
case g751:
|
||||||
|
case lm75:
|
||||||
|
case lm75a:
|
||||||
|
data->resolution = 9;
|
||||||
|
data->sample_time = HZ / 2;
|
||||||
|
break;
|
||||||
|
case lm75b:
|
||||||
|
data->resolution = 11;
|
||||||
|
data->sample_time = HZ / 4;
|
||||||
|
break;
|
||||||
|
case max6625:
|
||||||
|
data->resolution = 9;
|
||||||
|
data->sample_time = HZ / 4;
|
||||||
|
break;
|
||||||
|
case max6626:
|
||||||
|
data->resolution = 12;
|
||||||
|
data->resolution_limits = 9;
|
||||||
|
data->sample_time = HZ / 4;
|
||||||
|
break;
|
||||||
|
case tcn75:
|
||||||
|
data->resolution = 9;
|
||||||
|
data->sample_time = HZ / 8;
|
||||||
|
break;
|
||||||
|
case mcp980x:
|
||||||
|
data->resolution_limits = 9;
|
||||||
|
/* fall through */
|
||||||
|
case tmp100:
|
||||||
|
case tmp101:
|
||||||
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
|
data->resolution = 12;
|
||||||
|
data->sample_time = HZ;
|
||||||
|
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||||
|
break;
|
||||||
|
case tmp112:
|
||||||
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
|
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||||
|
data->resolution = 12;
|
||||||
|
data->sample_time = HZ / 4;
|
||||||
|
break;
|
||||||
|
case tmp105:
|
||||||
|
case tmp175:
|
||||||
|
case tmp275:
|
||||||
|
case tmp75:
|
||||||
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
|
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||||
|
data->resolution = 12;
|
||||||
|
data->sample_time = HZ / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* configure as specified */
|
||||||
|
status = lm75_read_value(client, LM75_REG_CONF);
|
||||||
|
if (status < 0) {
|
||||||
|
dev_dbg(dev, "Can't read config? %d\n", status);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
data->orig_conf = status;
|
||||||
|
new = status & ~clr_mask;
|
||||||
|
new |= set_mask;
|
||||||
|
if (status != new)
|
||||||
|
lm75_write_value(client, LM75_REG_CONF, new);
|
||||||
|
dev_dbg(dev, "Config %02x\n", new);
|
||||||
|
|
||||||
|
data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name,
|
||||||
|
data, lm75_groups);
|
||||||
|
if (IS_ERR(data->hwmon_dev))
|
||||||
|
return PTR_ERR(data->hwmon_dev);
|
||||||
|
|
||||||
|
data->tz = thermal_zone_of_sensor_register(data->hwmon_dev,
|
||||||
|
0,
|
||||||
|
data->hwmon_dev,
|
||||||
|
lm75_read_temp, NULL);
|
||||||
|
if (IS_ERR(data->tz))
|
||||||
|
data->tz = NULL;
|
||||||
|
|
||||||
|
dev_info(dev, "%s: sensor '%s'\n",
|
||||||
|
dev_name(data->hwmon_dev), client->name);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lm75_remove(struct i2c_client *client)
|
||||||
|
{
|
||||||
|
struct lm75_data *data = i2c_get_clientdata(client);
|
||||||
|
|
||||||
|
thermal_zone_of_sensor_unregister(data->hwmon_dev, data->tz);
|
||||||
|
hwmon_device_unregister(data->hwmon_dev);
|
||||||
|
lm75_write_value(client, LM75_REG_CONF, data->orig_conf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct i2c_device_id lm75_ids[] = {
|
||||||
|
{ "adt75", adt75, },
|
||||||
|
{ "ds1775", ds1775, },
|
||||||
|
{ "ds75", ds75, },
|
||||||
|
{ "ds7505", ds7505, },
|
||||||
|
{ "g751", g751, },
|
||||||
|
{ "lm75", lm75, },
|
||||||
|
{ "lm75a", lm75a, },
|
||||||
|
{ "dx010_lm75b", lm75b, },
|
||||||
|
{ "max6625", max6625, },
|
||||||
|
{ "max6626", max6626, },
|
||||||
|
{ "mcp980x", mcp980x, },
|
||||||
|
{ "stds75", stds75, },
|
||||||
|
{ "tcn75", tcn75, },
|
||||||
|
{ "tmp100", tmp100, },
|
||||||
|
{ "tmp101", tmp101, },
|
||||||
|
{ "tmp105", tmp105, },
|
||||||
|
{ "tmp112", tmp112, },
|
||||||
|
{ "tmp175", tmp175, },
|
||||||
|
{ "tmp275", tmp275, },
|
||||||
|
{ "tmp75", tmp75, },
|
||||||
|
{ /* LIST END */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(i2c, lm75_ids);
|
||||||
|
|
||||||
|
#define LM75A_ID 0xA1
|
||||||
|
|
||||||
|
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||||
|
static int lm75_detect(struct i2c_client *new_client,
|
||||||
|
struct i2c_board_info *info)
|
||||||
|
{
|
||||||
|
struct i2c_adapter *adapter = new_client->adapter;
|
||||||
|
int i;
|
||||||
|
int conf, hyst, os;
|
||||||
|
bool is_lm75a = 0;
|
||||||
|
|
||||||
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||||
|
I2C_FUNC_SMBUS_WORD_DATA))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now, we do the remaining detection. There is no identification-
|
||||||
|
* dedicated register so we have to rely on several tricks:
|
||||||
|
* unused bits, registers cycling over 8-address boundaries,
|
||||||
|
* addresses 0x04-0x07 returning the last read value.
|
||||||
|
* The cycling+unused addresses combination is not tested,
|
||||||
|
* since it would significantly slow the detection down and would
|
||||||
|
* hardly add any value.
|
||||||
|
*
|
||||||
|
* The National Semiconductor LM75A is different than earlier
|
||||||
|
* LM75s. It has an ID byte of 0xaX (where X is the chip
|
||||||
|
* revision, with 1 being the only revision in existence) in
|
||||||
|
* register 7, and unused registers return 0xff rather than the
|
||||||
|
* last read value.
|
||||||
|
*
|
||||||
|
* Note that this function only detects the original National
|
||||||
|
* Semiconductor LM75 and the LM75A. Clones from other vendors
|
||||||
|
* aren't detected, on purpose, because they are typically never
|
||||||
|
* found on PC hardware. They are found on embedded designs where
|
||||||
|
* they can be instantiated explicitly so detection is not needed.
|
||||||
|
* The absence of identification registers on all these clones
|
||||||
|
* would make their exhaustive detection very difficult and weak,
|
||||||
|
* and odds are that the driver would bind to unsupported devices.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Unused bits */
|
||||||
|
conf = i2c_smbus_read_byte_data(new_client, 1);
|
||||||
|
if (conf & 0xe0)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* First check for LM75A */
|
||||||
|
if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) {
|
||||||
|
/* LM75A returns 0xff on unused registers so
|
||||||
|
just to be sure we check for that too. */
|
||||||
|
if (i2c_smbus_read_byte_data(new_client, 4) != 0xff
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 5) != 0xff
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 6) != 0xff)
|
||||||
|
return -ENODEV;
|
||||||
|
is_lm75a = 1;
|
||||||
|
hyst = i2c_smbus_read_byte_data(new_client, 2);
|
||||||
|
os = i2c_smbus_read_byte_data(new_client, 3);
|
||||||
|
} else { /* Traditional style LM75 detection */
|
||||||
|
/* Unused addresses */
|
||||||
|
hyst = i2c_smbus_read_byte_data(new_client, 2);
|
||||||
|
if (i2c_smbus_read_byte_data(new_client, 4) != hyst
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 5) != hyst
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 6) != hyst
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 7) != hyst)
|
||||||
|
return -ENODEV;
|
||||||
|
os = i2c_smbus_read_byte_data(new_client, 3);
|
||||||
|
if (i2c_smbus_read_byte_data(new_client, 4) != os
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 5) != os
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 6) != os
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, 7) != os)
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Addresses cycling */
|
||||||
|
for (i = 8; i <= 248; i += 40) {
|
||||||
|
if (i2c_smbus_read_byte_data(new_client, i + 1) != conf
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, i + 2) != hyst
|
||||||
|
|| i2c_smbus_read_byte_data(new_client, i + 3) != os)
|
||||||
|
return -ENODEV;
|
||||||
|
if (is_lm75a && i2c_smbus_read_byte_data(new_client, i + 7)
|
||||||
|
!= LM75A_ID)
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
strlcpy(info->type, is_lm75a ? "lm75a" : "lm75", I2C_NAME_SIZE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM
|
||||||
|
static int lm75_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
|
status = lm75_read_value(client, LM75_REG_CONF);
|
||||||
|
if (status < 0) {
|
||||||
|
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
status = status | LM75_SHUTDOWN;
|
||||||
|
lm75_write_value(client, LM75_REG_CONF, status);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lm75_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
|
status = lm75_read_value(client, LM75_REG_CONF);
|
||||||
|
if (status < 0) {
|
||||||
|
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
status = status & ~LM75_SHUTDOWN;
|
||||||
|
lm75_write_value(client, LM75_REG_CONF, status);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct dev_pm_ops lm75_dev_pm_ops = {
|
||||||
|
.suspend = lm75_suspend,
|
||||||
|
.resume = lm75_resume,
|
||||||
|
};
|
||||||
|
#define LM75_DEV_PM_OPS (&lm75_dev_pm_ops)
|
||||||
|
#else
|
||||||
|
#define LM75_DEV_PM_OPS NULL
|
||||||
|
#endif /* CONFIG_PM */
|
||||||
|
|
||||||
|
static struct i2c_driver lm75_driver = {
|
||||||
|
.class = I2C_CLASS_HWMON,
|
||||||
|
.driver = {
|
||||||
|
.name = "dx010_lm75",
|
||||||
|
.pm = LM75_DEV_PM_OPS,
|
||||||
|
},
|
||||||
|
.probe = lm75_probe,
|
||||||
|
.remove = lm75_remove,
|
||||||
|
.id_table = lm75_ids,
|
||||||
|
/*
|
||||||
|
.detect = lm75_detect,
|
||||||
|
.address_list = normal_i2c,
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* register access */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All registers are word-sized, except for the configuration register.
|
||||||
|
* LM75 uses a high-byte first convention, which is exactly opposite to
|
||||||
|
* the SMBus standard.
|
||||||
|
*/
|
||||||
|
static int lm75_read_value(struct i2c_client *client, u8 reg)
|
||||||
|
{
|
||||||
|
if (reg == LM75_REG_CONF)
|
||||||
|
return i2c_smbus_read_byte_data(client, reg);
|
||||||
|
else
|
||||||
|
return i2c_smbus_read_word_swapped(client, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value)
|
||||||
|
{
|
||||||
|
if (reg == LM75_REG_CONF)
|
||||||
|
return i2c_smbus_write_byte_data(client, reg, value);
|
||||||
|
else
|
||||||
|
return i2c_smbus_write_word_swapped(client, reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct lm75_data *lm75_update_device(struct device *dev)
|
||||||
|
{
|
||||||
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
struct lm75_data *ret = data;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
|
if (time_after(jiffies, data->last_updated + data->sample_time)
|
||||||
|
|| !data->valid) {
|
||||||
|
int i;
|
||||||
|
dev_dbg(&client->dev, "Starting lm75 update\n");
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(data->temp); i++) {
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = lm75_read_value(client, LM75_REG_TEMP[i]);
|
||||||
|
if (unlikely(status < 0)) {
|
||||||
|
dev_dbg(dev,
|
||||||
|
"LM75: Failed to read value: reg %d, error %d\n",
|
||||||
|
LM75_REG_TEMP[i], status);
|
||||||
|
ret = ERR_PTR(status);
|
||||||
|
data->valid = 0;
|
||||||
|
goto abort;
|
||||||
|
}
|
||||||
|
data->temp[i] = status;
|
||||||
|
}
|
||||||
|
data->last_updated = jiffies;
|
||||||
|
data->valid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
abort:
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_i2c_driver(lm75_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
||||||
|
MODULE_DESCRIPTION("LM75 driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
lm75.h - Part of lm_sensors, Linux kernel modules for hardware
|
||||||
|
monitoring
|
||||||
|
Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file contains common code for encoding/decoding LM75 type
|
||||||
|
temperature readings, which are emulated by many of the chips
|
||||||
|
we support. As the user is unlikely to load more than one driver
|
||||||
|
which contains this code, we don't worry about the wasted space.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
|
||||||
|
/* straight from the datasheet */
|
||||||
|
#define LM75_TEMP_MIN (-55000)
|
||||||
|
#define LM75_TEMP_MAX 125000
|
||||||
|
#define LM75_SHUTDOWN 0x01
|
||||||
|
|
||||||
|
/* TEMP: 0.001C/bit (-55C to +125C)
|
||||||
|
REG: (0.5C/bit, two's complement) << 7 */
|
||||||
|
static inline u16 LM75_TEMP_TO_REG(long temp)
|
||||||
|
{
|
||||||
|
int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||||
|
ntemp += (ntemp < 0 ? -250 : 250);
|
||||||
|
return (u16)((ntemp / 500) << 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int LM75_TEMP_FROM_REG(u16 reg)
|
||||||
|
{
|
||||||
|
/* use integer division instead of equivalent right shift to
|
||||||
|
guarantee arithmetic shift and preserve the sign */
|
||||||
|
return ((s16)reg / 128) * 500;
|
||||||
|
}
|
@ -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");
|
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#Usage:
|
||||||
|
# TBD
|
||||||
|
|
||||||
|
echo "Do we need to check qsfp?"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user