[platform]: incorporate sonic-platform-modules-cel into sonic buildimage repo

This commit is contained in:
Guohan Lu 2018-02-16 03:22:49 +00:00
parent 759edc93c9
commit f489bea105
21 changed files with 2895 additions and 4 deletions

3
.gitmodules vendored
View File

@ -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

View 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

View 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.

View File

@ -0,0 +1 @@
platform drivers for Celestica DX010 for the SONiC project

View File

@ -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

View File

@ -0,0 +1 @@
9

View 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

View File

@ -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

View File

@ -0,0 +1,2 @@
dx010/scripts/dx010_check_qsfp.sh usr/local/bin
dx010/cfg/dx010-modules.conf etc/modules-load.d

View 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)

View File

@ -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

View File

@ -0,0 +1 @@
obj-m := dx010_cpld.o mc24lc64t.o emc2305.o dx010_wdt.o leds-dx010.o lm75.o

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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");

View File

@ -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;
}

View File

@ -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");

View File

@ -0,0 +1,7 @@
#!/bin/bash
#Usage:
# TBD
echo "Do we need to check qsfp?"