From 37280723862708b2af74c32ddfc5907faec8998f Mon Sep 17 00:00:00 2001 From: Tian Fang Date: Fri, 24 Apr 2015 14:33:32 +0100 Subject: Add support for openbmc (wedge) --- Documentation/hwmon/pmbus | 214 +++ Documentation/hwmon/pmbus-core | 283 ++++ arch/arm/Kconfig | 4 +- arch/arm/configs/wedge_defconfig | 1479 +++++++++++++++++ arch/arm/kernel/head.S | 4 +- arch/arm/mach-aspeed/include/mach/ast_wdt.h | 2 +- arch/arm/mach-aspeed/include/mach/debug-macro.S | 5 +- arch/arm/mach-aspeed/include/mach/system.h | 2 +- arch/arm/mach-aspeed/include/mach/uncompress.h | 4 +- arch/arm/mach-aspeed/include/mach/vmalloc.h | 2 +- arch/arm/plat-aspeed/Makefile | 5 +- arch/arm/plat-aspeed/ast-scu.c | 17 +- arch/arm/plat-aspeed/dev-i2c.c | 183 +- arch/arm/plat-aspeed/dev-lpc.c | 22 +- arch/arm/plat-aspeed/dev-pwm-fan.c | 8 - arch/arm/plat-aspeed/dev-rtc.c | 2 +- arch/arm/plat-aspeed/dev-spi.c | 74 +- arch/arm/plat-aspeed/dev-uart.c | 12 + arch/arm/plat-aspeed/dev-virthub.c | 62 + arch/arm/plat-aspeed/devs.c | 10 +- arch/arm/plat-aspeed/include/plat/ast-scu.h | 1 + arch/arm/plat-aspeed/include/plat/devs.h | 2 + arch/arm/plat-aspeed/include/plat/regs-lpc.h | 6 + arch/arm/plat-aspeed/include/plat/regs-pwm_fan.h | 32 +- arch/arm/plat-aspeed/timer.c | 223 ++- drivers/Makefile | 1 + drivers/hwmon/Kconfig | 21 + drivers/hwmon/Makefile | 4 + drivers/hwmon/ast_adc.c | 83 +- drivers/hwmon/ast_pwm_fan.c | 159 +- drivers/hwmon/fb_panther_plus.c | 722 ++++++++ drivers/hwmon/max127.c | 312 ++++ drivers/hwmon/pmbus/Kconfig | 144 ++ drivers/hwmon/pmbus/Makefile | 17 + drivers/hwmon/pmbus/adm1275.c | 457 +++++ drivers/hwmon/pmbus/lm25066.c | 457 +++++ drivers/hwmon/pmbus/ltc2978.c | 496 ++++++ drivers/hwmon/pmbus/max16064.c | 127 ++ drivers/hwmon/pmbus/max34440.c | 435 +++++ drivers/hwmon/pmbus/max8688.c | 204 +++ drivers/hwmon/pmbus/pfe1100.c | 249 +++ drivers/hwmon/pmbus/pfe3000.c | 133 ++ drivers/hwmon/pmbus/pmbus.c | 217 +++ drivers/hwmon/pmbus/pmbus.h | 413 +++++ drivers/hwmon/pmbus/pmbus_core.c | 1940 ++++++++++++++++++++++ drivers/hwmon/pmbus/ucd9000.c | 246 +++ drivers/hwmon/pmbus/ucd9200.c | 180 ++ drivers/hwmon/pmbus/zl6100.c | 419 +++++ drivers/i2c/busses/i2c-ast.c | 115 +- drivers/i2c/i2c-core.c | 22 +- drivers/i2c/i2c-dev.c | 33 +- drivers/mtd/devices/m25p80.c | 16 +- drivers/net/ftgmac100_26.c | 22 +- drivers/net/ftgmac100_26.h | 3 +- drivers/usb/gadget/Kconfig | 12 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/aspeed_udc.c | 1043 ++++++++++++ drivers/usb/gadget/aspeed_udc.h | 161 ++ drivers/usb/gadget/epautoconf.c | 3 + drivers/usb/gadget/gadget_chips.h | 9 + drivers/watchdog/ast_wdt.c | 189 ++- include/linux/i2c.h | 12 + include/linux/i2c/pmbus.h | 45 + tools/.gitignore | 1 + 64 files changed, 11480 insertions(+), 301 deletions(-) create mode 100644 Documentation/hwmon/pmbus create mode 100644 Documentation/hwmon/pmbus-core create mode 100644 arch/arm/configs/wedge_defconfig create mode 100644 arch/arm/plat-aspeed/dev-virthub.c create mode 100644 drivers/hwmon/fb_panther_plus.c create mode 100644 drivers/hwmon/max127.c create mode 100644 drivers/hwmon/pmbus/Kconfig create mode 100644 drivers/hwmon/pmbus/Makefile create mode 100644 drivers/hwmon/pmbus/adm1275.c create mode 100644 drivers/hwmon/pmbus/lm25066.c create mode 100644 drivers/hwmon/pmbus/ltc2978.c create mode 100644 drivers/hwmon/pmbus/max16064.c create mode 100644 drivers/hwmon/pmbus/max34440.c create mode 100644 drivers/hwmon/pmbus/max8688.c create mode 100644 drivers/hwmon/pmbus/pfe1100.c create mode 100644 drivers/hwmon/pmbus/pfe3000.c create mode 100644 drivers/hwmon/pmbus/pmbus.c create mode 100644 drivers/hwmon/pmbus/pmbus.h create mode 100644 drivers/hwmon/pmbus/pmbus_core.c create mode 100644 drivers/hwmon/pmbus/ucd9000.c create mode 100644 drivers/hwmon/pmbus/ucd9200.c create mode 100644 drivers/hwmon/pmbus/zl6100.c create mode 100644 drivers/usb/gadget/aspeed_udc.c create mode 100644 drivers/usb/gadget/aspeed_udc.h create mode 100644 include/linux/i2c/pmbus.h create mode 100644 tools/.gitignore diff --git a/Documentation/hwmon/pmbus b/Documentation/hwmon/pmbus new file mode 100644 index 000000000000..bc342af5a666 --- /dev/null +++ b/Documentation/hwmon/pmbus @@ -0,0 +1,214 @@ +Kernel driver pmbus +==================== + +Supported chips: + * Ericsson BMR453, BMR454 + Prefixes: 'bmr453', 'bmr454' + Addresses scanned: - + Datasheet: + http://archive.ericsson.net/service/internet/picov/get?DocNo=28701-EN/LZT146395 + * ON Semiconductor ADP4000, NCP4200, NCP4208 + Prefixes: 'adp4000', 'ncp4200', 'ncp4208' + Addresses scanned: - + Datasheets: + http://www.onsemi.com/pub_link/Collateral/ADP4000-D.PDF + http://www.onsemi.com/pub_link/Collateral/NCP4200-D.PDF + http://www.onsemi.com/pub_link/Collateral/JUNE%202009-%20REV.%200.PDF + * Lineage Power + Prefixes: 'mdt040', 'pdt003', 'pdt006', 'pdt012', 'udt020' + Addresses scanned: - + Datasheets: + http://www.lineagepower.com/oem/pdf/PDT003A0X.pdf + http://www.lineagepower.com/oem/pdf/PDT006A0X.pdf + http://www.lineagepower.com/oem/pdf/PDT012A0X.pdf + http://www.lineagepower.com/oem/pdf/UDT020A0X.pdf + http://www.lineagepower.com/oem/pdf/MDT040A0X.pdf + * Texas Instruments TPS40400, TPS40422 + Prefixes: 'tps40400', 'tps40422' + Addresses scanned: - + Datasheets: + http://www.ti.com/lit/gpn/tps40400 + http://www.ti.com/lit/gpn/tps40422 + * Generic PMBus devices + Prefix: 'pmbus' + Addresses scanned: - + Datasheet: n.a. + +Author: Guenter Roeck + + +Description +----------- + +This driver supports hardware montoring for various PMBus compliant devices. +It supports voltage, current, power, and temperature sensors as supported +by the device. + +Each monitored channel has its own high and low limits, plus a critical +limit. + +Fan support will be added in a later version of this driver. + + +Usage Notes +----------- + +This driver does not probe for PMBus devices, since there is no register +which can be safely used to identify the chip (The MFG_ID register is not +supported by all chips), and since there is no well defined address range for +PMBus devices. You will have to instantiate the devices explicitly. + +Example: the following will load the driver for an LTC2978 at address 0x60 +on I2C bus #1: +$ modprobe pmbus +[KML: Not for the backport] +$ echo ltc2978 0x60 > /sys/bus/i2c/devices/i2c-1/new_device + + +Platform data support +--------------------- + +Support for additional PMBus chips can be added by defining chip parameters in +a new chip specific driver file. For example, (untested) code to add support for +Emerson DS1200 power modules might look as follows. + +static struct pmbus_driver_info ds1200_info = { + .pages = 1, + /* Note: All other sensors are in linear mode */ + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .direct[PSC_CURRENT_OUT] = true, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 3, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12, +}; + +static int ds1200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &ds1200_info); +} + +static int ds1200_remove(struct i2c_client *client) +{ + return pmbus_do_remove(client); +} + +static const struct i2c_device_id ds1200_id[] = { + {"ds1200", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ds1200_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ds1200_driver = { + .driver = { + .name = "ds1200", + }, + .probe = ds1200_probe, + .remove = ds1200_remove, + .id_table = ds1200_id, +}; + +static int __init ds1200_init(void) +{ + return i2c_add_driver(&ds1200_driver); +} + +static void __exit ds1200_exit(void) +{ + i2c_del_driver(&ds1200_driver); +} + + +Sysfs entries +------------- + +When probing the chip, the driver identifies which PMBus registers are +supported, and determines available sensors from this information. +Attribute files only exist if respective sensors are supported by the chip. +Labels are provided to inform the user about the sensor associated with +a given sysfs entry. + +The following attributes are supported. Limits are read-write; all other +attributes are read-only. + +inX_input Measured voltage. From READ_VIN or READ_VOUT register. +inX_min Minimum Voltage. + From VIN_UV_WARN_LIMIT or VOUT_UV_WARN_LIMIT register. +inX_max Maximum voltage. + From VIN_OV_WARN_LIMIT or VOUT_OV_WARN_LIMIT register. +inX_lcrit Critical minimum Voltage. + From VIN_UV_FAULT_LIMIT or VOUT_UV_FAULT_LIMIT register. +inX_crit Critical maximum voltage. + From VIN_OV_FAULT_LIMIT or VOUT_OV_FAULT_LIMIT register. +inX_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status. +inX_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status. +inX_lcrit_alarm Voltage critical low alarm. + From VOLTAGE_UV_FAULT status. +inX_crit_alarm Voltage critical high alarm. + From VOLTAGE_OV_FAULT status. +inX_label "vin", "vcap", or "voutY" + +currX_input Measured current. From READ_IIN or READ_IOUT register. +currX_max Maximum current. + From IIN_OC_WARN_LIMIT or IOUT_OC_WARN_LIMIT register. +currX_lcrit Critical minimum output current. + From IOUT_UC_FAULT_LIMIT register. +currX_crit Critical maximum current. + From IIN_OC_FAULT_LIMIT or IOUT_OC_FAULT_LIMIT register. +currX_alarm Current high alarm. + From IIN_OC_WARNING or IOUT_OC_WARNING status. +currX_max_alarm Current high alarm. + From IIN_OC_WARN_LIMIT or IOUT_OC_WARN_LIMIT status. +currX_lcrit_alarm Output current critical low alarm. + From IOUT_UC_FAULT status. +currX_crit_alarm Current critical high alarm. + From IIN_OC_FAULT or IOUT_OC_FAULT status. +currX_label "iin" or "ioutY" + +powerX_input Measured power. From READ_PIN or READ_POUT register. +powerX_cap Output power cap. From POUT_MAX register. +powerX_max Power limit. From PIN_OP_WARN_LIMIT or + POUT_OP_WARN_LIMIT register. +powerX_crit Critical output power limit. + From POUT_OP_FAULT_LIMIT register. +powerX_alarm Power high alarm. + From PIN_OP_WARNING or POUT_OP_WARNING status. +powerX_crit_alarm Output power critical high alarm. + From POUT_OP_FAULT status. +powerX_label "pin" or "poutY" + +tempX_input Measured temperature. + From READ_TEMPERATURE_X register. +tempX_min Mimimum temperature. From UT_WARN_LIMIT register. +tempX_max Maximum temperature. From OT_WARN_LIMIT register. +tempX_lcrit Critical low temperature. + From UT_FAULT_LIMIT register. +tempX_crit Critical high temperature. + From OT_FAULT_LIMIT register. +tempX_min_alarm Chip temperature low alarm. Set by comparing + READ_TEMPERATURE_X with UT_WARN_LIMIT if + TEMP_UT_WARNING status is set. +tempX_max_alarm Chip temperature high alarm. Set by comparing + READ_TEMPERATURE_X with OT_WARN_LIMIT if + TEMP_OT_WARNING status is set. +tempX_lcrit_alarm Chip temperature critical low alarm. Set by comparing + READ_TEMPERATURE_X with UT_FAULT_LIMIT if + TEMP_UT_FAULT status is set. +tempX_crit_alarm Chip temperature critical high alarm. Set by comparing + READ_TEMPERATURE_X with OT_FAULT_LIMIT if + TEMP_OT_FAULT status is set. diff --git a/Documentation/hwmon/pmbus-core b/Documentation/hwmon/pmbus-core new file mode 100644 index 000000000000..31e4720fed18 --- /dev/null +++ b/Documentation/hwmon/pmbus-core @@ -0,0 +1,283 @@ +PMBus core driver and internal API +================================== + +Introduction +============ + +[from pmbus.org] The Power Management Bus (PMBus) is an open standard +power-management protocol with a fully defined command language that facilitates +communication with power converters and other devices in a power system. The +protocol is implemented over the industry-standard SMBus serial interface and +enables programming, control, and real-time monitoring of compliant power +conversion products. This flexible and highly versatile standard allows for +communication between devices based on both analog and digital technologies, and +provides true interoperability which will reduce design complexity and shorten +time to market for power system designers. Pioneered by leading power supply and +semiconductor companies, this open power system standard is maintained and +promoted by the PMBus Implementers Forum (PMBus-IF), comprising 30+ adopters +with the objective to provide support to, and facilitate adoption among, users. + +Unfortunately, while PMBus commands are standardized, there are no mandatory +commands, and manufacturers can add as many non-standard commands as they like. +Also, different PMBUs devices act differently if non-supported commands are +executed. Some devices return an error, some devices return 0xff or 0xffff and +set a status error flag, and some devices may simply hang up. + +Despite all those difficulties, a generic PMBus device driver is still useful +and supported since kernel version 2.6.39. However, it was necessary to support +device specific extensions in addition to the core PMBus driver, since it is +simply unknown what new device specific functionality PMBus device developers +come up with next. + +To make device specific extensions as scalable as possible, and to avoid having +to modify the core PMBus driver repeatedly for new devices, the PMBus driver was +split into core, generic, and device specific code. The core code (in +pmbus_core.c) provides generic functionality. The generic code (in pmbus.c) +provides support for generic PMBus devices. Device specific code is responsible +for device specific initialization and, if needed, maps device specific +functionality into generic functionality. This is to some degree comparable +to PCI code, where generic code is augmented as needed with quirks for all kinds +of devices. + +PMBus device capabilities auto-detection +======================================== + +For generic PMBus devices, code in pmbus.c attempts to auto-detect all supported +PMBus commands. Auto-detection is somewhat limited, since there are simply too +many variables to consider. For example, it is almost impossible to autodetect +which PMBus commands are paged and which commands are replicated across all +pages (see the PMBus specification for details on multi-page PMBus devices). + +For this reason, it often makes sense to provide a device specific driver if not +all commands can be auto-detected. The data structures in this driver can be +used to inform the core driver about functionality supported by individual +chips. + +Some commands are always auto-detected. This applies to all limit commands +(lcrit, min, max, and crit attributes) as well as associated alarm attributes. +Limits and alarm attributes are auto-detected because there are simply too many +possible combinations to provide a manual configuration interface. + +PMBus internal API +================== + +The API between core and device specific PMBus code is defined in +drivers/hwmon/pmbus/pmbus.h. In addition to the internal API, pmbus.h defines +standard PMBus commands and virtual PMBus commands. + +Standard PMBus commands +----------------------- + +Standard PMBus commands (commands values 0x00 to 0xff) are defined in the PMBUs +specification. + +Virtual PMBus commands +---------------------- + +Virtual PMBus commands are provided to enable support for non-standard +functionality which has been implemented by several chip vendors and is thus +desirable to support. + +Virtual PMBus commands start with command value 0x100 and can thus easily be +distinguished from standard PMBus commands (which can not have values larger +than 0xff). Support for virtual PMBus commands is device specific and thus has +to be implemented in device specific code. + +Virtual commands are named PMBUS_VIRT_xxx and start with PMBUS_VIRT_BASE. All +virtual commands are word sized. + +There are currently two types of virtual commands. + +- READ commands are read-only; writes are either ignored or return an error. +- RESET commands are read/write. Reading reset registers returns zero + (used for detection), writing any value causes the associated history to be + reset. + +Virtual commands have to be handled in device specific driver code. Chip driver +code returns non-negative values if a virtual command is supported, or a +negative error code if not. The chip driver may return -ENODATA or any other +Linux error code in this case, though an error code other than -ENODATA is +handled more efficiently and thus preferred. Either case, the calling PMBus +core code will abort if the chip driver returns an error code when reading +or writing virtual registers (in other words, the PMBus core code will never +send a virtual command to a chip). + +PMBus driver information +------------------------ + +PMBus driver information, defined in struct pmbus_driver_info, is the main means +for device specific drivers to pass information to the core PMBus driver. +Specifically, it provides the following information. + +- For devices supporting its data in Direct Data Format, it provides coefficients + for converting register values into normalized data. This data is usually + provided by chip manufacturers in device datasheets. +- Supported chip functionality can be provided to the core driver. This may be + necessary for chips which react badly if non-supported commands are executed, + and/or to speed up device detection and initialization. +- Several function entry points are provided to support overriding and/or + augmenting generic command execution. This functionality can be used to map + non-standard PMBus commands to standard commands, or to augment standard + command return values with device specific information. + + API functions + ------------- + + Functions provided by chip driver + --------------------------------- + + All functions return the command return value (read) or zero (write) if + successful. A return value of -ENODATA indicates that there is no manufacturer + specific command, but that a standard PMBus command may exist. Any other + negative return value indicates that the commands does not exist for this + chip, and that no attempt should be made to read or write the standard + command. + + As mentioned above, an exception to this rule applies to virtual commands, + which _must_ be handled in driver specific code. See "Virtual PMBus Commands" + above for more details. + + Command execution in the core PMBus driver code is as follows. + + if (chip_access_function) { + status = chip_access_function(); + if (status != -ENODATA) + return status; + } + if (command >= PMBUS_VIRT_BASE) /* For word commands/registers only */ + return -EINVAL; + return generic_access(); + + Chip drivers may provide pointers to the following functions in struct + pmbus_driver_info. All functions are optional. + + int (*read_byte_data)(struct i2c_client *client, int page, int reg); + + Read byte from page , register . + may be -1, which means "current page". + + int (*read_word_data)(struct i2c_client *client, int page, int reg); + + Read word from page , register . + + int (*write_word_data)(struct i2c_client *client, int page, int reg, + u16 word); + + Write word to page , register . + + int (*write_byte)(struct i2c_client *client, int page, u8 value); + + Write byte to page , register . + may be -1, which means "current page". + + int (*identify)(struct i2c_client *client, struct pmbus_driver_info *info); + + Determine supported PMBus functionality. This function is only necessary + if a chip driver supports multiple chips, and the chip functionality is not + pre-determined. It is currently only used by the generic pmbus driver + (pmbus.c). + + Functions exported by core driver + --------------------------------- + + Chip drivers are expected to use the following functions to read or write + PMBus registers. Chip drivers may also use direct I2C commands. If direct I2C + commands are used, the chip driver code must not directly modify the current + page, since the selected page is cached in the core driver and the core driver + will assume that it is selected. Using pmbus_set_page() to select a new page + is mandatory. + + int pmbus_set_page(struct i2c_client *client, u8 page); + + Set PMBus page register to for subsequent commands. + + int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg); + + Read word data from , . Similar to i2c_smbus_read_word_data(), but + selects page first. + + int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, + u16 word); + + Write word data to , . Similar to i2c_smbus_write_word_data(), but + selects page first. + + int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg); + + Read byte data from , . Similar to i2c_smbus_read_byte_data(), but + selects page first. may be -1, which means "current page". + + int pmbus_write_byte(struct i2c_client *client, int page, u8 value); + + Write byte data to , . Similar to i2c_smbus_write_byte(), but + selects page first. may be -1, which means "current page". + + void pmbus_clear_faults(struct i2c_client *client); + + Execute PMBus "Clear Fault" command on all chip pages. + This function calls the device specific write_byte function if defined. + Therefore, it must _not_ be called from that function. + + bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); + + Check if byte register exists. Return true if the register exists, false + otherwise. + This function calls the device specific write_byte function if defined to + obtain the chip status. Therefore, it must _not_ be called from that function. + + bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); + + Check if word register exists. Return true if the register exists, false + otherwise. + This function calls the device specific write_byte function if defined to + obtain the chip status. Therefore, it must _not_ be called from that function. + + int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info); + + Execute probe function. Similar to standard probe function for other drivers, + with the pointer to struct pmbus_driver_info as additional argument. Calls + identify function if supported. Must only be called from device probe + function. + + void pmbus_do_remove(struct i2c_client *client); + + Execute driver remove function. Similar to standard driver remove function. + + const struct pmbus_driver_info + *pmbus_get_driver_info(struct i2c_client *client); + + Return pointer to struct pmbus_driver_info as passed to pmbus_do_probe(). + + +PMBus driver platform data +========================== + +PMBus platform data is defined in include/linux/i2c/pmbus.h. Platform data +currently only provides a flag field with a single bit used. + +#define PMBUS_SKIP_STATUS_CHECK (1 << 0) + +struct pmbus_platform_data { + u32 flags; /* Device specific flags */ +}; + + +Flags +----- + +PMBUS_SKIP_STATUS_CHECK + +During register detection, skip checking the status register for +communication or command errors. + +Some PMBus chips respond with valid data when trying to read an unsupported +register. For such chips, checking the status register is mandatory when +trying to determine if a chip register exists or not. +Other PMBus chips don't support the STATUS_CML register, or report +communication errors for no explicable reason. For such chips, checking the +status register must be disabled. + +Some i2c controllers do not support single-byte commands (write commands with +no data, i2c_smbus_write_byte()). With such controllers, clearing the status +register is impossible, and the PMBUS_SKIP_STATUS_CHECK flag must be set. diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 71db83ffe935..1869327f0f6a 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -203,8 +203,10 @@ config ARCH_ASPEED bool "ASPEED AST Family" select ARM_AMBA select PLAT_ASPEED - select GENERIC_GPIO select ARCH_REQUIRE_GPIOLIB + select GENERIC_GPIO + select GENERIC_TIME + select GENERIC_CLOCKEVENTS config ARCH_AAEC2000 diff --git a/arch/arm/configs/wedge_defconfig b/arch/arm/configs/wedge_defconfig new file mode 100644 index 000000000000..23a9fe3403f8 --- /dev/null +++ b/arch/arm/configs/wedge_defconfig @@ -0,0 +1,1479 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.28.9 +# Tue Feb 3 16:41:40 2015 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_GENERIC_GPIO=y +CONFIG_GENERIC_TIME=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_MMU=y +# CONFIG_NO_IOPORT is not set +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_KERNEL_GZIP=y +# CONFIG_KERNEL_BZIP2 is not set +# CONFIG_KERNEL_LZMA is not set +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=16 +# CONFIG_CGROUPS is not set +# CONFIG_GROUP_SCHED is not set +CONFIG_SYSFS_DEPRECATED=y +CONFIG_SYSFS_DEPRECATED_V2=y +# CONFIG_RELAY is not set +CONFIG_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_IPC_NS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_SYSCTL=y +CONFIG_ANON_INODES=y +# CONFIG_EMBEDDED is not set +CONFIG_UID16=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS=y +CONFIG_KALLSYMS_EXTRA_PASS=y +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_SLUB_DEBUG=y +CONFIG_COMPAT_BRK=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +# CONFIG_PROFILING is not set +# CONFIG_MARKERS is not set +CONFIG_HAVE_OPROFILE=y +# CONFIG_KPROBES is not set +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +# CONFIG_TINY_SHMEM is not set +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODULE_FORCE_UNLOAD is not set +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_INTEGRITY is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_AS=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_CLASSIC_RCU=y +CONFIG_FREEZER=y + +# +# System Type +# +CONFIG_ARCH_ASPEED=y +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set +# CONFIG_ARCH_MXC is not set +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_MSM is not set +CONFIG_IRMP=y +# CONFIG_PCEXT is not set +# CONFIG_REMOTEFX is not set +# CONFIG_ARCH_AST1100 is not set +# CONFIG_ARCH_AST2100 is not set +# CONFIG_ARCH_AST2200 is not set +# CONFIG_ARCH_AST2300 is not set +CONFIG_ARCH_AST2400=y +# CONFIG_ARCH_AST2500 is not set + +# +# FLASH Chip Select +# +# CONFIG_AST_CS0_NOR is not set +# CONFIG_AST_CS0_NAND is not set +CONFIG_AST_CS0_SPI=y +# CONFIG_AST_CS0_NONE is not set +# CONFIG_AST_CS1_NOR is not set +# CONFIG_AST_CS1_NAND is not set +# CONFIG_AST_CS1_SPI is not set +CONFIG_AST_CS1_NONE=y +# CONFIG_AST_CS2_NOR is not set +# CONFIG_AST_CS2_NAND is not set +# CONFIG_AST_CS2_SPI is not set +CONFIG_AST_CS2_NONE=y +# CONFIG_AST_CS3_NOR is not set +# CONFIG_AST_CS3_NAND is not set +# CONFIG_AST_CS3_SPI is not set +CONFIG_AST_CS3_NONE=y +# CONFIG_AST_CS4_NOR is not set +# CONFIG_AST_CS4_NAND is not set +# CONFIG_AST_CS4_SPI is not set +CONFIG_AST_CS4_NONE=y +# CONFIG_ARCH_AST1070 is not set +# CONFIG_AST_SCU_LOCK is not set + +# +# Boot options +# + +# +# Power management +# +CONFIG_PLAT_ASPEED=y + +# +# Processor Type +# +CONFIG_CPU_32=y +CONFIG_CPU_ARM926T=y +CONFIG_CPU_32v5=y +CONFIG_CPU_ABRT_EV5TJ=y +CONFIG_CPU_PABRT_NOIFAR=y +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_V4WB=y +CONFIG_CPU_TLB_V4WBI=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +# CONFIG_CPU_CACHE_ROUND_ROBIN is not set +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +CONFIG_ARM_AMBA=y +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_VMSPLIT_3G=y +# CONFIG_VMSPLIT_2G is not set +# CONFIG_VMSPLIT_1G is not set +CONFIG_PAGE_OFFSET=0xC0000000 +# CONFIG_PREEMPT is not set +CONFIG_HZ=100 +CONFIG_AEABI=y +CONFIG_OABI_COMPAT=y +CONFIG_ARCH_FLATMEM_HAS_HOLES=y +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_SPLIT_PTLOCK_CPUS=4096 +# CONFIG_RESOURCES_64BIT is not set +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_ZONE_DMA_FLAG=0 +CONFIG_VIRT_TO_BUS=y +CONFIG_UNEVICTABLE_LRU=y +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0 +CONFIG_ZBOOT_ROM_BSS=0 +CONFIG_CMDLINE="" +# CONFIG_XIP_KERNEL is not set +# CONFIG_KEXEC is not set + +# +# CPU Power Management +# +# CONFIG_CPU_IDLE is not set + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_FPE_NWFPE=y +CONFIG_FPE_NWFPE_XP=y +# CONFIG_FPE_FASTFPE is not set +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_HAVE_AOUT=y +CONFIG_BINFMT_AOUT=y +# CONFIG_BINFMT_MISC is not set + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_APM_EMULATION is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +# CONFIG_PACKET_MMAP is not set +CONFIG_UNIX=y +CONFIG_XFRM=y +# CONFIG_XFRM_USER is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_IP_MROUTE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +CONFIG_INET_TUNNEL=m +CONFIG_INET_XFRM_MODE_TRANSPORT=y +CONFIG_INET_XFRM_MODE_TUNNEL=y +CONFIG_INET_XFRM_MODE_BEET=y +# CONFIG_INET_LRO is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +CONFIG_IPV6=m +# CONFIG_IPV6_PRIVACY is not set +# CONFIG_IPV6_ROUTER_PREF is not set +# CONFIG_IPV6_OPTIMISTIC_DAD is not set +# CONFIG_INET6_AH is not set +# CONFIG_INET6_ESP is not set +# CONFIG_INET6_IPCOMP is not set +# CONFIG_IPV6_MIP6 is not set +# CONFIG_INET6_XFRM_TUNNEL is not set +# CONFIG_INET6_TUNNEL is not set +CONFIG_INET6_XFRM_MODE_TRANSPORT=m +CONFIG_INET6_XFRM_MODE_TUNNEL=m +CONFIG_INET6_XFRM_MODE_BEET=m +# CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION is not set +CONFIG_IPV6_SIT=m +CONFIG_IPV6_NDISC_NODETYPE=y +# CONFIG_IPV6_TUNNEL is not set +# CONFIG_IPV6_MULTIPLE_TABLES is not set +# CONFIG_IPV6_MROUTE is not set +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_NET_DSA is not set +CONFIG_VLAN_8021Q=m +# CONFIG_VLAN_8021Q_GVRP is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +CONFIG_WAN_ROUTER=y +# CONFIG_NET_SCHED is not set + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_PHONET is not set +# CONFIG_WIRELESS is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +CONFIG_FIRMWARE_IN_KERNEL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +CONFIG_MTD=y +# CONFIG_MTD_DEBUG is not set +CONFIG_MTD_CONCAT=y +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_REDBOOT_PARTS is not set +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set + +# +# User Modules And Translation Layers +# +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_FTL is not set +# CONFIG_NFTL is not set +# CONFIG_INFTL is not set +# CONFIG_RFD_FTL is not set +# CONFIG_SSFDC is not set +# CONFIG_MTD_OOPS is not set + +# +# RAM/ROM/Flash chip drivers +# +# CONFIG_MTD_CFI is not set +# CONFIG_MTD_JEDECPROBE is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +CONFIG_MTD_MAP_BANK_WIDTH_2=y +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_ABSENT is not set + +# +# Mapping drivers for chip access +# +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_PLATRAM is not set + +# +# Self-contained MTD device drivers +# +CONFIG_MTD_DATAFLASH=m +CONFIG_MTD_DATAFLASH_WRITE_VERIFY=y +# CONFIG_MTD_DATAFLASH_OTP is not set +CONFIG_MTD_M25P80=y +CONFIG_M25PXX_USE_FAST_READ=y +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_BLOCK2MTD is not set + +# +# Disk-On-Chip Device Drivers +# +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +# CONFIG_MTD_NAND is not set +# CONFIG_MTD_ONENAND is not set + +# +# UBI - Unsorted block images +# +# CONFIG_MTD_UBI is not set +# CONFIG_PARPORT is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +CONFIG_BLK_DEV_NBD=y +# CONFIG_BLK_DEV_UB is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=16384 +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_RD_BZIP2 is not set +CONFIG_RD_LZMA=y +CONFIG_RD_GZIP=y +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_C2PORT is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=y +CONFIG_SCSI_DMA=y +CONFIG_SCSI_TGT=y +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_BLK_DEV_SR is not set +CONFIG_CHR_DEV_SG=y +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +CONFIG_SCSI_ISCSI_ATTRS=m +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_SCSI_DH is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_DUMMY is not set +CONFIG_BONDING=m +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +CONFIG_TUN=m +# CONFIG_VETH is not set +# CONFIG_NET_ETHERNET is not set +CONFIG_NETDEV_1000=y +CONFIG_ASPEEDMAC=y +# CONFIG_NETDEV_10000 is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +# CONFIG_WLAN_80211 is not set +# CONFIG_IWLWIFI_LEDS is not set + +# +# USB Network Adapters +# +# CONFIG_USB_CATC is not set +# CONFIG_USB_KAWETH is not set +# CONFIG_USB_PEGASUS is not set +# CONFIG_USB_RTL8150 is not set +# CONFIG_USB_USBNET is not set +# CONFIG_WAN is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_PSAUX=y +CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 +# CONFIG_INPUT_JOYDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +CONFIG_KEYBOARD_ATKBD=y +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_GPIO is not set +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=y +CONFIG_MOUSE_PS2_ALPS=y +CONFIG_MOUSE_PS2_LOGIPS2PP=y +CONFIG_MOUSE_PS2_SYNAPTICS=y +CONFIG_MOUSE_PS2_LIFEBOOK=y +CONFIG_MOUSE_PS2_TRACKPOINT=y +# CONFIG_MOUSE_PS2_ELANTECH is not set +# CONFIG_MOUSE_PS2_TOUCHKIT is not set +CONFIG_MOUSE_SERIAL=y +# CONFIG_MOUSE_APPLETOUCH is not set +# CONFIG_MOUSE_BCM5974 is not set +# CONFIG_MOUSE_VSXXXAA is not set +# CONFIG_MOUSE_GPIO is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +# CONFIG_INPUT_TOUCHSCREEN is not set +# CONFIG_INPUT_MISC is not set + +# +# Hardware I/O ports +# +CONFIG_SERIO=y +CONFIG_SERIO_SERPORT=y +# CONFIG_SERIO_AMBAKMI is not set +CONFIG_SERIO_LIBPS2=y +# CONFIG_SERIO_RAW is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set +CONFIG_DEVKMEM=y +CONFIG_SERIAL_NONSTANDARD=y +# CONFIG_N_HDLC is not set +# CONFIG_RISCOM8 is not set +# CONFIG_SPECIALIX is not set +# CONFIG_RIO is not set +# CONFIG_STALDRV is not set + +# +# Serial drivers +# +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=4 +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +# CONFIG_SERIAL_AST_DMA_UART is not set +# CONFIG_SERIAL_8250_EXTENDED is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_AMBA_PL010 is not set +# CONFIG_SERIAL_AMBA_PL011 is not set +# CONFIG_SERIAL_AST is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +CONFIG_LEGACY_PTYS=y +CONFIG_LEGACY_PTY_COUNT=256 +# CONFIG_IPMI_HANDLER is not set +CONFIG_AST_MISC=y +# CONFIG_AST_VIDEO is not set +# CONFIG_ADC_CAT9883 is not set +# CONFIG_AST_SPI_BIOS is not set +CONFIG_AST_PECI=y +# CONFIG_AST_KCS is not set +# CONFIG_AST_GPIO is not set +# CONFIG_HW_RANDOM is not set +CONFIG_NVRAM=y +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_HELPER_AUTO=y + +# +# I2C Hardware Bus support +# + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_GPIO is not set +# CONFIG_I2C_OCORES is not set +CONFIG_I2C_AST=y +CONFIG_AST_I2C_SLAVE_MODE=y +CONFIG_AST_I2C_SLAVE_EEPROM=y +# CONFIG_AST_I2C_SLAVE_RDWR is not set +# CONFIG_I2C_SIMTEC is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_TINY_USB is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_STUB is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_DS1682 is not set +CONFIG_AT24=m +# CONFIG_SENSORS_EEPROM is not set +CONFIG_SENSORS_PCF8574=m +# CONFIG_PCF8575 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_TPS65010 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set +CONFIG_SPI=y +CONFIG_SPI_MASTER=y + +# +# SPI Master Controller Drivers +# +CONFIG_SPI_AST=y +CONFIG_SPI_FMC=y +CONFIG_SPI_BITBANG=y + +# +# SPI Protocol Masters +# +CONFIG_SPI_AT25=m +# CONFIG_SPI_SPIDEV is not set +# CONFIG_SPI_TLE62X0 is not set +CONFIG_ARCH_REQUIRE_GPIOLIB=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_SYSFS=y + +# +# Memory mapped GPIO expanders: +# + +# +# I2C GPIO expanders: +# +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCF857X is not set + +# +# PCI GPIO expanders: +# + +# +# SPI GPIO expanders: +# +# CONFIG_GPIO_MAX7301 is not set +# CONFIG_GPIO_MCP23S08 is not set +# CONFIG_W1 is not set +# CONFIG_POWER_SUPPLY is not set +CONFIG_HWMON=y +# CONFIG_HWMON_VID is not set +# CONFIG_SENSORS_AD7414 is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADCXX is not set +# CONFIG_SENSORS_ADM1021 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7462 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ADT7473 is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM70 is not set +CONFIG_SENSORS_LM75=m +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +CONFIG_SENSORS_MAX127=m +# CONFIG_SENSORS_MAX1111 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +CONFIG_SENSORS_ADS7828=m +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83L786NG is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +CONFIG_SENSORS_AST_ADC=y +CONFIG_SENSORS_AST_PWM_FAN=y +CONFIG_SENSORS_FB_PANTHER_PLUS=m +CONFIG_PMBUS=m +CONFIG_SENSORS_PMBUS=m +CONFIG_SENSORS_ADM1275=m +# CONFIG_SENSORS_LM25066 is not set +# CONFIG_SENSORS_LTC2978 is not set +# CONFIG_SENSORS_MAX16064 is not set +# CONFIG_SENSORS_MAX34440 is not set +# CONFIG_SENSORS_MAX8688 is not set +CONFIG_SENSORS_PFE1100=m +CONFIG_SENSORS_PFE3000=m +# CONFIG_SENSORS_UCD9000 is not set +# CONFIG_SENSORS_UCD9200 is not set +# CONFIG_SENSORS_ZL6100 is not set +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y +CONFIG_WATCHDOG=y +# CONFIG_WATCHDOG_NOWAYOUT is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_AST_WATCHDOG=y + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_ASIC3 is not set +# CONFIG_HTC_EGPIO is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_MFD_TC6393XB is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM8350_I2C is not set + +# +# Multimedia devices +# + +# +# Multimedia core support +# +# CONFIG_VIDEO_DEV is not set +# CONFIG_DVB_CORE is not set +# CONFIG_VIDEO_MEDIA is not set + +# +# Multimedia drivers +# +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +# CONFIG_VIDEO_OUTPUT_CONTROL is not set +# CONFIG_FB is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set + +# +# Display device support +# +# CONFIG_DISPLAY_SUPPORT is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +# CONFIG_SOUND is not set +# CONFIG_HID_SUPPORT is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_ARCH_HAS_OHCI is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB=m +# CONFIG_USB_DEBUG is not set +# CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +CONFIG_USB_DEVICE_CLASS=y +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_SUSPEND is not set +# CONFIG_USB_OTG is not set +# CONFIG_USB_MON is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_C67X00_HCD is not set +# CONFIG_USB_EHCI_HCD is not set +# CONFIG_USB_ISP116X_HCD is not set +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +# CONFIG_USB_HWA_HCD is not set + +# +# AST USB Drivers +# +CONFIG_AST_USB_UHCI_HCD=y +# CONFIG_AST_USB_UHCI_MULTIPORT_1 is not set +# CONFIG_AST_USB_UHCI_MULTIPORT_2 is not set +CONFIG_AST_USB_UHCI_MULTIPORT_4=y +# CONFIG_USB_EHCI_SPLIT_ISO is not set + +# +# USB Device Class drivers +# +# CONFIG_USB_ACM is not set +# CONFIG_USB_PRINTER is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_TMC is not set + +# +# NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; +# + +# +# see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=m +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_DPCM is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set + +# +# USB port drivers +# +# CONFIG_USB_SERIAL is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_ISIGHTFW is not set +# CONFIG_USB_VST is not set +CONFIG_USB_GADGET=y +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_VBUS_DRAW=2 +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_PXA25X is not set +# CONFIG_USB_GADGET_PXA27X is not set +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_FSL_QE is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_GOKU is not set +CONFIG_USB_GADGET_ASPEED_AST=y +CONFIG_USB_ASPEED_AST=y +# CONFIG_USB_GADGET_DUMMY_HCD is not set +CONFIG_USB_GADGET_DUALSPEED=y +CONFIG_USB_ZERO=m +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +CONFIG_USB_GADGETFS=m +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +CONFIG_USB_G_SERIAL=m +# CONFIG_USB_MIDI_GADGET is not set +# CONFIG_USB_G_PRINTER is not set +CONFIG_USB_CDC_COMPOSITE=m +# CONFIG_MMC is not set +# CONFIG_MEMSTICK is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_NEW_LEDS is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_HCTOSYS is not set +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8581 is not set + +# +# SPI RTC drivers +# +# CONFIG_RTC_DRV_M41T94 is not set +# CONFIG_RTC_DRV_DS1305 is not set +# CONFIG_RTC_DRV_DS1390 is not set +# CONFIG_RTC_DRV_MAX6902 is not set +# CONFIG_RTC_DRV_R9701 is not set +# CONFIG_RTC_DRV_RS5C348 is not set +# CONFIG_RTC_DRV_DS3234 is not set + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +# CONFIG_RTC_DRV_PL030 is not set +# CONFIG_RTC_DRV_PL031 is not set +CONFIG_RTC_DRV_ASPEED=y +# CONFIG_DMADEVICES is not set +# CONFIG_REGULATOR is not set +# CONFIG_UIO is not set + +# +# File systems +# +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT2_FS_POSIX_ACL=y +CONFIG_EXT2_FS_SECURITY=y +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +CONFIG_FS_MBCACHE=y +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +CONFIG_FS_POSIX_ACL=y +CONFIG_FILE_LOCKING=y +# CONFIG_XFS_FS is not set +# CONFIG_OCFS2_FS is not set +CONFIG_DNOTIFY=y +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +# CONFIG_FUSE_FS is not set +CONFIG_GENERIC_ACL=y + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_NTFS_FS=y +# CONFIG_NTFS_DEBUG is not set +CONFIG_NTFS_RW=y + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +# CONFIG_HUGETLB_PAGE is not set +CONFIG_CONFIGFS_FS=m + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_YAFFS1=y +# CONFIG_YAFFS_9BYTE_TAGS is not set +# CONFIG_YAFFS_DOES_ECC is not set +CONFIG_YAFFS_YAFFS2=y +CONFIG_YAFFS_AUTO_YAFFS2=y +# CONFIG_YAFFS_DISABLE_TAGS_ECC is not set +# CONFIG_YAFFS_DISABLE_LAZY_LOAD is not set +# CONFIG_YAFFS_DISABLE_WIDE_TNODES is not set +# CONFIG_YAFFS_ALWAYS_CHECK_CHUNK_ERASED is not set +CONFIG_YAFFS_SHORT_NAMES_IN_RAM=y +# CONFIG_YAFFS_EMPTY_LOST_AND_FOUND is not set +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_DEBUG=0 +CONFIG_JFFS2_FS_WRITEBUFFER=y +# CONFIG_JFFS2_FS_WBUF_VERIFY is not set +# CONFIG_JFFS2_SUMMARY is not set +# CONFIG_JFFS2_FS_XATTR is not set +# CONFIG_JFFS2_COMPRESSION_OPTIONS is not set +CONFIG_JFFS2_ZLIB=y +# CONFIG_JFFS2_LZO is not set +CONFIG_JFFS2_RTIME=y +# CONFIG_JFFS2_RUBIN is not set +# CONFIG_CRAMFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=y +# CONFIG_NFS_V3 is not set +# CONFIG_NFS_V4 is not set +CONFIG_ROOT_NFS=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=y +# CONFIG_SUNRPC_REGISTER_V4 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_CODEPAGE_950=y +CONFIG_NLS_CODEPAGE_932=y +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=y +# CONFIG_DLM is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +# CONFIG_ENABLE_WARN_DEPRECATED is not set +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=1024 +CONFIG_MAGIC_SYSRQ=y +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +CONFIG_DEBUG_BUGVERBOSE=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_FRAME_POINTER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +# CONFIG_LATENCYTOP is not set +# CONFIG_SYSCTL_SYSCALL_CHECK is not set +CONFIG_HAVE_FUNCTION_TRACER=y + +# +# Tracers +# +# CONFIG_DYNAMIC_PRINTK_DEBUG is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_DEBUG_USER is not set + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +# CONFIG_CRYPTO_FIPS is not set +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +CONFIG_CRYPTO_AEAD=m +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_BLKCIPHER=y +CONFIG_CRYPTO_BLKCIPHER2=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +# CONFIG_CRYPTO_GF128MUL is not set +CONFIG_CRYPTO_NULL=y +# CONFIG_CRYPTO_CRYPTD is not set +CONFIG_CRYPTO_AUTHENC=m +# CONFIG_CRYPTO_TEST is not set + +# +# Authenticated Encryption with Associated Data +# +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_SEQIV is not set + +# +# Block modes +# +CONFIG_CRYPTO_CBC=y +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_ECB is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_XTS is not set + +# +# Hash modes +# +CONFIG_CRYPTO_HMAC=y +# CONFIG_CRYPTO_XCBC is not set + +# +# Digest +# +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_MD4 is not set +CONFIG_CRYPTO_MD5=y +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +CONFIG_CRYPTO_SHA1=y +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_WP512 is not set + +# +# Ciphers +# +# CONFIG_CRYPTO_AES is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_ARC4 is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +CONFIG_CRYPTO_DES=y +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set + +# +# Compression +# +CONFIG_CRYPTO_DEFLATE=m +# CONFIG_CRYPTO_LZO is not set + +# +# Random Number Generation +# +# CONFIG_CRYPTO_ANSI_CPRNG is not set +# CONFIG_CRYPTO_HW is not set + +# +# Library routines +# +CONFIG_BITREVERSE=y +# CONFIG_CRC_CCITT is not set +# CONFIG_CRC16 is not set +# CONFIG_CRC_T10DIF is not set +CONFIG_CRC_ITU_T=m +CONFIG_CRC32=y +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAS_DMA=y + + +# GUC USB Drivers +# +CONFIG_GUC_USB_UHCI_HCD=m +# CONFIG_GUC_USB_UHCI_MULTIPORT_1 is not set +# CONFIG_GUC_USB_UHCI_MULTIPORT_2 is not set +CONFIG_GUC_USB_UHCI_MULTIPORT_4=y +# CONFIG_USB_GADGET_MUSB_HDRC is not set diff --git a/arch/arm/kernel/head.S b/arch/arm/kernel/head.S index 8ff0e2379fd8..21e17dc94cb5 100644 --- a/arch/arm/kernel/head.S +++ b/arch/arm/kernel/head.S @@ -21,7 +21,6 @@ #include #include #include -#include #if (PHYS_OFFSET & 0x001fffff) #error "PHYS_OFFSET must be at an even 2MiB boundary!" @@ -77,14 +76,13 @@ */ .section ".text.head", "ax" ENTRY(stext) - ldr r5, =machine_arch_type @ find the machine type msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p' -@ bl __lookup_machine_type @ r5=machinfo + bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a' bl __vet_atags diff --git a/arch/arm/mach-aspeed/include/mach/ast_wdt.h b/arch/arm/mach-aspeed/include/mach/ast_wdt.h index 6d7d7f470b11..f9125a112fa2 100755 --- a/arch/arm/mach-aspeed/include/mach/ast_wdt.h +++ b/arch/arm/mach-aspeed/include/mach/ast_wdt.h @@ -8,4 +8,4 @@ * your option) any later version. */ - extern void ast_soc_wdt_reset(void); + extern void ast_wdt_reset_full(void); diff --git a/arch/arm/mach-aspeed/include/mach/debug-macro.S b/arch/arm/mach-aspeed/include/mach/debug-macro.S index 0b7c9278b9b0..ff3195ac820f 100644 --- a/arch/arm/mach-aspeed/include/mach/debug-macro.S +++ b/arch/arm/mach-aspeed/include/mach/debug-macro.S @@ -13,9 +13,8 @@ .macro addruart, rx, tmp mrc p15, 0, \rx, c1, c0 tst \rx, #1 @ MMU enabled? - ldreq \rx, =AST_UART0_BASE - ldrne \rx, =IO_ADDRESS(AST_UART0_BASE) - orr \rx, \rx, #0x00012000 + ldreq \rx, =AST_UART3_BASE + ldrne \rx, =IO_ADDRESS(AST_UART3_BASE) .endm #define UART_SHIFT 2 diff --git a/arch/arm/mach-aspeed/include/mach/system.h b/arch/arm/mach-aspeed/include/mach/system.h index 96e90dad52fc..926268b948e1 100644 --- a/arch/arm/mach-aspeed/include/mach/system.h +++ b/arch/arm/mach-aspeed/include/mach/system.h @@ -37,7 +37,7 @@ static inline void arch_reset(char mode) * Use WDT to restart system */ #if defined(CONFIG_AST_WATCHDOG) || defined(CONFIG_AST_WATCHDOG_MODULE) - ast_soc_wdt_reset(); + ast_wdt_reset_full(); #endif } diff --git a/arch/arm/mach-aspeed/include/mach/uncompress.h b/arch/arm/mach-aspeed/include/mach/uncompress.h index 896b8542a530..80e560db4033 100644 --- a/arch/arm/mach-aspeed/include/mach/uncompress.h +++ b/arch/arm/mach-aspeed/include/mach/uncompress.h @@ -12,8 +12,8 @@ #include #include -#define UART_PUT_CHAR (*(volatile unsigned char *)(AST_UART0_BASE + UART_THR)) -#define UART_GET_LSR (*(volatile unsigned char *)(AST_UART0_BASE + UART_LSR)) +#define UART_PUT_CHAR (*(volatile unsigned char *)(AST_UART3_BASE + UART_THR)) +#define UART_GET_LSR (*(volatile unsigned char *)(AST_UART3_BASE + UART_LSR)) static void putc(int c) { diff --git a/arch/arm/mach-aspeed/include/mach/vmalloc.h b/arch/arm/mach-aspeed/include/mach/vmalloc.h index 3706cf1f168e..51912aefaa38 100644 --- a/arch/arm/mach-aspeed/include/mach/vmalloc.h +++ b/arch/arm/mach-aspeed/include/mach/vmalloc.h @@ -23,7 +23,7 @@ #define VMALLOC_OFFSET (8*1024*1024) #define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) #define VMALLOC_VMADDR(x) ((unsigned long)(x)) -#define VMALLOC_END (PAGE_OFFSET + 0x10000000) +#define VMALLOC_END (PAGE_OFFSET + 0x20000000) #else #define VMALLOC_END 0xf8000000UL #endif \ No newline at end of file diff --git a/arch/arm/plat-aspeed/Makefile b/arch/arm/plat-aspeed/Makefile index faba830357bd..b63191c706f1 100644 --- a/arch/arm/plat-aspeed/Makefile +++ b/arch/arm/plat-aspeed/Makefile @@ -18,7 +18,7 @@ obj-y += dev-uart.o dev-vuart.o dev-wdt.o dev-rtc.o dev-gpio.o dev-sgpio.o obj-y += dev-nor.o dev-nand.o dev-sdhci.o #bus -obj-y += dev-i2c.o dev-spi.o dev-ehci.o dev-uhci.o dev-lpc.o dev-peci.o dev-kcs.o dev-mbx.o dev-snoop.o +obj-y += dev-i2c.o dev-spi.o dev-ehci.o dev-uhci.o dev-lpc.o dev-peci.o dev-kcs.o dev-mbx.o dev-snoop.o dev-lpc.o #dev #obj-y += dev-udc11.o @@ -33,3 +33,6 @@ obj-y += dev-fb.o dev-video.o #obj-m := #obj-n := #obj- := + +#usbdevicegadgetthing +obj-y += dev-virthub.o diff --git a/arch/arm/plat-aspeed/ast-scu.c b/arch/arm/plat-aspeed/ast-scu.c index 1f1dde27608f..76722f481c78 100644 --- a/arch/arm/plat-aspeed/ast-scu.c +++ b/arch/arm/plat-aspeed/ast-scu.c @@ -251,6 +251,15 @@ ast_scu_init_usb20(void) } +extern void +ast_scu_init_vhub(void) { + //start USB20 clock + ast_scu_write(ast_scu_read(AST_SCU_CLK_STOP) | SCU_USB20_CLK_EN, AST_SCU_CLK_STOP); + mdelay(10); + //disable USB20 reset + ast_scu_write(ast_scu_read(AST_SCU_RESET) & ~SCU_RESET_USB20, AST_SCU_RESET); +} + EXPORT_SYMBOL(ast_scu_init_usb20); extern void @@ -739,7 +748,7 @@ ast_scu_multi_func_uart(u8 uart) { switch(uart) { case 1: - ast_scu_write(ast_scu_read(AST_SCU_FUN_PIN_CTRL1) | + ast_scu_write(ast_scu_read(AST_SCU_FUN_PIN_CTRL2) | SCU_FUN_PIN_UART1_RXD | SCU_FUN_PIN_UART1_TXD | SCU_FUN_PIN_UART1_NRTS | @@ -748,10 +757,10 @@ ast_scu_multi_func_uart(u8 uart) SCU_FUN_PIN_UART1_NDSR | SCU_FUN_PIN_UART1_NDCD | SCU_FUN_PIN_UART1_NCTS, - AST_SCU_FUN_PIN_CTRL1); + AST_SCU_FUN_PIN_CTRL2); break; case 2: - ast_scu_write(ast_scu_read(AST_SCU_FUN_PIN_CTRL1) | + ast_scu_write(ast_scu_read(AST_SCU_FUN_PIN_CTRL2) | SCU_FUN_PIN_UART2_RXD | SCU_FUN_PIN_UART2_TXD | SCU_FUN_PIN_UART2_NRTS | @@ -760,7 +769,7 @@ ast_scu_multi_func_uart(u8 uart) SCU_FUN_PIN_UART2_NDSR | SCU_FUN_PIN_UART2_NDCD | SCU_FUN_PIN_UART2_NCTS, - AST_SCU_FUN_PIN_CTRL1); + AST_SCU_FUN_PIN_CTRL2); break; case 3: ast_scu_write(ast_scu_read(AST_SCU_FUN_PIN_CTRL1) | diff --git a/arch/arm/plat-aspeed/dev-i2c.c b/arch/arm/plat-aspeed/dev-i2c.c index 47cd15249b10..990539024336 100644 --- a/arch/arm/plat-aspeed/dev-i2c.c +++ b/arch/arm/plat-aspeed/dev-i2c.c @@ -46,7 +46,8 @@ #if defined (CONFIG_ARCH_AST2400) #define I2C_PAGE_SIZE 8 -struct buf_page page_info[I2C_PAGE_SIZE] = +static spinlock_t page_info_lock = SPIN_LOCK_UNLOCKED; +static struct buf_page page_info[I2C_PAGE_SIZE] = { [0] = { .flag = 0, @@ -117,9 +118,10 @@ static void pool_buff_page_init(u32 buf_pool_addr) static u8 request_pool_buff_page(struct buf_page **req_page) { int i; + unsigned long flags; //TODO - spinlock_t lock; - spin_lock(&lock); + + spin_lock_irqsave(&page_info_lock, flags); for(i=0;i= I2C_PAGE_SIZE); } static void free_pool_buff_page(struct buf_page *req_page) { + unsigned long flags; + spin_lock_irqsave(&page_info_lock, flags); + req_page->flag = 0; // I2CDBUG( "free page addr %x \n", req_page->page_addr); req_page = NULL; + spin_unlock_irqrestore(&page_info_lock, flags); } #elif defined (CONFIG_ARCH_AST2300) #define I2C_PAGE_SIZE 5 -struct buf_page page_info[I2C_PAGE_SIZE] = +static spinlock_t page_info_lock = SPIN_LOCK_UNLOCKED; +static struct buf_page page_info[I2C_PAGE_SIZE] = { [0] = { .flag = 0, @@ -186,27 +193,31 @@ static void pool_buff_page_init(u32 buf_pool_addr) static u8 request_pool_buff_page(struct buf_page **req_page) { int i; + unsigned long flags; //TODO - spinlock_t lock; - spin_lock(&lock); + + spin_lock_irqsave(&page_info_lock, flags); for(i=0;iflag = 0; req_page = NULL; + spin_unlock_irqrestore(&page_info_lock, flags); } #else @@ -592,19 +603,148 @@ static struct i2c_board_info __initdata ast_i2c_board_info_1[] = { } }; + +//Under I2C Dev 2 +static struct i2c_board_info __initdata ast_i2c_board_info_2[] = { + // Looks like ncp4200 i2c address could be floating depending + // on the system. List all possibilities here (0x60 - 0x63). + // Hope the address will not change after probing. + { + I2C_BOARD_INFO("ncp4200", 0x60), + }, + { + I2C_BOARD_INFO("ncp4200", 0x61), + }, + { + I2C_BOARD_INFO("ncp4200", 0x62), + }, + { + I2C_BOARD_INFO("ncp4200", 0x63), + }, +}; + + +//Under I2C Dev 3 +static struct i2c_board_info __initdata ast_i2c_board_info_3[] = { + // Looks like ncp4200 i2c address could be floating depending + // on the system. List all possibilities here (0x60 - 0x63) + // Hope the address will not change after probing. + { + I2C_BOARD_INFO("ncp4200", 0x60), + }, + { + I2C_BOARD_INFO("ncp4200", 0x61), + }, + { + I2C_BOARD_INFO("ncp4200", 0x62), + }, + { + I2C_BOARD_INFO("ncp4200", 0x63), + }, +}; + + //Under I2C Dev 4 static struct i2c_board_info __initdata ast_i2c_board_info_4[] = { + // Temperature sensors on Wedge: + { + I2C_BOARD_INFO("tmp75", 0x48), + }, + { + I2C_BOARD_INFO("tmp75", 0x49), + }, { - I2C_BOARD_INFO("24c128", 0x50), + I2C_BOARD_INFO("tmp75", 0x4a), + }, +}; +//Under I2C Dev 5 +static struct i2c_board_info __initdata ast_i2c_board_info_5[] = { + /* Panther+ microserver */ + { + I2C_BOARD_INFO("fb_panther_plus", 0x40), + }, + // Temperature sensor on uServer: + { + I2C_BOARD_INFO("tmp75", 0x4c), + }, + { + I2C_BOARD_INFO("ads7828", 0x4b), + }, + { + I2C_BOARD_INFO("24c128", 0x51), + }, +}; +//Under I2C Dev 7 +static struct i2c_board_info __initdata ast_i2c_board_info_7[] = { + // Wedge devices + { + I2C_BOARD_INFO("max127", 0x28), + }, + { + // Differs from the schematic, but appears to be correct + I2C_BOARD_INFO("pcf8574", 0x3f), + }, + { + I2C_BOARD_INFO("24c64", 0x50), } }; + + //Under I2C Dev 8 static struct i2c_board_info __initdata ast_i2c_board_info_8[] = { { + // Eval board: I2C_BOARD_INFO("lm75b", 0x4a), - } + }, + // EEPROMS on the pfe1100 power supplies + { + I2C_BOARD_INFO("24c64", 0x51), + }, + { + I2C_BOARD_INFO("24c64", 0x52), + }, + { + I2C_BOARD_INFO("pfe1100", 0x59), + }, + { + I2C_BOARD_INFO("pfe1100", 0x5a), + }, +}; + + +//Under I2C Dev 9 +static struct i2c_board_info __initdata ast_i2c_board_info_9[] = { + // Looks like ncp4200 i2c address could be floating depending + // on the system. List all possibilities here (0x60 - 0x63) + // Hope the address will not change after probing. + { + I2C_BOARD_INFO("ncp4200", 0x60), + }, + { + I2C_BOARD_INFO("ncp4200", 0x61), + }, + { + I2C_BOARD_INFO("ncp4200", 0x62), + }, + { + I2C_BOARD_INFO("ncp4200", 0x63), + }, +}; + +//Under I2C Dev 12 +static struct i2c_board_info __initdata ast_i2c_board_info_12[] = { + { + I2C_BOARD_INFO("pfe3000", 0x10), + }, +}; + +//Under I2C Dev 13 +static struct i2c_board_info __initdata ast_i2c_board_info_13[] = { + { + I2C_BOARD_INFO("adm1278", 0x10), + }, }; #endif @@ -642,25 +782,36 @@ void __init ast_add_device_i2c(void) platform_device_register(&ast_i2c_dev1_device); i2c_register_board_info(0, ast_i2c_board_info_1, ARRAY_SIZE(ast_i2c_board_info_1)); platform_device_register(&ast_i2c_dev2_device); + i2c_register_board_info(1, ast_i2c_board_info_2, ARRAY_SIZE(ast_i2c_board_info_2)); platform_device_register(&ast_i2c_dev3_device); + i2c_register_board_info(2, ast_i2c_board_info_3, ARRAY_SIZE(ast_i2c_board_info_3)); platform_device_register(&ast_i2c_dev4_device); i2c_register_board_info(3, ast_i2c_board_info_4, ARRAY_SIZE(ast_i2c_board_info_4)); platform_device_register(&ast_i2c_dev5_device); + i2c_register_board_info(4, ast_i2c_board_info_5, ARRAY_SIZE(ast_i2c_board_info_5)); platform_device_register(&ast_i2c_dev6_device); platform_device_register(&ast_i2c_dev7_device); + i2c_register_board_info(6, ast_i2c_board_info_7, ARRAY_SIZE(ast_i2c_board_info_7)); platform_device_register(&ast_i2c_dev8_device); i2c_register_board_info(7, ast_i2c_board_info_8, ARRAY_SIZE(ast_i2c_board_info_8)); platform_device_register(&ast_i2c_dev9_device); + i2c_register_board_info(8, ast_i2c_board_info_9, ARRAY_SIZE(ast_i2c_board_info_9)); #if defined(CONFIG_ARCH_AST2400) platform_device_register(&ast_i2c_dev10_device); #if defined(CONFIG_MMC_AST) //Due to share pin with SD #else - platform_device_register(&ast_i2c_dev11_device); + /* + * On Wedge, bus 13 is used as i2c bus. Bus 12 is used on other + * hardware. Pins for bus 11, 12, and 14 are used as GPIOs, on + * various hardware, but enabling the i2c bus does not seem to + * interfere with the GPIOs. + */ platform_device_register(&ast_i2c_dev12_device); + i2c_register_board_info(11, ast_i2c_board_info_12, ARRAY_SIZE(ast_i2c_board_info_12)); platform_device_register(&ast_i2c_dev13_device); - platform_device_register(&ast_i2c_dev14_device); + i2c_register_board_info(12, ast_i2c_board_info_13, ARRAY_SIZE(ast_i2c_board_info_13)); #endif #endif } diff --git a/arch/arm/plat-aspeed/dev-lpc.c b/arch/arm/plat-aspeed/dev-lpc.c index 50eb4e6b9a03..945e320945dc 100644 --- a/arch/arm/plat-aspeed/dev-lpc.c +++ b/arch/arm/plat-aspeed/dev-lpc.c @@ -25,22 +25,14 @@ #include #include +#include #include #include #include #include +#include - - -#include -#include -#include - -#include -#include -#include -#include - +static u32 ast_lpc_base = IO_ADDRESS(AST_LPC_BASE); /* -------------------------------------------------------------------- * LPC @@ -100,6 +92,12 @@ void __init ast_add_device_lpc(void) platform_device_register(&ast_lpc_plus_device); } #else -void __init ast_add_device_lpc(void) {} +void __init ast_add_device_lpc(void) { + // Since we disable LPC, bring the UART1 and UART2 out from LPC control + writel((readl(ast_lpc_base + AST_LPC_HICR9) + & ~(LPC_HICR9_SOURCE_UART1|LPC_HICR9_SOURCE_UART2 + |LPC_HICR9_SOURCE_UART3|LPC_HICR9_SOURCE_UART4)), + ast_lpc_base + AST_LPC_HICR9); +} #endif diff --git a/arch/arm/plat-aspeed/dev-pwm-fan.c b/arch/arm/plat-aspeed/dev-pwm-fan.c index 85570bb6196c..c66719479c0e 100644 --- a/arch/arm/plat-aspeed/dev-pwm-fan.c +++ b/arch/arm/plat-aspeed/dev-pwm-fan.c @@ -65,14 +65,6 @@ struct platform_device ast_pwm_fan_device = { void __init ast_add_device_pwm_fan(void) { - //SCU Initial - - //SCU Pin-MUX //PWM & TACHO - ast_scu_multi_func_pwm_tacho(); - - //SCU PWM CTRL Reset - ast_scu_init_pwm_tacho(); - platform_device_register(&ast_pwm_fan_device); } #else diff --git a/arch/arm/plat-aspeed/dev-rtc.c b/arch/arm/plat-aspeed/dev-rtc.c index 214aa686dcd5..a8d9b2fe40b5 100644 --- a/arch/arm/plat-aspeed/dev-rtc.c +++ b/arch/arm/plat-aspeed/dev-rtc.c @@ -33,7 +33,7 @@ * Watchdog * -------------------------------------------------------------------- */ -#if defined(CONFIG_RTC_DRV_AST) || defined(CONFIG_RTC_DRV_AST_MODULE) +#if defined(CONFIG_RTC_DRV_ASPEED) || defined(CONFIG_RTC_DRV_ASPEED_MODULE) static struct resource ast_rtc_resource[] = { [0] = { diff --git a/arch/arm/plat-aspeed/dev-spi.c b/arch/arm/plat-aspeed/dev-spi.c index 7ddd2e437212..e22c49ea3cbf 100644 --- a/arch/arm/plat-aspeed/dev-spi.c +++ b/arch/arm/plat-aspeed/dev-spi.c @@ -210,8 +210,8 @@ static struct ast_spi_driver_data ast_spi0_data = { static struct resource ast_spi_resource0[] = { { - .start = AST_SPI0_BASE, - .end = AST_SPI0_BASE + SZ_16, + .start = AST_SPI_BASE, + .end = AST_SPI_BASE + SZ_16, .flags = IORESOURCE_MEM, }, { @@ -299,21 +299,21 @@ static struct mtd_partition ast_spi_flash_partitions[] = { static struct mtd_partition ast_spi_flash_partitions[] = { { .name = "u-boot", - .offset = 0, - .size = 0x80000, + .offset = 0, /* From 0 */ + .size = 0x60000, /* Size 384K */ .mask_flags = MTD_WRITEABLE, + }, { + .name = "env", + .offset = 0x60000, /* From 384K */ + .size = 0x20000, /* Size 128K, two sectors */ }, { - .name = "kernel", - .offset = 0x80000, - .size = 0x200000, + .name = "kernel", + .offset = 0x80000, /* From 512K */ + .size = 0x200000, /* Size 2M */ }, { - .name = "rootfs", - .offset = 0x300000, - .size = 0x4F0000, - }, { - .name = "env", - .offset = 0x7f0000, - .size = 0x10000, + .name = "rootfs", + .offset = 0x300000, /* From 3M */ + .size = 0xC00000, /* Size 12M */ }, { .name = "data0", .offset = MTDPART_OFS_APPEND, @@ -334,7 +334,34 @@ static struct flash_platform_data ast_spi_flash_data = { .parts = ast_spi_flash_partitions, }; +#ifdef CONFIG_ARCH_AST2400 +static struct flash_platform_data wedge_spi_flash_data = { + .type = "n25q128a13", + .nr_parts = ARRAY_SIZE(ast_spi_flash_partitions), + .parts = ast_spi_flash_partitions, +}; +#endif + + +/* Device info for the flash on ast-spi */ +#ifdef CONFIG_ARCH_AST2400 +static struct mtd_partition ast_spi5_flash_partitions[] = { + { + .name = "led-fpga", + .offset = 0, /* From 0 */ + .size = MTDPART_SIZ_FULL, + }, +}; + +static struct flash_platform_data wedge_spi5_flash_data = { + .type = "at45db011d", + .nr_parts = ARRAY_SIZE(ast_spi5_flash_partitions), + .parts = ast_spi5_flash_partitions, +}; +#endif + static struct spi_board_info ast_spi_devices[] = { +#if 0 { .modalias = "m25p80", .platform_data = &ast_spi_flash_data, @@ -343,6 +370,25 @@ static struct spi_board_info ast_spi_devices[] = { .bus_num = 0, // This chooses if SPI0 or SPI1 of the SoC is used. .mode = SPI_MODE_0, }, +#endif +#ifdef CONFIG_ARCH_AST2400 + { + .modalias = "mtd_dataflash", + .platform_data = &wedge_spi5_flash_data, + .chip_select = 0, + .max_speed_hz = 33 * 1000 * 1000, + .bus_num = 5, + .mode = SPI_MODE_0, + }, + { + .modalias = "m25p80", + .platform_data = &wedge_spi_flash_data, + .chip_select = 0, //.chip_select This tells your device driver which chipselect to use. + .max_speed_hz = 50 * 1000 * 1000, + .bus_num = 0, // This chooses if SPI0 or SPI1 of the SoC is used. + .mode = SPI_MODE_0, + }, +#endif { .modalias = "spidev", .chip_select = 0, diff --git a/arch/arm/plat-aspeed/dev-uart.c b/arch/arm/plat-aspeed/dev-uart.c index 592ef4fdadd5..0b7b61410285 100644 --- a/arch/arm/plat-aspeed/dev-uart.c +++ b/arch/arm/plat-aspeed/dev-uart.c @@ -98,6 +98,17 @@ static struct plat_serial8250_port ast_uart_data[] = { }, #endif #endif +#ifdef AST_UART1_BASE + { + .mapbase = AST_UART1_BASE, + .membase = (char*)(IO_ADDRESS(AST_UART1_BASE)), + .irq = IRQ_UART1, + .uartclk = (24*1000000L), + .regshift = 2, + .iotype = UPIO_MEM, + .flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST, + }, +#endif #ifdef AST_UART3_BASE { .mapbase = AST_UART3_BASE, @@ -134,6 +145,7 @@ void __init ast_add_device_uart(void) { #if defined(CONFIG_ARCH_AST1010) #else + ast_scu_multi_func_uart(1); ast_scu_multi_func_uart(3); ast_scu_multi_func_uart(4); #endif diff --git a/arch/arm/plat-aspeed/dev-virthub.c b/arch/arm/plat-aspeed/dev-virthub.c new file mode 100644 index 000000000000..34a5ae1aefe8 --- /dev/null +++ b/arch/arm/plat-aspeed/dev-virthub.c @@ -0,0 +1,62 @@ +/* + * dev-virthub + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * 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 +#include +#include + +#include +#include +#include +#include + +static struct resource ast_virthub_resource[] = { + [0] = { + .start = AST_USB20_BASE, + .end = AST_USB20_BASE + SZ_1K, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_USB20_HUB, + .end = IRQ_USB20_HUB, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 ast_virthub_dma_mask = 0xfffffff8UL; + +static struct platform_device ast_virthub_device = { + .name = "aspeed_udc", + .id = 0, + .dev = { + .dma_mask = &ast_virthub_dma_mask, + .coherent_dma_mask = 0xffffffff, + }, + .resource = ast_virthub_resource, + .num_resources = ARRAY_SIZE(ast_virthub_resource), +}; + +void __init ast_add_device_virthub(void) +{ + ast_scu_multi_func_usb20_host_hub(0); + ast_scu_init_vhub(); + printk("virtual hub inited?\n"); + platform_device_register(&ast_virthub_device); +} diff --git a/arch/arm/plat-aspeed/devs.c b/arch/arm/plat-aspeed/devs.c index 7906b9cbbce8..be6d7f426634 100644 --- a/arch/arm/plat-aspeed/devs.c +++ b/arch/arm/plat-aspeed/devs.c @@ -31,13 +31,14 @@ typedef void (init_fnc_t) (void); init_fnc_t __initdata *init_all_device[] = { + ast_add_device_lpc, ast_add_device_uart, ast_add_device_vuart, ast_add_device_watchdog, ast_add_device_rtc, ast_add_device_i2c, ast_add_device_spi, - ast_add_device_ehci, +//ast_add_device_ehci, disabled by tfang for USB HUB mode ast_add_device_nand, ast_add_device_flash, ast_add_device_pwm_fan, @@ -46,13 +47,14 @@ init_fnc_t __initdata *init_all_device[] = { ast_add_device_sgpio, ast_add_device_peci, ast_add_device_fb, - ast_add_device_sdhci, - ast_add_device_uhci, - ast_add_device_video, +// ast_add_device_sdhci, disabled by tfang +// ast_add_device_uhci, disabled by tfang +// ast_add_device_video, disabled by tfang ast_add_device_kcs, ast_add_device_mailbox, ast_add_device_snoop, ast_add_device_gmac, + ast_add_device_virthub, // ast_add_device_nand, NULL, }; diff --git a/arch/arm/plat-aspeed/include/plat/ast-scu.h b/arch/arm/plat-aspeed/include/plat/ast-scu.h index 77169ee3279c..14a437fb56c6 100644 --- a/arch/arm/plat-aspeed/include/plat/ast-scu.h +++ b/arch/arm/plat-aspeed/include/plat/ast-scu.h @@ -49,6 +49,7 @@ extern void ast_scu_init_lpc(void); extern u8 ast_scu_get_lpc_plus_enable(void); extern void ast_scu_init_udc11(void); extern void ast_scu_init_usb20(void); +extern void ast_scu_init_vhub(void); extern void ast_scu_init_uhci(void); extern void ast_scu_init_sdhci(void); extern void ast_scu_init_i2c(void); diff --git a/arch/arm/plat-aspeed/include/plat/devs.h b/arch/arm/plat-aspeed/include/plat/devs.h index 41cbea934421..8653e959c24a 100644 --- a/arch/arm/plat-aspeed/include/plat/devs.h +++ b/arch/arm/plat-aspeed/include/plat/devs.h @@ -41,6 +41,8 @@ extern void __init ast_add_device_uhci(void); extern void __init ast_add_device_gmac(void); extern void __init ast_add_device_udc11(void); extern void __init ast_add_device_hid(void); +extern void __init ast_add_device_lpc(void); +extern void __init ast_add_device_virthub(void); extern void __init ast_add_device_pcie(void); diff --git a/arch/arm/plat-aspeed/include/plat/regs-lpc.h b/arch/arm/plat-aspeed/include/plat/regs-lpc.h index f4523d7eaeb0..92c51301960f 100644 --- a/arch/arm/plat-aspeed/include/plat/regs-lpc.h +++ b/arch/arm/plat-aspeed/include/plat/regs-lpc.h @@ -186,6 +186,12 @@ #define GET_LPC_SNPD1(x) ((x >> 7) & 0xff) #define GET_LPC_SNPD0(x) (x & 0xff) +/* AST_LPC_HICR9 0x098 - LPC Host Interface Control Register 9 */ +#define LPC_HICR9_SOURCE_UART1 (1 << 4) +#define LPC_HICR9_SOURCE_UART2 (1 << 5) +#define LPC_HICR9_SOURCE_UART3 (1 << 6) +#define LPC_HICR9_SOURCE_UART4 (1 << 7) + /*AST_LPC_PCCR0 0x130 - Post Code Contol Register 0 */ #define LPC_POST_DMA_INT_EN (1 << 31) #define LPC_POST_DMA_MODE_EN (1 << 14) diff --git a/arch/arm/plat-aspeed/include/plat/regs-pwm_fan.h b/arch/arm/plat-aspeed/include/plat/regs-pwm_fan.h index 23d5b77ed524..f1c474c9e0bd 100644 --- a/arch/arm/plat-aspeed/include/plat/regs-pwm_fan.h +++ b/arch/arm/plat-aspeed/include/plat/regs-pwm_fan.h @@ -90,14 +90,14 @@ #define AST_PTCR_CTRL_FAN_NUM_EN(x) (0x1 << (16+x)) -#define AST_PTCR_CTRL_PMWD (11) -#define AST_PTCR_CTRL_PMWD_EN (0x1 << 11) -#define AST_PTCR_CTRL_PMWC (10) -#define AST_PTCR_CTRL_PMWC_EN (0x1 << 10) -#define AST_PTCR_CTRL_PMWB (9) -#define AST_PTCR_CTRL_PMWB_EN (0x1 << 9) -#define AST_PTCR_CTRL_PMWA (8) -#define AST_PTCR_CTRL_PMWA_EN (0x1 << 8) +#define AST_PTCR_CTRL_PWMD (11) +#define AST_PTCR_CTRL_PWMD_EN (0x1 << 11) +#define AST_PTCR_CTRL_PWMC (10) +#define AST_PTCR_CTRL_PWMC_EN (0x1 << 10) +#define AST_PTCR_CTRL_PWMB (9) +#define AST_PTCR_CTRL_PWMB_EN (0x1 << 9) +#define AST_PTCR_CTRL_PWMA (8) +#define AST_PTCR_CTRL_PWMA_EN (0x1 << 8) #define AST_PTCR_CTRL_CLK_MCLK 0x2 //0:24Mhz, 1:MCLK #define AST_PTCR_CTRL_CLK_EN 0x1 @@ -209,14 +209,14 @@ #define AST_PTCR_CTRL_GET_PWME_TYPE(x) (((x&(0x1<<4))>>3) | ((x&(0x1<<12))>>12)) #define AST_PTCR_CTRL_SET_PWME_TYPE_MASK ((0x1<<4) | (0x1<<12)) -#define AST_PTCR_CTRL_PMWH (11) -#define AST_PTCR_CTRL_PMWH_EN (0x1 << 11) -#define AST_PTCR_CTRL_PMWG (10) -#define AST_PTCR_CTRL_PMWG_EN (0x1 << 10) -#define AST_PTCR_CTRL_PMWF (9) -#define AST_PTCR_CTRL_PMWF_EN (0x1 << 9) -#define AST_PTCR_CTRL_PMWE (8) -#define AST_PTCR_CTRL_PMWE_EN (0x1 << 8) +#define AST_PTCR_CTRL_PWMH (11) +#define AST_PTCR_CTRL_PWMH_EN (0x1 << 11) +#define AST_PTCR_CTRL_PWMG (10) +#define AST_PTCR_CTRL_PWMG_EN (0x1 << 10) +#define AST_PTCR_CTRL_PWMF (9) +#define AST_PTCR_CTRL_PWMF_EN (0x1 << 9) +#define AST_PTCR_CTRL_PWME (8) +#define AST_PTCR_CTRL_PWME_EN (0x1 << 8) // AST_PTCR_CLK_EXT_CTRL : 0x44 - Clock Control Extension #1 //TYPE O diff --git a/arch/arm/plat-aspeed/timer.c b/arch/arm/plat-aspeed/timer.c index 079d958c6e3f..6805beb24a12 100644 --- a/arch/arm/plat-aspeed/timer.c +++ b/arch/arm/plat-aspeed/timer.c @@ -16,19 +16,13 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include +#include +#include #include -#include #include -#include -#include #include -#include - #include -#include -#include -#include -#include #include #include @@ -37,72 +31,145 @@ #define ASPEED_TIMER2_VA_BASE (IO_ADDRESS(AST_TIMER_BASE)+ASPEED_TIMER2_OFFSET) #define ASPEED_TIMERC_VA_BASE (IO_ADDRESS(AST_TIMER_BASE)+ASPEED_TIMERRC_OFFSET) -/* - * Returns number of ms since last clock interrupt. Note that interrupts - * will have been disabled by do_gettimeoffset() - */ -static unsigned long ast_gettimeoffset(void) +#define ASPEED_TIMER_RELOAD_MAX 0xFFFFFFFF +#define ASPEED_TIMER_RELOAD_MIN 1 + +static struct clock_event_device clockevent_ast; + +static inline unsigned long ast_timer_read_count(void *base) { - volatile TimerStruct_t *timer0 = (TimerStruct_t *) ASPEED_TIMER0_VA_BASE; - unsigned long ticks1, ticks2;//, status; + volatile TimerStruct_t *timer = (volatile TimerStruct_t *)(base); + return timer->TimerValue; +} - /* - * Get the current number of ticks. Note that there is a race - * condition between us reading the timer and checking for - * an interrupt. We get around this by ensuring that the - * counter has not reloaded between our two reads. - */ - ticks2 = timer0->TimerValue; - do { - ticks1 = ticks2; -// status = readl(AST_RAW_STS(0));// __raw_readl(IO_ADDRESS(ASPEED_VIC_BASE) + ASPEED_VIC_RAW_STATUS_OFFSET); - ticks2 = timer0->TimerValue; - } while (ticks2 > ticks1); +/* change the timer count and load value (if requeseted) */ +static inline void ast_timer_set_count(void *base, unsigned long count, + unsigned long reload) +{ + volatile TimerStruct_t *timer = (volatile TimerStruct_t *)(base); + timer->TimerValue = count; + timer->TimerLoad = reload; +} - /* - * Number of ticks since last interrupt. - */ - ticks1 = TIMER_RELOAD - ticks2; +#define AST_TIMER_DISABLE 0 +#define AST_TIMER_ENABLE 1 - /* - * Interrupt pending? If so, we've reloaded once already. - */ -// if (status & (1 << IRQ_TIMER0)) -// ticks1 += TIMER_RELOAD; +static inline void ast_timer0_ctrl(int enable) +{ + volatile __u32 *timerc = (volatile __u32*) ASPEED_TIMERC_VA_BASE; + if (enable == AST_TIMER_ENABLE) { + *timerc |= TIMER0_ENABLE | TIMER0_RefExt; + } else { + *timerc &= ~TIMER0_ENABLE; + } +} - /* - * Convert the ticks to usecs - */ - return TICKS2USECS(ticks1); +static inline void ast_timer1_ctrl(int enable) +{ + volatile __u32 *timerc = (volatile __u32*) ASPEED_TIMERC_VA_BASE; + if (enable == AST_TIMER_ENABLE) { + *timerc |= TIMER1_ENABLE | TIMER1_RefExt; + } else { + *timerc &= ~TIMER1_ENABLE; + } } +static inline void ast_timer_disable_all() +{ + volatile __u32 *timerc = (volatile __u32*) ASPEED_TIMERC_VA_BASE; + *timerc = 0; +} /* - * IRQ handler for the timer + * clocksource */ -static irqreturn_t -ast_timer_interrupt(int irq, void *dev_id) +static irqreturn_t ast_clocksource_interrupt(int irq, void *dev_id) { + return IRQ_HANDLED; +} -// write_seqlock(&xtime_lock); +static struct irqaction ast_clocksource_irq = { + .name = "ast-clocksource", + .flags = IRQF_DISABLED | IRQF_TIMER, + .handler = ast_clocksource_interrupt, +}; + +static cycle_t read_cycles(void) +{ +#if 1 + return (cycles_t)(ASPEED_TIMER_RELOAD_MAX + - ast_timer_read_count(ASPEED_TIMER1_VA_BASE)); +#else + return (cycles_t) ast_timer_read_count(ASPEED_TIMER1_VA_BASE); +#endif +} + +static struct clocksource clocksource_ast = { + .name = "ast-clocksource", + .rating = 300, + .read = read_cycles, + .mask = CLOCKSOURCE_MASK(32), + .shift = 20, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; /* - * clear the interrupt in Irq.c + * clockevent */ -// IRQ_EDGE_CLEAR(0,IRQ_TIMER0); - - timer_tick(); +/* IRQ handler for the timer */ +static irqreturn_t ast_clockevent_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = &clockevent_ast; + evt->event_handler(evt); + return IRQ_HANDLED; +} +static struct irqaction ast_clockevent_irq = { + .name = "ast-clockevent", + .flags = IRQF_DISABLED | IRQF_TIMER, + .handler = ast_clockevent_interrupt, +}; -// write_sequnlock(&xtime_lock); +static int ast_timer_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + /* In this case, we shall not set the load value. */ + ast_timer_set_count(ASPEED_TIMER0_VA_BASE, cycles, 0); + /* turn on the timer */ + ast_timer0_ctrl(AST_TIMER_ENABLE); + return 0; +} - return IRQ_HANDLED; +static void ast_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + /* stop timer first */ + ast_timer0_ctrl(AST_TIMER_DISABLE); + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + ast_timer_set_count(ASPEED_TIMER0_VA_BASE, + TIMER_RELOAD - 1, TIMER_RELOAD - 1); + ast_timer0_ctrl(AST_TIMER_ENABLE); + break; + case CLOCK_EVT_MODE_ONESHOT: + /* + * Leave the timer disabled, ast_timer_set_next_event() will + * enable it later + */ + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + } } -static struct irqaction ast_timer_irq = { - .name = "ast timer", - .flags = IRQF_DISABLED | IRQF_TIMER, - .handler = ast_timer_interrupt, +static struct clock_event_device clockevent_ast = { + .name = "ast-clockevent", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .shift = 32, + .set_next_event = ast_timer_set_next_event, + .set_mode = ast_timer_set_mode, }; /* @@ -110,28 +177,50 @@ static struct irqaction ast_timer_irq = { */ static void __init ast_setup_timer(void) { - volatile TimerStruct_t *timer0 = (volatile TimerStruct_t *) ASPEED_TIMER0_VA_BASE; - volatile __u32 *timerc = (volatile __u32*) ASPEED_TIMERC_VA_BASE; - /* * Initialise to a known state (all timers off) */ - *timerc = 0; - - timer0->TimerLoad = TIMER_RELOAD - 1; - timer0->TimerValue = TIMER_RELOAD - 1; - *timerc = TIMER0_ENABLE | TIMER0_RefExt; + ast_timer_disable_all(); /* - * Make irqs happen for the system timer + * For clock event, set the value and reload to 0, so that no interrupt even + * after enabling timer. */ + ast_timer_set_count(ASPEED_TIMER0_VA_BASE, 0, 0); + /* + * For clock source, set the value and reload to the max + */ + ast_timer_set_count(ASPEED_TIMER1_VA_BASE, + ASPEED_TIMER_RELOAD_MAX, ASPEED_TIMER_RELOAD_MAX); + + /* Enable timer */ + ast_timer0_ctrl(AST_TIMER_ENABLE); + ast_timer1_ctrl(AST_TIMER_ENABLE); + ast_scu_show_system_info(); - setup_irq(IRQ_TIMER0, &ast_timer_irq); - + /* irqs happen for the system timer */ + setup_irq(IRQ_TIMER0, &ast_clockevent_irq); + setup_irq(IRQ_TIMER1, &ast_clocksource_irq); + + /* setup clocksource */ + clocksource_ast.mult = clocksource_hz2mult(ASPEED_TIMER_CLKRATE, + clocksource_ast.shift); + if (clocksource_register(&clocksource_ast)) { + printk(KERN_ERR "Failed to register clock source %s", clocksource_ast.name); + } + + /* setup clockevent */ + clockevent_ast.mult = div_sc(ASPEED_TIMER_CLKRATE, NSEC_PER_SEC, + clockevent_ast.shift); + clockevent_ast.max_delta_ns = clockevent_delta2ns(ASPEED_TIMER_RELOAD_MAX, + &clockevent_ast); + clockevent_ast.min_delta_ns = clockevent_delta2ns(ASPEED_TIMER_RELOAD_MIN, + &clockevent_ast); + clockevent_ast.cpumask = cpumask_of_cpu(0); + clockevents_register_device(&clockevent_ast); } struct sys_timer ast_timer = { .init = ast_setup_timer, -// .offset = ast_gettimeoffset, }; diff --git a/drivers/Makefile b/drivers/Makefile index fceb71a741c3..0f19c40f0459 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -102,3 +102,4 @@ obj-$(CONFIG_SSB) += ssb/ obj-$(CONFIG_VIRTIO) += virtio/ obj-$(CONFIG_REGULATOR) += regulator/ obj-$(CONFIG_STAGING) += staging/ + diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 681782be1b90..6055720a174b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -548,6 +548,16 @@ config SENSORS_LM93 This driver can also be built as a module. If so, the module will be called lm93. +config SENSORS_MAX127 + tristate "Maxim MAX127 sensor chip" + depends on I2C + help + If you say yes here you get support for the MAX127, + 5 channel, 12-bit DAC sensor chip. + + This driver can also be built as a module. If so, the module + will be called max127. + config SENSORS_MAX1111 tristate "Maxim MAX1111 Multichannel, Serial 8-bit ADC chip" depends on SPI_MASTER @@ -870,6 +880,15 @@ config SENSORS_AST_PWM_FAN This driver provides support for the ASPEED PWM & FAN Tacho Controller, which provides an Sensor, fan control. +config SENSORS_FB_PANTHER_PLUS + tristate "Facebook Panther+ Microserver Driver" + depends on ARCH_ASPEED && I2C + default n + help + This driver provides support for Facebook Panther+ microserver. + + Say Y here if you run on the BMC which connects to Panther+ microserver + config SENSORS_APPLESMC tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" depends on INPUT && X86 @@ -895,6 +914,8 @@ config SENSORS_APPLESMC Say Y here if you have an applicable laptop and want to experience the awesome power of applesmc. +source drivers/hwmon/pmbus/Kconfig + config HWMON_DEBUG_CHIP bool "Hardware Monitoring Chip debugging messages" default n diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 562972747cce..80395153a1d3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_AST_ADC) += ast_adc.o obj-$(CONFIG_SENSORS_AST_PWM_FAN) += ast_pwm_fan.o +obj-$(CONFIG_SENSORS_FB_PANTHER_PLUS) += fb_panther_plus.o obj-$(CONFIG_SENSORS_AMS) += ams/ obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o @@ -64,6 +65,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM93) += lm93.o +obj-$(CONFIG_SENSORS_MAX127) += max127.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o @@ -81,6 +83,8 @@ obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o +obj-$(CONFIG_PMBUS) += pmbus/ + ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG endif diff --git a/drivers/hwmon/ast_adc.c b/drivers/hwmon/ast_adc.c index 0969e398a8c8..3f95dc6f0f06 100644 --- a/drivers/hwmon/ast_adc.c +++ b/drivers/hwmon/ast_adc.c @@ -42,7 +42,7 @@ #include -#define REST_DESIGN 0 +#define REST_DESIGN 5 struct adc_vcc_ref_data { int v2; @@ -50,7 +50,7 @@ struct adc_vcc_ref_data { int r2; }; -static struct adc_vcc_ref_data adc_vcc_ref[5] = { +static struct adc_vcc_ref_data adc_vcc_ref[6] = { [0] = { .v2 = 0, .r1 = 5600, @@ -76,8 +76,20 @@ static struct adc_vcc_ref_data adc_vcc_ref[5] = { .r1 = 56000, .r2 = 1000, }, + [5] = { + .v2 = 0, + .r1 = 1000, + .r2 = 1000, + }, }; +/* Divisors for voltage sense; right now adc5 & adc6 divide by 2 */ + +static int adc_divisor[] = { 1, 1, 1, 1, + 1, 2, 2, 1, + 1, 1, 1, 1, + 1, 1, 1, 1}; + struct ast_adc_data { struct device *hwmon_dev; void __iomem *reg_base; /* virtual */ @@ -388,6 +400,22 @@ ast_set_adc_en(struct ast_adc_data *ast_adc, u8 adc_ch, u8 enable) } +/* NAME sysfs */ +static ssize_t +show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "ast_adc\n"); +} +static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, show_name, NULL, 0, 0); +static struct attribute *name_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; +static const struct attribute_group name_attribute_groups = { + .attrs = name_attributes, +}; + /* attr ADC sysfs 0~max adc channel * 0 - show/store channel enable * 1 - show value @@ -399,12 +427,31 @@ ast_set_adc_en(struct ast_adc_data *ast_adc, u8 adc_ch, u8 enable) * 7 - show/store hystersis low */ +static u32 +ast_get_voltage(int idx) { + u16 tmp; + u32 voltage, tmp1, tmp2, tmp3; + tmp = ast_get_adc_value(ast_adc, idx); + // Voltage Sense Method + tmp1 = (adc_vcc_ref[REST_DESIGN].r1 + adc_vcc_ref[REST_DESIGN].r2) * tmp * 25 * 10; + tmp2 = adc_vcc_ref[REST_DESIGN].r2 * 1024 ; + tmp3 = (adc_vcc_ref[REST_DESIGN].r1 * adc_vcc_ref[REST_DESIGN].v2) / adc_vcc_ref[REST_DESIGN].r2; + // printk("tmp3 = %d \n",tmp3); + voltage = (tmp1/tmp2) - tmp3; + + // Higher voltage inputs require a divisor + + if (adc_divisor[idx]) + voltage /= adc_divisor[idx]; + + return voltage; +} + static ssize_t ast_show_adc(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { struct sensor_device_attribute_2 *sensor_attr = to_sensor_dev_attr_2(attr); - u16 tmp; - u32 voltage,tmp1, tmp2,tmp3; + u32 voltage; //sensor_attr->index : pwm_ch# //sensor_attr->nr : attr# @@ -414,15 +461,7 @@ ast_show_adc(struct device *dev, struct device_attribute *attr, char *sysfsbuf) return sprintf(sysfsbuf, "%d : %s\n", ast_get_adc_en(ast_adc,sensor_attr->index),ast_get_adc_en(ast_adc,sensor_attr->index) ? "Enable":"Disable"); break; case 1: //value - tmp = ast_get_adc_value(ast_adc, sensor_attr->index); - //Voltage Sense Method - tmp1 = (adc_vcc_ref[REST_DESIGN].r1 + adc_vcc_ref[REST_DESIGN].r2) * tmp * 25 * 10; - tmp2 = adc_vcc_ref[REST_DESIGN].r2 * 1023 ; - - tmp3 = (adc_vcc_ref[REST_DESIGN].r1 * adc_vcc_ref[REST_DESIGN].v2) / adc_vcc_ref[REST_DESIGN].r2; - // printk("tmp3 = %d \n",tmp3); - voltage = (tmp1/tmp2) - tmp3; - + voltage = ast_get_voltage(sensor_attr->index); return sprintf(sysfsbuf, "%d.%d (V)\n",voltage/100, voltage%100); break; case 2: //alarm @@ -443,6 +482,9 @@ ast_show_adc(struct device *dev, struct device_attribute *attr, char *sysfsbuf) case 7: //hystersis lower return sprintf(sysfsbuf, "%d \n", ast_get_adc_hyster_lower(ast_adc,sensor_attr->index)); break; + case 8: + voltage = ast_get_voltage(sensor_attr->index); + return sprintf(sysfsbuf, "%d\n",voltage * 10); default: return -EINVAL; @@ -504,6 +546,7 @@ ast_store_adc(struct device *dev, struct device_attribute *attr, const char *sys * 5 - show/store hystersis enable * 6 - show/store hystersis upper * 7 - show/store hystersis low +* 8 - show value as 1000s, expected by lm-sensors */ #define sysfs_adc_ch(index) \ @@ -531,6 +574,9 @@ static SENSOR_DEVICE_ATTR_2(adc##index##_hyster_upper, S_IRUGO | S_IWUSR, \ static SENSOR_DEVICE_ATTR_2(adc##index##_hyster_lower, S_IRUGO | S_IWUSR, \ ast_show_adc, ast_store_adc, 7, index); \ \ +static SENSOR_DEVICE_ATTR_2(in##index##_input, S_IRUGO | S_IWUSR, \ + ast_show_adc, NULL, 8, index); \ +\ static struct attribute *adc##index##_attributes[] = { \ &sensor_dev_attr_adc##index##_en.dev_attr.attr, \ &sensor_dev_attr_adc##index##_value.dev_attr.attr, \ @@ -540,6 +586,7 @@ static struct attribute *adc##index##_attributes[] = { \ &sensor_dev_attr_adc##index##_hyster_en.dev_attr.attr, \ &sensor_dev_attr_adc##index##_hyster_upper.dev_attr.attr, \ &sensor_dev_attr_adc##index##_hyster_lower.dev_attr.attr, \ + &sensor_dev_attr_in##index##_input.dev_attr.attr, \ NULL \ }; @@ -637,10 +684,14 @@ ast_adc_probe(struct platform_device *pdev) goto out_region; } + err = sysfs_create_group(&pdev->dev.kobj, &name_attribute_groups); + if (err) + goto out_region; + for(i=0; idev.kobj, &adc_attribute_groups[i]); if (err) - goto out_region; + goto out_sysfs00; } ast_adc_ctrl_init(); @@ -652,6 +703,8 @@ ast_adc_probe(struct platform_device *pdev) //out_irq: // free_irq(ast_adc->irq, NULL); +out_sysfs00: + sysfs_remove_group(&pdev->dev.kobj, &name_attribute_groups); out_region: release_mem_region(res->start, res->end - res->start + 1); out_mem: @@ -674,6 +727,8 @@ ast_adc_remove(struct platform_device *pdev) for(i=0; i<5; i++) sysfs_remove_group(&pdev->dev.kobj, &adc_attribute_groups[i]); + sysfs_remove_group(&pdev->dev.kobj, &name_attribute_groups); + platform_set_drvdata(pdev, NULL); // free_irq(ast_adc->irq, ast_adc); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/drivers/hwmon/ast_pwm_fan.c b/drivers/hwmon/ast_pwm_fan.c index 02784c5b9e1e..5864f5cc6827 100644 --- a/drivers/hwmon/ast_pwm_fan.c +++ b/drivers/hwmon/ast_pwm_fan.c @@ -65,6 +65,8 @@ #include #endif +#include + //#define MCLK 1 struct ast_pwm_tacho_data { @@ -119,6 +121,8 @@ ast_pwm_tacho_read(struct ast_pwm_tacho_data *ast_pwm_tacho, u32 reg) static void ast_pwm_taco_init(void) { + uint32_t val; + //Enable PWM TACH CLK ************************************************** // Set M/N/O out is 25Khz //The PWM frequency = 24Mhz / (16 * 6 * (9 + 1)) = 25Khz @@ -153,15 +157,20 @@ static void ast_pwm_taco_init(void) ast_pwm_tacho_write(ast_pwm_tacho, 0x0, AST_PTCR_TACH_SOURCE); ast_pwm_tacho_write(ast_pwm_tacho, 0x0, AST_PTCR_TACH_SOURCE_EXT); - //PWM A~D -> Disable , type M, + //PWM A~H -> Disable , type M, //Tacho 0~15 Disable //CLK source 24Mhz + val = AST_PTCR_CTRL_PWMA_EN | AST_PTCR_CTRL_PWMB_EN + | AST_PTCR_CTRL_PWMC_EN | AST_PTCR_CTRL_PWMD_EN + | AST_PTCR_CTRL_CLK_EN; #ifdef MCLK - ast_pwm_tacho_write(ast_pwm_tacho, AST_PTCR_CTRL_CLK_MCLK | AST_PTCR_CTRL_CLK_EN, AST_PTCR_CTRL); + ast_pwm_tacho_write(ast_pwm_tacho, val|AST_PTCR_CTRL_CLK_MCLK, AST_PTCR_CTRL); #else - ast_pwm_tacho_write(ast_pwm_tacho, AST_PTCR_CTRL_CLK_EN, AST_PTCR_CTRL); + ast_pwm_tacho_write(ast_pwm_tacho, val, AST_PTCR_CTRL); #endif - + val = AST_PTCR_CTRL_PWME_EN | AST_PTCR_CTRL_PWMF_EN + | AST_PTCR_CTRL_PWMG_EN | AST_PTCR_CTRL_PWMH_EN; + ast_pwm_tacho_write(ast_pwm_tacho, val, AST_PTCR_CTRL_EXT); } /*index 0 : clk_en , 1: clk_source*/ @@ -724,7 +733,7 @@ ast_get_tacho_rpm(struct ast_pwm_tacho_data *ast_pwm_tacho, u8 tacho_ch) else clk_source = 24*1000*1000; - printk("raw_data %d, clk_source %d, tacho_clk_div %d \n",raw_data, clk_source, tacho_clk_div); + // printk("raw_data %d, clk_source %d, tacho_clk_div %d \n",raw_data, clk_source, tacho_clk_div); rpm = (clk_source * 60) / (2 * raw_data * tacho_clk_div); return rpm; @@ -923,28 +932,28 @@ ast_get_pwm_en(struct ast_pwm_tacho_data *ast_pwm_tacho, u8 pwm_ch) switch (pwm_ch) { case PWMA: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PMWA_EN) >> AST_PTCR_CTRL_PMWA; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PWMA_EN) >> AST_PTCR_CTRL_PWMA; break; case PWMB: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PMWB_EN) >> AST_PTCR_CTRL_PMWB; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PWMB_EN) >> AST_PTCR_CTRL_PWMB; break; case PWMC: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PMWC_EN) >> AST_PTCR_CTRL_PMWC; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PWMC_EN) >> AST_PTCR_CTRL_PWMC; break; case PWMD: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PMWD_EN) >> AST_PTCR_CTRL_PMWD; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & AST_PTCR_CTRL_PWMD_EN) >> AST_PTCR_CTRL_PWMD; break; case PWME: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PMWE_EN) >> AST_PTCR_CTRL_PMWE; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PWME_EN) >> AST_PTCR_CTRL_PWME; break; case PWMF: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PMWF_EN) >> AST_PTCR_CTRL_PMWF; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PWMF_EN) >> AST_PTCR_CTRL_PWMF; break; case PWMG: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PMWG_EN) >> AST_PTCR_CTRL_PMWG; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PWMG_EN) >> AST_PTCR_CTRL_PWMG; break; case PWMH: - tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PMWH_EN) >> AST_PTCR_CTRL_PMWH; + tmp = (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & AST_PTCR_CTRL_PWMH_EN) >> AST_PTCR_CTRL_PWMH; break; default: printk("error channel ast_get_pwm_type %d \n",pwm_ch); @@ -962,87 +971,87 @@ ast_set_pwm_en(struct ast_pwm_tacho_data *ast_pwm_tacho, u8 pwm_ch, u8 enable) case PWMA: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PMWA_EN, + ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PWMA_EN, AST_PTCR_CTRL); else ast_pwm_tacho_write(ast_pwm_tacho, - ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PMWA_EN, + ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PWMA_EN, AST_PTCR_CTRL); break; case PWMB: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PMWB_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PWMB_EN), AST_PTCR_CTRL); else ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PMWB_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PWMB_EN), AST_PTCR_CTRL); break; case PWMC: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PMWC_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PWMC_EN), AST_PTCR_CTRL); else ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PMWC_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PWMC_EN), AST_PTCR_CTRL); break; case PWMD: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PMWD_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) | AST_PTCR_CTRL_PWMD_EN), AST_PTCR_CTRL); else ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PMWD_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL) & ~AST_PTCR_CTRL_PWMD_EN), AST_PTCR_CTRL); break; case PWME: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PMWE_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PWME_EN), AST_PTCR_CTRL_EXT); else ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PMWE_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PWME_EN), AST_PTCR_CTRL_EXT); break; case PWMF: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PMWF_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PWMF_EN), AST_PTCR_CTRL_EXT); else ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PMWF_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PWMF_EN), AST_PTCR_CTRL_EXT); break; case PWMG: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PMWG_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PWMG_EN), AST_PTCR_CTRL_EXT); else ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PMWG_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PWMG_EN), AST_PTCR_CTRL_EXT); break; case PWMH: if(enable) ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PMWH_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) | AST_PTCR_CTRL_PWMH_EN), AST_PTCR_CTRL_EXT); else ast_pwm_tacho_write(ast_pwm_tacho, - (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PMWH_EN), + (ast_pwm_tacho_read(ast_pwm_tacho, AST_PTCR_CTRL_EXT) & ~AST_PTCR_CTRL_PWMH_EN), AST_PTCR_CTRL_EXT); break; @@ -1383,6 +1392,22 @@ ast_set_pwm_duty_falling(struct ast_pwm_tacho_data *ast_pwm_tacho, u8 pwm_ch, u8 } +/* NAME sysfs */ +static ssize_t +show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "ast_pwm\n"); +} +static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, show_name, NULL, 0, 0); +static struct attribute *name_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; +static const struct attribute_group name_attribute_groups = { + .attrs = name_attributes, +}; + /*PWM M/N/O Type sysfs*/ /* * Macro defining SENSOR_DEVICE_ATTR for a pwm sysfs entries. @@ -1937,6 +1962,51 @@ static const struct attribute_group tacho_attribute_groups[] = { { .attrs = tacho15_attributes }, }; +/* Create fan sysfs for lm-sensors, index starts from 1 */ +#define sysfs_fan_speeds_num(index) \ +static SENSOR_DEVICE_ATTR_2(fan##index##_input, S_IRUGO, \ + ast_show_tacho_speed, NULL, 2, index - 1); \ +static struct attribute *fan##index##_attributes[] = { \ + &sensor_dev_attr_fan##index##_input.dev_attr.attr, \ + NULL \ +}; + +sysfs_fan_speeds_num(1); +sysfs_fan_speeds_num(2); +sysfs_fan_speeds_num(3); +sysfs_fan_speeds_num(4); +sysfs_fan_speeds_num(5); +sysfs_fan_speeds_num(6); +sysfs_fan_speeds_num(7); +sysfs_fan_speeds_num(8); +sysfs_fan_speeds_num(9); +sysfs_fan_speeds_num(10); +sysfs_fan_speeds_num(11); +sysfs_fan_speeds_num(12); +sysfs_fan_speeds_num(13); +sysfs_fan_speeds_num(14); +sysfs_fan_speeds_num(15); +sysfs_fan_speeds_num(16); + +static const struct attribute_group fan_attribute_groups[] = { + { .attrs = fan1_attributes }, + { .attrs = fan2_attributes }, + { .attrs = fan3_attributes }, + { .attrs = fan4_attributes }, + { .attrs = fan5_attributes }, + { .attrs = fan6_attributes }, + { .attrs = fan7_attributes }, + { .attrs = fan8_attributes }, + { .attrs = fan9_attributes }, + { .attrs = fan10_attributes }, + { .attrs = fan11_attributes }, + { .attrs = fan12_attributes }, + { .attrs = fan13_attributes }, + { .attrs = fan14_attributes }, + { .attrs = fan15_attributes }, + { .attrs = fan16_attributes }, +}; + static int ast_pwm_tacho_probe(struct platform_device *pdev) { @@ -1947,6 +2017,12 @@ ast_pwm_tacho_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "ast_pwm_fan_probe \n"); + //SCU Pin-MUX //PWM & TACHO + ast_scu_multi_func_pwm_tacho(); + + //SCU PWM CTRL Reset + ast_scu_init_pwm_tacho(); + ast_pwm_tacho = kzalloc(sizeof(struct ast_pwm_tacho_data), GFP_KERNEL); if (!ast_pwm_tacho) { ret = -ENOMEM; @@ -1982,10 +2058,14 @@ ast_pwm_tacho_probe(struct platform_device *pdev) } /* Register sysfs hooks */ - err = sysfs_create_group(&pdev->dev.kobj, &clk_attribute_groups); + err = sysfs_create_group(&pdev->dev.kobj, &name_attribute_groups); if (err) goto out_region; + err = sysfs_create_group(&pdev->dev.kobj, &clk_attribute_groups); + if (err) + goto out_sysfs00; + ast_pwm_tacho->hwmon_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(ast_pwm_tacho->hwmon_dev)) { ret = PTR_ERR(ast_pwm_tacho->hwmon_dev); @@ -2017,12 +2097,22 @@ ast_pwm_tacho_probe(struct platform_device *pdev) goto out_sysfs3; } + for(i=0; i< TACHO_NUM; i++) { + err = sysfs_create_group(&pdev->dev.kobj, &fan_attribute_groups[i]); + if (err) + goto out_sysfs4; + } + ast_pwm_taco_init(); printk(KERN_INFO "ast_pwm_tacho: driver successfully loaded.\n"); return 0; +out_sysfs4: + for(i=0; i< PWM_TYPE_NUM; i++) + sysfs_remove_group(&pdev->dev.kobj, &tacho_type_attribute_groups[i]); + out_sysfs3: for(i=0; i< TACHO_NUM; i++) sysfs_remove_group(&pdev->dev.kobj, &tacho_attribute_groups[i]); @@ -2036,6 +2126,8 @@ out_sysfs1: sysfs_remove_group(&pdev->dev.kobj, &pwm_attribute_groups[i]); out_sysfs0: sysfs_remove_group(&pdev->dev.kobj, &clk_attribute_groups); +out_sysfs00: + sysfs_remove_group(&pdev->dev.kobj, &name_attribute_groups); //out_irq: // free_irq(ast_pwm_tacho->irq, NULL); @@ -2058,9 +2150,10 @@ ast_pwm_tacho_remove(struct platform_device *pdev) hwmon_device_unregister(ast_pwm_tacho->hwmon_dev); - for(i=0; i<16; i++) + for(i=0; i<16; i++) { sysfs_remove_group(&pdev->dev.kobj, &tacho_attribute_groups[i]); - + sysfs_remove_group(&pdev->dev.kobj, &fan_attribute_groups[i]); + } for(i=0; i<3; i++) sysfs_remove_group(&pdev->dev.kobj, &pwm_type_attribute_groups[i]); @@ -2069,6 +2162,8 @@ ast_pwm_tacho_remove(struct platform_device *pdev) sysfs_remove_group(&pdev->dev.kobj, &clk_attribute_groups); + sysfs_remove_group(&pdev->dev.kobj, &name_attribute_groups); + platform_set_drvdata(pdev, NULL); // free_irq(ast_pwm_tacho->irq, ast_pwm_tacho); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/drivers/hwmon/fb_panther_plus.c b/drivers/hwmon/fb_panther_plus.c new file mode 100644 index 000000000000..1dde2ec1e029 --- /dev/null +++ b/drivers/hwmon/fb_panther_plus.c @@ -0,0 +1,722 @@ +/* + * fb_panther_plus.c - Driver for Panther+ microserver + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * 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. + */ + +//#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG + +#define PP_DEBUG(fmt, ...) do { \ + printk(KERN_DEBUG "%s:%d " fmt "\n", \ + __FUNCTION__, __LINE__, ##__VA_ARGS__); \ +} while (0) + +#else /* !DEBUG */ + +#define PP_DEBUG(fmt, ...) + +#endif + +static const unsigned short normal_i2c[] = { + 0x40, I2C_CLIENT_END +}; + +/* + * Insmod parameters + */ + +I2C_CLIENT_INSMOD_1(panther_plus); + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id panther_plus_id[] = { + { "fb_panther_plus", panther_plus }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, panther_plus_id); + +struct panther_plus_data { + struct device *hwmon_dev; + struct mutex update_lock; +}; + +// Identifies the sysfs attribute panther_plus_show is requesting. +enum { + PANTHER_PLUS_SYSFS_CPU_TEMP, + PANTHER_PLUS_SYSFS_DIMM_TEMP, + PANTHER_PLUS_SYSFS_GPIO_INPUTS, + PANTHER_PLUS_SYSFS_SMS_KCS, + PANTHER_PLUS_SYSFS_ALERT_CONTROL, + PANTHER_PLUS_SYSFS_ALERT_STATUS, + PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER, + PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER, + PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID, + PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID, + PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID, +}; + +// Function Block ID identifiers. +enum panther_plus_fbid_en { + PANTHER_PLUS_FBID_IPMI_SMS_KCS = 0x0, + PANTHER_PLUS_FBID_GPIO_INPUTS = 0xd, + PANTHER_PLUS_FBID_READ_REGISTER = 0x10, + PANTHER_PLUS_FBID_ALERT_CONTROL = 0xFD, + PANTHER_PLUS_FBID_ALERT_STATUS = 0xFE, + PANTHER_PLUS_FBID_DISCOVERY = 0xFF, +}; + +static inline void panther_plus_make_read(struct i2c_msg *msg, + u8 addr, + u8 *buf, + int len) +{ + msg->addr = addr; + msg->flags = I2C_M_RD; + msg->buf = buf; + msg->len = len; +} + +static inline void panther_plus_make_write(struct i2c_msg *msg, + u8 addr, + u8 *buf, + int len) +{ + msg->addr = addr; + msg->flags = 0; + msg->buf = buf; + msg->len = len; +} + +static int panther_plus_fbid_io(struct i2c_client *client, + enum panther_plus_fbid_en fbid, + const u8 *write_buf, u8 write_len, + u8 *read_buf, u8 read_len) +{ + // The Intel uServer Module Management Interface Spec defines SMBus blocks, + // but block sizes exceed the SMBus maximum block sizes + // (32, see I2C_SMBUS_BLOCK_MAX). So we basically have to re-implement the + // smbus functions with a larger max. + struct i2c_msg msg[2]; + u8 buf[255]; + u8 buf_len; + int rc; + u8 num_msgs = 1; + + if (write_len + 1 > sizeof(buf)) { + return -EINVAL; + } + + /* first, write the FBID, followed by the write_buf if there is one */ + buf[0] = fbid; + buf_len = 1; + if (write_buf) { + memcpy(&buf[1], write_buf, write_len); + buf_len += write_len; + } + panther_plus_make_write(&msg[0], client->addr, buf, buf_len); + + /* then, read */ + if (read_buf) { + panther_plus_make_read(&msg[1], client->addr, read_buf, read_len); + num_msgs = 2; + } + + rc = i2c_transfer(client->adapter, msg, num_msgs); + if (rc < 0) { + PP_DEBUG("Failed to read FBID: %d, error=%d", fbid, rc); + return rc; + } + + if (rc != num_msgs) { /* expect 2 */ + PP_DEBUG("Unexpected rc (%d != %d) when reading FBID: %d", rc, num_msgs, fbid); + return -EIO; + } + + /* the first byte read should match fbid */ + + if (read_buf && read_buf[0] != fbid) { + PP_DEBUG("Unexpected FBID returned (%d != %d)", read_buf[0], fbid); + return -EIO; + } + + return 0; +} + +#define PP_GPIO_POWER_ON 0x1 +#define PP_GPIO_PWRGD_P1V35 (0x1 << 1) +#define PP_GPIO_RST_EAGE_N (0x1 << 2) +#define PP_GPIO_FM_BIOS_POST_CMPLT_N (0x1 << 3) +#define PP_GPIO_IERR_FPGA (0x1 << 4) +#define PP_GPIO_AVN_PLD_PROCHOT_N (0x1 << 5) +#define PP_GPIO_BUS1_ERROR (0x1 << 6) +#define PP_GPIO_AVN_PLD_THERMTRIP_N (0x1 << 7) +#define PP_GPIO_MCERR_FPGA (0x1 << 8) +#define PP_GPIO_ERROR_AVN_2 (0x1 << 9) +#define PP_GPIO_ERROR_AVN_1 (0x1 << 10) +#define PP_GPIO_ERROR_AVN_0 (0x1 << 11) +#define PP_GPIO_H_MEMHOT_CO_N (0x1 << 12) +#define PP_GPIO_SLP_S45_N (0x1 << 13) +#define PP_GPIO_PLTRST_FPGA_N (0x1 << 14) +#define PP_GPIO_FPGA_GPI_PWD_FAIL (0x1 << 15) +#define PP_GPIO_FPGA_GPI_NMI (0x1 << 16) +#define PP_GPIO_GPI_VCCP_VRHOT_N (0x1 << 17) +#define PP_GPIO_FPGA_GPI_TMP75_ALERT (0x1 << 18) +#define PP_GPIO_LPC_CLKRUN_N (0x1 << 19) + +static int panther_plus_read_gpio_inputs_value( + struct i2c_client *client, u32 *val) +{ + int rc; + u8 read_buf[5]; + + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_GPIO_INPUTS, + NULL, 0, read_buf, sizeof(read_buf)); + if (rc < 0) { + return rc; + } + + /* + * expect receiving 5 bytes as: + * 0xd 0x3 + */ + if (read_buf[1] != 0x3) { + PP_DEBUG("Unexpected length %d != 3", read_buf[1]); + return -EIO; + } + + *val = read_buf[2] | (read_buf[3] << 8) | (read_buf[4] << 16); + + return 0; +} + +static int panther_plus_is_in_post(struct i2c_client *client) +{ + u32 val; + int rc; + + rc = panther_plus_read_gpio_inputs_value(client, &val); + if (rc < 0) { + /* failed to read gpio, treat it as in post */ + return 1; + } + + /* if PP_GPIO_FM_BIOS_POST_CMPLT_N is set, post is _not_ done yet */ + return (val & PP_GPIO_FM_BIOS_POST_CMPLT_N); +} + +static int panther_plus_read_register(struct i2c_client *client, + u8 reg_idx, u32 *reg_val) +{ + u8 write_buf[2]; + u8 read_buf[8]; + int rc; + + /* need to send the register index for the reading */ + write_buf[0] = 0x1; /* one byte */ + write_buf[1] = reg_idx; + + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_READ_REGISTER, + write_buf, sizeof(write_buf), + read_buf, sizeof(read_buf)); + if (rc < 0) { + return -EIO; + } + + /* + * expect receiving 8 bytes as: + * 0x10 0x6 LSB LSB+1 LSB+2 LSB+3 valid + */ + if (read_buf[1] != 0x6 + || read_buf[2] != reg_idx + || read_buf[7] != 0) { + return -EIO; + } + + *reg_val = read_buf[3] | (read_buf[4] << 8) + | (read_buf[5] << 16) | (read_buf[6] << 24); + + PP_DEBUG("Read register %d: 0x%x", reg_idx, *reg_val); + + return 0; +} + +#define PANTHER_PLUS_REG_SOC_TJMAX 0 +#define PANTHER_PLUS_REG_SOC_RUNTIME 1 +#define PANTHER_PLUS_REG_SOC_THERMAL_MARGIN 2 +#define PANTHER_PLUS_REG_SOC_DIMM0_A_TEMP 3 +#define PANTHER_PLUS_REG_SOC_DIMM0_B_TEMP 4 +#define PANTHER_PLUS_REG_SOC_POWER_UNIT 5 +#define PANTHER_PLUS_REG_SOC_POWER_CONSUMPTION 6 +#define PANTHER_PLUS_REG_SOC_POWER_CALC 7 +#define PANTHER_PLUS_REG_SOC_DIMM1_A_TEMP 8 +#define PANTHER_PLUS_REG_SOC_DIMM1_B_TEMP 9 + +static int panther_plus_read_cpu_temp(struct i2c_client *client, char *ret) +{ + int rc; + u32 tjmax; + u32 tmargin; + int val; + int temp; + + /* + * make sure POST is done, accessing CPU temperature during POST phase could + * confusing POST and make it hang + */ + if (panther_plus_is_in_post(client)) { + return -EBUSY; + } + + mdelay(10); + + /* first read Tjmax: register 0, bit[16-23] */ + rc = panther_plus_read_register(client, PANTHER_PLUS_REG_SOC_TJMAX, &tjmax); + if (rc < 0) { + return rc; + } + + mdelay(10); + + /* then, read the thermal margin */ + rc = panther_plus_read_register(client, PANTHER_PLUS_REG_SOC_THERMAL_MARGIN, + &tmargin); + if (rc < 0) { + return rc; + } + /* + * thermal margin is 16b 2's complement value representing a number of 1/64 + * degress centigrade. + */ + tmargin &= 0xFFFF; + if ((tmargin & 0x8000)) { + /* signed */ + val = -((tmargin - 1) ^ 0xFFFF); + } else { + val = tmargin; + } + + /* + * now val holds the margin (a number of 1/64), add it to the Tjmax. + * Times 1000 for lm-sensors. + */ + temp = ((tjmax >> 16) & 0xFF) * 1000 + val * 1000 / 64; + + return sprintf(ret, "%d\n", temp); +} + +static int panther_plus_read_dimm_temp(struct i2c_client *client, + int dimm, char *ret) +{ + int rc; + u32 val; + int temp; + + /* + * make sure POST is done, accessing DIMM temperature will fail anyway if + * POST is not done. + */ + if (panther_plus_is_in_post(client)) { + return -EBUSY; + } + + mdelay(10); + + rc = panther_plus_read_register(client, dimm, &val); + if (rc < 0) { + return rc; + } + + /* + * DIMM temperature is encoded in 16b as the following: + * b15-b12: TCRIT HIGH LOW SIGN + * b11-b08: 128 64 32 16 + * b07-b04: 8 4 2 1 + * b03-b00: 0.5 0.25 0.125 0.0625 + */ + /* For now, only care about those 12 data bits and SIGN */ + val &= 0x1FFF; + if ((val & 0x1000)) { + /* signed */ + val = -((val - 1) ^ 0x1FFF); + } + + /* + * now val holds the value as a number of 1/16, times 1000 for lm-sensors */ + temp = val * 1000 / 16; + + return sprintf(ret, "%d\n", temp); +} + +static int panther_plus_read_gpio_inputs(struct i2c_client *client, char *ret) +{ + u32 val; + int rc; + + rc = panther_plus_read_gpio_inputs_value(client, &val); + if (rc < 0) { + return rc; + } + return sprintf(ret, "0x%x\n", val); +} + +static int panther_plus_read_sms_kcs(struct i2c_client *client, char *ret) +{ + int rc; + u8 read_buf[255] = {0x0}; + + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_IPMI_SMS_KCS, + NULL, 0, read_buf, sizeof(read_buf)); + if (rc < 0) { + return rc; + } + + memcpy(ret, read_buf, read_buf[1]+2); + + return (read_buf[1]+2); +} + +static int panther_plus_write_sms_kcs(struct i2c_client *client, const char *buf, u8 count) +{ + int rc; + + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_IPMI_SMS_KCS, + buf, count, NULL, 0); + if (rc < 0) { + return rc; + } + + return count; +} + +static int panther_plus_read_alert_status(struct i2c_client *client, char *ret) +{ + int rc; + u8 rbuf[5] = {0}; + + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_ALERT_STATUS, + NULL, 0, rbuf, sizeof(rbuf)); + if (rc < 0) { + return rc; + } + + memcpy(ret, rbuf, rbuf[1]+2); + + return (rbuf[1]+2); +} + +static int panther_plus_read_alert_control(struct i2c_client *client, char *ret) +{ + int rc; + u8 rbuf[5] = {0}; + + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_ALERT_CONTROL, + NULL, 0, rbuf, sizeof(rbuf)); + if (rc < 0) { + return rc; + } + + memcpy(ret, rbuf, rbuf[1]+2); + + return (rbuf[1]+2); +} + +static int panther_plus_read_discovery(struct i2c_client *client, char *ret, + int which_attribute) +{ + int rc; + u8 datalen; +#define DISCOVERY_DATA_SIZE 10 + u8 rbuf[DISCOVERY_DATA_SIZE+2] = {0}; + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_DISCOVERY, + NULL, 0, rbuf, sizeof(rbuf)); + if (rc < 0) { + return rc; + } + datalen = rbuf[1]; + if (datalen < DISCOVERY_DATA_SIZE) { + return -EINVAL; + } + switch (which_attribute) { + case PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER: + return scnprintf(ret, PAGE_SIZE, "%u.%u\n", rbuf[2], rbuf[3]); + case PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER: + return scnprintf(ret, PAGE_SIZE, "%u.%u\n", rbuf[4], rbuf[5]); + case PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID: + return scnprintf(ret, PAGE_SIZE, "0x%02X%02X%02X\n", rbuf[8], rbuf[7], + rbuf[6]); + case PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID: + return scnprintf(ret, PAGE_SIZE, "0x%02X\n", rbuf[9]); + case PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID: + return scnprintf(ret, PAGE_SIZE, "0x%02X%02X\n", rbuf[11], rbuf[10]); + default: + return -EINVAL; + } + return -EINVAL; +} + +static int panther_plus_write_alert_control(struct i2c_client *client, const char *buf, u8 count) +{ + int rc; + + rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_ALERT_CONTROL, + buf, count, NULL, 0); + if (rc < 0) { + return rc; + } + + return count; +} + +static ssize_t panther_plus_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct panther_plus_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = to_sensor_dev_attr_2(attr); + int which = sensor_attr->index; + int rc = -EIO; + + mutex_lock(&data->update_lock); + switch (which) { + case PANTHER_PLUS_SYSFS_CPU_TEMP: + rc = panther_plus_read_cpu_temp(client, buf); + break; + case PANTHER_PLUS_SYSFS_DIMM_TEMP: + rc = panther_plus_read_dimm_temp(client, sensor_attr->nr, buf); + break; + case PANTHER_PLUS_SYSFS_GPIO_INPUTS: + rc = panther_plus_read_gpio_inputs(client, buf); + break; + case PANTHER_PLUS_SYSFS_SMS_KCS: + rc = panther_plus_read_sms_kcs(client, buf); + break; + case PANTHER_PLUS_SYSFS_ALERT_STATUS: + rc = panther_plus_read_alert_status(client, buf); + break; + case PANTHER_PLUS_SYSFS_ALERT_CONTROL: + rc = panther_plus_read_alert_control(client, buf); + break; + case PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER: + case PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER: + case PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID: + case PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID: + case PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID: + rc = panther_plus_read_discovery(client, buf, which); + default: + break; + } + + /* + * With the current i2c driver, the bus/kernel could hang if accessing the + * FPGA too fast. Adding some delay here until we fix the i2c driver bug + */ + mdelay(10); + + mutex_unlock(&data->update_lock); + + return rc; +} + +static ssize_t panther_plus_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct panther_plus_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sensor_attr = to_sensor_dev_attr_2(attr); + int which = sensor_attr->index; + + int rc = -EIO; + mutex_lock(&data->update_lock); + switch (which) { + case PANTHER_PLUS_SYSFS_SMS_KCS: + rc = panther_plus_write_sms_kcs(client, buf, count); + break; + case PANTHER_PLUS_SYSFS_ALERT_CONTROL: + rc = panther_plus_write_alert_control(client, buf, count); + break; + default: + break; + } + + mdelay(10); + + mutex_unlock(&data->update_lock); + + return rc; +} + +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_CPU_TEMP); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, panther_plus_show, NULL, + PANTHER_PLUS_REG_SOC_DIMM0_A_TEMP, + PANTHER_PLUS_SYSFS_DIMM_TEMP); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, panther_plus_show, NULL, + PANTHER_PLUS_REG_SOC_DIMM0_B_TEMP, + PANTHER_PLUS_SYSFS_DIMM_TEMP); +static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, panther_plus_show, NULL, + PANTHER_PLUS_REG_SOC_DIMM1_A_TEMP, + PANTHER_PLUS_SYSFS_DIMM_TEMP); +static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, panther_plus_show, NULL, + PANTHER_PLUS_REG_SOC_DIMM1_B_TEMP, + PANTHER_PLUS_SYSFS_DIMM_TEMP); +static SENSOR_DEVICE_ATTR_2(gpio_inputs, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_GPIO_INPUTS); +static SENSOR_DEVICE_ATTR_2(sms_kcs, S_IWUSR | S_IRUGO, panther_plus_show, panther_plus_set, + 0, PANTHER_PLUS_SYSFS_SMS_KCS); +static SENSOR_DEVICE_ATTR_2(alert_status, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_ALERT_STATUS); +static SENSOR_DEVICE_ATTR_2(alert_control, S_IWUSR | S_IRUGO, panther_plus_show, panther_plus_set, + 0, PANTHER_PLUS_SYSFS_ALERT_CONTROL); +static SENSOR_DEVICE_ATTR_2(spec_ver, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER); +static SENSOR_DEVICE_ATTR_2(hw_ver, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER); +static SENSOR_DEVICE_ATTR_2(manufacturer_id, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID); +static SENSOR_DEVICE_ATTR_2(device_id, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID); +static SENSOR_DEVICE_ATTR_2(product_id, S_IRUGO, panther_plus_show, NULL, + 0, PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID); + +static struct attribute *panther_plus_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_gpio_inputs.dev_attr.attr, + &sensor_dev_attr_sms_kcs.dev_attr.attr, + &sensor_dev_attr_alert_status.dev_attr.attr, + &sensor_dev_attr_alert_control.dev_attr.attr, + &sensor_dev_attr_spec_ver.dev_attr.attr, + &sensor_dev_attr_hw_ver.dev_attr.attr, + &sensor_dev_attr_manufacturer_id.dev_attr.attr, + &sensor_dev_attr_device_id.dev_attr.attr, + &sensor_dev_attr_product_id.dev_attr.attr, + NULL +}; + +static const struct attribute_group panther_plus_group = { + .attrs = panther_plus_attributes, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int panther_plus_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + /* + * We don't currently do any detection of the Panther+, although + * presumably we could try to query FBID 0xFF for HW ID. + */ + strlcpy(info->type, "panther_plus", I2C_NAME_SIZE); + return 0; +} + +static int panther_plus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct panther_plus_data *data; + int err; + + data = kzalloc(sizeof(struct panther_plus_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Register sysfs hooks */ + if ((err = sysfs_create_group(&client->dev.kobj, &panther_plus_group))) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + printk(KERN_INFO "Panther+ driver successfully loaded.\n"); + + return 0; + + exit_remove_files: + sysfs_remove_group(&client->dev.kobj, &panther_plus_group); + exit_free: + kfree(data); + exit: + return err; +} + +static int panther_plus_remove(struct i2c_client *client) +{ + struct panther_plus_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &panther_plus_group); + + kfree(data); + return 0; +} + +static struct i2c_driver panther_plus_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "panther_plus", + }, + .probe = panther_plus_probe, + .remove = panther_plus_remove, + .id_table = panther_plus_id, + .detect = panther_plus_detect, + .address_data = &addr_data, +}; + +static int __init sensors_panther_plus_init(void) +{ + return i2c_add_driver(&panther_plus_driver); +} + +static void __exit sensors_panther_plus_exit(void) +{ + i2c_del_driver(&panther_plus_driver); +} + +MODULE_AUTHOR("Tian Fang "); +MODULE_DESCRIPTION("Panther+ Driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_panther_plus_init); +module_exit(sensors_panther_plus_exit); diff --git a/drivers/hwmon/max127.c b/drivers/hwmon/max127.c new file mode 100644 index 000000000000..a223f94f56cc --- /dev/null +++ b/drivers/hwmon/max127.c @@ -0,0 +1,312 @@ +/* + * max127.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (C) 2003-2004 Alexey Fisher + * Jean Delvare + * + * Based on the max1619 driver, which was based on the lm90 driver. + * The MAX127 is a voltage sensor chip made by Maxim. It reports + * up to eight voltages, with a choice of maximums * of 5V or 10V. + * In addition, it can read either only positive voltages, + * or negative voltages as well, for a maximum range of -10V to +10V. + * + * Complete datasheet can be obtained from Maxim's website at: + * http://datasheets.maximintegrated.com/en/ds/MAX127-MAX128B.pdf + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const unsigned short normal_i2c[] = { + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; + +/* + * Insmod parameters + */ + +I2C_CLIENT_INSMOD_1(max127); + +static int scaling; +module_param(scaling, int, 0); +MODULE_PARM_DESC(scaling, "Fixed-point scaling factor (* 10000), ie 24414"); + + +/* + * The MAX127 I2C messages + */ + +/* We send a single query byte to the device, setting the following bits: */ + +#define MAX127_REG_R_START 0x80 /* Top bit must be set */ +#define MAX127_REG_R_SEL_MASK 0x70 /* Which of 8 inputs to get */ +#define MAX127_REG_R_SEL_SHIFT 4 +#define MAX127_REG_R_RNG 0x08 /* 10v (otherwise 5v) */ +#define MAX127_REG_R_BIP 0x04 /* show negative voltage */ +#define MAX127_REG_R_PD1 0x02 /* power saving controls */ +#define MAX127_REG_R_PD0 0x01 + +/* Must shift return value to get a 12-bit value */ +#define MAX127_RESULT_SHIFT 4 + +#define MAX127_CHANNELS 8 + +/* + * Functions declaration + */ + +static int max127_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int max127_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info); +static int max127_remove(struct i2c_client *client); +static void max127_update_device(struct device *dev, int which); + +/* + * Driver data (common to all clients) + */ + +static const struct i2c_device_id max127_id[] = { + { "max127", max127 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max127_id); + +static struct i2c_driver max127_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max127", + }, + .probe = max127_probe, + .remove = max127_remove, + .id_table = max127_id, + .detect = max127_detect, + .address_data = &addr_data, +}; + +/* + * Client data (each client gets its own) + */ + +struct max127_data { + struct device *hwmon_dev; + struct mutex update_lock; + u16 valid; /* zero until following fields are valid */ + + u16 voltage; +}; + +/* + * Sysfs stuff + */ + +static ssize_t show_in(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max127_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int which = sensor_attr->index; + int valid; + unsigned voltage; + + mutex_lock(&data->update_lock); + max127_update_device(dev, which); + valid = data->valid; + voltage = data->voltage; + mutex_unlock(&data->update_lock); + + if (scaling) + voltage = voltage * scaling / 10000; + + if (!valid) + return -EIO; + return sprintf(buf, "%u\n", voltage); +} + + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in, NULL, 0); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in, NULL, 2); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in, NULL, 3); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in, NULL, 4); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_in, NULL, 5); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_in, NULL, 6); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_in, NULL, 7); + +static struct attribute *max127_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + NULL +}; + +static const struct attribute_group max127_group = { + .attrs = max127_attributes, +}; + +/* + * Real code + */ + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max127_detect(struct i2c_client *new_client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + + /* + * We don't currently do any detection of the MAX127, although + * presumably we could try setting and unsetting the top + * bit in a query to see whether it does conversions or fails. + */ + + strlcpy(info->type, "max127", I2C_NAME_SIZE); + + return 0; +} + +static int max127_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct max127_data *data; + int err; + + data = kzalloc(sizeof(struct max127_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(new_client, data); + data->valid = 0; + mutex_init(&data->update_lock); + + /* Register sysfs hooks */ + if ((err = sysfs_create_group(&new_client->dev.kobj, &max127_group))) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&new_client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&new_client->dev.kobj, &max127_group); +exit_free: + kfree(data); +exit: + return err; +} + +static int max127_remove(struct i2c_client *client) +{ + struct max127_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &max127_group); + + kfree(data); + return 0; +} + +static void max127_update_device(struct device *dev, int which) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max127_data *data = i2c_get_clientdata(client); + struct i2c_msg msg; + int status; + u8 buf[2]; + + /* + * The MAX127 doesn't use standard SMBus queries; it needs a + * write to specify what conversion to make, followed by an i2c + * STOP. It can then be read for the two-byte voltage value. + * Perhaps the idea is that a query can be started, then + * checked at an arbitrarily later time. We don't support + * that -- we just get a result immediately. + * + * We have to use i2c_transfer to do the second read without + * writing to any registers, rather than using the i2c_smbus_xxxxxx + * queries that most of the other hwmon drivers do. + */ + + dev_dbg(&client->dev, "Updating max127 data for probe %d.\n", which); + data->valid = 0; + + buf[0] = MAX127_REG_R_START | (which << MAX127_REG_R_SEL_SHIFT) | + MAX127_REG_R_RNG; + msg.addr = client->addr; + msg.flags = 0; + msg.buf = buf; + msg.len = 1; + status = i2c_transfer(client->adapter, &msg, 1); + + if (status != 1) { + return; + } + + msg.addr = client->addr; + msg.flags = I2C_M_RD; + msg.buf = buf; + msg.len = 2; + status = i2c_transfer(client->adapter, &msg, 1); + + data->voltage = (buf[0] << 8) | buf[1]; + data->voltage >>= MAX127_RESULT_SHIFT; + + if (status == 1) + data->valid = 1; +} + +static int __init sensors_max127_init(void) +{ + return i2c_add_driver(&max127_driver); +} + +static void __exit sensors_max127_exit(void) +{ + i2c_del_driver(&max127_driver); +} + +MODULE_AUTHOR("Kevin Lahey "); +MODULE_DESCRIPTION("MAX127 sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_max127_init); +module_exit(sensors_max127_exit); diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig new file mode 100644 index 000000000000..52a3c7e83ea1 --- /dev/null +++ b/drivers/hwmon/pmbus/Kconfig @@ -0,0 +1,144 @@ +# +# PMBus chip drivers configuration +# + +menuconfig PMBUS + tristate "PMBus support" + depends on I2C + default n + help + Say yes here if you want to enable PMBus support. + + This driver can also be built as a module. If so, the module will + be called pmbus_core. + +if PMBUS + +config SENSORS_PMBUS + tristate "Generic PMBus devices" + default y + help + If you say yes here you get hardware monitoring support for generic + PMBus devices, including but not limited to ADP4000, BMR453, BMR454, + MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, UDT020, TPS40400, + and TPS40422. + + This driver can also be built as a module. If so, the module will + be called pmbus. + +config SENSORS_ADM1275 + tristate "Analog Devices ADM1275 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1075, ADM1275, and ADM1276 Hot-Swap Controller and Digital + Power Monitors. + + This driver can also be built as a module. If so, the module will + be called adm1275. + +config SENSORS_LM25066 + tristate "National Semiconductor LM25066 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for National + Semiconductor LM25056, LM25066, LM5064, and LM5066. + + This driver can also be built as a module. If so, the module will + be called lm25066. + +config SENSORS_LTC2978 + tristate "Linear Technologies LTC2974, LTC2978, LTC3880, and LTC3883" + default n + help + If you say yes here you get hardware monitoring support for Linear + Technology LTC2974, LTC2978, LTC3880, and LTC3883. + + This driver can also be built as a module. If so, the module will + be called ltc2978. + +config SENSORS_MAX16064 + tristate "Maxim MAX16064" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX16064. + + This driver can also be built as a module. If so, the module will + be called max16064. + +config SENSORS_MAX34440 + tristate "Maxim MAX34440 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX34440, MAX34441, MAX34446, MAX34460, and MAX34461. + + This driver can also be built as a module. If so, the module will + be called max34440. + +config SENSORS_MAX8688 + tristate "Maxim MAX8688" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX8688. + + This driver can also be built as a module. If so, the module will + be called max8688. + +config SENSORS_PFE1100 + tristate "Power One PFE1100" + default n + help + If you say yes here you get hardware monitoring support for + Power One PFE1100 devices. + + This driver can also be built as a module. If so, the module will + be called pfe1100. + +config SENSORS_PFE3000 + tristate "Power One PFE3000" + default n + help + If you say yes here you get hardware monitoring support for + Power One PFE3000 devices. + + This driver can also be built as a module. If so, the module will + be called pfe3000. + +config SENSORS_UCD9000 + tristate "TI UCD90120, UCD90124, UCD9090, UCD90910" + default n + help + If you say yes here you get hardware monitoring support for TI + UCD90120, UCD90124, UCD9090, UCD90910 Sequencer and System Health + Controllers. + + This driver can also be built as a module. If so, the module will + be called ucd9000. + +config SENSORS_UCD9200 + tristate "TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, UCD9248" + default n + help + If you say yes here you get hardware monitoring support for TI + UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248 + Digital PWM System Controllers. + + This driver can also be built as a module. If so, the module will + be called ucd9200. + +config SENSORS_ZL6100 + tristate "Intersil ZL6100 and compatibles" + default n + help + If you say yes here you get hardware monitoring support for Intersil + ZL2004, ZL2005, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, ZL6105, + ZL9101M, and ZL9117M Digital DC/DC Controllers, as well as for + Ericsson BMR450, BMR451, BMR462, BMR463, and BMR464. + + This driver can also be built as a module. If so, the module will + be called zl6100. + +endif # PMBUS diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile new file mode 100644 index 000000000000..6cb3f6d1dd8a --- /dev/null +++ b/drivers/hwmon/pmbus/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for PMBus chip drivers. +# + +obj-$(CONFIG_PMBUS) += pmbus_core.o +obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o +obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o +obj-$(CONFIG_SENSORS_LM25066) += lm25066.o +obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o +obj-$(CONFIG_SENSORS_MAX16064) += max16064.o +obj-$(CONFIG_SENSORS_MAX34440) += max34440.o +obj-$(CONFIG_SENSORS_MAX8688) += max8688.o +obj-$(CONFIG_SENSORS_PFE1100) += pfe1100.o +obj-$(CONFIG_SENSORS_PFE3000) += pfe3000.o +obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o +obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o +obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c new file mode 100644 index 000000000000..d29f60415664 --- /dev/null +++ b/drivers/hwmon/pmbus/adm1275.c @@ -0,0 +1,457 @@ +/* + * Hardware monitoring driver for Analog Devices ADM1275 Hot-Swap Controller + * and Digital Power Monitor + * + * Copyright (c) 2011 Ericsson AB. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { adm1075, adm1275, adm1276, adm1278 }; + +#define ADM1275_PEAK_IOUT 0xd0 +#define ADM1275_PEAK_VIN 0xd1 +#define ADM1275_PEAK_VOUT 0xd2 +#define ADM1278_PMON_CONTROL 0xd3 +#define ADM1275_PMON_CONFIG 0xd4 + +#define ADM1278_CFG_TSFLT (1 << 15) +#define ADM1278_CFG_SIMULTANEOUS (1 << 14) +#define ADM1278_CFG_PMON_MODE (1 << 4) +#define ADM1278_CFG_TEMP1_EN (1 << 3) +#define ADM1278_CFG_VIN_EN (1 << 2) +#define ADM1278_CFG_VOUT_EN (1 << 1) + +#define ADM1275_VIN_VOUT_SELECT (1 << 6) +#define ADM1275_VRANGE (1 << 5) +#define ADM1075_IRANGE_50 (1 << 4) +#define ADM1075_IRANGE_25 (1 << 3) +#define ADM1075_IRANGE_MASK ((1 << 3) | (1 << 4)) + +#define ADM1275_IOUT_WARN2_LIMIT 0xd7 +#define ADM1275_DEVICE_CONFIG 0xd8 + +#define ADM1275_IOUT_WARN2_SELECT (1 << 4) + +#define ADM1276_PEAK_PIN 0xda + +#define ADM1275_MFR_STATUS_IOUT_WARN2 (1 << 0) + +#define ADM1075_READ_VAUX 0xdd +#define ADM1075_VAUX_OV_WARN_LIMIT 0xde +#define ADM1075_VAUX_UV_WARN_LIMIT 0xdf +#define ADM1075_VAUX_STATUS 0xf6 + +#define ADM1075_VAUX_OV_WARN (1<<7) +#define ADM1075_VAUX_UV_WARN (1<<6) + +struct adm1275_data { + int id; + bool have_oc_fault; + struct pmbus_driver_info info; +}; + +#define to_adm1275_data(x) container_of(x, struct adm1275_data, info) + +#define ADM1278_R_SENSE 500 /* R_sense resistor value in microohms */ + +static int r_sense = ADM1278_R_SENSE; +module_param(r_sense, int, 0644); +MODULE_PARM_DESC(r_sense, "Rsense resistor value in microohms"); + + +static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int ret = 0; + + if (page) + return -ENXIO; + + switch (reg) { + case PMBUS_IOUT_UC_FAULT_LIMIT: + if (data->have_oc_fault) { + ret = -ENXIO; + break; + } + ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + if (!data->have_oc_fault) { + ret = -ENXIO; + break; + } + ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); + break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = pmbus_read_word_data(client, 0, + ADM1075_VAUX_OV_WARN_LIMIT); + break; + case PMBUS_VOUT_UV_WARN_LIMIT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = pmbus_read_word_data(client, 0, + ADM1075_VAUX_UV_WARN_LIMIT); + break; + case PMBUS_READ_VOUT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = pmbus_read_word_data(client, 0, ADM1075_READ_VAUX); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VOUT); + break; + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN); + break; + case PMBUS_VIRT_READ_PIN_MAX: + if (data->id == adm1275) { + ret = -ENXIO; + break; + } + ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + if (data->id == adm1275) + ret = -ENXIO; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int adm1275_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + int ret; + + if (page) + return -ENXIO; + + switch (reg) { + case PMBUS_IOUT_UC_FAULT_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + ret = pmbus_write_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT, + word); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VOUT, 0); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = pmbus_write_word_data(client, 0, ADM1276_PEAK_PIN, 0); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct adm1275_data *data = to_adm1275_data(info); + int mfr_status, ret; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_STATUS_IOUT: + ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_IOUT); + if (ret < 0) + break; + mfr_status = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr_status < 0) { + ret = mfr_status; + break; + } + if (mfr_status & ADM1275_MFR_STATUS_IOUT_WARN2) { + ret |= data->have_oc_fault ? + PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT; + } + break; + case PMBUS_STATUS_VOUT: + if (data->id != adm1075) { + ret = -ENODATA; + break; + } + ret = 0; + mfr_status = pmbus_read_byte_data(client, 0, + ADM1075_VAUX_STATUS); + if (mfr_status & ADM1075_VAUX_OV_WARN) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfr_status & ADM1075_VAUX_UV_WARN) + ret |= PB_VOLTAGE_UV_WARNING; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id adm1275_id[] = { + { "adm1075", adm1075 }, + { "adm1275", adm1275 }, + { "adm1276", adm1276 }, + { "adm1278", adm1278 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adm1275_id); + +static int adm1275_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + int config, device_config; + int ret; + struct pmbus_driver_info *info; + struct adm1275_data *data; + const struct i2c_device_id *mid; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA + | I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if (ret != 3 || strncmp(block_buffer, "ADI", 3)) { + dev_err(&client->dev, "Unsupported Manufacturer ID\n"); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; + } + for (mid = adm1275_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG); + if (config < 0) + return config; + + device_config = i2c_smbus_read_byte_data(client, ADM1275_DEVICE_CONFIG); + if (device_config < 0) + return device_config; + + data = devm_kzalloc(&client->dev, sizeof(struct adm1275_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = mid->driver_data; + + info = &data->info; + + info->pages = 1; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_OUT] = direct; + if (data->id == adm1278) + info->m[PSC_CURRENT_OUT] = 807 * r_sense / 1000; + else + info->m[PSC_CURRENT_OUT] = 807; + info->b[PSC_CURRENT_OUT] = 20475; + info->R[PSC_CURRENT_OUT] = -1; + info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + + if (data->id == adm1278) { + /* Configure monitoring */ + ret = i2c_smbus_write_byte_data(client, + ADM1278_PMON_CONTROL, 0); + if (ret < 0) + return ret; + ret = i2c_smbus_read_word_data(client, ADM1275_PMON_CONFIG); + ret = i2c_smbus_write_word_data(client, ADM1275_PMON_CONFIG, + ADM1278_CFG_PMON_MODE | + ADM1278_CFG_TEMP1_EN | + ADM1278_CFG_VIN_EN | + ADM1278_CFG_VOUT_EN); + if (ret < 0) + return ret; + ret = i2c_smbus_read_word_data(client, ADM1275_PMON_CONFIG); + ret = i2c_smbus_write_byte_data(client, ADM1278_PMON_CONTROL, 1); + if (ret < 0) + return ret; + } else { + /* TODO klahey -- there might be adm1278 issues here, too. */ + info->read_word_data = adm1275_read_word_data; + info->read_byte_data = adm1275_read_byte_data; + info->write_word_data = adm1275_write_word_data; + } + + if (data->id == adm1278) { + info->m[PSC_VOLTAGE_IN] = 19599; + info->b[PSC_VOLTAGE_IN] = 0; + info->R[PSC_VOLTAGE_IN] = -2; + info->m[PSC_VOLTAGE_OUT] = 19599; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = -2; + } else if (data->id == adm1075) { + info->m[PSC_VOLTAGE_IN] = 27169; + info->b[PSC_VOLTAGE_IN] = 0; + info->R[PSC_VOLTAGE_IN] = -1; + info->m[PSC_VOLTAGE_OUT] = 27169; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = -1; + } else if (config & ADM1275_VRANGE) { + info->m[PSC_VOLTAGE_IN] = 19199; + info->b[PSC_VOLTAGE_IN] = 0; + info->R[PSC_VOLTAGE_IN] = -2; + info->m[PSC_VOLTAGE_OUT] = 19199; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = -2; + } else { + info->m[PSC_VOLTAGE_IN] = 6720; + info->b[PSC_VOLTAGE_IN] = 0; + info->R[PSC_VOLTAGE_IN] = -1; + info->m[PSC_VOLTAGE_OUT] = 6720; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = -1; + } + + if (device_config & ADM1275_IOUT_WARN2_SELECT) + data->have_oc_fault = true; + + switch (data->id) { + case adm1075: + info->format[PSC_POWER] = direct; + info->b[PSC_POWER] = 0; + info->R[PSC_POWER] = -1; + switch (config & ADM1075_IRANGE_MASK) { + case ADM1075_IRANGE_25: + info->m[PSC_POWER] = 8549; + info->m[PSC_CURRENT_OUT] = 806; + break; + case ADM1075_IRANGE_50: + info->m[PSC_POWER] = 4279; + info->m[PSC_CURRENT_OUT] = 404; + break; + default: + dev_err(&client->dev, "Invalid input current range"); + info->m[PSC_POWER] = 0; + info->m[PSC_CURRENT_OUT] = 0; + break; + } + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + break; + case adm1275: + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + else + info->func[0] |= + PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; + break; + case adm1276: + info->format[PSC_POWER] = direct; + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN + | PMBUS_HAVE_STATUS_INPUT; + if (config & ADM1275_VIN_VOUT_SELECT) + info->func[0] |= + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + if (config & ADM1275_VRANGE) { + info->m[PSC_POWER] = 6043; + info->b[PSC_POWER] = 0; + info->R[PSC_POWER] = -2; + } else { + info->m[PSC_POWER] = 2115; + info->b[PSC_POWER] = 0; + info->R[PSC_POWER] = -1; + } + break; + case adm1278: + info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + info->m[PSC_POWER] = 6123 * r_sense / 1000; + info->b[PSC_POWER] = 0; + info->R[PSC_POWER] = -2; + info->m[PSC_TEMPERATURE] = 42; + info->b[PSC_TEMPERATURE] = 31880; + info->R[PSC_TEMPERATURE] = -1; + break; + } + + return pmbus_do_probe(client, id, info); +} + +static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", + }, + .probe = adm1275_probe, + .remove = pmbus_do_remove, + .id_table = adm1275_id, +}; + +module_i2c_driver(adm1275_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c new file mode 100644 index 000000000000..6a9d6edaacb3 --- /dev/null +++ b/drivers/hwmon/pmbus/lm25066.c @@ -0,0 +1,457 @@ +/* + * Hardware monitoring driver for LM25056 / LM25066 / LM5064 / LM5066 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2013 Guenter Roeck + * + * 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 +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { lm25056, lm25066, lm5064, lm5066 }; + +#define LM25066_READ_VAUX 0xd0 +#define LM25066_MFR_READ_IIN 0xd1 +#define LM25066_MFR_READ_PIN 0xd2 +#define LM25066_MFR_IIN_OC_WARN_LIMIT 0xd3 +#define LM25066_MFR_PIN_OP_WARN_LIMIT 0xd4 +#define LM25066_READ_PIN_PEAK 0xd5 +#define LM25066_CLEAR_PIN_PEAK 0xd6 +#define LM25066_DEVICE_SETUP 0xd9 +#define LM25066_READ_AVG_VIN 0xdc +#define LM25066_READ_AVG_VOUT 0xdd +#define LM25066_READ_AVG_IIN 0xde +#define LM25066_READ_AVG_PIN 0xdf + +#define LM25066_DEV_SETUP_CL (1 << 4) /* Current limit */ + +/* LM25056 only */ + +#define LM25056_VAUX_OV_WARN_LIMIT 0xe3 +#define LM25056_VAUX_UV_WARN_LIMIT 0xe4 + +#define LM25056_MFR_STS_VAUX_OV_WARN (1 << 1) +#define LM25056_MFR_STS_VAUX_UV_WARN (1 << 0) + +struct __coeff { + short m, b, R; +}; + +#define PSC_CURRENT_IN_L (PSC_NUM_CLASSES) +#define PSC_POWER_L (PSC_NUM_CLASSES + 1) + +static struct __coeff lm25066_coeff[4][PSC_NUM_CLASSES + 2] = { + [lm25056] = { + [PSC_VOLTAGE_IN] = { + .m = 16296, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 13797, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 6726, + .R = -2, + }, + [PSC_POWER] = { + .m = 5501, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 26882, + .R = -4, + }, + [PSC_TEMPERATURE] = { + .m = 1580, + .b = -14500, + .R = -2, + }, + }, + [lm25066] = { + [PSC_VOLTAGE_IN] = { + .m = 22070, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 22070, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 13661, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 6852, + .R = -2, + }, + [PSC_POWER] = { + .m = 736, + .R = -2, + }, + [PSC_POWER_L] = { + .m = 369, + .R = -2, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5064] = { + [PSC_VOLTAGE_IN] = { + .m = 4611, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4621, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 10742, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 5456, + .R = -2, + }, + [PSC_POWER] = { + .m = 1204, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 612, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, + [lm5066] = { + [PSC_VOLTAGE_IN] = { + .m = 4587, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4587, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 10753, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 5405, + .R = -2, + }, + [PSC_POWER] = { + .m = 1204, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 605, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, +}; + +struct lm25066_data { + int id; + struct pmbus_driver_info info; +}; + +#define to_lm25066_data(x) container_of(x, struct lm25066_data, info) + +static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct lm25066_data *data = to_lm25066_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, 0, LM25066_READ_VAUX); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + switch (data->id) { + case lm25056: + /* VIN: 6.14 mV VAUX: 293 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + case lm25066: + /* VIN: 4.54 mV VAUX: 283.2 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); + break; + case lm5064: + /* VIN: 4.53 mV VAUX: 700 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 70, 453); + break; + case lm5066: + /* VIN: 2.18 mV VAUX: 725 uV LSB */ + ret = DIV_ROUND_CLOSEST(ret * 725, 2180); + break; + } + break; + case PMBUS_READ_IIN: + ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_IIN); + break; + case PMBUS_READ_PIN: + ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_PIN); + break; + case PMBUS_IIN_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25066_MFR_IIN_OC_WARN_LIMIT); + break; + case PMBUS_PIN_OP_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25066_MFR_PIN_OP_WARN_LIMIT); + break; + case PMBUS_VIRT_READ_VIN_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VIN); + break; + case PMBUS_VIRT_READ_VOUT_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VOUT); + break; + case PMBUS_VIRT_READ_IIN_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_IIN); + break; + case PMBUS_VIRT_READ_PIN_AVG: + ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_PIN); + break; + case PMBUS_VIRT_READ_PIN_MAX: + ret = pmbus_read_word_data(client, 0, LM25066_READ_PIN_PEAK); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25056_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25056_VAUX_UV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = pmbus_read_word_data(client, 0, + LM25056_VAUX_OV_WARN_LIMIT); + if (ret < 0) + break; + /* Adjust returned value to match VIN coefficients */ + ret = DIV_ROUND_CLOSEST(ret * 293, 6140); + break; + default: + ret = lm25066_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int lm25056_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret, s; + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + s = 0; + if (ret & LM25056_MFR_STS_VAUX_UV_WARN) + s |= PB_VOLTAGE_UV_WARNING; + if (ret & LM25056_MFR_STS_VAUX_OV_WARN) + s |= PB_VOLTAGE_OV_WARNING; + ret = s; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, 0x0fff); + ret = pmbus_write_word_data(client, 0, reg, word); + pmbus_clear_cache(client); + break; + case PMBUS_IIN_OC_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, 0x0fff); + ret = pmbus_write_word_data(client, 0, + LM25066_MFR_IIN_OC_WARN_LIMIT, + word); + pmbus_clear_cache(client); + break; + case PMBUS_PIN_OP_WARN_LIMIT: + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, 0x0fff); + ret = pmbus_write_word_data(client, 0, + LM25066_MFR_PIN_OP_WARN_LIMIT, + word); + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, 0x0fff); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_UV_WARN_LIMIT, word); + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + /* Adjust from VIN coefficients (for LM25056) */ + word = DIV_ROUND_CLOSEST((int)word * 6140, 293); + word = ((s16)word < 0) ? 0 : clamp_val(word, 0, 0x0fff); + ret = pmbus_write_word_data(client, 0, + LM25056_VAUX_OV_WARN_LIMIT, word); + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_RESET_PIN_HISTORY: + ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int lm25066_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int config; + struct lm25066_data *data; + struct pmbus_driver_info *info; + struct __coeff *coeff; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct lm25066_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + config = i2c_smbus_read_byte_data(client, LM25066_DEVICE_SETUP); + if (config < 0) + return config; + + data->id = id->driver_data; + info = &data->info; + + info->pages = 1; + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_IN] = direct; + info->format[PSC_TEMPERATURE] = direct; + info->format[PSC_POWER] = direct; + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VMON + | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + if (data->id == lm25056) { + info->func[0] |= PMBUS_HAVE_STATUS_VMON; + info->read_word_data = lm25056_read_word_data; + info->read_byte_data = lm25056_read_byte_data; + } else { + info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + info->read_word_data = lm25066_read_word_data; + } + info->write_word_data = lm25066_write_word_data; + + coeff = &lm25066_coeff[data->id][0]; + info->m[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].m; + info->b[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].b; + info->R[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].R; + info->m[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].m; + info->b[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].b; + info->R[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].R; + info->m[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].m; + info->b[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].b; + info->R[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].R; + info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].b; + info->R[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].R; + info->b[PSC_POWER] = coeff[PSC_POWER].b; + info->R[PSC_POWER] = coeff[PSC_POWER].R; + if (config & LM25066_DEV_SETUP_CL) { + info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].m; + info->m[PSC_POWER] = coeff[PSC_POWER_L].m; + } else { + info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].m; + info->m[PSC_POWER] = coeff[PSC_POWER].m; + } + + return pmbus_do_probe(client, id, info); +} + +static const struct i2c_device_id lm25066_id[] = { + {"lm25056", lm25056}, + {"lm25066", lm25066}, + {"lm5064", lm5064}, + {"lm5066", lm5066}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, lm25066_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver lm25066_driver = { + .driver = { + .name = "lm25066", + }, + .probe = lm25066_probe, + .remove = pmbus_do_remove, + .id_table = lm25066_id, +}; + +module_i2c_driver(lm25066_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for LM25056/LM25066/LM5064/LM5066"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c new file mode 100644 index 000000000000..586a89ef9e0f --- /dev/null +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -0,0 +1,496 @@ +/* + * Hardware monitoring driver for LTC2974, LTC2978, LTC3880, and LTC3883 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2013 Guenter Roeck + * + * 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 +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { ltc2974, ltc2978, ltc3880, ltc3883 }; + +/* Common for all chips */ +#define LTC2978_MFR_VOUT_PEAK 0xdd +#define LTC2978_MFR_VIN_PEAK 0xde +#define LTC2978_MFR_TEMPERATURE_PEAK 0xdf +#define LTC2978_MFR_SPECIAL_ID 0xe7 + +/* LTC2974 and LTC2978 */ +#define LTC2978_MFR_VOUT_MIN 0xfb +#define LTC2978_MFR_VIN_MIN 0xfc +#define LTC2978_MFR_TEMPERATURE_MIN 0xfd + +/* LTC2974 only */ +#define LTC2974_MFR_IOUT_PEAK 0xd7 +#define LTC2974_MFR_IOUT_MIN 0xd8 + +/* LTC3880 and LTC3883 */ +#define LTC3880_MFR_IOUT_PEAK 0xd7 +#define LTC3880_MFR_CLEAR_PEAKS 0xe3 +#define LTC3880_MFR_TEMPERATURE2_PEAK 0xf4 + +/* LTC3883 only */ +#define LTC3883_MFR_IIN_PEAK 0xe1 + +#define LTC2974_ID 0x0212 +#define LTC2978_ID_REV1 0x0121 +#define LTC2978_ID_REV2 0x0122 +#define LTC3880_ID 0x4000 +#define LTC3880_ID_MASK 0xff00 +#define LTC3883_ID 0x4300 +#define LTC3883_ID_MASK 0xff00 + +#define LTC2974_NUM_PAGES 4 +#define LTC2978_NUM_PAGES 8 +#define LTC3880_NUM_PAGES 2 +#define LTC3883_NUM_PAGES 1 + +/* + * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which + * happens pretty much each time chip data is updated. Raw peak data therefore + * does not provide much value. To be able to provide useful peak data, keep an + * internal cache of measured peak data, which is only cleared if an explicit + * "clear peak" command is executed for the sensor in question. + */ + +struct ltc2978_data { + enum chips id; + u16 vin_min, vin_max; + u16 temp_min[LTC2974_NUM_PAGES], temp_max[LTC2974_NUM_PAGES]; + u16 vout_min[LTC2978_NUM_PAGES], vout_max[LTC2978_NUM_PAGES]; + u16 iout_min[LTC2974_NUM_PAGES], iout_max[LTC2974_NUM_PAGES]; + u16 iin_max; + u16 temp2_max; + struct pmbus_driver_info info; +}; + +#define to_ltc2978_data(x) container_of(x, struct ltc2978_data, info) + +static inline int lin11_to_val(int data) +{ + s16 e = ((s16)data) >> 11; + s32 m = (((s16)(data << 5)) >> 5); + + /* + * mantissa is 10 bit + sign, exponent adds up to 15 bit. + * Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31). + */ + e += 6; + return (e < 0 ? m >> -e : m << e); +} + +static int ltc2978_read_word_data_common(struct i2c_client *client, int page, + int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) > lin11_to_val(data->vin_max)) + data->vin_max = ret; + ret = data->vin_max; + } + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_PEAK); + if (ret >= 0) { + /* + * VOUT is 16 bit unsigned with fixed exponent, + * so we can compare it directly + */ + if (ret > data->vout_max[page]) + data->vout_max[page] = ret; + ret = data->vout_max[page]; + } + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, + LTC2978_MFR_TEMPERATURE_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) + > lin11_to_val(data->temp_max[page])) + data->temp_max[page] = ret; + ret = data->temp_max[page]; + } + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MIN: + ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_MIN); + if (ret >= 0) { + if (lin11_to_val(ret) < lin11_to_val(data->vin_min)) + data->vin_min = ret; + ret = data->vin_min; + } + break; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_MIN); + if (ret >= 0) { + /* + * VOUT_MIN is known to not be supported on some lots + * of LTC2978 revision 1, and will return the maximum + * possible voltage if read. If VOUT_MAX is valid and + * lower than the reading of VOUT_MIN, use it instead. + */ + if (data->vout_max[page] && ret > data->vout_max[page]) + ret = data->vout_max[page]; + if (ret < data->vout_min[page]) + data->vout_min[page] = ret; + ret = data->vout_min[page]; + } + break; + case PMBUS_VIRT_READ_TEMP_MIN: + ret = pmbus_read_word_data(client, page, + LTC2978_MFR_TEMPERATURE_MIN); + if (ret >= 0) { + if (lin11_to_val(ret) + < lin11_to_val(data->temp_min[page])) + data->temp_min[page] = ret; + ret = data->temp_min[page]; + } + break; + case PMBUS_VIRT_READ_IOUT_MAX: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_READ_TEMP2_MAX: + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + ret = -ENXIO; + break; + default: + ret = ltc2978_read_word_data_common(client, page, reg); + break; + } + return ret; +} + +static int ltc2974_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, LTC2974_MFR_IOUT_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) + > lin11_to_val(data->iout_max[page])) + data->iout_max[page] = ret; + ret = data->iout_max[page]; + } + break; + case PMBUS_VIRT_READ_IOUT_MIN: + ret = pmbus_read_word_data(client, page, LTC2974_MFR_IOUT_MIN); + if (ret >= 0) { + if (lin11_to_val(ret) + < lin11_to_val(data->iout_min[page])) + data->iout_min[page] = ret; + ret = data->iout_min[page]; + } + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, LTC3880_MFR_IOUT_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) + > lin11_to_val(data->iout_max[page])) + data->iout_max[page] = ret; + ret = data->iout_max[page]; + } + break; + case PMBUS_VIRT_READ_TEMP2_MAX: + ret = pmbus_read_word_data(client, page, + LTC3880_MFR_TEMPERATURE2_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) > lin11_to_val(data->temp2_max)) + data->temp2_max = ret; + ret = data->temp2_max; + } + break; + case PMBUS_VIRT_READ_VIN_MIN: + case PMBUS_VIRT_READ_VOUT_MIN: + case PMBUS_VIRT_READ_TEMP_MIN: + ret = -ENXIO; + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + ret = 0; + break; + default: + ret = ltc2978_read_word_data_common(client, page, reg); + break; + } + return ret; +} + +static int ltc3883_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_IIN_MAX: + ret = pmbus_read_word_data(client, page, LTC3883_MFR_IIN_PEAK); + if (ret >= 0) { + if (lin11_to_val(ret) + > lin11_to_val(data->iin_max)) + data->iin_max = ret; + ret = data->iin_max; + } + break; + case PMBUS_VIRT_RESET_IIN_HISTORY: + ret = 0; + break; + default: + ret = ltc3880_read_word_data(client, page, reg); + break; + } + return ret; +} + +static int ltc2978_clear_peaks(struct i2c_client *client, int page, + enum chips id) +{ + int ret; + + if (id == ltc3880 || id == ltc3883) + ret = pmbus_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS); + else + ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); + + return ret; +} + +static int ltc2978_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc2978_data *data = to_ltc2978_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_IIN_HISTORY: + data->iin_max = 0x7c00; + ret = ltc2978_clear_peaks(client, page, data->id); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + data->iout_max[page] = 0x7c00; + data->iout_min[page] = 0xfbff; + ret = ltc2978_clear_peaks(client, page, data->id); + break; + case PMBUS_VIRT_RESET_TEMP2_HISTORY: + data->temp2_max = 0x7c00; + ret = ltc2978_clear_peaks(client, page, data->id); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + data->vout_min[page] = 0xffff; + data->vout_max[page] = 0; + ret = ltc2978_clear_peaks(client, page, data->id); + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + data->vin_min = 0x7bff; + data->vin_max = 0x7c00; + ret = ltc2978_clear_peaks(client, page, data->id); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + data->temp_min[page] = 0x7bff; + data->temp_max[page] = 0x7c00; + ret = ltc2978_clear_peaks(client, page, data->id); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ltc2978_id[] = { + {"ltc2974", ltc2974}, + {"ltc2978", ltc2978}, + {"ltc3880", ltc3880}, + {"ltc3883", ltc3883}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc2978_id); + +static int ltc2978_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int chip_id, i; + struct ltc2978_data *data; + struct pmbus_driver_info *info; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(struct ltc2978_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID); + if (chip_id < 0) + return chip_id; + + if (chip_id == LTC2974_ID) { + data->id = ltc2974; + } else if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) { + data->id = ltc2978; + } else if ((chip_id & LTC3880_ID_MASK) == LTC3880_ID) { + data->id = ltc3880; + } else if ((chip_id & LTC3883_ID_MASK) == LTC3883_ID) { + data->id = ltc3883; + } else { + dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id); + return -ENODEV; + } + if (data->id != id->driver_data) + dev_warn(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, + ltc2978_id[data->id].name); + + info = &data->info; + info->write_word_data = ltc2978_write_word_data; + + data->vin_min = 0x7bff; + data->vin_max = 0x7c00; + for (i = 0; i < ARRAY_SIZE(data->vout_min); i++) + data->vout_min[i] = 0xffff; + for (i = 0; i < ARRAY_SIZE(data->iout_min); i++) + data->iout_min[i] = 0xfbff; + for (i = 0; i < ARRAY_SIZE(data->iout_max); i++) + data->iout_max[i] = 0x7c00; + for (i = 0; i < ARRAY_SIZE(data->temp_min); i++) + data->temp_min[i] = 0x7bff; + for (i = 0; i < ARRAY_SIZE(data->temp_max); i++) + data->temp_max[i] = 0x7c00; + data->temp2_max = 0x7c00; + + switch (data->id) { + case ltc2974: + info->read_word_data = ltc2974_read_word_data; + info->pages = LTC2974_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_TEMP2; + for (i = 0; i < info->pages; i++) { + info->func[i] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + } + break; + case ltc2978: + info->read_word_data = ltc2978_read_word_data; + info->pages = LTC2978_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + for (i = 1; i < LTC2978_NUM_PAGES; i++) { + info->func[i] = PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT; + } + break; + case ltc3880: + info->read_word_data = ltc3880_read_word_data; + info->pages = LTC3880_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + break; + case ltc3883: + info->read_word_data = ltc3883_read_word_data; + info->pages = LTC3883_NUM_PAGES; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN + | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + break; + default: + return -ENODEV; + } + return pmbus_do_probe(client, id, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc2978_driver = { + .driver = { + .name = "ltc2978", + }, + .probe = ltc2978_probe, + .remove = pmbus_do_remove, + .id_table = ltc2978_id, +}; + +module_i2c_driver(ltc2978_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for LTC2974, LTC2978, LTC3880, and LTC3883"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max16064.c b/drivers/hwmon/pmbus/max16064.c new file mode 100644 index 000000000000..fa237a3c3291 --- /dev/null +++ b/drivers/hwmon/pmbus/max16064.c @@ -0,0 +1,127 @@ +/* + * Hardware monitoring driver for Maxim MAX16064 + * + * Copyright (c) 2011 Ericsson AB. + * + * 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 +#include +#include +#include +#include +#include "pmbus.h" + +#define MAX16064_MFR_VOUT_PEAK 0xd4 +#define MAX16064_MFR_TEMPERATURE_PEAK 0xd6 + +static int max16064_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, + MAX16064_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, + MAX16064_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max16064_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX16064_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX16064_MFR_TEMPERATURE_PEAK, + 0xffff); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max16064_info = { + .pages = 4, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .read_word_data = max16064_read_word_data, + .write_word_data = max16064_write_word_data, +}; + +static int max16064_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max16064_info); +} + +static const struct i2c_device_id max16064_id[] = { + {"max16064", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max16064_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16064_driver = { + .driver = { + .name = "max16064", + }, + .probe = max16064_probe, + .remove = pmbus_do_remove, + .id_table = max16064_id, +}; + +module_i2c_driver(max16064_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX16064"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c new file mode 100644 index 000000000000..7e930c3ce1ab --- /dev/null +++ b/drivers/hwmon/pmbus/max34440.c @@ -0,0 +1,435 @@ +/* + * Hardware monitoring driver for Maxim MAX34440/MAX34441 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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 +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { max34440, max34441, max34446, max34460, max34461 }; + +#define MAX34440_MFR_VOUT_PEAK 0xd4 +#define MAX34440_MFR_IOUT_PEAK 0xd5 +#define MAX34440_MFR_TEMPERATURE_PEAK 0xd6 +#define MAX34440_MFR_VOUT_MIN 0xd7 + +#define MAX34446_MFR_POUT_PEAK 0xe0 +#define MAX34446_MFR_POUT_AVG 0xe1 +#define MAX34446_MFR_IOUT_AVG 0xe2 +#define MAX34446_MFR_TEMPERATURE_AVG 0xe3 + +#define MAX34440_STATUS_OC_WARN (1 << 0) +#define MAX34440_STATUS_OC_FAULT (1 << 1) +#define MAX34440_STATUS_OT_FAULT (1 << 5) +#define MAX34440_STATUS_OT_WARN (1 << 6) + +struct max34440_data { + int id; + struct pmbus_driver_info info; +}; + +#define to_max34440_data(x) container_of(x, struct max34440_data, info) + +static int max34440_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_VOUT_MIN); + break; + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_IOUT_AVG); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_POUT_AVG: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_POUT_AVG); + break; + case PMBUS_VIRT_READ_POUT_MAX: + if (data->id != max34446) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_POUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_AVG: + if (data->id != max34446 && data->id != max34460 && + data->id != max34461) + return -ENXIO; + ret = pmbus_read_word_data(client, page, + MAX34446_MFR_TEMPERATURE_AVG); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, + MAX34440_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_POUT_HISTORY: + if (data->id != max34446) + return -ENXIO; + ret = 0; + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34440_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max34440_data *data = to_max34440_data(info); + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); + if (ret) + break; + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_AVG, 0); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_VOUT_MIN, 0x7fff); + if (ret) + break; + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_VOUT_PEAK, 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && data->id == max34446) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_TEMPERATURE_PEAK, + 0x8000); + if (!ret && data->id == max34446) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_TEMPERATURE_AVG, 0); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max34440_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page >= 0) { + ret = pmbus_set_page(client, page); + if (ret < 0) + return ret; + } + + switch (reg) { + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OC_WARN) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX34440_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OT_WARN) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX34440_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max34440_info[] = { + [max34440] = { + .pages = 14, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, /* R = 0 in datasheet reflects mA */ + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[12] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34441] = { + .pages = 12, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_FAN] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34446] = { + .pages = 7, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[5] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[14] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[15] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, + [max34461] = { + .pages = 23, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[12] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[13] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[14] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[15] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + /* page 16 is reserved */ + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34440_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_word_data = max34440_write_word_data, + }, +}; + +static int max34440_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max34440_data *data; + + data = devm_kzalloc(&client->dev, sizeof(struct max34440_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + data->id = id->driver_data; + data->info = max34440_info[id->driver_data]; + + return pmbus_do_probe(client, id, &data->info); +} + +static const struct i2c_device_id max34440_id[] = { + {"max34440", max34440}, + {"max34441", max34441}, + {"max34446", max34446}, + {"max34460", max34460}, + {"max34461", max34461}, + {} +}; +MODULE_DEVICE_TABLE(i2c, max34440_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max34440_driver = { + .driver = { + .name = "max34440", + }, + .probe = max34440_probe, + .remove = pmbus_do_remove, + .id_table = max34440_id, +}; + +module_i2c_driver(max34440_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX34440/MAX34441"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max8688.c b/drivers/hwmon/pmbus/max8688.c new file mode 100644 index 000000000000..f04454a42fdd --- /dev/null +++ b/drivers/hwmon/pmbus/max8688.c @@ -0,0 +1,204 @@ +/* + * Hardware monitoring driver for Maxim MAX8688 + * + * Copyright (c) 2011 Ericsson AB. + * + * 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 +#include +#include +#include +#include +#include "pmbus.h" + +#define MAX8688_MFR_VOUT_PEAK 0xd4 +#define MAX8688_MFR_IOUT_PEAK 0xd5 +#define MAX8688_MFR_TEMPERATURE_PEAK 0xd6 +#define MAX8688_MFG_STATUS 0xd8 + +#define MAX8688_STATUS_OC_FAULT (1 << 4) +#define MAX8688_STATUS_OV_FAULT (1 << 5) +#define MAX8688_STATUS_OV_WARNING (1 << 8) +#define MAX8688_STATUS_UV_FAULT (1 << 9) +#define MAX8688_STATUS_UV_WARNING (1 << 10) +#define MAX8688_STATUS_UC_FAULT (1 << 11) +#define MAX8688_STATUS_OC_WARNING (1 << 12) +#define MAX8688_STATUS_OT_FAULT (1 << 13) +#define MAX8688_STATUS_OT_WARNING (1 << 14) + +static int max8688_read_word_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + if (page) + return -ENXIO; + + switch (reg) { + case PMBUS_VIRT_READ_VOUT_MAX: + ret = pmbus_read_word_data(client, 0, MAX8688_MFR_VOUT_PEAK); + break; + case PMBUS_VIRT_READ_IOUT_MAX: + ret = pmbus_read_word_data(client, 0, MAX8688_MFR_IOUT_PEAK); + break; + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, 0, + MAX8688_MFR_TEMPERATURE_PEAK); + break; + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = 0; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max8688_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, MAX8688_MFR_VOUT_PEAK, + 0); + break; + case PMBUS_VIRT_RESET_IOUT_HISTORY: + ret = pmbus_write_word_data(client, 0, MAX8688_MFR_IOUT_PEAK, + 0); + break; + case PMBUS_VIRT_RESET_TEMP_HISTORY: + ret = pmbus_write_word_data(client, 0, + MAX8688_MFR_TEMPERATURE_PEAK, + 0xffff); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static int max8688_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_STATUS_VOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UV_WARNING) + ret |= PB_VOLTAGE_UV_WARNING; + if (mfg_status & MAX8688_STATUS_UV_FAULT) + ret |= PB_VOLTAGE_UV_FAULT; + if (mfg_status & MAX8688_STATUS_OV_WARNING) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfg_status & MAX8688_STATUS_OV_FAULT) + ret |= PB_VOLTAGE_OV_FAULT; + break; + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UC_FAULT) + ret |= PB_IOUT_UC_FAULT; + if (mfg_status & MAX8688_STATUS_OC_WARNING) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX8688_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_OT_WARNING) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX8688_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max8688_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_CURRENT_OUT] = 23109, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -2, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max8688_read_byte_data, + .read_word_data = max8688_read_word_data, + .write_word_data = max8688_write_word_data, +}; + +static int max8688_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max8688_info); +} + +static const struct i2c_device_id max8688_id[] = { + {"max8688", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max8688_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max8688_driver = { + .driver = { + .name = "max8688", + }, + .probe = max8688_probe, + .remove = pmbus_do_remove, + .id_table = max8688_id, +}; + +module_i2c_driver(max8688_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX8688"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pfe1100.c b/drivers/hwmon/pmbus/pfe1100.c new file mode 100644 index 000000000000..3731fa06c593 --- /dev/null +++ b/drivers/hwmon/pmbus/pfe1100.c @@ -0,0 +1,249 @@ +/* + * Hardware monitoring driver for PFE1100 and compatibles + * Based on the zl6100 driver with the following copyright: + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * Copyright 2004-present Facebook. All Rights Reserved. + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { SPDFCBK_15G, SPAFCBK_14G }; + +struct pfe1100_data { + int id; + struct pmbus_driver_info info; +}; + +#define to_pfe1100_data(x) container_of(x, struct pfe1100_data, info) + + +#define PFE1100_WAIT_TIME 5000 /* uS */ + +static ushort delay = PFE1100_WAIT_TIME; +module_param(delay, ushort, 0644); +MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); + +static const struct i2c_device_id pfe1100_id[] = { + {"pfe1100dc", SPDFCBK_15G}, + {"pfe1100ac", SPAFCBK_14G}, + {"pfe1100", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, pfe1100_id); + +static int pfe1100_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct pfe1100_data *data = to_pfe1100_data(info); + int ret; + + if (data->id != SPAFCBK_14G && page > 0) + return -ENXIO; + + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + + switch (reg) { + case PMBUS_FAN_COMMAND_1: + case PMBUS_STATUS_WORD: + case PMBUS_READ_VIN: + case PMBUS_READ_IIN: + case PMBUS_READ_VOUT: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_READ_TEMPERATURE_2: + case PMBUS_READ_TEMPERATURE_3: + case PMBUS_READ_FAN_SPEED_1: + case PMBUS_READ_POUT: + case PMBUS_READ_PIN: + case PMBUS_MFR_LOCATION: + ret = pmbus_read_word_data(client, page, reg); + return ret; + default: + return -ENXIO; + } +} + +static int pfe1100_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct pfe1100_data *data = to_pfe1100_data(info); + int ret; + + if (data->id != SPAFCBK_14G && page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_PAGE: + case PMBUS_OPERATION: + case PMBUS_CLEAR_FAULTS: + case PMBUS_CAPABILITY: + case PMBUS_VOUT_MODE: + case PMBUS_FAN_CONFIG_12: + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_VOUT: + case PMBUS_STATUS_IOUT: + case PMBUS_STATUS_INPUT: + case PMBUS_STATUS_TEMPERATURE: + case PMBUS_STATUS_CML: + case PMBUS_STATUS_OTHER: + case PMBUS_STATUS_MFR_SPECIFIC: + case PMBUS_STATUS_FAN_12: + ret = pmbus_read_byte_data(client, page, reg); + return ret; + default: + return -ENXIO; + } +} + +static int pfe1100_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct pfe1100_data *data = to_pfe1100_data(info); + int ret; + + if (data->id != SPAFCBK_14G && page > 0) + return -ENXIO; + + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + + if (reg == PMBUS_FAN_COMMAND_1) + ret = pmbus_write_word_data(client, page, reg, word); + else + ret = -ENXIO; + + return ret; +} + +static int pfe1100_write_byte(struct i2c_client *client, int page, u8 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct pfe1100_data *data = to_pfe1100_data(info); + int ret; + + if (data->id != SPAFCBK_14G && page > 0) + return -ENXIO; + + switch (value) { + case PMBUS_PAGE: + case PMBUS_OPERATION: + case PMBUS_CLEAR_FAULTS: + ret = pmbus_write_byte(client, page, value); + return ret; + default: + return -ENXIO; + } +} + +static int pfe1100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + int kind; + struct pfe1100_data *data; + struct pmbus_driver_info *info; + u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + kind = SPDFCBK_15G; + } else { + device_id[ret] = 0; + if (strncmp(device_id, "SPAFCBK-14G", ret)) + kind = SPDFCBK_15G; + else + kind = SPAFCBK_14G; + dev_notice(&client->dev, "MFR_ID is [%s]\n", device_id); + } + + data = devm_kzalloc(&client->dev, sizeof(struct pfe1100_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = kind; + + info = &data->info; + info->delay = delay; + if (kind == SPAFCBK_14G) + info->pages = 2; + else + info->pages = 1; + + /* + * It seems reasonable to just scan the device for supported + * values, but most drivers just seem to jam these values in + * there, so that's what we'll do. + */ + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN | + PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_MFRDATA; + + /* AC units have a third temperature sensor, and data from 3V input: */ + + if (kind == SPAFCBK_14G) { + info->func[0] |= PMBUS_HAVE_TEMP3; + info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT; + } + + info->read_word_data = pfe1100_read_word_data; + info->read_byte_data = pfe1100_read_byte_data; + info->write_word_data = pfe1100_write_word_data; + info->write_byte = pfe1100_write_byte; + + return pmbus_do_probe(client, id, info); +} + +static struct i2c_driver pfe1100_driver = { + .driver = { + .name = "pfe1100", + }, + .probe = pfe1100_probe, + .remove = pmbus_do_remove, + .id_table = pfe1100_id, +}; + +module_i2c_driver(pfe1100_driver); + +MODULE_AUTHOR("Kevin Lahey, based on work by Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for PFE1100"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pfe3000.c b/drivers/hwmon/pmbus/pfe3000.c new file mode 100644 index 000000000000..0ee2453284ef --- /dev/null +++ b/drivers/hwmon/pmbus/pfe3000.c @@ -0,0 +1,133 @@ +/* + * Hardware monitoring driver for PFE3000 and compatibles + * Based on the zl6100 driver with the following copyright: + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * Copyright 2004-present Facebook. All Rights Reserved. + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { PFE3000 }; + +struct pfe3000_data { + struct pmbus_driver_info info; +}; + +#define to_pfe3000_data(x) container_of(x, struct pfe3000_data, info) + +/* + * Other PowerOne device require a wait time; this is included in case + * it is necessary for future debugging. + */ + +#define PFE3000_WAIT_TIME 0 /* uS */ + +static ushort delay = PFE3000_WAIT_TIME; +module_param(delay, ushort, 0644); +MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); + + +static const struct i2c_device_id pfe3000_id[] = { + {"pfe3000", PFE3000 }, + {"pfe3000", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, pfe3000_id); + +static struct pmbus_platform_data platform_data = { + .flags = PMBUS_SKIP_STATUS_CHECK +}; + +static int pfe3000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct pfe3000_data *data; + struct pmbus_driver_info *info; + + data = devm_kzalloc(&client->dev, sizeof(struct pfe3000_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + info = &data->info; + info->pages = 7; + info->delay = delay; + + /* + * Skip extra status checks; this is required to read the + * VOUT_MODE register to determine the exponent to apply + * to the VOUT values. + */ + dev->platform_data = &platform_data; + + /* Make sure that the page isn't pointing elsewhere! */ + + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + /* + * It seems reasonable to just scan the device for supported + * values, but most drivers seem to jam these values in + * there, so that's what we'll do. + */ + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN | + PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | + PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_FAN12 | + PMBUS_HAVE_MFRDATA; + info->func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IIN | + PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_FAN12 | + PMBUS_HAVE_STATUS_INPUT; + info->func[2] = PMBUS_HAVE_VOUT; + info->func[4] = PMBUS_HAVE_VOUT; + info->func[5] = PMBUS_HAVE_VOUT; + info->func[6] = PMBUS_HAVE_VOUT; + + return pmbus_do_probe(client, id, info); +} + +static struct i2c_driver pfe3000_driver = { + .driver = { + .name = "pfe3000", + }, + .probe = pfe3000_probe, + .remove = pmbus_do_remove, + .id_table = pfe3000_id, +}; + +module_i2c_driver(pfe3000_driver); + +MODULE_AUTHOR("Kevin Lahey, based on work by Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for PFE1100"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c new file mode 100644 index 000000000000..7e91700131a7 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.c @@ -0,0 +1,217 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * 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 +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * Find sensor groups and status registers on each page. + */ +static void pmbus_find_sensor_groups(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int page; + + /* Sensors detected on page 0 only */ + if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) + info->func[0] |= PMBUS_HAVE_VIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) + info->func[0] |= PMBUS_HAVE_VCAP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) + info->func[0] |= PMBUS_HAVE_IIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) + info->func[0] |= PMBUS_HAVE_PIN; + if (info->func[0] + && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT)) + info->func[0] |= PMBUS_HAVE_STATUS_INPUT; + if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) && + pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) { + info->func[0] |= PMBUS_HAVE_FAN12; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN12; + } + if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) && + pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) { + info->func[0] |= PMBUS_HAVE_FAN34; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN34; + } + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1)) + info->func[0] |= PMBUS_HAVE_TEMP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2)) + info->func[0] |= PMBUS_HAVE_TEMP2; + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3)) + info->func[0] |= PMBUS_HAVE_TEMP3; + if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_TEMP3) + && pmbus_check_byte_register(client, 0, + PMBUS_STATUS_TEMPERATURE)) + info->func[0] |= PMBUS_HAVE_STATUS_TEMP; + + /* Sensors detected on all pages */ + for (page = 0; page < info->pages; page++) { + if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) { + info->func[page] |= PMBUS_HAVE_VOUT; + if (pmbus_check_byte_register(client, page, + PMBUS_STATUS_VOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_VOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) { + info->func[page] |= PMBUS_HAVE_IOUT; + if (pmbus_check_byte_register(client, 0, + PMBUS_STATUS_IOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_IOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_POUT)) + info->func[page] |= PMBUS_HAVE_POUT; + } +} + +/* + * Identify chip parameters. + */ +static int pmbus_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int ret = 0; + + if (!info->pages) { + /* + * Check if the PAGE command is supported. If it is, + * keep setting the page number until it fails or until the + * maximum number of pages has been reached. Assume that + * this is the number of pages supported by the chip. + */ + if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) { + int page; + + for (page = 1; page < PMBUS_PAGES; page++) { + if (pmbus_set_page(client, page) < 0) + break; + } + pmbus_set_page(client, 0); + info->pages = page; + } else { + info->pages = 1; + } + } + + if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) { + int vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + switch (vout_mode >> 5) { + case 0: + break; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + break; + case 2: + info->format[PSC_VOLTAGE_OUT] = direct; + break; + default: + ret = -ENODEV; + goto abort; + } + } + } + + /* + * We should check if the COEFFICIENTS register is supported. + * If it is, and the chip is configured for direct mode, we can read + * the coefficients from the chip, one set per group of sensor + * registers. + * + * To do this, we will need access to a chip which actually supports the + * COEFFICIENTS command, since the command is too complex to implement + * without testing it. Until then, abort if a chip configured for direct + * mode was detected. + */ + if (info->format[PSC_VOLTAGE_OUT] == direct) { + ret = -ENODEV; + goto abort; + } + + /* Try to find sensor groups */ + pmbus_find_sensor_groups(client, info); +abort: + return ret; +} + +static int pmbus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pmbus_driver_info *info; + + info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pages = id->driver_data; + info->identify = pmbus_identify; + + return pmbus_do_probe(client, id, info); +} + +/* + * Use driver_data to set the number of pages supported by the chip. + */ +static const struct i2c_device_id pmbus_id[] = { + {"adp4000", 1}, + {"bmr453", 1}, + {"bmr454", 1}, + {"mdt040", 1}, + {"ncp4200", 1}, + {"ncp4208", 1}, + {"pdt003", 1}, + {"pdt006", 1}, + {"pdt012", 1}, + {"pmbus", 0}, + {"tps40400", 1}, + {"tps40422", 2}, + {"udt020", 1}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pmbus_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver pmbus_driver = { + .driver = { + .name = "pmbus", + }, + .probe = pmbus_probe, + .remove = pmbus_do_remove, + .id_table = pmbus_id, +}; + +module_i2c_driver(pmbus_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("Generic PMBus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h new file mode 100644 index 000000000000..a6b5fba4dcc5 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.h @@ -0,0 +1,413 @@ +/* + * pmbus.h - Common defines and structures for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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. + */ + +#ifndef PMBUS_H +#define PMBUS_H + +/* KML: Hacks to make this work on 2.6.28. Ugh. */ + +#define DIV_ROUND_CLOSEST(x, divisor)( \ +{ \ + typeof(x) __x = x; \ + typeof(divisor) __d = divisor; \ + (((typeof(x))-1) > 0 || \ + ((typeof(divisor))-1) > 0 || (__x) > 0) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +} \ +) + + +#define module_i2c_driver(__i2c_driver) \ + static int __init __i2c_driver##_init(void) {return i2c_add_driver(&__i2c_driver);} \ + static void __exit __i2c_driver##_exit(void) {i2c_del_driver(&__i2c_driver);} \ +module_init(__i2c_driver##_init); \ +module_exit(__i2c_driver##_exit); + +/* KML: Those are all the hacks needed, surprisingly. */ + +/* + * Registers + */ +#define PMBUS_PAGE 0x00 +#define PMBUS_OPERATION 0x01 +#define PMBUS_ON_OFF_CONFIG 0x02 +#define PMBUS_CLEAR_FAULTS 0x03 +#define PMBUS_PHASE 0x04 + +#define PMBUS_CAPABILITY 0x19 +#define PMBUS_QUERY 0x1A + +#define PMBUS_VOUT_MODE 0x20 +#define PMBUS_VOUT_COMMAND 0x21 +#define PMBUS_VOUT_TRIM 0x22 +#define PMBUS_VOUT_CAL_OFFSET 0x23 +#define PMBUS_VOUT_MAX 0x24 +#define PMBUS_VOUT_MARGIN_HIGH 0x25 +#define PMBUS_VOUT_MARGIN_LOW 0x26 +#define PMBUS_VOUT_TRANSITION_RATE 0x27 +#define PMBUS_VOUT_DROOP 0x28 +#define PMBUS_VOUT_SCALE_LOOP 0x29 +#define PMBUS_VOUT_SCALE_MONITOR 0x2A + +#define PMBUS_COEFFICIENTS 0x30 +#define PMBUS_POUT_MAX 0x31 + +#define PMBUS_FAN_CONFIG_12 0x3A +#define PMBUS_FAN_COMMAND_1 0x3B +#define PMBUS_FAN_COMMAND_2 0x3C +#define PMBUS_FAN_CONFIG_34 0x3D +#define PMBUS_FAN_COMMAND_3 0x3E +#define PMBUS_FAN_COMMAND_4 0x3F + +#define PMBUS_VOUT_OV_FAULT_LIMIT 0x40 +#define PMBUS_VOUT_OV_FAULT_RESPONSE 0x41 +#define PMBUS_VOUT_OV_WARN_LIMIT 0x42 +#define PMBUS_VOUT_UV_WARN_LIMIT 0x43 +#define PMBUS_VOUT_UV_FAULT_LIMIT 0x44 +#define PMBUS_VOUT_UV_FAULT_RESPONSE 0x45 +#define PMBUS_IOUT_OC_FAULT_LIMIT 0x46 +#define PMBUS_IOUT_OC_FAULT_RESPONSE 0x47 +#define PMBUS_IOUT_OC_LV_FAULT_LIMIT 0x48 +#define PMBUS_IOUT_OC_LV_FAULT_RESPONSE 0x49 +#define PMBUS_IOUT_OC_WARN_LIMIT 0x4A +#define PMBUS_IOUT_UC_FAULT_LIMIT 0x4B +#define PMBUS_IOUT_UC_FAULT_RESPONSE 0x4C + +#define PMBUS_OT_FAULT_LIMIT 0x4F +#define PMBUS_OT_FAULT_RESPONSE 0x50 +#define PMBUS_OT_WARN_LIMIT 0x51 +#define PMBUS_UT_WARN_LIMIT 0x52 +#define PMBUS_UT_FAULT_LIMIT 0x53 +#define PMBUS_UT_FAULT_RESPONSE 0x54 +#define PMBUS_VIN_OV_FAULT_LIMIT 0x55 +#define PMBUS_VIN_OV_FAULT_RESPONSE 0x56 +#define PMBUS_VIN_OV_WARN_LIMIT 0x57 +#define PMBUS_VIN_UV_WARN_LIMIT 0x58 +#define PMBUS_VIN_UV_FAULT_LIMIT 0x59 + +#define PMBUS_IIN_OC_FAULT_LIMIT 0x5B +#define PMBUS_IIN_OC_WARN_LIMIT 0x5D + +#define PMBUS_POUT_OP_FAULT_LIMIT 0x68 +#define PMBUS_POUT_OP_WARN_LIMIT 0x6A +#define PMBUS_PIN_OP_WARN_LIMIT 0x6B + +#define PMBUS_STATUS_BYTE 0x78 +#define PMBUS_STATUS_WORD 0x79 +#define PMBUS_STATUS_VOUT 0x7A +#define PMBUS_STATUS_IOUT 0x7B +#define PMBUS_STATUS_INPUT 0x7C +#define PMBUS_STATUS_TEMPERATURE 0x7D +#define PMBUS_STATUS_CML 0x7E +#define PMBUS_STATUS_OTHER 0x7F +#define PMBUS_STATUS_MFR_SPECIFIC 0x80 +#define PMBUS_STATUS_FAN_12 0x81 +#define PMBUS_STATUS_FAN_34 0x82 + +#define PMBUS_READ_VIN 0x88 +#define PMBUS_READ_IIN 0x89 +#define PMBUS_READ_VCAP 0x8A +#define PMBUS_READ_VOUT 0x8B +#define PMBUS_READ_IOUT 0x8C +#define PMBUS_READ_TEMPERATURE_1 0x8D +#define PMBUS_READ_TEMPERATURE_2 0x8E +#define PMBUS_READ_TEMPERATURE_3 0x8F +#define PMBUS_READ_FAN_SPEED_1 0x90 +#define PMBUS_READ_FAN_SPEED_2 0x91 +#define PMBUS_READ_FAN_SPEED_3 0x92 +#define PMBUS_READ_FAN_SPEED_4 0x93 +#define PMBUS_READ_DUTY_CYCLE 0x94 +#define PMBUS_READ_FREQUENCY 0x95 +#define PMBUS_READ_POUT 0x96 +#define PMBUS_READ_PIN 0x97 + +#define PMBUS_REVISION 0x98 +#define PMBUS_MFR_ID 0x99 +#define PMBUS_MFR_MODEL 0x9A +#define PMBUS_MFR_REVISION 0x9B +#define PMBUS_MFR_LOCATION 0x9C +#define PMBUS_MFR_DATE 0x9D +#define PMBUS_MFR_SERIAL 0x9E + +/* + * Virtual registers. + * Useful to support attributes which are not supported by standard PMBus + * registers but exist as manufacturer specific registers on individual chips. + * Must be mapped to real registers in device specific code. + * + * Semantics: + * Virtual registers are all word size. + * READ registers are read-only; writes are either ignored or return an error. + * RESET registers are read/write. Reading reset registers returns zero + * (used for detection), writing any value causes the associated history to be + * reset. + * Virtual registers have to be handled in device specific driver code. Chip + * driver code returns non-negative register values if a virtual register is + * supported, or a negative error code if not. The chip driver may return + * -ENODATA or any other error code in this case, though an error code other + * than -ENODATA is handled more efficiently and thus preferred. Either case, + * the calling PMBus core code will abort if the chip driver returns an error + * code when reading or writing virtual registers. + */ +#define PMBUS_VIRT_BASE 0x100 +#define PMBUS_VIRT_READ_TEMP_AVG (PMBUS_VIRT_BASE + 0) +#define PMBUS_VIRT_READ_TEMP_MIN (PMBUS_VIRT_BASE + 1) +#define PMBUS_VIRT_READ_TEMP_MAX (PMBUS_VIRT_BASE + 2) +#define PMBUS_VIRT_RESET_TEMP_HISTORY (PMBUS_VIRT_BASE + 3) +#define PMBUS_VIRT_READ_VIN_AVG (PMBUS_VIRT_BASE + 4) +#define PMBUS_VIRT_READ_VIN_MIN (PMBUS_VIRT_BASE + 5) +#define PMBUS_VIRT_READ_VIN_MAX (PMBUS_VIRT_BASE + 6) +#define PMBUS_VIRT_RESET_VIN_HISTORY (PMBUS_VIRT_BASE + 7) +#define PMBUS_VIRT_READ_IIN_AVG (PMBUS_VIRT_BASE + 8) +#define PMBUS_VIRT_READ_IIN_MIN (PMBUS_VIRT_BASE + 9) +#define PMBUS_VIRT_READ_IIN_MAX (PMBUS_VIRT_BASE + 10) +#define PMBUS_VIRT_RESET_IIN_HISTORY (PMBUS_VIRT_BASE + 11) +#define PMBUS_VIRT_READ_PIN_AVG (PMBUS_VIRT_BASE + 12) +#define PMBUS_VIRT_READ_PIN_MAX (PMBUS_VIRT_BASE + 13) +#define PMBUS_VIRT_RESET_PIN_HISTORY (PMBUS_VIRT_BASE + 14) +#define PMBUS_VIRT_READ_POUT_AVG (PMBUS_VIRT_BASE + 15) +#define PMBUS_VIRT_READ_POUT_MAX (PMBUS_VIRT_BASE + 16) +#define PMBUS_VIRT_RESET_POUT_HISTORY (PMBUS_VIRT_BASE + 17) +#define PMBUS_VIRT_READ_VOUT_AVG (PMBUS_VIRT_BASE + 18) +#define PMBUS_VIRT_READ_VOUT_MIN (PMBUS_VIRT_BASE + 19) +#define PMBUS_VIRT_READ_VOUT_MAX (PMBUS_VIRT_BASE + 20) +#define PMBUS_VIRT_RESET_VOUT_HISTORY (PMBUS_VIRT_BASE + 21) +#define PMBUS_VIRT_READ_IOUT_AVG (PMBUS_VIRT_BASE + 22) +#define PMBUS_VIRT_READ_IOUT_MIN (PMBUS_VIRT_BASE + 23) +#define PMBUS_VIRT_READ_IOUT_MAX (PMBUS_VIRT_BASE + 24) +#define PMBUS_VIRT_RESET_IOUT_HISTORY (PMBUS_VIRT_BASE + 25) +#define PMBUS_VIRT_READ_TEMP2_AVG (PMBUS_VIRT_BASE + 26) +#define PMBUS_VIRT_READ_TEMP2_MIN (PMBUS_VIRT_BASE + 27) +#define PMBUS_VIRT_READ_TEMP2_MAX (PMBUS_VIRT_BASE + 28) +#define PMBUS_VIRT_RESET_TEMP2_HISTORY (PMBUS_VIRT_BASE + 29) + +#define PMBUS_VIRT_READ_VMON (PMBUS_VIRT_BASE + 30) +#define PMBUS_VIRT_VMON_UV_WARN_LIMIT (PMBUS_VIRT_BASE + 31) +#define PMBUS_VIRT_VMON_OV_WARN_LIMIT (PMBUS_VIRT_BASE + 32) +#define PMBUS_VIRT_VMON_UV_FAULT_LIMIT (PMBUS_VIRT_BASE + 33) +#define PMBUS_VIRT_VMON_OV_FAULT_LIMIT (PMBUS_VIRT_BASE + 34) +#define PMBUS_VIRT_STATUS_VMON (PMBUS_VIRT_BASE + 35) + +/* + * CAPABILITY + */ +#define PB_CAPABILITY_SMBALERT (1<<4) +#define PB_CAPABILITY_ERROR_CHECK (1<<7) + +/* + * VOUT_MODE + */ +#define PB_VOUT_MODE_MODE_MASK 0xe0 +#define PB_VOUT_MODE_PARAM_MASK 0x1f + +#define PB_VOUT_MODE_LINEAR 0x00 +#define PB_VOUT_MODE_VID 0x20 +#define PB_VOUT_MODE_DIRECT 0x40 + +/* + * Fan configuration + */ +#define PB_FAN_2_PULSE_MASK ((1 << 0) | (1 << 1)) +#define PB_FAN_2_RPM (1 << 2) +#define PB_FAN_2_INSTALLED (1 << 3) +#define PB_FAN_1_PULSE_MASK ((1 << 4) | (1 << 5)) +#define PB_FAN_1_RPM (1 << 6) +#define PB_FAN_1_INSTALLED (1 << 7) + +/* + * STATUS_BYTE, STATUS_WORD (lower) + */ +#define PB_STATUS_NONE_ABOVE (1<<0) +#define PB_STATUS_CML (1<<1) +#define PB_STATUS_TEMPERATURE (1<<2) +#define PB_STATUS_VIN_UV (1<<3) +#define PB_STATUS_IOUT_OC (1<<4) +#define PB_STATUS_VOUT_OV (1<<5) +#define PB_STATUS_OFF (1<<6) +#define PB_STATUS_BUSY (1<<7) + +/* + * STATUS_WORD (upper) + */ +#define PB_STATUS_UNKNOWN (1<<8) +#define PB_STATUS_OTHER (1<<9) +#define PB_STATUS_FANS (1<<10) +#define PB_STATUS_POWER_GOOD_N (1<<11) +#define PB_STATUS_WORD_MFR (1<<12) +#define PB_STATUS_INPUT (1<<13) +#define PB_STATUS_IOUT_POUT (1<<14) +#define PB_STATUS_VOUT (1<<15) + +/* + * STATUS_IOUT + */ +#define PB_POUT_OP_WARNING (1<<0) +#define PB_POUT_OP_FAULT (1<<1) +#define PB_POWER_LIMITING (1<<2) +#define PB_CURRENT_SHARE_FAULT (1<<3) +#define PB_IOUT_UC_FAULT (1<<4) +#define PB_IOUT_OC_WARNING (1<<5) +#define PB_IOUT_OC_LV_FAULT (1<<6) +#define PB_IOUT_OC_FAULT (1<<7) + +/* + * STATUS_VOUT, STATUS_INPUT + */ +#define PB_VOLTAGE_UV_FAULT (1<<4) +#define PB_VOLTAGE_UV_WARNING (1<<5) +#define PB_VOLTAGE_OV_WARNING (1<<6) +#define PB_VOLTAGE_OV_FAULT (1<<7) + +/* + * STATUS_INPUT + */ +#define PB_PIN_OP_WARNING (1<<0) +#define PB_IIN_OC_WARNING (1<<1) +#define PB_IIN_OC_FAULT (1<<2) + +/* + * STATUS_TEMPERATURE + */ +#define PB_TEMP_UT_FAULT (1<<4) +#define PB_TEMP_UT_WARNING (1<<5) +#define PB_TEMP_OT_WARNING (1<<6) +#define PB_TEMP_OT_FAULT (1<<7) + +/* + * STATUS_FAN + */ +#define PB_FAN_AIRFLOW_WARNING (1<<0) +#define PB_FAN_AIRFLOW_FAULT (1<<1) +#define PB_FAN_FAN2_SPEED_OVERRIDE (1<<2) +#define PB_FAN_FAN1_SPEED_OVERRIDE (1<<3) +#define PB_FAN_FAN2_WARNING (1<<4) +#define PB_FAN_FAN1_WARNING (1<<5) +#define PB_FAN_FAN2_FAULT (1<<6) +#define PB_FAN_FAN1_FAULT (1<<7) + +/* + * CML_FAULT_STATUS + */ +#define PB_CML_FAULT_OTHER_MEM_LOGIC (1<<0) +#define PB_CML_FAULT_OTHER_COMM (1<<1) +#define PB_CML_FAULT_PROCESSOR (1<<3) +#define PB_CML_FAULT_MEMORY (1<<4) +#define PB_CML_FAULT_PACKET_ERROR (1<<5) +#define PB_CML_FAULT_INVALID_DATA (1<<6) +#define PB_CML_FAULT_INVALID_COMMAND (1<<7) + +enum pmbus_sensor_classes { + PSC_VOLTAGE_IN = 0, + PSC_VOLTAGE_OUT, + PSC_CURRENT_IN, + PSC_CURRENT_OUT, + PSC_POWER, + PSC_TEMPERATURE, + PSC_FAN, + PSC_NUM_CLASSES /* Number of power sensor classes */ +}; + +#define PMBUS_PAGES 32 /* Per PMBus specification */ + +/* Functionality bit mask */ +#define PMBUS_HAVE_VIN (1 << 0) +#define PMBUS_HAVE_VCAP (1 << 1) +#define PMBUS_HAVE_VOUT (1 << 2) +#define PMBUS_HAVE_IIN (1 << 3) +#define PMBUS_HAVE_IOUT (1 << 4) +#define PMBUS_HAVE_PIN (1 << 5) +#define PMBUS_HAVE_POUT (1 << 6) +#define PMBUS_HAVE_FAN12 (1 << 7) +#define PMBUS_HAVE_FAN34 (1 << 8) +#define PMBUS_HAVE_TEMP (1 << 9) +#define PMBUS_HAVE_TEMP2 (1 << 10) +#define PMBUS_HAVE_TEMP3 (1 << 11) +#define PMBUS_HAVE_STATUS_VOUT (1 << 12) +#define PMBUS_HAVE_STATUS_IOUT (1 << 13) +#define PMBUS_HAVE_STATUS_INPUT (1 << 14) +#define PMBUS_HAVE_STATUS_TEMP (1 << 15) +#define PMBUS_HAVE_STATUS_FAN12 (1 << 16) +#define PMBUS_HAVE_STATUS_FAN34 (1 << 17) +#define PMBUS_HAVE_VMON (1 << 18) +#define PMBUS_HAVE_STATUS_VMON (1 << 19) +#define PMBUS_HAVE_MFRDATA (1 << 20) + +enum pmbus_data_format { linear = 0, direct, vid }; + +struct pmbus_driver_info { + int pages; /* Total number of pages */ + ushort delay; + enum pmbus_data_format format[PSC_NUM_CLASSES]; + /* + * Support one set of coefficients for each sensor type + * Used for chips providing data in direct mode. + */ + int m[PSC_NUM_CLASSES]; /* mantissa for direct data format */ + int b[PSC_NUM_CLASSES]; /* offset */ + int R[PSC_NUM_CLASSES]; /* exponent */ + + u32 func[PMBUS_PAGES]; /* Functionality, per page */ + /* + * The following functions map manufacturing specific register values + * to PMBus standard register values. Specify only if mapping is + * necessary. + * Functions return the register value (read) or zero (write) if + * successful. A return value of -ENODATA indicates that there is no + * manufacturer specific register, but that a standard PMBus register + * may exist. Any other negative return value indicates that the + * register does not exist, and that no attempt should be made to read + * the standard register. + */ + int (*read_byte_data)(struct i2c_client *client, int page, int reg); + int (*read_word_data)(struct i2c_client *client, int page, int reg); + int (*write_word_data)(struct i2c_client *client, int page, int reg, + u16 word); + int (*write_byte)(struct i2c_client *client, int page, u8 value); + /* + * The identify function determines supported PMBus functionality. + * This function is only necessary if a chip driver supports multiple + * chips, and the chip functionality is not pre-determined. + */ + int (*identify)(struct i2c_client *client, + struct pmbus_driver_info *info); +}; + +/* Function declarations */ + +void pmbus_wait(struct i2c_client *client); +void pmbus_update_wait(struct i2c_client *client); +void pmbus_clear_cache(struct i2c_client *client); +int pmbus_set_page(struct i2c_client *client, u8 page); +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg); +int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, u16 word); +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg); +int pmbus_write_byte(struct i2c_client *client, int page, u8 value); +void pmbus_clear_faults(struct i2c_client *client); +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info); +int pmbus_do_remove(struct i2c_client *client); +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client + *client); + +#endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c new file mode 100644 index 000000000000..d547fc8e1ce3 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -0,0 +1,1940 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* + * Number of additional attribute pointers to allocate + * with each call to krealloc + */ +#define PMBUS_ATTR_ALLOC_SIZE 32 + +/* + * Index into status register array, per status register group + */ +#define PB_STATUS_BASE 0 +#define PB_STATUS_VOUT_BASE (PB_STATUS_BASE + PMBUS_PAGES) +#define PB_STATUS_IOUT_BASE (PB_STATUS_VOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN34_BASE (PB_STATUS_FAN_BASE + PMBUS_PAGES) +#define PB_STATUS_TEMP_BASE (PB_STATUS_FAN34_BASE + PMBUS_PAGES) +#define PB_STATUS_INPUT_BASE (PB_STATUS_TEMP_BASE + PMBUS_PAGES) +#define PB_STATUS_VMON_BASE (PB_STATUS_INPUT_BASE + 1) + +#define PB_NUM_STATUS_REG (PB_STATUS_VMON_BASE + 1) + +#define PMBUS_NAME_SIZE 24 + +struct pmbus_sensor { + struct pmbus_sensor *next; + char name[PMBUS_NAME_SIZE]; /* sysfs sensor name */ + struct device_attribute attribute; + u8 page; /* page number */ + u16 reg; /* register */ + enum pmbus_sensor_classes class; /* sensor class */ + bool update; /* runtime sensor update needed */ + int data; /* Sensor data. + Negative if there was a read error */ +}; +#define to_pmbus_sensor(_attr) \ + container_of(_attr, struct pmbus_sensor, attribute) + +struct pmbus_boolean { + char name[PMBUS_NAME_SIZE]; /* sysfs boolean name */ + struct sensor_device_attribute attribute; + struct pmbus_sensor *s1; + struct pmbus_sensor *s2; +}; +#define to_pmbus_boolean(_attr) \ + container_of(_attr, struct pmbus_boolean, attribute) + +struct pmbus_label { + char name[PMBUS_NAME_SIZE]; /* sysfs label name */ + struct device_attribute attribute; + char label[PMBUS_NAME_SIZE]; /* label */ +}; +#define to_pmbus_label(_attr) \ + container_of(_attr, struct pmbus_label, attribute) + +struct pmbus_data { + struct device *dev; + struct device *hwmon_dev; + + u32 flags; /* from platform data */ + + int exponent; /* linear mode: exponent for output voltages */ + + const struct pmbus_driver_info *info; + + int max_attributes; + int num_attributes; + struct attribute_group group; + + struct pmbus_sensor *sensors; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + ktime_t access; + + /* + * A single status register covers multiple attributes, + * so we keep them all together. + */ + u8 status[PB_NUM_STATUS_REG]; + u8 status_register; + + u8 currpage; +}; + +/* Some chips need a delay between accesses */ +void pmbus_wait(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + + if (info->delay) { + s64 delta = ktime_us_delta(ktime_get(), data->access); + if (delta < info->delay) + /* + * Note that udelay is busy waiting. msleep is + * quite a bit slower (it actually takes a + * minimum of 20ms), but doesn't busy wait. Hmmm. + */ + udelay(info->delay - delta); + } +} +EXPORT_SYMBOL_GPL(pmbus_wait); + +void pmbus_update_wait(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + + if (info->delay) + data->access = ktime_get(); +} +EXPORT_SYMBOL_GPL(pmbus_update_wait); + +void pmbus_clear_cache(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + data->valid = false; +} +EXPORT_SYMBOL_GPL(pmbus_clear_cache); + +int pmbus_set_page(struct i2c_client *client, u8 page) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int rv = 0; + int newpage; + + if (page != data->currpage) { + pmbus_wait(client); + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + pmbus_update_wait(client); + pmbus_wait(client); + newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + pmbus_update_wait(client); + if (newpage != page) + rv = -EIO; + else + data->currpage = page; + } + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_set_page); + +int pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ + int rv; + + if (page >= 0) { + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + } + + pmbus_wait(client); + rv = i2c_smbus_write_byte(client, value); + pmbus_update_wait(client); + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_write_byte); + +/* + * _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if + * a device specific mapping funcion exists and calls it if necessary. + */ +static int _pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_byte) { + status = info->write_byte(client, page, value); + if (status != -ENODATA) + return status; + } + return pmbus_write_byte(client, page, value); +} + +int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, u16 word) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + pmbus_wait(client); + rv = i2c_smbus_write_word_data(client, reg, word); + pmbus_update_wait(client); + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_write_word_data); + +/* + * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_word_data) { + status = info->write_word_data(client, page, reg, word); + if (status != -ENODATA) + return status; + } + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + return pmbus_write_word_data(client, page, reg, word); +} + +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + pmbus_wait(client); + rv = i2c_smbus_read_word_data(client, reg); + pmbus_update_wait(client); + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_read_word_data); + +/* + * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_word_data) { + status = info->read_word_data(client, page, reg); + if (status != -ENODATA) + return status; + } + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + return pmbus_read_word_data(client, page, reg); +} + +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg) +{ + int rv; + + if (page >= 0) { + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + } + + pmbus_wait(client); + rv = i2c_smbus_read_byte_data(client, reg); + pmbus_update_wait(client); + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_read_byte_data); + +/* + * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_byte_data) { + status = info->read_byte_data(client, page, reg); + if (status != -ENODATA) + return status; + } + return pmbus_read_byte_data(client, page, reg); +} + +static void pmbus_clear_fault_page(struct i2c_client *client, int page) +{ + _pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); +} + +void pmbus_clear_faults(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int i; + + for (i = 0; i < data->info->pages; i++) + pmbus_clear_fault_page(client, i); +} +EXPORT_SYMBOL_GPL(pmbus_clear_faults); + +static int pmbus_check_status_cml(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int status, status2; + + status = _pmbus_read_byte_data(client, -1, data->status_register); + if (status < 0 || (status & PB_STATUS_CML)) { + status2 = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML); + if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND)) + return -EIO; + } + return 0; +} + +static bool pmbus_check_register(struct i2c_client *client, + int (*func)(struct i2c_client *client, + int page, int reg), + int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + + rv = func(client, page, reg); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client); + pmbus_clear_fault_page(client, -1); + return rv >= 0; +} + +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg) +{ + return pmbus_check_register(client, _pmbus_read_byte_data, page, reg); +} +EXPORT_SYMBOL_GPL(pmbus_check_byte_register); + +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg) +{ + return pmbus_check_register(client, _pmbus_read_word_data, page, reg); +} +EXPORT_SYMBOL_GPL(pmbus_check_word_register); + +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->info; +} +EXPORT_SYMBOL_GPL(pmbus_get_driver_info); + +static struct _pmbus_status { + u32 func; + u16 base; + u16 reg; +} pmbus_status[] = { + { PMBUS_HAVE_STATUS_VOUT, PB_STATUS_VOUT_BASE, PMBUS_STATUS_VOUT }, + { PMBUS_HAVE_STATUS_IOUT, PB_STATUS_IOUT_BASE, PMBUS_STATUS_IOUT }, + { PMBUS_HAVE_STATUS_TEMP, PB_STATUS_TEMP_BASE, + PMBUS_STATUS_TEMPERATURE }, + { PMBUS_HAVE_STATUS_FAN12, PB_STATUS_FAN_BASE, PMBUS_STATUS_FAN_12 }, + { PMBUS_HAVE_STATUS_FAN34, PB_STATUS_FAN34_BASE, PMBUS_STATUS_FAN_34 }, +}; + +static struct pmbus_data *pmbus_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + struct pmbus_sensor *sensor; + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i, j; + + for (i = 0; i < info->pages; i++) { + data->status[PB_STATUS_BASE + i] + = _pmbus_read_byte_data(client, i, + data->status_register); + for (j = 0; j < ARRAY_SIZE(pmbus_status); j++) { + struct _pmbus_status *s = &pmbus_status[j]; + + if (!(info->func[i] & s->func)) + continue; + data->status[s->base + i] + = _pmbus_read_byte_data(client, i, + s->reg); + } + } + + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + data->status[PB_STATUS_INPUT_BASE] + = _pmbus_read_byte_data(client, 0, + PMBUS_STATUS_INPUT); + + if (info->func[0] & PMBUS_HAVE_STATUS_VMON) + data->status[PB_STATUS_VMON_BASE] + = _pmbus_read_byte_data(client, 0, + PMBUS_VIRT_STATUS_VMON); + + for (sensor = data->sensors; sensor; sensor = sensor->next) { + if (!data->valid || sensor->update) + sensor->data + = _pmbus_read_word_data(client, + sensor->page, + sensor->reg); + } + pmbus_clear_faults(client); + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Convert linear sensor values to milli- or micro-units + * depending on sensor type. + */ +static long pmbus_reg2data_linear(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + s16 exponent; + s32 mantissa; + long val; + + if (sensor->class == PSC_VOLTAGE_OUT) { /* LINEAR16 */ + exponent = data->exponent; + mantissa = (u16) sensor->data; + } else { /* LINEAR11 */ + exponent = ((s16)sensor->data) >> 11; + mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5; + } + + val = mantissa; + + /* scale result to milli-units for all sensors except fans */ + if (sensor->class != PSC_FAN) + val = val * 1000L; + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +/* + * Convert direct sensor values to milli- or micro-units + * depending on sensor type. + */ +static long pmbus_reg2data_direct(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + long val = (s16) sensor->data; + long m, b, R; + + m = data->info->m[sensor->class]; + b = data->info->b[sensor->class]; + R = data->info->R[sensor->class]; + + if (m == 0) + return 0; + + /* X = 1/m * (Y * 10^-R - b) */ + R = -R; + /* scale result to milli-units for everything but fans */ + if (sensor->class != PSC_FAN) { + R += 3; + b *= 1000; + } + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) { + R += 3; + b *= 1000; + } + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = DIV_ROUND_CLOSEST(val, 10); + R++; + } + + return (val - b) / m; +} + +/* + * Convert VID sensor values to milli- or micro-units + * depending on sensor type. + * We currently only support VR11. + */ +static long pmbus_reg2data_vid(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + long val = sensor->data; + + if (val < 0x02 || val > 0xb2) + return 0; + return DIV_ROUND_CLOSEST(160000 - (val - 2) * 625, 100); +} + +static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) +{ + long val; + + switch (data->info->format[sensor->class]) { + case direct: + val = pmbus_reg2data_direct(data, sensor); + break; + case vid: + val = pmbus_reg2data_vid(data, sensor); + break; + case linear: + default: + val = pmbus_reg2data_linear(data, sensor); + break; + } + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 pmbus_data2reg_linear(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (class == PSC_VOLTAGE_OUT) { + /* LINEAR16 does not support negative voltages */ + if (val < 0) + return 0; + + /* + * For a static exponents, we don't have a choice + * but to adjust the value to it. + */ + if (data->exponent < 0) + val <<= -data->exponent; + else + val >>= data->exponent; + val = DIV_ROUND_CLOSEST(val, 1000); + return val & 0xffff; + } + + if (val < 0) { + negative = true; + val = -val; + } + + /* Power is in uW. Convert to mW before converting. */ + if (class == PSC_POWER) + val = DIV_ROUND_CLOSEST(val, 1000L); + + /* + * For simplicity, convert fan data to milli-units + * before calculating the exponent. + */ + if (class == PSC_FAN) + val = val * 1000; + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static u16 pmbus_data2reg_direct(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + long m, b, R; + + m = data->info->m[class]; + b = data->info->b[class]; + R = data->info->R[class]; + + /* Power is in uW. Adjust R and b. */ + if (class == PSC_POWER) { + R -= 3; + b *= 1000; + } + + /* Calculate Y = (m * X + b) * 10^R */ + if (class != PSC_FAN) { + R -= 3; /* Adjust R and b for data in milli-units */ + b *= 1000; + } + val = val * m + b; + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = DIV_ROUND_CLOSEST(val, 10); + R++; + } + + return val; +} + +static u16 pmbus_data2reg_vid(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + val = clamp_val(val, 500, 1600); + + return 2 + DIV_ROUND_CLOSEST((1600 - val) * 100, 625); +} + +static u16 pmbus_data2reg(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + u16 regval; + + switch (data->info->format[class]) { + case direct: + regval = pmbus_data2reg_direct(data, class, val); + break; + case vid: + regval = pmbus_data2reg_vid(data, class, val); + break; + case linear: + default: + regval = pmbus_data2reg_linear(data, class, val); + break; + } + return regval; +} + +/* + * Return boolean calculated from converted data. + * defines a status register index and mask. + * The mask is in the lower 8 bits, the register index is in bits 8..23. + * + * The associated pmbus_boolean structure contains optional pointers to two + * sensor attributes. If specified, those attributes are compared against each + * other to determine if a limit has been exceeded. + * + * If the sensor attribute pointers are NULL, the function returns true if + * (status[reg] & mask) is true. + * + * If sensor attribute pointers are provided, a comparison against a specified + * limit has to be performed to determine the boolean result. + * In this case, the function returns true if v1 >= v2 (where v1 and v2 are + * sensor values referenced by sensor attribute pointers s1 and s2). + * + * To determine if an object exceeds upper limits, specify = . + * To determine if an object exceeds lower limits, specify = . + * + * If a negative value is stored in any of the referenced registers, this value + * reflects an error code which will be returned. + */ +static int pmbus_get_boolean(struct pmbus_data *data, struct pmbus_boolean *b, + int index) +{ + struct pmbus_sensor *s1 = b->s1; + struct pmbus_sensor *s2 = b->s2; + u16 reg = (index >> 8) & 0xffff; + u8 mask = index & 0xff; + int ret, status; + u8 regval; + + status = data->status[reg]; + if (status < 0) + return status; + + regval = status & mask; + if (!s1 && !s2) { + ret = !!regval; + } else if (!s1 || !s2) { + BUG(); + return 0; + } else { + long v1, v2; + + if (s1->data < 0) + return s1->data; + if (s2->data < 0) + return s2->data; + + v1 = pmbus_reg2data(data, s1); + v2 = pmbus_reg2data(data, s2); + ret = !!(regval && v1 >= v2); + } + return ret; +} + +static ssize_t pmbus_show_boolean(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_boolean *boolean = to_pmbus_boolean(attr); + struct pmbus_data *data = pmbus_update_device(dev); + int val; + + val = pmbus_get_boolean(data, boolean, attr->index); + if (val < 0) + return val; + if (val == 0xff) + return 0; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t pmbus_show_sensor(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct pmbus_data *data = pmbus_update_device(dev); + struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + + if (sensor->data < 0) + return sensor->data; + if (sensor->data == 0xffff) + return 0; + + return snprintf(buf, PAGE_SIZE, "%ld\n", pmbus_reg2data(data, sensor)); +} + +static ssize_t pmbus_set_sensor(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + ssize_t rv = count; + long val = 0; + int ret; + u16 regval; + + if ((val = simple_strtol(buf, NULL, 10)) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + regval = pmbus_data2reg(data, sensor->class, val); + ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval); + if (ret < 0) + rv = ret; + else + sensor->data = regval; + mutex_unlock(&data->update_lock); + return rv; +} + +static ssize_t pmbus_show_label(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct pmbus_label *label = to_pmbus_label(da); + + return snprintf(buf, PAGE_SIZE, "%s\n", label->label); +} + +static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr) +{ + if (data->num_attributes >= data->max_attributes - 1) { + int new_max_attrs = data->max_attributes + PMBUS_ATTR_ALLOC_SIZE; + void *new_attrs = krealloc(data->group.attrs, + new_max_attrs * sizeof(void *), + GFP_KERNEL); + if (!new_attrs) + return -ENOMEM; + data->group.attrs = new_attrs; + data->max_attributes = new_max_attrs; + } + + data->group.attrs[data->num_attributes++] = attr; + data->group.attrs[data->num_attributes] = NULL; + return 0; +} + +static void pmbus_dev_attr_init(struct device_attribute *dev_attr, + const char *name, + umode_t mode, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count)) +{ + // KML: Unnecessary in 2.6.28: + //sysfs_attr_init(&dev_attr->attr); + dev_attr->attr.name = name; + dev_attr->attr.mode = mode; + dev_attr->show = show; + dev_attr->store = store; +} + +static void pmbus_attr_init(struct sensor_device_attribute *a, + const char *name, + umode_t mode, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count), + int idx) +{ + pmbus_dev_attr_init(&a->dev_attr, name, mode, show, store); + a->index = idx; +} + +static int pmbus_add_boolean(struct pmbus_data *data, + const char *name, const char *type, int seq, + struct pmbus_sensor *s1, + struct pmbus_sensor *s2, + u16 reg, u8 mask) +{ + struct pmbus_boolean *boolean; + struct sensor_device_attribute *a; + + boolean = devm_kzalloc(data->dev, sizeof(*boolean), GFP_KERNEL); + if (!boolean) + return -ENOMEM; + + a = &boolean->attribute; + + snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s", + name, seq, type); + boolean->s1 = s1; + boolean->s2 = s2; + pmbus_attr_init(a, boolean->name, S_IRUGO, pmbus_show_boolean, NULL, + (reg << 8) | mask); + + return pmbus_add_attribute(data, &a->dev_attr.attr); +} + +static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, + const char *name, const char *type, + int seq, int page, int reg, + enum pmbus_sensor_classes class, + bool update, bool readonly) +{ + struct pmbus_sensor *sensor; + struct device_attribute *a; + + sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return NULL; + a = &sensor->attribute; + + snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", + name, seq, type); + sensor->page = page; + sensor->reg = reg; + sensor->class = class; + sensor->update = update; + pmbus_dev_attr_init(a, sensor->name, + readonly ? S_IRUGO : S_IRUGO | S_IWUSR, + pmbus_show_sensor, pmbus_set_sensor); + + if (pmbus_add_attribute(data, &a->attr)) + return NULL; + + sensor->next = data->sensors; + data->sensors = sensor; + + return sensor; +} + +static int pmbus_add_label(struct pmbus_data *data, + const char *name, int seq, + const char *lstring, int index) +{ + struct pmbus_label *label; + struct device_attribute *a; + + label = devm_kzalloc(data->dev, sizeof(*label), GFP_KERNEL); + if (!label) + return -ENOMEM; + + a = &label->attribute; + + if (seq == -1) + snprintf(label->name, sizeof(label->name), "%s_label", name); + else + snprintf(label->name, sizeof(label->name), "%s%d_label", + name, seq); + if (!index) + strncpy(label->label, lstring, sizeof(label->label) - 1); + else + snprintf(label->label, sizeof(label->label), "%s%d", lstring, + index); + + pmbus_dev_attr_init(a, label->name, S_IRUGO, pmbus_show_label, NULL); + return pmbus_add_attribute(data, &a->attr); +} + +/* + * Search for attributes. Allocate sensors, booleans, and labels as needed. + */ + +/* + * The pmbus_limit_attr structure describes a single limit attribute + * and its associated alarm attribute. + */ +struct pmbus_limit_attr { + u16 reg; /* Limit register */ + u16 sbit; /* Alarm attribute status bit */ + bool update; /* True if register needs updates */ + bool low; /* True if low limit; for limits with compare + functions only */ + const char *attr; /* Attribute name */ + const char *alarm; /* Alarm attribute name */ +}; + +/* + * The pmbus_sensor_attr structure describes one sensor attribute. This + * description includes a reference to the associated limit attributes. + */ +struct pmbus_sensor_attr { + u16 reg; /* sensor register */ + u8 gbit; /* generic status bit */ + u8 nlimit; /* # of limit registers */ + enum pmbus_sensor_classes class;/* sensor class */ + const char *label; /* sensor label */ + bool paged; /* true if paged sensor */ + bool update; /* true if update needed */ + bool compare; /* true if compare function needed */ + u32 func; /* sensor mask */ + u32 sfunc; /* sensor status mask */ + int sbase; /* status base register */ + const struct pmbus_limit_attr *limit;/* limit registers */ +}; + +/* + * Add a set of limit attributes and, if supported, the associated + * alarm attributes. + * returns 0 if no alarm register found, 1 if an alarm register was found, + * < 0 on errors. + */ +static int pmbus_add_limit_attrs(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, int index, int page, + struct pmbus_sensor *base, + const struct pmbus_sensor_attr *attr) +{ + const struct pmbus_limit_attr *l = attr->limit; + int nlimit = attr->nlimit; + int have_alarm = 0; + int i, ret; + struct pmbus_sensor *curr; + + for (i = 0; i < nlimit; i++) { + if (pmbus_check_word_register(client, page, l->reg)) { + curr = pmbus_add_sensor(data, name, l->attr, index, + page, l->reg, attr->class, + attr->update || l->update, + false); + if (!curr) + return -ENOMEM; + if (l->sbit && (info->func[page] & attr->sfunc)) { + ret = pmbus_add_boolean(data, name, + l->alarm, index, + attr->compare ? l->low ? curr : base + : NULL, + attr->compare ? l->low ? base : curr + : NULL, + attr->sbase + page, l->sbit); + if (ret) + return ret; + have_alarm = 1; + } + } + l++; + } + return have_alarm; +} + +static int pmbus_add_sensor_attrs_one(struct i2c_client *client, + struct pmbus_data *data, + const struct pmbus_driver_info *info, + const char *name, + int index, int page, + const struct pmbus_sensor_attr *attr) +{ + struct pmbus_sensor *base; + int ret; + + if (attr->label) { + ret = pmbus_add_label(data, name, index, attr->label, + attr->paged ? page + 1 : 0); + if (ret) + return ret; + } + base = pmbus_add_sensor(data, name, "input", index, page, attr->reg, + attr->class, true, true); + if (!base) + return -ENOMEM; + if (attr->sfunc) { + ret = pmbus_add_limit_attrs(client, data, info, name, + index, page, base, attr); + if (ret < 0) + return ret; + /* + * Add generic alarm attribute only if there are no individual + * alarm attributes, if there is a global alarm bit, and if + * the generic status register for this page is accessible. + */ + if (!ret && attr->gbit && + pmbus_check_byte_register(client, page, + data->status_register)) { + ret = pmbus_add_boolean(data, name, "alarm", index, + NULL, NULL, + PB_STATUS_BASE + page, + attr->gbit); + if (ret) + return ret; + } + } + return 0; +} + +static int pmbus_add_sensor_attrs(struct i2c_client *client, + struct pmbus_data *data, + const char *name, + const struct pmbus_sensor_attr *attrs, + int nattrs) +{ + const struct pmbus_driver_info *info = data->info; + int index, i; + int ret; + + index = 1; + for (i = 0; i < nattrs; i++) { + int page, pages; + + pages = attrs->paged ? info->pages : 1; + for (page = 0; page < pages; page++) { + if (!(info->func[page] & attrs->func)) + continue; + ret = pmbus_add_sensor_attrs_one(client, data, info, + name, index, page, + attrs); + if (ret) + return ret; + index++; + } + attrs++; + } + return 0; +} + +static const struct pmbus_limit_attr vin_limit_attrs[] = { + { + .reg = PMBUS_VIN_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VIN_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VIN_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VIN_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + }, { + .reg = PMBUS_VIRT_READ_VIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_VIN_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_VIN_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_VIN_HISTORY, + .attr = "reset_history", + }, +}; + +static const struct pmbus_limit_attr vmon_limit_attrs[] = { + { + .reg = PMBUS_VIRT_VMON_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VIRT_VMON_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VIRT_VMON_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VIRT_VMON_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + } +}; + +static const struct pmbus_limit_attr vout_limit_attrs[] = { + { + .reg = PMBUS_VOUT_UV_WARN_LIMIT, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_VOLTAGE_UV_WARNING, + }, { + .reg = PMBUS_VOUT_UV_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_VOLTAGE_UV_FAULT, + }, { + .reg = PMBUS_VOUT_OV_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_VOLTAGE_OV_WARNING, + }, { + .reg = PMBUS_VOUT_OV_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_VOLTAGE_OV_FAULT, + }, { + .reg = PMBUS_VIRT_READ_VOUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_VOUT_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_VOUT_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_VOUT_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_sensor_attr voltage_attributes[] = { + { + .reg = PMBUS_READ_VIN, + .class = PSC_VOLTAGE_IN, + .label = "vin", + .func = PMBUS_HAVE_VIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .gbit = PB_STATUS_VIN_UV, + .limit = vin_limit_attrs, + .nlimit = ARRAY_SIZE(vin_limit_attrs), + }, { + .reg = PMBUS_VIRT_READ_VMON, + .class = PSC_VOLTAGE_IN, + .label = "vmon", + .func = PMBUS_HAVE_VMON, + .sfunc = PMBUS_HAVE_STATUS_VMON, + .sbase = PB_STATUS_VMON_BASE, + .limit = vmon_limit_attrs, + .nlimit = ARRAY_SIZE(vmon_limit_attrs), + }, { + .reg = PMBUS_READ_VCAP, + .class = PSC_VOLTAGE_IN, + .label = "vcap", + .func = PMBUS_HAVE_VCAP, + }, { + .reg = PMBUS_READ_VOUT, + .class = PSC_VOLTAGE_OUT, + .label = "vout", + .paged = true, + .func = PMBUS_HAVE_VOUT, + .sfunc = PMBUS_HAVE_STATUS_VOUT, + .sbase = PB_STATUS_VOUT_BASE, + .gbit = PB_STATUS_VOUT_OV, + .limit = vout_limit_attrs, + .nlimit = ARRAY_SIZE(vout_limit_attrs), + } +}; + +/* Current attributes */ + +static const struct pmbus_limit_attr iin_limit_attrs[] = { + { + .reg = PMBUS_IIN_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IIN_OC_WARNING, + }, { + .reg = PMBUS_IIN_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IIN_OC_FAULT, + }, { + .reg = PMBUS_VIRT_READ_IIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_IIN_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_IIN_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_IIN_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr iout_limit_attrs[] = { + { + .reg = PMBUS_IOUT_OC_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_IOUT_OC_WARNING, + }, { + .reg = PMBUS_IOUT_UC_FAULT_LIMIT, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_IOUT_UC_FAULT, + }, { + .reg = PMBUS_IOUT_OC_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_IOUT_OC_FAULT, + }, { + .reg = PMBUS_VIRT_READ_IOUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_IOUT_MIN, + .update = true, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_IOUT_MAX, + .update = true, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_IOUT_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_sensor_attr current_attributes[] = { + { + .reg = PMBUS_READ_IIN, + .class = PSC_CURRENT_IN, + .label = "iin", + .func = PMBUS_HAVE_IIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .limit = iin_limit_attrs, + .nlimit = ARRAY_SIZE(iin_limit_attrs), + }, { + .reg = PMBUS_READ_IOUT, + .class = PSC_CURRENT_OUT, + .label = "iout", + .paged = true, + .func = PMBUS_HAVE_IOUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sbase = PB_STATUS_IOUT_BASE, + .gbit = PB_STATUS_IOUT_OC, + .limit = iout_limit_attrs, + .nlimit = ARRAY_SIZE(iout_limit_attrs), + } +}; + +/* Power attributes */ + +static const struct pmbus_limit_attr pin_limit_attrs[] = { + { + .reg = PMBUS_PIN_OP_WARN_LIMIT, + .attr = "max", + .alarm = "alarm", + .sbit = PB_PIN_OP_WARNING, + }, { + .reg = PMBUS_VIRT_READ_PIN_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_PIN_MAX, + .update = true, + .attr = "input_highest", + }, { + .reg = PMBUS_VIRT_RESET_PIN_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr pout_limit_attrs[] = { + { + .reg = PMBUS_POUT_MAX, + .attr = "cap", + .alarm = "cap_alarm", + .sbit = PB_POWER_LIMITING, + }, { + .reg = PMBUS_POUT_OP_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_POUT_OP_WARNING, + }, { + .reg = PMBUS_POUT_OP_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_POUT_OP_FAULT, + }, { + .reg = PMBUS_VIRT_READ_POUT_AVG, + .update = true, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_POUT_MAX, + .update = true, + .attr = "input_highest", + }, { + .reg = PMBUS_VIRT_RESET_POUT_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_sensor_attr power_attributes[] = { + { + .reg = PMBUS_READ_PIN, + .class = PSC_POWER, + .label = "pin", + .func = PMBUS_HAVE_PIN, + .sfunc = PMBUS_HAVE_STATUS_INPUT, + .sbase = PB_STATUS_INPUT_BASE, + .limit = pin_limit_attrs, + .nlimit = ARRAY_SIZE(pin_limit_attrs), + }, { + .reg = PMBUS_READ_POUT, + .class = PSC_POWER, + .label = "pout", + .paged = true, + .func = PMBUS_HAVE_POUT, + .sfunc = PMBUS_HAVE_STATUS_IOUT, + .sbase = PB_STATUS_IOUT_BASE, + .limit = pout_limit_attrs, + .nlimit = ARRAY_SIZE(pout_limit_attrs), + } +}; + +/* Temperature atributes */ + +static const struct pmbus_limit_attr temp_limit_attrs[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + }, { + .reg = PMBUS_VIRT_READ_TEMP_MIN, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_TEMP_AVG, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_TEMP_MAX, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_TEMP_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr temp_limit_attrs2[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + }, { + .reg = PMBUS_VIRT_READ_TEMP2_MIN, + .attr = "lowest", + }, { + .reg = PMBUS_VIRT_READ_TEMP2_AVG, + .attr = "average", + }, { + .reg = PMBUS_VIRT_READ_TEMP2_MAX, + .attr = "highest", + }, { + .reg = PMBUS_VIRT_RESET_TEMP2_HISTORY, + .attr = "reset_history", + } +}; + +static const struct pmbus_limit_attr temp_limit_attrs3[] = { + { + .reg = PMBUS_UT_WARN_LIMIT, + .low = true, + .attr = "min", + .alarm = "min_alarm", + .sbit = PB_TEMP_UT_WARNING, + }, { + .reg = PMBUS_UT_FAULT_LIMIT, + .low = true, + .attr = "lcrit", + .alarm = "lcrit_alarm", + .sbit = PB_TEMP_UT_FAULT, + }, { + .reg = PMBUS_OT_WARN_LIMIT, + .attr = "max", + .alarm = "max_alarm", + .sbit = PB_TEMP_OT_WARNING, + }, { + .reg = PMBUS_OT_FAULT_LIMIT, + .attr = "crit", + .alarm = "crit_alarm", + .sbit = PB_TEMP_OT_FAULT, + } +}; + +static const struct pmbus_sensor_attr temp_attributes[] = { + { + .reg = PMBUS_READ_TEMPERATURE_1, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs, + .nlimit = ARRAY_SIZE(temp_limit_attrs), + }, { + .reg = PMBUS_READ_TEMPERATURE_2, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP2, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs2, + .nlimit = ARRAY_SIZE(temp_limit_attrs2), + }, { + .reg = PMBUS_READ_TEMPERATURE_3, + .class = PSC_TEMPERATURE, + .paged = true, + .update = true, + .compare = true, + .func = PMBUS_HAVE_TEMP3, + .sfunc = PMBUS_HAVE_STATUS_TEMP, + .sbase = PB_STATUS_TEMP_BASE, + .gbit = PB_STATUS_TEMPERATURE, + .limit = temp_limit_attrs3, + .nlimit = ARRAY_SIZE(temp_limit_attrs3), + } +}; + +static const int pmbus_fan_registers[] = { + PMBUS_READ_FAN_SPEED_1, + PMBUS_READ_FAN_SPEED_2, + PMBUS_READ_FAN_SPEED_3, + PMBUS_READ_FAN_SPEED_4 +}; + +static const int pmbus_fan_config_registers[] = { + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_34, + PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_status_registers[] = { + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_34, + PMBUS_STATUS_FAN_34 +}; + +static const u32 pmbus_fan_flags[] = { + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN12, + PMBUS_HAVE_FAN34, + PMBUS_HAVE_FAN34 +}; + +static const u32 pmbus_fan_status_flags[] = { + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN12, + PMBUS_HAVE_STATUS_FAN34, + PMBUS_HAVE_STATUS_FAN34 +}; + +/* Fans */ +static int pmbus_add_fan_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int index = 1; + int page; + int ret; + + for (page = 0; page < info->pages; page++) { + int f; + + for (f = 0; f < ARRAY_SIZE(pmbus_fan_registers); f++) { + int regval; + + if (!(info->func[page] & pmbus_fan_flags[f])) + break; + + if (!pmbus_check_word_register(client, page, + pmbus_fan_registers[f])) + break; + + /* + * Skip fan if not installed. + * Each fan configuration register covers multiple fans, + * so we have to do some magic. + */ + regval = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[f]); + if (regval < 0 || + (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) + continue; + + if (pmbus_add_sensor(data, "fan", "input", index, + page, pmbus_fan_registers[f], + PSC_FAN, true, true) == NULL) + return -ENOMEM; + + /* + * Each fan status register covers multiple fans, + * so we have to do some magic. + */ + if ((info->func[page] & pmbus_fan_status_flags[f]) && + pmbus_check_byte_register(client, + page, pmbus_fan_status_registers[f])) { + int base; + + if (f > 1) /* fan 3, 4 */ + base = PB_STATUS_FAN34_BASE + page; + else + base = PB_STATUS_FAN_BASE + page; + ret = pmbus_add_boolean(data, "fan", + "alarm", index, NULL, NULL, base, + PB_FAN_FAN1_WARNING >> (f & 1)); + if (ret) + return ret; + ret = pmbus_add_boolean(data, "fan", + "fault", index, NULL, NULL, base, + PB_FAN_FAN1_FAULT >> (f & 1)); + if (ret) + return ret; + } + index++; + } + } + return 0; +} + +static const u32 pmbus_mfr_registers[] = { + PMBUS_MFR_ID, + PMBUS_MFR_MODEL, + PMBUS_MFR_REVISION, + /* + * PMBUS_MFR_LOCATION is not implemented according to spec + * in the pfe1100; rather than showing up as a block read, + * it's a word read. Even worse, our block read implementation + * will get the first byte, 'A', and stomp all over our buffer, + * rather than politely declining to read 65 bytes, as it should. + * + * Clearly, we should fix the implementation rather than hack it + * in here, but we want to get this out the door. With more + * experience, hopefully we can come up with a more general + * implmentation of the MFR register reads. + */ + PMBUS_MFR_DATE, + PMBUS_MFR_SERIAL, +}; + +static const char *pmbus_mfr_names[] = { + "mfr_id", + "mfr_model", + "mfr_revision", + /* "mfr_location", as mentioned above, is not readable */ + "mfr_date", + "mfr_serial", +}; + +/* MFR info */ +static int pmbus_add_mfr_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + int f; + char buf[I2C_SMBUS_BLOCK_MAX + 1]; + + if ((data->info->func[0] & PMBUS_HAVE_MFRDATA) == 0 || + !i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return 0; + + for (f = 0; f < ARRAY_SIZE(pmbus_mfr_registers); f++) { + int ret; + + pmbus_wait(client); + ret = i2c_smbus_read_block_data(client, pmbus_mfr_registers[f], + buf); + pmbus_update_wait(client); + if (ret <= 0) + continue; + + buf[ret] = 0; + if (!(data->flags & PMBUS_SKIP_STATUS_CHECK)) { + ret = pmbus_check_status_cml(client); + pmbus_clear_fault_page(client, -1); + if (ret < 0) + continue; + } + + /* Note that the label code truncates to PMBUS_NAME_SIZE */ + + ret = pmbus_add_label(data, pmbus_mfr_names[f], -1, buf, 0); + if (ret) + return ret; + } + return 0; +} + +static int pmbus_find_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + int ret; + + /* Voltage sensors */ + ret = pmbus_add_sensor_attrs(client, data, "in", voltage_attributes, + ARRAY_SIZE(voltage_attributes)); + if (ret) + return ret; + + /* Current sensors */ + ret = pmbus_add_sensor_attrs(client, data, "curr", current_attributes, + ARRAY_SIZE(current_attributes)); + if (ret) + return ret; + + /* Power sensors */ + ret = pmbus_add_sensor_attrs(client, data, "power", power_attributes, + ARRAY_SIZE(power_attributes)); + if (ret) + return ret; + + /* Temperature sensors */ + ret = pmbus_add_sensor_attrs(client, data, "temp", temp_attributes, + ARRAY_SIZE(temp_attributes)); + if (ret) + return ret; + + /* Fans */ + ret = pmbus_add_fan_attributes(client, data); + if (ret) + return ret; + + /* Manufacturer strings */ + ret = pmbus_add_mfr_attributes(client, data); + return ret; +} + +/* + * Identify chip parameters. + * This function is called for all chips. + */ +static int pmbus_identify_common(struct i2c_client *client, + struct pmbus_data *data) +{ + int vout_mode = -1; + + if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) + vout_mode = _pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + /* + * Not all chips support the VOUT_MODE command, + * so a failure to read it is not an error. + */ + switch (vout_mode >> 5) { + case 0: /* linear mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != linear) + return -ENODEV; + + data->exponent = ((s8)(vout_mode << 3)) >> 3; + break; + case 1: /* VID mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != vid) + return -ENODEV; + break; + case 2: /* direct mode */ + if (data->info->format[PSC_VOLTAGE_OUT] != direct) + return -ENODEV; + break; + default: + return -ENODEV; + } + } + + pmbus_clear_fault_page(client, 0); + return 0; +} + +static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, + struct pmbus_driver_info *info) +{ + struct device *dev = &client->dev; + int ret; + + /* + * Some PMBus chips don't support PMBUS_STATUS_BYTE, so try + * to use PMBUS_STATUS_WORD instead if that is the case. + * Bail out if both registers are not supported. + */ + data->status_register = PMBUS_STATUS_WORD; + pmbus_wait(client); + ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD); + pmbus_update_wait(client); + if (ret < 0 || ret == 0xffff) { + data->status_register = PMBUS_STATUS_BYTE; + pmbus_wait(client); + ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); + pmbus_update_wait(client); + if (ret < 0 || ret == 0xff) { + dev_err(dev, "PMBus status register not found\n"); + return -ENODEV; + } + } + + pmbus_clear_faults(client); + + if (info->identify) { + ret = (*info->identify)(client, info); + if (ret < 0) { + dev_err(dev, "Chip identification failed\n"); + return ret; + } + } + + if (info->pages <= 0 || info->pages > PMBUS_PAGES) { + dev_err(dev, "Bad number of PMBus pages: %d\n", info->pages); + return -ENODEV; + } + + ret = pmbus_identify_common(client, data); + if (ret < 0) { + dev_err(dev, "Failed to identify chip capabilities\n"); + return ret; + } + return 0; +} + +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info) +{ + struct device *dev = &client->dev; + const struct pmbus_platform_data *pdata = dev->platform_data; + struct pmbus_data *data; + int ret; + + if (!info) + return -ENODEV; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->dev = dev; + + if (pdata) + data->flags = pdata->flags; + data->info = info; + pmbus_update_wait(client); /* Set time of access if info->delay */ + + ret = pmbus_init_common(client, data, info); + if (ret < 0) + return ret; + + ret = pmbus_find_attributes(client, data); + if (ret) + goto out_kfree; + + /* + * If there are no attributes, something is wrong. + * Bail out instead of trying to register nothing. + */ + if (!data->num_attributes) { + dev_err(dev, "No attributes found\n"); + ret = -ENODEV; + goto out_kfree; + } + + /* Register sysfs hooks */ + ret = sysfs_create_group(&dev->kobj, &data->group); + if (ret) { + dev_err(dev, "Failed to create sysfs entries\n"); + goto out_kfree; + } + data->hwmon_dev = hwmon_device_register(dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + dev_err(dev, "Failed to register hwmon device\n"); + goto out_hwmon_device_register; + } + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&dev->kobj, &data->group); +out_kfree: + kfree(data->group.attrs); + return ret; +} +EXPORT_SYMBOL_GPL(pmbus_do_probe); + +int pmbus_do_remove(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &data->group); + kfree(data->group.attrs); + return 0; +} +EXPORT_SYMBOL_GPL(pmbus_do_remove); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c new file mode 100644 index 000000000000..fbb1479d3ad4 --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -0,0 +1,246 @@ +/* + * Hardware monitoring driver for UCD90xxx Sequencer and System Health + * Controller series + * + * Copyright (C) 2011 Ericsson AB. + * + * 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 +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { ucd9000, ucd90120, ucd90124, ucd9090, ucd90910 }; + +#define UCD9000_MONITOR_CONFIG 0xd5 +#define UCD9000_NUM_PAGES 0xd6 +#define UCD9000_FAN_CONFIG_INDEX 0xe7 +#define UCD9000_FAN_CONFIG 0xe8 +#define UCD9000_DEVICE_ID 0xfd + +#define UCD9000_MON_TYPE(x) (((x) >> 5) & 0x07) +#define UCD9000_MON_PAGE(x) ((x) & 0x0f) + +#define UCD9000_MON_VOLTAGE 1 +#define UCD9000_MON_TEMPERATURE 2 +#define UCD9000_MON_CURRENT 3 +#define UCD9000_MON_VOLTAGE_HW 4 + +#define UCD9000_NUM_FAN 4 + +struct ucd9000_data { + u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; + struct pmbus_driver_info info; +}; +#define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) + +static int ucd9000_get_fan_config(struct i2c_client *client, int fan) +{ + int fan_config = 0; + struct ucd9000_data *data + = to_ucd9000_data(pmbus_get_driver_info(client)); + + if (data->fan_data[fan][3] & 1) + fan_config |= PB_FAN_2_INSTALLED; /* Use lower bit position */ + + /* Pulses/revolution */ + fan_config |= (data->fan_data[fan][3] & 0x06) >> 1; + + return fan_config; +} + +static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int fan_config; + + switch (reg) { + case PMBUS_FAN_CONFIG_12: + if (page > 0) + return -ENXIO; + + ret = ucd9000_get_fan_config(client, 0); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 1); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + case PMBUS_FAN_CONFIG_34: + if (page > 0) + return -ENXIO; + + ret = ucd9000_get_fan_config(client, 2); + if (ret < 0) + return ret; + fan_config = ret << 4; + ret = ucd9000_get_fan_config(client, 3); + if (ret < 0) + return ret; + fan_config |= ret; + ret = fan_config; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static const struct i2c_device_id ucd9000_id[] = { + {"ucd9000", ucd9000}, + {"ucd90120", ucd90120}, + {"ucd90124", ucd90124}, + {"ucd9090", ucd9090}, + {"ucd90910", ucd90910}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9000_id); + +static int ucd9000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct ucd9000_data *data; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + int i, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9000_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + for (mid = ucd9000_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + + if (id->driver_data != ucd9000 && id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + data = devm_kzalloc(&client->dev, sizeof(struct ucd9000_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + info = &data->info; + + ret = i2c_smbus_read_byte_data(client, UCD9000_NUM_PAGES); + if (ret < 0) { + dev_err(&client->dev, + "Failed to read number of active pages\n"); + return ret; + } + info->pages = ret; + if (!info->pages) { + dev_err(&client->dev, "No pages configured\n"); + return -ENODEV; + } + + /* The internal temperature sensor is always active */ + info->func[0] = PMBUS_HAVE_TEMP; + + /* Everything else is configurable */ + ret = i2c_smbus_read_block_data(client, UCD9000_MONITOR_CONFIG, + block_buffer); + if (ret <= 0) { + dev_err(&client->dev, "Failed to read configuration data\n"); + return -ENODEV; + } + for (i = 0; i < ret; i++) { + int page = UCD9000_MON_PAGE(block_buffer[i]); + + if (page >= info->pages) + continue; + + switch (UCD9000_MON_TYPE(block_buffer[i])) { + case UCD9000_MON_VOLTAGE: + case UCD9000_MON_VOLTAGE_HW: + info->func[page] |= PMBUS_HAVE_VOUT + | PMBUS_HAVE_STATUS_VOUT; + break; + case UCD9000_MON_TEMPERATURE: + info->func[page] |= PMBUS_HAVE_TEMP2 + | PMBUS_HAVE_STATUS_TEMP; + break; + case UCD9000_MON_CURRENT: + info->func[page] |= PMBUS_HAVE_IOUT + | PMBUS_HAVE_STATUS_IOUT; + break; + default: + break; + } + } + + /* Fan configuration */ + if (mid->driver_data == ucd90124) { + for (i = 0; i < UCD9000_NUM_FAN; i++) { + i2c_smbus_write_byte_data(client, + UCD9000_FAN_CONFIG_INDEX, i); + ret = i2c_smbus_read_block_data(client, + UCD9000_FAN_CONFIG, + data->fan_data[i]); + if (ret < 0) + return ret; + } + i2c_smbus_write_byte_data(client, UCD9000_FAN_CONFIG_INDEX, 0); + + info->read_byte_data = ucd9000_read_byte_data; + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 + | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34; + } + + return pmbus_do_probe(client, mid, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9000_driver = { + .driver = { + .name = "ucd9000", + }, + .probe = ucd9000_probe, + .remove = pmbus_do_remove, + .id_table = ucd9000_id, +}; + +module_i2c_driver(ucd9000_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD90xxx"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ucd9200.c b/drivers/hwmon/pmbus/ucd9200.c new file mode 100644 index 000000000000..033d6aca47d3 --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -0,0 +1,180 @@ +/* + * Hardware monitoring driver for ucd9200 series Digital PWM System Controllers + * + * Copyright (C) 2011 Ericsson AB. + * + * 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 +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define UCD9200_PHASE_INFO 0xd2 +#define UCD9200_DEVICE_ID 0xfd + +enum chips { ucd9200, ucd9220, ucd9222, ucd9224, ucd9240, ucd9244, ucd9246, + ucd9248 }; + +static const struct i2c_device_id ucd9200_id[] = { + {"ucd9200", ucd9200}, + {"ucd9220", ucd9220}, + {"ucd9222", ucd9222}, + {"ucd9224", ucd9224}, + {"ucd9240", ucd9240}, + {"ucd9244", ucd9244}, + {"ucd9246", ucd9246}, + {"ucd9248", ucd9248}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ucd9200_id); + +static int ucd9200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct pmbus_driver_info *info; + const struct i2c_device_id *mid; + int i, j, ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, UCD9200_DEVICE_ID, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + block_buffer[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", block_buffer); + + for (mid = ucd9200_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + if (id->driver_data != ucd9200 && id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = i2c_smbus_read_block_data(client, UCD9200_PHASE_INFO, + block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read phase information\n"); + return ret; + } + + /* + * Calculate number of configured pages (rails) from PHASE_INFO + * register. + * Rails have to be sequential, so we can abort after finding + * the first unconfigured rail. + */ + info->pages = 0; + for (i = 0; i < ret; i++) { + if (!block_buffer[i]) + break; + info->pages++; + } + if (!info->pages) { + dev_err(&client->dev, "No rails configured\n"); + return -ENODEV; + } + dev_info(&client->dev, "%d rails configured\n", info->pages); + + /* + * Set PHASE registers on all pages to 0xff to ensure that phase + * specific commands will apply to all phases of a given page (rail). + * This only affects the READ_IOUT and READ_TEMPERATURE2 registers. + * READ_IOUT will return the sum of currents of all phases of a rail, + * and READ_TEMPERATURE2 will return the maximum temperature detected + * for the the phases of the rail. + */ + for (i = 0; i < info->pages; i++) { + /* + * Setting PAGE & PHASE fails once in a while for no obvious + * reason, so we need to retry a couple of times. + */ + for (j = 0; j < 3; j++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + continue; + ret = i2c_smbus_write_byte_data(client, PMBUS_PHASE, + 0xff); + if (ret < 0) + continue; + break; + } + if (ret < 0) { + dev_err(&client->dev, + "Failed to initialize PHASE registers\n"); + return ret; + } + } + if (info->pages > 1) + i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + for (i = 1; i < info->pages; i++) + info->func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + + /* ucd9240 supports a single fan */ + if (mid->driver_data == ucd9240) + info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12; + + return pmbus_do_probe(client, mid, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9200_driver = { + .driver = { + .name = "ucd9200", + }, + .probe = ucd9200_probe, + .remove = pmbus_do_remove, + .id_table = ucd9200_id, +}; + +module_i2c_driver(ucd9200_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD922x, UCD924x"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c new file mode 100644 index 000000000000..819644121259 --- /dev/null +++ b/drivers/hwmon/pmbus/zl6100.c @@ -0,0 +1,419 @@ +/* + * Hardware monitoring driver for ZL6100 and compatibles + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105, + zl9101, zl9117 }; + +struct zl6100_data { + int id; + ktime_t access; /* chip access time */ + int delay; /* Delay between chip accesses in uS */ + struct pmbus_driver_info info; +}; + +#define to_zl6100_data(x) container_of(x, struct zl6100_data, info) + +#define ZL6100_MFR_CONFIG 0xd0 +#define ZL6100_DEVICE_ID 0xe4 + +#define ZL6100_MFR_XTEMP_ENABLE (1 << 7) + +#define MFR_VMON_OV_FAULT_LIMIT 0xf5 +#define MFR_VMON_UV_FAULT_LIMIT 0xf6 +#define MFR_READ_VMON 0xf7 + +#define VMON_UV_WARNING (1 << 5) +#define VMON_OV_WARNING (1 << 4) +#define VMON_UV_FAULT (1 << 1) +#define VMON_OV_FAULT (1 << 0) + +#define ZL6100_WAIT_TIME 1000 /* uS */ + +static ushort delay = ZL6100_WAIT_TIME; +module_param(delay, ushort, 0644); +MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); + +/* Convert linear sensor value to milli-units */ +static long zl6100_l2d(s16 l) +{ + s16 exponent; + s32 mantissa; + long val; + + exponent = l >> 11; + mantissa = ((s16)((l & 0x7ff) << 5)) >> 5; + + val = mantissa; + + /* scale result to milli-units */ + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 zl6100_d2l(long val) +{ + s16 exponent = 0, mantissa; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (val < 0) { + negative = true; + val = -val; + } + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +/* Some chips need a delay between accesses */ +static inline void zl6100_wait(const struct zl6100_data *data) +{ + if (data->delay) { + s64 delta = ktime_us_delta(ktime_get(), data->access); + if (delta < data->delay) + udelay(data->delay - delta); + } +} + +static int zl6100_read_word_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret, vreg; + + if (page > 0) + return -ENXIO; + + if (data->id == zl2005) { + /* + * Limit register detection is not reliable on ZL2005. + * Make sure registers are not erroneously detected. + */ + switch (reg) { + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + return -ENXIO; + } + } + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + vreg = MFR_READ_VMON; + break; + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + break; + } + + zl6100_wait(data); + ret = pmbus_read_word_data(client, page, vreg); + data->access = ktime_get(); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 9, 10)); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 11, 10)); + break; + } + + return ret; +} + +static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret, status; + + if (page > 0) + return -ENXIO; + + zl6100_wait(data); + + switch (reg) { + case PMBUS_VIRT_STATUS_VMON: + ret = pmbus_read_byte_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (ret < 0) + break; + + status = 0; + if (ret & VMON_UV_WARNING) + status |= PB_VOLTAGE_UV_WARNING; + if (ret & VMON_OV_WARNING) + status |= PB_VOLTAGE_OV_WARNING; + if (ret & VMON_UV_FAULT) + status |= PB_VOLTAGE_UV_FAULT; + if (ret & VMON_OV_FAULT) + status |= PB_VOLTAGE_OV_FAULT; + ret = status; + break; + default: + ret = pmbus_read_byte_data(client, page, reg); + break; + } + data->access = ktime_get(); + + return ret; +} + +static int zl6100_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret, vreg; + + if (page > 0) + return -ENXIO; + + switch (reg) { + case PMBUS_VIRT_VMON_OV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 9)); + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: + vreg = MFR_VMON_OV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_WARN_LIMIT: + word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 11)); + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: + vreg = MFR_VMON_UV_FAULT_LIMIT; + pmbus_clear_cache(client); + break; + default: + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + vreg = reg; + } + + zl6100_wait(data); + ret = pmbus_write_word_data(client, page, vreg, word); + data->access = ktime_get(); + + return ret; +} + +static int zl6100_write_byte(struct i2c_client *client, int page, u8 value) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct zl6100_data *data = to_zl6100_data(info); + int ret; + + if (page > 0) + return -ENXIO; + + zl6100_wait(data); + ret = pmbus_write_byte(client, page, value); + data->access = ktime_get(); + + return ret; +} + +static const struct i2c_device_id zl6100_id[] = { + {"bmr450", zl2005}, + {"bmr451", zl2005}, + {"bmr462", zl2008}, + {"bmr463", zl2008}, + {"bmr464", zl2008}, + {"zl2004", zl2004}, + {"zl2005", zl2005}, + {"zl2006", zl2006}, + {"zl2008", zl2008}, + {"zl2105", zl2105}, + {"zl2106", zl2106}, + {"zl6100", zl6100}, + {"zl6105", zl6105}, + {"zl9101", zl9101}, + {"zl9117", zl9117}, + { } +}; +MODULE_DEVICE_TABLE(i2c, zl6100_id); + +static int zl6100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct zl6100_data *data; + struct pmbus_driver_info *info; + u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; + const struct i2c_device_id *mid; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA + | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) + return -ENODEV; + + ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID, + device_id); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device ID\n"); + return ret; + } + device_id[ret] = '\0'; + dev_info(&client->dev, "Device ID %s\n", device_id); + + mid = NULL; + for (mid = zl6100_id; mid->name[0]; mid++) { + if (!strncasecmp(mid->name, device_id, strlen(mid->name))) + break; + } + if (!mid->name[0]) { + dev_err(&client->dev, "Unsupported device\n"); + return -ENODEV; + } + if (id->driver_data != mid->driver_data) + dev_notice(&client->dev, + "Device mismatch: Configured %s, detected %s\n", + id->name, mid->name); + + data = devm_kzalloc(&client->dev, sizeof(struct zl6100_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->id = mid->driver_data; + + /* + * According to information from the chip vendor, all currently + * supported chips are known to require a wait time between I2C + * accesses. + */ + data->delay = delay; + + /* + * Since there was a direct I2C device access above, wait before + * accessing the chip again. + */ + data->access = ktime_get(); + zl6100_wait(data); + + info = &data->info; + + info->pages = 1; + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT + | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + /* + * ZL2004, ZL9101M, and ZL9117M support monitoring an extra voltage + * (VMON for ZL2004, VDRV for ZL9101M and ZL9117M). Report it as vmon. + */ + if (data->id == zl2004 || data->id == zl9101 || data->id == zl9117) + info->func[0] |= PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON; + + ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG); + if (ret < 0) + return ret; + + if (ret & ZL6100_MFR_XTEMP_ENABLE) + info->func[0] |= PMBUS_HAVE_TEMP2; + + data->access = ktime_get(); + zl6100_wait(data); + + info->read_word_data = zl6100_read_word_data; + info->read_byte_data = zl6100_read_byte_data; + info->write_word_data = zl6100_write_word_data; + info->write_byte = zl6100_write_byte; + + return pmbus_do_probe(client, mid, info); +} + +static struct i2c_driver zl6100_driver = { + .driver = { + .name = "zl6100", + }, + .probe = zl6100_probe, + .remove = pmbus_do_remove, + .id_table = zl6100_id, +}; + +module_i2c_driver(zl6100_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-ast.c b/drivers/i2c/busses/i2c-ast.c index bccf5a33bc1e..7a083de714df 100644 --- a/drivers/i2c/busses/i2c-ast.c +++ b/drivers/i2c/busses/i2c-ast.c @@ -88,8 +88,10 @@ struct ast_i2c_dev { struct i2c_msg slave_rx_msg[I2C_S_RX_BUF_NUM + 1]; struct i2c_msg slave_tx_msg; +static spinlock_t slave_rx_lock = SPIN_LOCK_UNLOCKED; #endif +static spinlock_t g_master_lock = SPIN_LOCK_UNLOCKED; static inline void ast_i2c_write(struct ast_i2c_dev *i2c_dev, u32 val, u32 reg) @@ -243,8 +245,9 @@ static void ast_i2c_slave_buff_init(struct ast_i2c_dev *i2c_dev) static void ast_i2c_slave_rdwr_xfer(struct ast_i2c_dev *i2c_dev) { int i; - spinlock_t lock; - spin_lock(&lock); + unsigned long flags; + + spin_lock_irqsave(&slave_rx_lock, flags); switch(i2c_dev->slave_event) { case I2C_SLAVE_EVENT_START_WRITE: @@ -291,7 +294,7 @@ static void ast_i2c_slave_rdwr_xfer(struct ast_i2c_dev *i2c_dev) i2c_dev->slave_msgs = &slave_tx_msg; break; } - spin_unlock(&lock); + spin_unlock_irqrestore(&slave_rx_lock, flags); } @@ -299,11 +302,14 @@ static int ast_i2c_slave_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs) { struct ast_i2c_dev *i2c_dev = adap->algo_data; int ret=0, i; + unsigned long flags; switch(msgs->flags) { case 0: // printk("slave read \n"); //cur_msg = get_free_msg; + spin_lock_irqsave(&slave_rx_lock, flags); + for(i=0; ibuf, slave_rx_msg[i].buf, slave_rx_msg[i].len); @@ -313,6 +319,7 @@ static int ast_i2c_slave_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs) break; } } + spin_unlock_irqrestore(&slave_rx_lock, flags); if(i == I2C_S_RX_BUF_NUM) { printk("No buffer ........ \n"); @@ -378,13 +385,15 @@ ast_i2c_bus_error_recover(struct ast_i2c_dev *i2c_dev) dev_dbg(i2c_dev->dev, "I2C's master is locking the bus, try to stop it.\n"); // init_completion(&i2c_dev->cmd_complete); + i2c_dev->cmd_err = 0; ast_i2c_write(i2c_dev, AST_I2CD_M_STOP_CMD, I2C_CMD_REG); r = wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, i2c_dev->adap.timeout*HZ); - if(i2c_dev->cmd_err) { + if(i2c_dev->cmd_err && + i2c_dev->cmd_err != AST_I2CD_INTR_STS_NORMAL_STOP) { dev_dbg(i2c_dev->dev, "recovery error \n"); return -1; } @@ -411,7 +420,8 @@ ast_i2c_bus_error_recover(struct ast_i2c_dev *i2c_dev) r = wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, i2c_dev->adap.timeout*HZ); - if (i2c_dev->cmd_err != 0) { + if (i2c_dev->cmd_err != 0 && + i2c_dev->cmd_err != AST_I2CD_INTR_STS_NORMAL_STOP) { dev_dbg(i2c_dev->dev, "ERROR!! Failed to do recovery command(0x%08x)\n", i2c_dev->cmd_err); return -1; } @@ -621,7 +631,9 @@ static void ast_i2c_do_dma_xfer(struct ast_i2c_dev *i2c_dev) }else { //should send next msg if(i2c_dev->master_xfer_cnt != i2c_dev->master_msgs->len) - printk("complete rx ... ERROR \n"); + printk("complete rx ... bus=%d addr=0x%x (%d vs. %d) ERROR\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); dev_dbg(i2c_dev->dev, "ast_i2c_do_byte_xfer complete \n"); i2c_dev->cmd_err = 0; @@ -812,7 +824,9 @@ static void ast_i2c_do_pool_xfer(struct ast_i2c_dev *i2c_dev) } else { //should send next msg if(i2c_dev->master_xfer_cnt != i2c_dev->master_msgs->len) - printk("complete rx ... ERROR \n"); + printk("complete rx ... bus=%d addr=0x%x (%d vs. %d) ERROR\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); dev_dbg(i2c_dev->dev, "ast_i2c_do_byte_xfer complete \n"); i2c_dev->cmd_err = 0; @@ -909,7 +923,9 @@ static void ast_i2c_do_byte_xfer(struct ast_i2c_dev *i2c_dev) } else { //should send next msg if(i2c_dev->master_xfer_cnt != i2c_dev->master_msgs->len) - printk("CNT ERROR \n"); + printk("CNT ERROR bus=%d addr=0x%x (%d vs. %d)\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + i2c_dev->master_xfer_cnt, i2c_dev->master_msgs->len); dev_dbg(i2c_dev->dev, "ast_i2c_do_byte_xfer complete \n"); i2c_dev->cmd_err = 0; @@ -1039,6 +1055,18 @@ static void ast_i2c_master_xfer_done(struct ast_i2c_dev *i2c_dev) u32 xfer_len; int i; u8 *pool_buf; + unsigned long flags; + + spin_lock_irqsave(&g_master_lock, flags); + + /* + * This function shall be involked during interrupt handling. + * Since the interrupt could be fired at anytime, we will need to make sure + * we have the buffer (i2c_dev->master_msgs) to handle the results. + */ + if (!i2c_dev->master_msgs) { + goto unlock_out; + } dev_dbg(i2c_dev->dev, "ast_i2c_master_xfer_done mode[%d]\n",i2c_dev->master_xfer_mode); @@ -1118,7 +1146,9 @@ next_xfer: if(xfer_len !=i2c_dev->master_xfer_len) { //TODO.. - printk(" ** xfer error \n"); + printk(" ** xfer error bus=%d addr=0x%x (%d vs. %d)\n", + i2c_dev->bus_id, i2c_dev->master_msgs->addr, + xfer_len, i2c_dev->master_xfer_len); //should goto stop.... i2c_dev->cmd_err = 1; goto done_out; @@ -1142,6 +1172,9 @@ done_out: dev_dbg(i2c_dev->dev,"msgs complete \n"); complete(&i2c_dev->cmd_complete); } + +unlock_out: + spin_unlock_irqrestore(&g_master_lock, flags); } static void ast_i2c_slave_addr_match(struct ast_i2c_dev *i2c_dev) @@ -1214,6 +1247,18 @@ static irqreturn_t i2c_ast_handler(int this_irq, void *dev_id) sts &= ~AST_I2CD_SMBUS_ALT_INTR_EN; } + if(AST_I2CD_INTR_STS_ABNORMAL & sts) { + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_ABNORMAL; + // Turn off interrupts for further abnormal + // conditions until we fix this one. + ast_i2c_write(i2c_dev, + ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) & + ~AST_I2CD_ABNORMAL_INTR_EN, + I2C_INTR_CTRL_REG); + complete(&i2c_dev->cmd_complete); + sts &= ~AST_I2CD_INTR_STS_ABNORMAL; + } + switch(sts) { case AST_I2CD_INTR_STS_TX_ACK: if(i2c_dev->slave_operation == 1) { @@ -1251,12 +1296,12 @@ static irqreturn_t i2c_ast_handler(int this_irq, void *dev_id) } else { dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_TX_NAK = %x\n",sts); ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK, I2C_INTR_STS_REG); - if(i2c_dev->master_msgs->flags == I2C_M_IGNORE_NAK) { + if (i2c_dev->master_msgs + && i2c_dev->master_msgs->flags & I2C_M_IGNORE_NAK) { dev_dbg(i2c_dev->dev, "I2C_M_IGNORE_NAK next send\n"); - i2c_dev->cmd_err = 0; } else { dev_dbg(i2c_dev->dev, "NAK error\n"); - i2c_dev->cmd_err = AST_I2CD_INTR_STS_TX_NAK; + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_TX_NAK; } complete(&i2c_dev->cmd_complete); } @@ -1270,7 +1315,7 @@ static irqreturn_t i2c_ast_handler(int this_irq, void *dev_id) dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_TX_NAK| AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); dev_dbg(i2c_dev->dev, "M TX NAK | NORMAL STOP \n"); - i2c_dev->cmd_err = AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP; + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_TX_NAK | AST_I2CD_INTR_STS_NORMAL_STOP; complete(&i2c_dev->cmd_complete); } break; @@ -1316,39 +1361,34 @@ static irqreturn_t i2c_ast_handler(int this_irq, void *dev_id) } else { dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); - i2c_dev->cmd_err = 0; + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_NORMAL_STOP; complete(&i2c_dev->cmd_complete); } break; case (AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP): - if((i2c_dev->xfer_last == 1) && (i2c_dev->slave_operation == 0)) { + /* Whether or not we're done, the hardware thinks we're done, so bail. */ + if(i2c_dev->slave_operation == 0) { dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP = %x\n",sts); ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_RX_DOWN | AST_I2CD_INTR_STS_NORMAL_STOP, I2C_INTR_STS_REG); //take care ast_i2c_write(i2c_dev, ast_i2c_read(i2c_dev,I2C_INTR_CTRL_REG) | AST_I2CD_RX_DOWN_INTR_EN, I2C_INTR_CTRL_REG); ast_i2c_master_xfer_done(i2c_dev); - } else { - printk("TODO .. .. ..\n"); } break; case AST_I2CD_INTR_STS_ARBIT_LOSS: dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_ARBIT_LOSS = %x\n",sts); ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_ARBIT_LOSS, I2C_INTR_STS_REG); - i2c_dev->cmd_err = AST_I2CD_INTR_STS_ARBIT_LOSS; - complete(&i2c_dev->cmd_complete); - break; - case AST_I2CD_INTR_STS_ABNORMAL: - i2c_dev->cmd_err = AST_I2CD_INTR_STS_ABNORMAL; + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_ARBIT_LOSS; complete(&i2c_dev->cmd_complete); break; case AST_I2CD_INTR_STS_SCL_TO: - i2c_dev->cmd_err = AST_I2CD_INTR_STS_SCL_TO; + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_SCL_TO; complete(&i2c_dev->cmd_complete); break; case AST_I2CD_INTR_STS_GCALL_ADDR: - i2c_dev->cmd_err = AST_I2CD_INTR_STS_GCALL_ADDR; + i2c_dev->cmd_err |= AST_I2CD_INTR_STS_GCALL_ADDR; complete(&i2c_dev->cmd_complete); break; @@ -1365,7 +1405,6 @@ static irqreturn_t i2c_ast_handler(int this_irq, void *dev_id) case AST_I2CD_INTR_STS_BUS_RECOVER: dev_dbg(i2c_dev->dev, "M clear isr: AST_I2CD_INTR_STS_BUS_RECOVER= %x\n",sts); ast_i2c_write(i2c_dev, AST_I2CD_INTR_STS_BUS_RECOVER, I2C_INTR_STS_REG); - i2c_dev->cmd_err = 0; complete(&i2c_dev->cmd_complete); break; default: @@ -1382,6 +1421,9 @@ static int ast_i2c_do_msgs_xfer(struct ast_i2c_dev *i2c_dev, struct i2c_msg *msg { int i; int ret = 1; + unsigned long flags; + + spin_lock_irqsave(&g_master_lock, flags); //request if(i2c_dev->ast_i2c_data->master_dma == BYTE_MODE) @@ -1407,6 +1449,7 @@ static int ast_i2c_do_msgs_xfer(struct ast_i2c_dev *i2c_dev, struct i2c_msg *msg i2c_dev->blk_r_flag = 0; init_completion(&i2c_dev->cmd_complete); + i2c_dev->cmd_err = 0; if(i2c_dev->master_msgs->flags & I2C_M_NOSTART) i2c_dev->master_xfer_cnt = 0; @@ -1415,31 +1458,43 @@ static int ast_i2c_do_msgs_xfer(struct ast_i2c_dev *i2c_dev, struct i2c_msg *msg i2c_dev->do_master_xfer(i2c_dev); + spin_unlock_irqrestore(&g_master_lock, flags); + ret = wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, i2c_dev->adap.timeout*HZ); + spin_lock_irqsave(&g_master_lock, flags); + i2c_dev->master_msgs = NULL; + if (ret == 0) { dev_dbg(i2c_dev->dev, "controller timed out\n"); i2c_dev->state = (ast_i2c_read(i2c_dev,I2C_CMD_REG) >> 19) & 0xf; // printk("sts [%x], isr sts [%x] \n",i2c_dev->state, ast_i2c_read(i2c_dev,I2C_INTR_STS_REG)); ret = -ETIMEDOUT; + spin_unlock_irqrestore(&g_master_lock, flags); goto stop; } - if(i2c_dev->cmd_err != 0) { + if(i2c_dev->cmd_err != 0 && + i2c_dev->cmd_err != AST_I2CD_INTR_STS_NORMAL_STOP) { ret = -EAGAIN; + spin_unlock_irqrestore(&g_master_lock, flags); goto stop; } - } - if(i2c_dev->cmd_err == 0) { + spin_unlock_irqrestore(&g_master_lock, flags); + + if(i2c_dev->cmd_err == 0 || + i2c_dev->cmd_err == AST_I2CD_INTR_STS_NORMAL_STOP) { ret = num; goto out; } stop: init_completion(&i2c_dev->cmd_complete); + if(i2c_dev->cmd_err & AST_I2CD_INTR_STS_NORMAL_STOP) + goto out; ast_i2c_write(i2c_dev, AST_I2CD_M_STOP_CMD, I2C_CMD_REG); wait_for_completion_interruptible_timeout(&i2c_dev->cmd_complete, i2c_dev->adap.timeout*HZ); @@ -1610,6 +1665,8 @@ static int ast_i2c_probe(struct platform_device *pdev) i2c_dev->blk_r_flag = 0; i2c_dev->adap.algo = &i2c_ast_algorithm; + ast_i2c_dev_init(i2c_dev); + ret = request_irq(i2c_dev->irq, i2c_ast_handler, IRQF_SHARED, i2c_dev->adap.name, i2c_dev); if (ret) { @@ -1617,8 +1674,6 @@ static int ast_i2c_probe(struct platform_device *pdev) goto ereqirq; } - ast_i2c_dev_init(i2c_dev); - #ifdef CONFIG_AST_I2C_SLAVE_RDWR ast_i2c_slave_buff_init(i2c_dev); #endif diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 5ced92c864f5..c8e3cf6606fe 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -1842,8 +1842,8 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr, need to use only one message; when reading, we need two. We initialize most things with sane defaults, to keep the code below somewhat simpler. */ - unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3]; - unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; + unsigned char msgbuf0[I2C_SMBUS_BLOCK_LARGE_MAX+3]; + unsigned char msgbuf1[I2C_SMBUS_BLOCK_LARGE_MAX+2]; int num = read_write == I2C_SMBUS_READ?2:1; struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 }, { addr, flags | I2C_M_RD, 0, msgbuf1 } @@ -1910,6 +1910,23 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr, msgbuf0[i] = data->block[i-1]; } break; + case I2C_SMBUS_BLOCK_LARGE_DATA: + if (read_write == I2C_SMBUS_READ) { + msg[1].flags |= I2C_M_RECV_LEN; + msg[1].len = 1; /* block length will be added by + the underlying bus driver */ + } else { + msg[0].len = data->block[0] + 2; + if (msg[0].len > I2C_SMBUS_BLOCK_LARGE_MAX + 2) { + dev_err(&adapter->dev, + "Invalid large block write size %d\n", + data->block[0]); + return -EINVAL; + } + for (i = 1; i < msg[0].len; i++) + msgbuf0[i] = data->block[i-1]; + } + break; case I2C_SMBUS_BLOCK_PROC_CALL: num = 2; /* Another special case */ read_write = I2C_SMBUS_READ; @@ -1989,6 +2006,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr, data->block[i+1] = msgbuf1[i]; break; case I2C_SMBUS_BLOCK_DATA: + case I2C_SMBUS_BLOCK_LARGE_DATA: case I2C_SMBUS_BLOCK_PROC_CALL: for (i = 0; i < msgbuf1[0] + 1; i++) data->block[i] = msgbuf1[i]; diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index 7d1f7e91f58c..07f393ddaa42 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -245,8 +245,7 @@ static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client, for (i = 0; i < rdwr_arg.nmsgs; i++) { /* Limit the size of the message to a sane amount; * and don't let length change either. */ - if ((rdwr_pa[i].len > 8192) || - (rdwr_pa[i].flags & I2C_M_RECV_LEN)) { + if (rdwr_pa[i].len > 8192) { res = -EINVAL; break; } @@ -262,6 +261,31 @@ static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client, res = -EFAULT; break; } + + /* From Linux 3.5: */ + /* + * If the message length is received from the slave (similar + * to SMBus block read), we must ensure that the buffer will + * be large enough to cope with a message length of + * I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus + * drivers allow. The first byte in the buffer must be + * pre-filled with the number of extra bytes, which must be + * at least one to hold the message length, but can be + * greater (for example to account for a checksum byte at + * the end of the message.) + */ + if (rdwr_pa[i].flags & I2C_M_RECV_LEN) { + if (!(rdwr_pa[i].flags & I2C_M_RD) || + rdwr_pa[i].buf[0] < 1 || + rdwr_pa[i].len < rdwr_pa[i].buf[0] + + I2C_SMBUS_BLOCK_MAX) { + res = -EINVAL; + break; + } + + rdwr_pa[i].len = rdwr_pa[i].buf[0]; + } + } if (res < 0) { int j; @@ -290,7 +314,7 @@ static noinline int i2cdev_ioctl_smbus(struct i2c_client *client, unsigned long arg) { struct i2c_smbus_ioctl_data data_arg; - union i2c_smbus_data temp; + union i2c_smbus_large_data temp; int datasize, res; if (copy_from_user(&data_arg, @@ -303,6 +327,7 @@ static noinline int i2cdev_ioctl_smbus(struct i2c_client *client, (data_arg.size != I2C_SMBUS_WORD_DATA) && (data_arg.size != I2C_SMBUS_PROC_CALL) && (data_arg.size != I2C_SMBUS_BLOCK_DATA) && + (data_arg.size != I2C_SMBUS_BLOCK_LARGE_DATA) && (data_arg.size != I2C_SMBUS_I2C_BLOCK_BROKEN) && (data_arg.size != I2C_SMBUS_I2C_BLOCK_DATA) && (data_arg.size != I2C_SMBUS_BLOCK_PROC_CALL)) { @@ -343,6 +368,8 @@ static noinline int i2cdev_ioctl_smbus(struct i2c_client *client, else if ((data_arg.size == I2C_SMBUS_WORD_DATA) || (data_arg.size == I2C_SMBUS_PROC_CALL)) datasize = sizeof(data_arg.data->word); + else if (data_arg.size == I2C_SMBUS_BLOCK_LARGE_DATA) + datasize = sizeof(union i2c_smbus_large_data); else /* size == smbus block, i2c block, or block proc. call */ datasize = sizeof(data_arg.data->block); diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 45cb05ad8f3d..10450cd0621f 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -503,15 +503,6 @@ static struct flash_info __devinitdata m25p_data [] = { { "at26df161a", 0x1f4601, 0, 64 * 1024, 32, SECT_4K, }, { "at26df321", 0x1f4701, 0, 64 * 1024, 64, SECT_4K, }, - /* Macronix */ - { "mx25l4005a", 0xc22013, 0, 64 * 1024, 8, SECT_4K }, - { "mx25l3205d", 0xc22016, 0, 64 * 1024, 64, 0 }, - { "mx25l6405d", 0xc22017, 0, 64 * 1024, 128, 0 }, - { "mx25l12805d", 0xc22018, 0, 64 * 1024, 256, 0 }, - - { "mx25l12855e", 0xc22618, 0, 64 * 1024, 256, 0 }, - { "mx25l25635e", 0xc22019, 0, 64 * 1024, 512, 0 }, - /* Spansion -- single (large) sector size only, at least * for the chips listed here (without boot sectors). */ @@ -637,7 +628,12 @@ static int __devinit m25p_probe(struct spi_device *spi) dev_warn(&spi->dev, "found %s, expected %s\n", chip ? chip->name : "UNKNOWN", info->name); - info = NULL; + if (chip) { + dev_warn(&spi->dev, "Use %s instead\n", chip->name); + info = chip; + } else { + dev_warn(&spi->dev, "Force to use %s\n", info->name); + } } } } else diff --git a/drivers/net/ftgmac100_26.c b/drivers/net/ftgmac100_26.c index 8575293015e7..fdc77fca1b5d 100644 --- a/drivers/net/ftgmac100_26.c +++ b/drivers/net/ftgmac100_26.c @@ -120,6 +120,8 @@ #err "Not define include for GMAC" #endif +#define PHY_DEFAULT_ADDR 0x1F + /*------------------------------------------------------------------------ . . Configuration options, for the experienced user to change. @@ -269,9 +271,11 @@ no_phy_access: if (out->miiPhyId == 0xFFFF) { //Realtek PHY at address 1 out->phyAddr = 1; } +#if 0 if (out->miiPhyId == 0x0362) { out->phyAddr = 1; } +#endif out->miiPhyId = ftgmac100_read_phy_register(dev->base_addr, out->phyAddr, 0x02); out->miiPhyId = (out->miiPhyId & 0xffff) << 16; out->miiPhyId |= ftgmac100_read_phy_register(dev->base_addr, out->phyAddr, 0x03) & 0xffff; @@ -280,7 +284,7 @@ no_phy_access: case 0x0040: // Broadcom case 0x0141: // Marvell case 0x001c: // Realtek - case 0x0362: // BCM54612 + case 0x0362: // BCM54612, BCM54616 break; default: @@ -334,7 +338,8 @@ static void ftgmac100_reset( struct net_device* dev ) speed = (tmp & PHY_SPEED_mask)>>14; netif_carrier_on(dev); } - else if (priv->ids.miiPhyId == PHYID_BCM54612E) { + else if (priv->ids.miiPhyId == PHYID_BCM54612E + || priv->ids.miiPhyId == PHYID_BCM54616S) { // Get link status // First Switch shadow register selector ftgmac100_write_phy_register(dev->base_addr, priv->ids.phyAddr, 0x1C, 0x2000); @@ -558,7 +563,8 @@ Re_Get_Link_Status: ,dev->base_addr + IER_REG ); } - else if (priv->ids.miiPhyId == PHYID_BCM54612E) { + else if (priv->ids.miiPhyId == PHYID_BCM54612E + || priv->ids.miiPhyId == PHYID_BCM54616S) { outl( // no link PHY link status pin PHYSTS_CHG_bit | AHB_ERR_bit | @@ -612,7 +618,8 @@ static void aspeed_mac_timer(unsigned long data) duplex = (tmp & PHY_DUPLEX_mask)>>13; speed = (tmp & PHY_SPEED_mask)>>14; } - else if (priv->ids.miiPhyId == PHYID_BCM54612E) { + else if (priv->ids.miiPhyId == PHYID_BCM54612E + || priv->ids.miiPhyId == PHYID_BCM54616S) { // Get link status // First Switch shadow register selector ftgmac100_write_phy_register(dev->base_addr, priv->ids.phyAddr, 0x1C, 0x2000); @@ -913,6 +920,7 @@ static void ftgmac100_phy_configure(struct net_device* dev) case PHYID_VENDOR_BROADCOM: switch (priv->ids.miiPhyId) { case PHYID_BCM54612E: + case PHYID_BCM54616S: ftgmac100_write_phy_register(ioaddr, priv->ids.phyAddr, 0x1C, 0x8C00); // Disable GTXCLK Clock Delay Enable ftgmac100_write_phy_register(ioaddr, priv->ids.phyAddr, 0x18, 0xF0E7); // Disable RGMII RXD to RXC Skew break; @@ -1303,7 +1311,8 @@ static irqreturn_t ftgmac100_interrupt(int irq, void * dev_id, struct pt_regs * // Bits [3:1] are {duplex, speed, link} change interrupts. tmp &= 0x000e; } - else if (priv->ids.miiPhyId == PHYID_BCM54612E) { + else if (priv->ids.miiPhyId == PHYID_BCM54612E + || priv->ids.miiPhyId == PHYID_BCM54616S) { tmp = ftgmac100_read_phy_register(ioaddr, priv->ids.phyAddr, 0x1A); PRINTK("%s: PHY interrupt status, read_phy_reg(0x1A) = 0x%04x\n", dev->name, tmp); @@ -1644,7 +1653,8 @@ static int ftgmac100_open(struct net_device *netdev) if (((priv->ids.miiPhyId & PHYID_VENDOR_MASK) == PHYID_VENDOR_BROADCOM) || ((priv->ids.miiPhyId & PHYID_VENDOR_MODEL_MASK) == PHYID_RTL8201EL) || - (priv->ids.miiPhyId == PHYID_BCM54612E)) { + (priv->ids.miiPhyId == PHYID_BCM54612E) || + (priv->ids.miiPhyId == PHYID_BCM54616S)) { init_timer(&priv->timer); priv->timer.data = (unsigned long)netdev; diff --git a/drivers/net/ftgmac100_26.h b/drivers/net/ftgmac100_26.h index f145b05a4d43..0d470244ca51 100644 --- a/drivers/net/ftgmac100_26.h +++ b/drivers/net/ftgmac100_26.h @@ -62,7 +62,7 @@ // -------------------------------------------------------------------- -// ISR_REG ¤Î IMR_REG +// ISR_REG / IMR_REG // -------------------------------------------------------------------- #define HPTXBUF_UNAVA_bit (1UL<<10) #define PHYSTS_CHG_bit (1UL<<9) @@ -473,6 +473,7 @@ typedef struct #define PHYID_RTL8211 0x001cc910 #define PHYID_RTL8211E 0x001cc915 #define PHYID_BCM54612E 0x03625E6A +#define PHYID_BCM54616S 0x03625D12 /* store this information for the driver.. */ diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index dd4cd5a51370..6c7fe6bb66ec 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -419,6 +419,18 @@ config USB_GOKU default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_ASPEED_AST + boolean "ASPEED AST Virtual Hub" + select USB_GADGET_DUALSPEED + depends on ARCH_ASPEED + help + USB device controller for ASPEED AST + +config USB_ASPEED_AST + tristate + depends on USB_GADGET_ASPEED_AST + default USB_GADGET + select USB_GADGET_SELECTED # # LAST -- dummy/emulated controller diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index bd4041b47dce..a0613321f44d 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o +obj-$(CONFIG_USB_ASPEED_AST) += aspeed_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/aspeed_udc.c b/drivers/usb/gadget/aspeed_udc.c new file mode 100644 index 000000000000..a65e52a0af62 --- /dev/null +++ b/drivers/usb/gadget/aspeed_udc.c @@ -0,0 +1,1043 @@ +/* + * aspeed_udc - Driver for Aspeed virtual hub (usb gadget) + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * 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. + */ + +//#define DEBUG +//#define VERBOSE_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "aspeed_udc.h" + +#define NUM_ENDPOINTS 14 +#define AST_UDC_EP0_MAXPACKET 64 + +struct ast_ep *eps; +static const struct usb_ep_ops ast_ep_ops; +static int ast_ep_queue(struct usb_ep* _ep, struct usb_request *_rq, gfp_t gfp_flags); +static void ep_dequeue_all(struct ast_ep* ep, int status); +static void ep_txrx_check_done(struct ast_ep* ep); +static void enable_upstream_port(void); +static void disable_upstream_port(void); +static int ast_udc_pullup(struct usb_gadget* gadget, int on); + +static inline const char* ast_udc_dir2str(u8 dir) { + return (dir & USB_DIR_IN) ? "IN" : "OUT"; +} + +static int ast_udc_get_frame(struct usb_gadget* gadget) { + return -EOPNOTSUPP; +} + +static struct usb_gadget_ops ast_gadget_ops = { + .get_frame = ast_udc_get_frame, + .pullup = ast_udc_pullup, +}; + +static void norelease(struct device *dev) { +} + +static struct aspeed_udc udc = { + .gadget = { + .ops = &ast_gadget_ops, + .ep_list = LIST_HEAD_INIT(udc.gadget.ep_list), + .is_dualspeed = 1, + .name = "aspeed_udc", + .dev = { + .bus_id = "gadget", + .release = norelease, + }, + }, + .pullup_on = 1, + .ep0_stage = EP0_STAGE_SETUP, + .ep0_dir = USB_DIR_IN, +}; + +static inline struct ast_usb_request *to_ast_req(struct usb_request* rq) +{ + return container_of(rq, struct ast_usb_request, req); +} + +static inline struct ast_ep *to_ast_ep(struct usb_ep* ep) +{ + return container_of(ep, struct ast_ep, ep); +} + +static void* ast_alloc_dma_memory(int sz, enum dma_data_direction dir, + dma_addr_t *phys) +{ + void* mem; + mem = kmalloc(sz, GFP_KERNEL | GFP_DMA); + if (!mem) { + return NULL; + } + + *phys = dma_map_single(udc.gadget.dev.parent, mem, sz, dir); + return mem; +} + +static void ast_free_dma_memory(int sz, enum dma_data_direction dir, + void *mem, dma_addr_t phys) +{ + if (phys) { + dma_unmap_single(udc.gadget.dev.parent, phys, sz, dir); + } + if (mem) { + kfree(mem); + } +} + +static int ast_udc_pullup(struct usb_gadget* gadget, int on) { + if(on) { + enable_upstream_port(); + } else { + disable_upstream_port(); + } + udc.pullup_on = on; + return 0; +} + +static struct ast_ep ep0_ep = { + .ep_regs = NULL, + .ep = { + .name = "ep0", + .ops = &ast_ep_ops, + .maxpacket = AST_UDC_EP0_MAXPACKET, + }, + .queue = LIST_HEAD_INIT(ep0_ep.queue), + .lock = SPIN_LOCK_UNLOCKED, + .dma_busy = 0, +}; + +static void clear_isr(u32 flag) { + ast_hwritel(&udc, ISR, flag); +} + +static void ep0_stall_ready(void) { + ast_hwritel(&udc, EP0_STATUS, AST_EP0_STALL); +} + +static void ep0_out_ready(void) { + ast_hwritel(&udc, EP0_STATUS, AST_EP0_OUT_READY); +} + +static void ep0_in_ready(size_t size) { + ast_hwritel(&udc, EP0_STATUS, (size << 8)); + ast_hwritel(&udc, EP0_STATUS, (size << 8) | AST_EP0_IN_READY); +} + +static void enable_upstream_port(void) { + ast_hwritel(&udc, STATUS, ast_hreadl(&udc, STATUS) | 0x5); +} + +static void disable_upstream_port(void) { + ast_hwritel(&udc, STATUS, ast_hreadl(&udc, STATUS) &~ 0x5); +} + +struct ast_usb_request empty_rq = { + .req = { + .buf = "", + .length = 0, + }, + .queue = LIST_HEAD_INIT(empty_rq.queue), + .in_transit = 0, +}; + +struct ast_ep* ep_by_addr(int addr) { + unsigned long flags; + int i; + struct ast_ep* ep = NULL; + for(i = 0; i < NUM_ENDPOINTS && ep == NULL; i++) { + struct ast_ep* test = &eps[i]; + spin_lock_irqsave(&test->lock, flags); + if(test->active && test->addr == addr) { + ep = test; + } + spin_unlock_irqrestore(&test->lock, flags); + } + return ep; +} + +static inline void ep0_init_stage(void) +{ + /* expect SETUP after */ + udc.ep0_stage = EP0_STAGE_SETUP; + udc.ep0_dir = USB_DIR_IN; +} + +static void ep0_stall(void) { + pr_debug("Prepare for EP0 stall, Reset EP0 stage to SETUP\n"); + /* reply stall on next pkt */ + ep0_stall_ready(); + ep0_init_stage(); +} + +static void ep0_next_stage(int has_data_phase, u8 data_dir) { +#ifdef DEBUG + int prev_stage = udc.ep0_stage; + int prev_dir = udc.ep0_dir; +#endif + + switch (udc.ep0_stage) { + case EP0_STAGE_SETUP: + if (has_data_phase) { + udc.ep0_stage = EP0_STAGE_DATA; + udc.ep0_dir = (data_dir == USB_DIR_IN) ? USB_DIR_IN : USB_DIR_OUT; + } else { + udc.ep0_stage = EP0_STAGE_STATUS; + udc.ep0_dir = USB_DIR_IN; + } + break; + case EP0_STAGE_DATA: + udc.ep0_stage = EP0_STAGE_STATUS; + udc.ep0_dir = (udc.ep0_dir == USB_DIR_IN) ? USB_DIR_OUT : USB_DIR_IN; + break; + case EP0_STAGE_STATUS: + udc.ep0_stage = EP0_STAGE_SETUP; + udc.ep0_dir = USB_DIR_IN; + break; + default: + pr_err("Wrong EP0 stage %d\n", udc.ep0_stage); + break; + } + +#ifdef DEBUG + pr_debug("EP0 stage is changed from %d (%s) to %d (%s)\n", + prev_stage, ast_udc_dir2str(prev_dir), + udc.ep0_stage, ast_udc_dir2str(udc.ep0_dir)); +#endif +} + +static void ep0_queue_run(void) { + struct ast_ep *ep = &ep0_ep; + + /* if ep0 is still doing DMA or no request pending, nothing to do */ + if (ep->dma_busy || list_empty(&ep->queue)) { + return; + } + + if (udc.ep0_dir == USB_DIR_OUT) { + /* just tell HW we are ready for OUT */ + ep0_out_ready(); + } else { + size_t sendable; + struct ast_usb_request *req; + + req = list_entry(ep->queue.next, struct ast_usb_request, queue); + sendable = req->req.length - req->req.actual; + if (sendable > AST_UDC_EP0_MAXPACKET) { + sendable = AST_UDC_EP0_MAXPACKET; + } + if (sendable) { + memcpy(udc.ep0_dma_virt, req->req.buf + req->req.actual, sendable); + } + dma_sync_single_for_device(udc.gadget.dev.parent, udc.ep0_dma_phys, + AST_UDC_EP0_MAXPACKET, DMA_TO_DEVICE); + ep0_in_ready(sendable); + req->lastpacket = sendable; + } + ep->dma_busy = 1; +} + +static int ep0_enqueue(struct usb_ep* _ep, struct usb_request *_rq, + gfp_t gfp_flags) { + struct ast_usb_request *req = to_ast_req(_rq); + struct ast_ep *ep = to_ast_ep(_ep); + unsigned long flags; + int rc = 0; + + _rq->status = -EINPROGRESS; + req->req.actual = 0; + + pr_debug("EP0 enqueue %d bytes at stage %d, %s\n", + req->req.length, udc.ep0_stage, ast_udc_dir2str(udc.ep0_dir)); + + spin_lock_irqsave(&ep->lock, flags); + + /* shall only happen when in data or status stage */ + if (udc.ep0_stage != EP0_STAGE_DATA && udc.ep0_stage != EP0_STAGE_STATUS) { + pr_err("EP0 enqueue happens at wrong stage %d\n", udc.ep0_stage); + rc = -EINPROGRESS; + goto out; + } + + /* ep0 shall only have one pending request a time */ + if (!list_empty(&ep->queue)) { + pr_err("EP0 enqueue happens with an existing entry on queue\n"); + } else { + list_add_tail(&req->queue, &ep->queue); + ep0_queue_run(); + } + + out: + if (rc < 0) { + ep0_stall(); + } + spin_unlock_irqrestore(&ep->lock, flags); + return rc; +} + +static void ep0_cancel_current(void) +{ + struct ast_ep *ep = &ep0_ep; + unsigned long flags; + ep_dequeue_all(ep, -ECONNRESET); + spin_lock_irqsave(&ep->lock, flags); + ep0_init_stage(); + spin_unlock_irqrestore(&ep->lock, flags); +} + +static int ep0_handle_setup(void) { + struct ast_ep *ep = &ep0_ep; + struct usb_ctrlrequest crq; + int rc = 0; + u16 val, idx, len; + unsigned long flags; + + /* make sure we are expecting setup packet */ + if (udc.ep0_stage != EP0_STAGE_SETUP) { + /* + * with g_cdc, we are seeing this message pretty often. could be an + * issue on g_cdc. make the log message as debug now + */ + pr_debug("Received SETUP pkt on wrong stage %d. Cancelling " + "the current SETUP\n", udc.ep0_stage); + ep0_cancel_current(); + } + + memcpy_fromio(&crq, udc.regs + AST_HUB_ROOT_SETUP_BUFFER, sizeof(crq)); + val = le16_to_cpu(crq.wValue); + idx = le16_to_cpu(crq.wIndex); + len = le16_to_cpu(crq.wLength); + + pr_debug("request=%d, reqType=0x%x val=0x%x idx=%d len=%d %s\n", + crq.bRequest, crq.bRequestType, val, idx, len, + ast_udc_dir2str(crq.bRequestType)); + + spin_lock_irqsave(&ep->lock, flags); + ep0_next_stage( + len, (crq.bRequestType & USB_DIR_IN) ? USB_DIR_IN : USB_DIR_OUT); + spin_unlock_irqrestore(&ep->lock, flags); + + switch (crq.bRequest) { + case USB_REQ_SET_ADDRESS: + udc.hub_address = val; + ast_hwritel(&udc, ROOT_CONFIG, udc.hub_address); + // reply with an empty pkt for STATUS + ep0_enqueue(&ep0_ep.ep, &empty_rq.req, GFP_KERNEL); + break; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + if ((crq.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) + break; + if ((crq.bRequestType & USB_RECIP_MASK) == USB_RECIP_ENDPOINT) { + int epaddr = idx & USB_ENDPOINT_NUMBER_MASK; + struct ast_ep *ep2; + if(val != 0 || len != 0 || epaddr > 15) { + ep0_stall(); + break; + } + ep2 = ep_by_addr(epaddr); + if (ep2) { + if(crq.bRequest == USB_REQ_SET_FEATURE) { + usb_ep_set_halt(&ep2->ep); + } else { + usb_ep_clear_halt(&ep2->ep); + } + } + } + // reply with an empty pkt for STATUS + ep0_enqueue(&ep0_ep.ep, &empty_rq.req, GFP_KERNEL); + break; + + default: + rc = udc.driver->setup(&udc.gadget, &crq); + if (rc < 0) { + printk("req %02x, %02x; fail: %d\n", crq.bRequestType, + crq.bRequest, rc); + ep0_stall(); + } + break; + } + + return rc; +} + +static void ep0_handle_ack(void) { + struct ast_usb_request *req; + struct ast_ep *ep = &ep0_ep; + unsigned long status; + unsigned long flags; + int done = 0; + + spin_lock_irqsave(&ep->lock, flags); + + if (!ep->dma_busy) { + goto out; + } + + status = ast_hreadl(&udc, EP0_STATUS); + + pr_debug("Check done with status 0x%lx: stage=%d %s\n", status, + udc.ep0_stage, ast_udc_dir2str((udc.ep0_dir))); + + if (list_empty(&ep->queue)) { + /* we requested DMA but no request */ + ep->dma_busy = 0; + pr_debug("Empty request queue with ep0 status: 0x%lx\n", status); + goto out; + } + + if ((udc.ep0_dir == USB_DIR_IN && (!(status & AST_EP0_IN_READY))) + || (udc.ep0_dir == USB_DIR_OUT && (!(status & AST_EP0_OUT_READY)))) { + /* something is ready */ + ep->dma_busy = 0; + req = container_of(ep->queue.next, struct ast_usb_request, queue); + req->in_transit = 0; + if (udc.ep0_dir == USB_DIR_OUT) { + req->lastpacket = (status >> 16) & 0x3F; + __cpuc_flush_kern_all(); + if (req->lastpacket) { + memcpy(req->req.buf + req->req.actual, udc.ep0_dma_virt, + req->lastpacket); + } + } + req->req.actual += req->lastpacket; + pr_debug("EP0 req actual=%d length=%d lastpacket=%d\n", + req->req.actual, req->req.length, req->lastpacket); + if (req->req.actual >= req->req.length + || req->lastpacket < AST_UDC_EP0_MAXPACKET) { + list_del_init(&req->queue); + done = 1; + ep0_next_stage(0, 0); + } else { + /* the request is not done yet, restart the queue */ + ep0_queue_run(); + } + } + + out: + + spin_unlock_irqrestore(&ep->lock, flags); + + if (done) { + req->req.status = 0; + if (req->req.complete) { + req->req.complete(&ep->ep, &req->req); + } + } +} + +static void ep0_handle_status(void) { + struct ast_ep *ep = &ep0_ep; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + /* + * If we expect a STATUS from host (OUT), enqueue a reqeust to get it going. + * For STATUS to host (IN), the SETUP packet handler needs to enqueue a + * STATUS message, therefore, no need to handle it here. + */ + if (udc.ep0_stage == EP0_STAGE_STATUS && udc.ep0_dir == USB_DIR_OUT) { + // request with an empty pkt for STATUS from host + ep0_enqueue(&ep0_ep.ep, &empty_rq.req, GFP_KERNEL); + } + + spin_unlock_irqrestore(&ep->lock, flags); +} + +static struct usb_request *ast_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) { + struct ast_usb_request *req; + (void) ep; + req = kzalloc(sizeof(*req), gfp_flags); + INIT_LIST_HEAD(&req->queue); + if(!req) + return NULL; + + return &req->req; +} + +static void ast_free_request(struct usb_ep *ep, struct usb_request *_req) { + struct ast_usb_request *req = to_ast_req(_req); + (void) ep; + kfree(req); +} + +static int ast_ep_enable(struct usb_ep* _ep, const struct usb_endpoint_descriptor *desc) { + struct ast_ep *ep = to_ast_ep(_ep); + size_t maxpacket = le16_to_cpu(desc->wMaxPacketSize); + int eptype = 0; + if(ep->active) { + pr_err("Enable an already enabled ep: %s\n", ep->ep.name); + return -EBUSY; + } + ep->ep.maxpacket = maxpacket; + pr_debug("Enabling endpoint %s (%p), maxpacket %d: ", + ep->ep.name, ep->ep_regs, ep->ep.maxpacket); + if (desc->bEndpointAddress & USB_DIR_IN) { + ep->to_host = 1; + switch (desc->bmAttributes) { + case USB_ENDPOINT_XFER_BULK: + pr_debug("bulk to host\n"); + eptype = AST_EP_TYPE_BULK_IN; + break; + case USB_ENDPOINT_XFER_INT: + pr_debug("int to host\n"); + eptype = AST_EP_TYPE_BULK_IN; + break; + case USB_ENDPOINT_XFER_ISOC: + pr_debug("isoc to host\n"); + eptype = AST_EP_TYPE_ISO_IN; + break; + } + } else { + ep->to_host = 0; + switch (desc->bmAttributes) { + case USB_ENDPOINT_XFER_BULK: + pr_debug("bulk from host\n"); + eptype = AST_EP_TYPE_BULK_OUT; + break; + case USB_ENDPOINT_XFER_INT: + pr_debug("int from host\n"); + eptype = AST_EP_TYPE_INT_OUT; + break; + case USB_ENDPOINT_XFER_ISOC: + pr_debug("isoc from host\n"); + eptype = AST_EP_TYPE_ISO_OUT; + break; + } + } + + ep_hwritel(ep, DMA_CONTROL, AST_EP_DL_RESET); + ep_hwritel(ep, DMA_CONTROL, AST_EP_SINGLE_STAGE); + + ep->txbuf = ast_alloc_dma_memory( + ep->ep.maxpacket, ep->to_host ? DMA_TO_DEVICE : DMA_FROM_DEVICE, + &ep->txbuf_phys); + + if (maxpacket == 1024) + maxpacket = 0; + ep_hwritel(ep, CONFIG, (maxpacket << 16) | (ep->addr << 8) | (eptype << 4) | AST_EP_ENABLED); + + ep_hwritel(ep, DESC_BASE, ep->txbuf_phys); + ep_hwritel(ep, DESC_STATUS, 0); + + ast_hwritel(&udc, EP_ACK_INT_ENABLE, 0x7fff); + ep->active = 1; + return 0; +} + +static void ep_dequeue_locked(struct ast_ep* ep, struct ast_usb_request *req) { + // Cancel in-progress transfer + req->req.status = -ECONNRESET; + if(req->in_transit) { + req->in_transit = 0; + ep->dma_busy = 0; + ep_hwritel(ep, DESC_STATUS, 0); + } + list_del_init(&req->queue); +} + +static void ep_dequeue_all(struct ast_ep* ep, int status) { + struct ast_usb_request *req; + struct ast_usb_request *n; + unsigned long flags; + spin_lock_irqsave(&ep->lock, flags); + list_for_each_entry_safe(req, n, &ep->queue, queue) { + ep_dequeue_locked(ep, req); + req->req.status = status; + if(req->req.complete) { + spin_unlock_irqrestore(&ep->lock, flags); + req->req.complete(&ep->ep, &req->req); + spin_lock_irqsave(&ep->lock, flags); + } + } + spin_unlock_irqrestore(&ep->lock, flags); +} + +static int ast_ep_dequeue(struct usb_ep* _ep, struct usb_request *_rq) { + struct ast_usb_request *req = to_ast_req(_rq); + struct ast_ep *ep = to_ast_ep(_ep); + unsigned long flags; + pr_debug("Dequeue a request (%d bytes) from %s\n", + req->req.length, ep->ep.name); + spin_lock_irqsave(&ep->lock, flags); + ep_dequeue_locked(ep, req); + if(req->req.complete) { + req->req.complete(&ep->ep, &req->req); + } + spin_unlock_irqrestore(&ep->lock, flags); + return 0; +} + +static int ast_ep_disable(struct usb_ep* _ep) { + struct ast_ep *ep = to_ast_ep(_ep); + unsigned long flags; + pr_debug("Disable %s\n", ep->ep.name); + if (!ep->active) + return 0; + ep_dequeue_all(ep, -ESHUTDOWN); + spin_lock_irqsave(&ep->lock, flags); + ast_free_dma_memory(ep->ep.maxpacket, + ep->to_host ? DMA_TO_DEVICE : DMA_FROM_DEVICE, + ep->txbuf, ep->txbuf_phys); + ep->txbuf_phys = 0; + ep->txbuf = NULL; + ep->active = 0; + ep->ep.maxpacket = 1024; + ep_hwritel(ep, CONFIG, 0); + spin_unlock_irqrestore(&ep->lock, flags); + return 0; +} + +static void disable_all_endpoints(void) { + int i; + for(i = 0; i < NUM_ENDPOINTS; i++) { + if(eps[i].active) { + ast_ep_disable(&eps[i].ep); + } + } +} + +static void ep_txrx(struct ast_ep* ep, void* buf, size_t len) { + if(ep->to_host) { + memcpy(ep->txbuf, buf, len); + } + ep_hwritel(ep, DESC_STATUS, len << 16); + ep_hwritel(ep, DESC_STATUS, (len << 16) | 1); +} + +static void ep_txrx_check_done(struct ast_ep* ep) { + struct ast_usb_request *req; + unsigned long flags; + u32 status; + spin_lock_irqsave(&ep->lock, flags); + status = ep_hreadl(ep, DESC_STATUS); + // if txrx complete; + if(!(status & 0xff) && + !list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct ast_usb_request, queue); + if(!req->in_transit) { + spin_unlock_irqrestore(&ep->lock, flags); + return; + } + //head rq completed + req->in_transit = 0; + ep->dma_busy = 0; + if(!ep->to_host) { + req->lastpacket = (status >> 16) & 0x3ff; + __cpuc_flush_kern_all(); + memcpy(req->req.buf + req->req.actual, ep->txbuf, req->lastpacket); + //print_hex_dump(KERN_DEBUG, "epXrx: ", DUMP_PREFIX_OFFSET, 16, 1, ep->txbuf, req->lastpacket, 0); + } + req->req.actual += req->lastpacket; + if(req->req.actual == req->req.length || + req->lastpacket < ep->ep.maxpacket) { + list_del_init(&req->queue); + spin_unlock_irqrestore(&ep->lock, flags); + //printk("rq done ep%d\n", ep->addr); + req->req.status = 0; + if(req->req.complete) { + req->req.complete(&ep->ep, &req->req); + } + } else { + //printk("rq partial ep%d %d/%d\n", ep->addr, req->req.actual, req->req.length); + spin_unlock_irqrestore(&ep->lock, flags); + } + } +} + +static int ast_ep_queue_run(struct ast_ep* ep) { + struct ast_usb_request *req; + unsigned long flags; + spin_lock_irqsave(&ep->lock, flags); + if(ep->active && !ep->dma_busy) { + //ep isn't busy.. + if(list_empty(&ep->queue)) { + // nothing + } else { + req = list_entry(ep->queue.next, struct ast_usb_request, queue); + req->lastpacket = req->req.length - req->req.actual; + if (req->lastpacket > ep->ep.maxpacket) { + req->lastpacket = ep->ep.maxpacket; + } + //printk("ep%d%sx:%d-%d/%d\n", + // ep->addr, + // ep->to_host ? "t" : "r", + // req->req.actual, + // req->req.actual + req->lastpacket, + // req->req.length); + ep_txrx(ep, req->req.buf + req->req.actual, req->lastpacket); + req->in_transit = 1; + ep->dma_busy = 1; + } + } + spin_unlock_irqrestore(&ep->lock, flags); + return 0; +} + +static void run_all_ep_queues(unsigned long data) { + int i; + unsigned long flags; + spin_lock_irqsave(&udc.lock, flags); + + for(i = 0; i < NUM_ENDPOINTS; i++) { + if(eps[i].active) { + ep_txrx_check_done(&eps[i]); + ast_ep_queue_run(&eps[i]); + } + } + + spin_unlock_irqrestore(&udc.lock, flags); +} + +DECLARE_TASKLET(check_ep_queues, run_all_ep_queues, 0); + +static int ast_ep_queue(struct usb_ep* _ep, struct usb_request *_rq, gfp_t gfp_flags) { + struct ast_usb_request *req = to_ast_req(_rq); + struct ast_ep *ep = to_ast_ep(_ep); + unsigned long flags; + + if (ep == &ep0_ep) { + return ep0_enqueue(_ep, _rq, gfp_flags); + } + + _rq->status = -EINPROGRESS; + spin_lock_irqsave(&ep->lock, flags); + list_add_tail(&req->queue, &ep->queue); + req->req.actual = 0; + spin_unlock_irqrestore(&ep->lock, flags); + ast_ep_queue_run(ep); + + return 0; +} + +static int ast_ep_set_halt(struct usb_ep* _ep, int value) { + struct ast_ep *ep = to_ast_ep(_ep); + unsigned long flags; + if (ep == &ep0_ep) { + /* cannot halt ep0, just return */ + return 0; + } + spin_lock_irqsave(&ep->lock, flags); + if(value) { + ep_hwritel(ep, CONFIG, ep_hreadl(ep, CONFIG) | AST_EP_STALL_ENABLED); + } else { + ep_hwritel(ep, CONFIG, ep_hreadl(ep, CONFIG) &~ AST_EP_STALL_ENABLED); + } + spin_unlock_irqrestore(&ep->lock, flags); + return 0; +} + +static const struct usb_ep_ops ast_ep_ops = { + .enable = ast_ep_enable, + .disable = ast_ep_disable, + .queue = ast_ep_queue, + .dequeue = ast_ep_dequeue, + .set_halt = ast_ep_set_halt, + .alloc_request = ast_alloc_request, + .free_request = ast_free_request, +}; + +static irqreturn_t ast_vhub_udc_irq(int irq, void* devid) { + irqreturn_t status = IRQ_NONE; + u32 istatus; + (void) devid; + + istatus = ast_hreadl(&udc, ISR); + + /* + * Must handle IN/OUT ACK first. + * For example, in the case of SETUP request DATA IN, after we send out data, + * we enqueue a request for STATUS ack from host, and stage will be + * STATUS(IN). When host receives the data, it sends out the STATUS msg + * and then a new SETUP. In this case, if we process the SETUP first, + * that will confuse the stage state machine, as it expects STATUS msg + * for now. + */ + if(istatus & AST_IRQ_EP0_OUT_ACK) { + clear_isr(AST_IRQ_EP0_OUT_ACK); + ep0_handle_ack(); + status = IRQ_HANDLED; + } + + if(istatus & AST_IRQ_EP0_IN_ACK) { + clear_isr(AST_IRQ_EP0_IN_ACK); + ep0_handle_ack(); + status = IRQ_HANDLED; + } + + if(istatus & AST_IRQ_EP0_SETUP) { + clear_isr(AST_IRQ_EP0_SETUP); + ep0_handle_setup(); + status = IRQ_HANDLED; + } + + if(istatus & AST_IRQ_EP_POOL_ACK) { + u32 acked = ast_hreadl(&udc, EP_ACK_ISR); + ast_hwritel(&udc, EP_ACK_ISR, acked); + clear_isr(AST_IRQ_EP_POOL_ACK); + status = IRQ_HANDLED; + tasklet_schedule(&check_ep_queues); + } + + if(istatus & AST_IRQ_EP_POOL_NAK) { + clear_isr(AST_IRQ_EP_POOL_NAK); + printk("Got NAK on the EP pool"); + status = IRQ_HANDLED; + } + + if (status != IRQ_HANDLED) { + printk("vhub: unhandled interrupts! ISR: %08x\n", istatus); + } else { + ep0_handle_status(); + } + + return status; +} + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) { + int err = 0; + unsigned long flags; + printk("gadget register driver: %s\n", driver->driver.name); + if(!udc.pdev) + return -ENODEV; + + spin_lock_irqsave(&udc.lock, flags); + udc.pullup_on = 1; + if (udc.driver) { + err = -EBUSY; + goto err; + } + udc.driver = driver; + udc.gadget.dev.driver = &driver->driver; +err: + spin_unlock_irqrestore(&udc.lock, flags); + if(err == 0) { + //ok so far + err = driver->bind(&udc.gadget); + } + if(err == 0) { + if(udc.pullup_on) { + enable_upstream_port(); + } + printk("vhub: driver registered, port on!\n"); + } else { + printk("vhub: driver failed to register: %d\n", err); + spin_lock_irqsave(&udc.lock, flags); + udc.driver = NULL; + udc.gadget.dev.driver = NULL; + spin_unlock_irqrestore(&udc.lock, flags); + } + return err; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) { + int err = 0; + unsigned long flags; + if(!udc.pdev) + return -ENODEV; + if(driver != udc.driver || !driver->unbind) { + return -EINVAL; + } + + spin_lock_irqsave(&udc.lock, flags); + + // disappear from the host + ast_udc_pullup(&udc.gadget, 0); + if(udc.driver->disconnect) { + udc.driver->disconnect(&udc.gadget); + } + + driver->unbind(&udc.gadget); + // turn off the EPs + disable_all_endpoints(); + + udc.gadget.dev.driver = NULL; + udc.driver = NULL; + + spin_unlock_irqrestore(&udc.lock, flags); + return err; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +static int __init ast_vhub_udc_probe(struct platform_device *pdev) { + struct resource *res; + int err = -ENODEV; + int irq; + int i; + + udc.regs = NULL; + irq = platform_get_irq(pdev, 0); + if(irq < 0) + return irq; + udc.irq = irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + spin_lock_init(&udc.lock); + err = -ENOMEM; + udc.regs = ioremap(res->start, res->end - res->start + 1); + if(!udc.regs) { + dev_err(&pdev->dev, "Couldn't map I/O memory!\n"); + goto error; + } + + dev_info(&pdev->dev, "aspeed_udc at 0x%08lx mapped at %p\n", + (unsigned long)res->start, udc.regs); + platform_set_drvdata(pdev, &udc); + device_initialize(&udc.gadget.dev); + + udc.gadget.dev.parent = &pdev->dev; + udc.gadget.dev.dma_mask = pdev->dev.dma_mask; + + /* disable all interrupts first */ + ast_hwritel(&udc, INTERRUPT_ENABLE, 0); + + ast_hwritel(&udc, ISR, 0x1ffff); + err = request_irq(irq, ast_vhub_udc_irq, 0, "aspeed_udc", &udc); + + if(err) { + dev_err(&pdev->dev, "failed to get irq %d: %d\n", irq, err); + goto error; + } + + udc.ep0_dma_virt = ast_alloc_dma_memory( + AST_UDC_EP0_MAXPACKET, DMA_BIDIRECTIONAL, &udc.ep0_dma_phys); + if (!udc.ep0_dma_virt) { + printk("vhub fatal: Couldn't get DMA memory!\n"); + goto error; + } + ast_hwritel(&udc, EP0_DMA_ADDR, udc.ep0_dma_phys); + + printk("virthub init...\n"); + ast_hwritel(&udc, SOFTRESET_ENABLE, 0x33f); + ast_hwritel(&udc, SOFTRESET_ENABLE, 0x0); + ast_hwritel(&udc, ISR, 0x1ffff); + ast_hwritel(&udc, EP_ACK_ISR, 0xffffffff); + ast_hwritel(&udc, EP_NAK_ISR, 0xffffffff); + + ast_hwritel(&udc, EP1_STATUS, 0x1); + ast_hwritel(&udc, STATUS, AST_HUB_RESET_DISABLE); + + udc.pdev = pdev; + + INIT_LIST_HEAD(&ep0_ep.ep.ep_list); + udc.gadget.ep0 = &ep0_ep.ep; + + eps = kzalloc(sizeof(struct ast_ep) * 14, GFP_KERNEL); + if(!eps) { + goto error; + } + + for (i = 0; i < NUM_ENDPOINTS; i++) { + struct ast_ep* ep = &eps[i]; + ep->ep_regs = udc.regs + 0x200 + (i * 0x10); + INIT_LIST_HEAD(&ep->queue); + ep->ep.ops = &ast_ep_ops; + ep->index = i; + // i+2, Can't use EP1 as the 'virtual hub' has it built in + // when using the root device. + ep->addr = i+2; + snprintf(ep->epname, 7, "ep%d", ep->addr); + ep->ep.name = ep->epname; + ep->ep.maxpacket = 1024; + spin_lock_init(&ep->lock); + list_add_tail(&ep->ep.ep_list, &udc.gadget.ep_list); + } + + /* enable interrupts */ + ast_hwritel(&udc, INTERRUPT_ENABLE, + AST_INT_EP_POOL_ACK + | AST_INT_EP0_IN_ACK + | AST_INT_EP0_OUT_ACK + | AST_INT_EP0_SETUP_ACK); + + err = device_add(&udc.gadget.dev); + if(err) { + dev_dbg(&pdev->dev, "Could not add gadget: %d\n", err); + goto error; + } + return 0; +error: + if(udc.regs) + iounmap(udc.regs); + platform_set_drvdata(pdev, NULL); + return err; +} + +static int ast_vhub_udc_remove(struct platform_device *pdev) { + if(udc.regs) + iounmap(udc.regs); + if(udc.irq) + free_irq(udc.irq, &udc); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver ast_vhub_udc_driver = { + .probe = ast_vhub_udc_probe, + .remove = ast_vhub_udc_remove, + .driver = { + .name = "aspeed_udc", + .owner = THIS_MODULE, + }, +}; + +static int __init ast_udc_init(void) { + return platform_driver_probe(&ast_vhub_udc_driver, ast_vhub_udc_probe); +} +module_init(ast_udc_init); + +static void __exit ast_udc_exit(void) { + ast_free_dma_memory(AST_UDC_EP0_MAXPACKET, DMA_BIDIRECTIONAL, + udc.ep0_dma_virt, udc.ep0_dma_phys); + platform_driver_unregister(&ast_vhub_udc_driver); +} +module_exit(ast_udc_exit); + +MODULE_DESCRIPTION("AST2400/1250 USB UDC Driver"); +MODULE_AUTHOR(""); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:aspeed_udc"); diff --git a/drivers/usb/gadget/aspeed_udc.h b/drivers/usb/gadget/aspeed_udc.h new file mode 100644 index 000000000000..5494afd93fa6 --- /dev/null +++ b/drivers/usb/gadget/aspeed_udc.h @@ -0,0 +1,161 @@ +/* + * Copyright 2014-present Facebook. All Rights Reserved. + * + * 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. + */ + +#ifndef __LINUX_USB_GADGET_ASPEED_UDC_H +#define __LINUX_USB_GADGET_ASPEED_UDC_H + +#define AST_INT_EP_POOL_NAK (1 << 17) +#define AST_INT_EP_POOL_ACK (1 << 16) +#define AST_INT_DEV5_CONTROLLER (1 << 13) +#define AST_INT_DEV4_CONTROLLER (1 << 12) +#define AST_INT_DEV3_CONTROLLER (1 << 11) +#define AST_INT_DEV2_CONTROLLER (1 << 10) +#define AST_INT_DEV1_CONTROLLER (1 << 9) + +/* Interrupt control bits */ +#define AST_INT_EP1_IN_ACK (1 << 5) +#define AST_INT_EP0_IN_NAK (1 << 4) +#define AST_INT_EP0_IN_ACK (1 << 3) +#define AST_INT_EP0_OUT_NAK (1 << 2) +#define AST_INT_EP0_OUT_ACK (1 << 1) +#define AST_INT_EP0_SETUP_ACK (1 << 0) + +/* Interrupt status bits */ +#define AST_IRQ_EP_POOL_NAK (1 << 17) +#define AST_IRQ_EP_POOL_ACK (1 << 16) +#define AST_IRQ_EP0_SETUP (1 << 0) +#define AST_IRQ_EP0_OUT_ACK (1 << 1) +#define AST_IRQ_EP0_OUT_NAK (1 << 2) +#define AST_IRQ_EP0_IN_ACK (1 << 3) +#define AST_IRQ_EP0_IN_NAK (1 << 4) +#define AST_IRQ_EP1_IN_ACK (1 << 5) + +#define AST_HUB_RESET_DISABLE (1 << 11) + +#define AST_HUB_BASE_ADDR 0x1e6a0000 +#define AST_HUB_STATUS 0x00 +#define AST_HUB_ISR 0x0c +#define AST_HUB_EP_ACK_ISR 0x18 +#define AST_HUB_EP_NAK_ISR 0x1C +#define AST_HUB_SOFTRESET_ENABLE 0x20 +#define AST_HUB_INTERRUPT_ENABLE 0x08 +#define AST_HUB_EP0_STATUS 0x30 +#define AST_HUB_EP1_STATUS 0x38 +#define AST_HUB_EP0_DMA_ADDR 0x34 +#define AST_HUB_ROOT_CONFIG 0x04 +#define AST_HUB_EP_ACK_INT_ENABLE 0x10 +#define AST_HUB_EP_NAK_INT_ENABLE 0x14 + +#define AST_HUB_ROOT_SETUP_BUFFER 0x80 + +#define AST_EP0_OUT_READY (1 << 2) +#define AST_EP0_IN_READY (1 << 1) +#define AST_EP0_STALL (1 << 0) + +#define AST_EP_REG_SIZE 0x10 +#define AST_EP_BASE(i) (0x200 + (i) * 0x10) + +#define AST_EP_CONFIG 0x00 +#define AST_EP_DMA_CONTROL 0x04 +#define AST_EP_DESC_BASE 0x08 +#define AST_EP_DESC_STATUS 0x0C + +#define AST_EP_DL_RESET (1 << 2) +#define AST_EP_SINGLE_STAGE (1 << 1) +#define AST_EP_STALL_ENABLED (1 << 12) + +#define AST_EP_TYPE_DISABLED 0 +#define AST_EP_TYPE_BULK_IN 2 +#define AST_EP_TYPE_BULK_OUT 3 +#define AST_EP_TYPE_INT_IN 4 +#define AST_EP_TYPE_INT_OUT 5 +#define AST_EP_TYPE_ISO_IN 6 +#define AST_EP_TYPE_ISO_OUT 7 + +#define AST_EP_ENABLED 1 + +#define ep_set_type(type, config) \ + (config | (AST_EP_TYPE_##type << 4)) + +#define ast_hreadl(udcp, reg) \ + ioread32((udcp)->regs + AST_HUB_##reg) + +#define ast_hwritel(udcp, reg, value) \ + iowrite32(value, (udcp)->regs + AST_HUB_##reg) + +#define ep_hreadl(ep, reg) \ + ioread32((ep)->ep_regs + AST_EP_##reg) + +#define ep_hwritel(ep, reg, value) \ + iowrite32(value, (ep)->ep_regs + AST_EP_##reg) + + +struct aspeed_udc { + spinlock_t lock; + + void __iomem *regs; + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + + struct dma_pool *pool; + + void* ep0_dma_virt; + dma_addr_t ep0_dma_phys; + + u16 hub_address; + int irq; + unsigned int pullup_on; + + enum { + EP0_STAGE_SETUP, + EP0_STAGE_DATA, + EP0_STAGE_STATUS, + } ep0_stage; + /* either USB_DIR_OUT or USB_DIR_IN, valid if it is in data or status stage */ + u8 ep0_dir; +}; + +struct ast_usb_request { + struct usb_request req; + struct list_head queue; + size_t lastpacket; + + unsigned int in_transit:1; +}; + +struct ast_ep { + struct usb_ep ep; + u8 addr; + u8 index; + char epname[7]; + void __iomem *ep_regs; + + spinlock_t lock; + struct list_head queue; + void *txbuf; + dma_addr_t txbuf_phys; + + unsigned int dma_busy:1; + unsigned int to_host:1; + unsigned int active:1; + // const struct usb_endpoint_descriptor *desc; +}; + + +#endif /* __LINUX_USB_GADGET_ASPEED_UDC_H */ diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 9462e30192d8..b631931b0020 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -182,6 +182,9 @@ ep_matches ( /* min() doesn't work on bitfields with gcc-3.5 */ if (size > 64) size = 64; + if (gadget->is_dualspeed) { + size = 512; + } desc->wMaxPacketSize = cpu_to_le16(size); } return 1; diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index 4e3107dd2f34..1f0514287d0a 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -158,6 +158,13 @@ #define gadget_is_fsl_qe(g) 0 #endif +/* ASPEED BMC Support */ +#ifdef CONFIG_USB_GADGET_ASPEED_AST +#define gadget_is_aspeed(g) !strcmp("aspeed_udc", (g)->name) +#else +#define gadget_is_aspeed(g) 0 +#endif + // CONFIG_USB_GADGET_SX2 // CONFIG_USB_GADGET_AU1X00 @@ -225,6 +232,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x21; else if (gadget_is_fsl_qe(gadget)) return 0x22; + else if (gadget_is_aspeed(gadget)) + return 0x23; return -ENOENT; } diff --git a/drivers/watchdog/ast_wdt.c b/drivers/watchdog/ast_wdt.c index 845f1db3d66d..9e7e84fb8740 100644 --- a/drivers/watchdog/ast_wdt.c +++ b/drivers/watchdog/ast_wdt.c @@ -78,6 +78,17 @@ typedef unsigned char bool_T; #define WDT_Clr (WDT_BASE_VA+0x14) #define WDT_RstWd (WDT_BASE_VA+0x18) +#define WDT_CTRL_B_SECOND_BOOT (0x1 << 7) +#define WDT_CTRL_B_RESET_SOC (0x00 << 5) /* yes, 0x00 */ +#define WDT_CTRL_B_RESET_FULL (0x01 << 5) +#define WDT_CTRL_B_RESET_ARM (0x2 << 5) +#define WDT_CTRL_B_RESET_MASK (0x3 << 5) +#define WDT_CTRL_B_1MCLK (0x1 << 4) +#define WDT_CTRL_B_EXT (0x1 << 3) +#define WDT_CTRL_B_INTR (0x1 << 2) +#define WDT_CTRL_B_CLEAR_AFTER (0x1 << 1) +#define WDT_CTRL_B_ENABLE (0x1 << 0) + #define AST_READ_REG(r) (*((volatile unsigned int *) (r))) #define AST_WRITE_REG(r,v) (*((volatile unsigned int *) (r)) = ((unsigned int) (v))) @@ -87,17 +98,24 @@ typedef unsigned char bool_T; #define WDT_CLK_SRC_PCLK 1 //Global Variables -#define WD_TIMO 6 /* Default heartbeat = 6 seconds */ +#define WDT_TIMO 30 /* Default heartbeat = 30 seconds */ + +#define WDT_INITIAL_TIMO (8*60) /* Initial timeout, 8m */ +#define WDT_TIMO2TICKS(t) (TICKS_PER_uSEC * 1000000 * (t)) -static int heartbeat = WD_TIMO; +static int heartbeat = WDT_TIMO; module_param(heartbeat, int, 0); -MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0