103e9bd8dSGuenter Roeck /* 203e9bd8dSGuenter Roeck * Hardware monitoring driver for LM25066 / LM5064 / LM5066 303e9bd8dSGuenter Roeck * 403e9bd8dSGuenter Roeck * Copyright (c) 2011 Ericsson AB. 503e9bd8dSGuenter Roeck * 603e9bd8dSGuenter Roeck * This program is free software; you can redistribute it and/or modify 703e9bd8dSGuenter Roeck * it under the terms of the GNU General Public License as published by 803e9bd8dSGuenter Roeck * the Free Software Foundation; either version 2 of the License, or 903e9bd8dSGuenter Roeck * (at your option) any later version. 1003e9bd8dSGuenter Roeck * 1103e9bd8dSGuenter Roeck * This program is distributed in the hope that it will be useful, 1203e9bd8dSGuenter Roeck * but WITHOUT ANY WARRANTY; without even the implied warranty of 1303e9bd8dSGuenter Roeck * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1403e9bd8dSGuenter Roeck * GNU General Public License for more details. 1503e9bd8dSGuenter Roeck * 1603e9bd8dSGuenter Roeck * You should have received a copy of the GNU General Public License 1703e9bd8dSGuenter Roeck * along with this program; if not, write to the Free Software 1803e9bd8dSGuenter Roeck * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 1903e9bd8dSGuenter Roeck */ 2003e9bd8dSGuenter Roeck 2103e9bd8dSGuenter Roeck #include <linux/kernel.h> 2203e9bd8dSGuenter Roeck #include <linux/module.h> 2303e9bd8dSGuenter Roeck #include <linux/init.h> 2403e9bd8dSGuenter Roeck #include <linux/err.h> 2503e9bd8dSGuenter Roeck #include <linux/slab.h> 2603e9bd8dSGuenter Roeck #include <linux/i2c.h> 2703e9bd8dSGuenter Roeck #include "pmbus.h" 2803e9bd8dSGuenter Roeck 2903e9bd8dSGuenter Roeck enum chips { lm25066, lm5064, lm5066 }; 3003e9bd8dSGuenter Roeck 3103e9bd8dSGuenter Roeck #define LM25066_READ_VAUX 0xd0 3203e9bd8dSGuenter Roeck #define LM25066_MFR_READ_IIN 0xd1 3303e9bd8dSGuenter Roeck #define LM25066_MFR_READ_PIN 0xd2 3403e9bd8dSGuenter Roeck #define LM25066_MFR_IIN_OC_WARN_LIMIT 0xd3 3503e9bd8dSGuenter Roeck #define LM25066_MFR_PIN_OP_WARN_LIMIT 0xd4 3603e9bd8dSGuenter Roeck #define LM25066_READ_PIN_PEAK 0xd5 3703e9bd8dSGuenter Roeck #define LM25066_CLEAR_PIN_PEAK 0xd6 3803e9bd8dSGuenter Roeck #define LM25066_DEVICE_SETUP 0xd9 3903e9bd8dSGuenter Roeck #define LM25066_READ_AVG_VIN 0xdc 4003e9bd8dSGuenter Roeck #define LM25066_READ_AVG_VOUT 0xdd 4103e9bd8dSGuenter Roeck #define LM25066_READ_AVG_IIN 0xde 4203e9bd8dSGuenter Roeck #define LM25066_READ_AVG_PIN 0xdf 4303e9bd8dSGuenter Roeck 4403e9bd8dSGuenter Roeck #define LM25066_DEV_SETUP_CL (1 << 4) /* Current limit */ 4503e9bd8dSGuenter Roeck 4603e9bd8dSGuenter Roeck struct lm25066_data { 4703e9bd8dSGuenter Roeck int id; 4803e9bd8dSGuenter Roeck struct pmbus_driver_info info; 4903e9bd8dSGuenter Roeck }; 5003e9bd8dSGuenter Roeck 5103e9bd8dSGuenter Roeck #define to_lm25066_data(x) container_of(x, struct lm25066_data, info) 5203e9bd8dSGuenter Roeck 5303e9bd8dSGuenter Roeck static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) 5403e9bd8dSGuenter Roeck { 5503e9bd8dSGuenter Roeck const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 5603e9bd8dSGuenter Roeck const struct lm25066_data *data = to_lm25066_data(info); 5703e9bd8dSGuenter Roeck int ret; 5803e9bd8dSGuenter Roeck 5903e9bd8dSGuenter Roeck if (page > 1) 60179144a0SGuenter Roeck return -ENXIO; 6103e9bd8dSGuenter Roeck 6203e9bd8dSGuenter Roeck /* Map READ_VAUX into READ_VOUT register on page 1 */ 6303e9bd8dSGuenter Roeck if (page == 1) { 6403e9bd8dSGuenter Roeck switch (reg) { 6503e9bd8dSGuenter Roeck case PMBUS_READ_VOUT: 6603e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, 6703e9bd8dSGuenter Roeck LM25066_READ_VAUX); 6803e9bd8dSGuenter Roeck if (ret < 0) 6903e9bd8dSGuenter Roeck break; 7003e9bd8dSGuenter Roeck /* Adjust returned value to match VOUT coefficients */ 7103e9bd8dSGuenter Roeck switch (data->id) { 7203e9bd8dSGuenter Roeck case lm25066: 7303e9bd8dSGuenter Roeck /* VOUT: 4.54 mV VAUX: 283.2 uV LSB */ 7403e9bd8dSGuenter Roeck ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); 7503e9bd8dSGuenter Roeck break; 7603e9bd8dSGuenter Roeck case lm5064: 7703e9bd8dSGuenter Roeck /* VOUT: 4.53 mV VAUX: 700 uV LSB */ 7803e9bd8dSGuenter Roeck ret = DIV_ROUND_CLOSEST(ret * 70, 453); 7903e9bd8dSGuenter Roeck break; 8003e9bd8dSGuenter Roeck case lm5066: 8103e9bd8dSGuenter Roeck /* VOUT: 2.18 mV VAUX: 725 uV LSB */ 8203e9bd8dSGuenter Roeck ret = DIV_ROUND_CLOSEST(ret * 725, 2180); 8303e9bd8dSGuenter Roeck break; 8403e9bd8dSGuenter Roeck } 8503e9bd8dSGuenter Roeck break; 8603e9bd8dSGuenter Roeck default: 8703e9bd8dSGuenter Roeck /* No other valid registers on page 1 */ 88179144a0SGuenter Roeck ret = -ENXIO; 8903e9bd8dSGuenter Roeck break; 9003e9bd8dSGuenter Roeck } 9103e9bd8dSGuenter Roeck goto done; 9203e9bd8dSGuenter Roeck } 9303e9bd8dSGuenter Roeck 9403e9bd8dSGuenter Roeck switch (reg) { 9503e9bd8dSGuenter Roeck case PMBUS_READ_IIN: 9603e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_IIN); 9703e9bd8dSGuenter Roeck break; 9803e9bd8dSGuenter Roeck case PMBUS_READ_PIN: 9903e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_PIN); 10003e9bd8dSGuenter Roeck break; 10103e9bd8dSGuenter Roeck case PMBUS_IIN_OC_WARN_LIMIT: 10203e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, 10303e9bd8dSGuenter Roeck LM25066_MFR_IIN_OC_WARN_LIMIT); 10403e9bd8dSGuenter Roeck break; 10503e9bd8dSGuenter Roeck case PMBUS_PIN_OP_WARN_LIMIT: 10603e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, 10703e9bd8dSGuenter Roeck LM25066_MFR_PIN_OP_WARN_LIMIT); 10803e9bd8dSGuenter Roeck break; 10903e9bd8dSGuenter Roeck case PMBUS_VIRT_READ_VIN_AVG: 11003e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VIN); 11103e9bd8dSGuenter Roeck break; 11203e9bd8dSGuenter Roeck case PMBUS_VIRT_READ_VOUT_AVG: 11303e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VOUT); 11403e9bd8dSGuenter Roeck break; 11503e9bd8dSGuenter Roeck case PMBUS_VIRT_READ_IIN_AVG: 11603e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_IIN); 11703e9bd8dSGuenter Roeck break; 11803e9bd8dSGuenter Roeck case PMBUS_VIRT_READ_PIN_AVG: 11903e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_PIN); 12003e9bd8dSGuenter Roeck break; 12103e9bd8dSGuenter Roeck case PMBUS_VIRT_READ_PIN_MAX: 12203e9bd8dSGuenter Roeck ret = pmbus_read_word_data(client, 0, LM25066_READ_PIN_PEAK); 12303e9bd8dSGuenter Roeck break; 12403e9bd8dSGuenter Roeck case PMBUS_VIRT_RESET_PIN_HISTORY: 12503e9bd8dSGuenter Roeck ret = 0; 12603e9bd8dSGuenter Roeck break; 12703e9bd8dSGuenter Roeck default: 12803e9bd8dSGuenter Roeck ret = -ENODATA; 12903e9bd8dSGuenter Roeck break; 13003e9bd8dSGuenter Roeck } 13103e9bd8dSGuenter Roeck done: 13203e9bd8dSGuenter Roeck return ret; 13303e9bd8dSGuenter Roeck } 13403e9bd8dSGuenter Roeck 13503e9bd8dSGuenter Roeck static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, 13603e9bd8dSGuenter Roeck u16 word) 13703e9bd8dSGuenter Roeck { 13803e9bd8dSGuenter Roeck int ret; 13903e9bd8dSGuenter Roeck 14003e9bd8dSGuenter Roeck if (page > 1) 141179144a0SGuenter Roeck return -ENXIO; 14203e9bd8dSGuenter Roeck 14303e9bd8dSGuenter Roeck switch (reg) { 14403e9bd8dSGuenter Roeck case PMBUS_IIN_OC_WARN_LIMIT: 14503e9bd8dSGuenter Roeck ret = pmbus_write_word_data(client, 0, 14603e9bd8dSGuenter Roeck LM25066_MFR_IIN_OC_WARN_LIMIT, 14703e9bd8dSGuenter Roeck word); 14803e9bd8dSGuenter Roeck break; 14903e9bd8dSGuenter Roeck case PMBUS_PIN_OP_WARN_LIMIT: 15003e9bd8dSGuenter Roeck ret = pmbus_write_word_data(client, 0, 15103e9bd8dSGuenter Roeck LM25066_MFR_PIN_OP_WARN_LIMIT, 15203e9bd8dSGuenter Roeck word); 15303e9bd8dSGuenter Roeck break; 15403e9bd8dSGuenter Roeck case PMBUS_VIRT_RESET_PIN_HISTORY: 15503e9bd8dSGuenter Roeck ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK); 15603e9bd8dSGuenter Roeck break; 15703e9bd8dSGuenter Roeck default: 15803e9bd8dSGuenter Roeck ret = -ENODATA; 15903e9bd8dSGuenter Roeck break; 16003e9bd8dSGuenter Roeck } 16103e9bd8dSGuenter Roeck return ret; 16203e9bd8dSGuenter Roeck } 16303e9bd8dSGuenter Roeck 1643a2805e8SGuenter Roeck static int lm25066_write_byte(struct i2c_client *client, int page, u8 value) 1653a2805e8SGuenter Roeck { 1663a2805e8SGuenter Roeck if (page > 1) 167179144a0SGuenter Roeck return -ENXIO; 1683a2805e8SGuenter Roeck 169da8e48abSGuenter Roeck if (page <= 0) 170da8e48abSGuenter Roeck return pmbus_write_byte(client, page, value); 1713a2805e8SGuenter Roeck 1723a2805e8SGuenter Roeck return 0; 1733a2805e8SGuenter Roeck } 1743a2805e8SGuenter Roeck 17503e9bd8dSGuenter Roeck static int lm25066_probe(struct i2c_client *client, 17603e9bd8dSGuenter Roeck const struct i2c_device_id *id) 17703e9bd8dSGuenter Roeck { 17803e9bd8dSGuenter Roeck int config; 17903e9bd8dSGuenter Roeck struct lm25066_data *data; 18003e9bd8dSGuenter Roeck struct pmbus_driver_info *info; 18103e9bd8dSGuenter Roeck 18203e9bd8dSGuenter Roeck if (!i2c_check_functionality(client->adapter, 18303e9bd8dSGuenter Roeck I2C_FUNC_SMBUS_READ_BYTE_DATA)) 18403e9bd8dSGuenter Roeck return -ENODEV; 18503e9bd8dSGuenter Roeck 186*8b313ca7SGuenter Roeck data = devm_kzalloc(&client->dev, sizeof(struct lm25066_data), 187*8b313ca7SGuenter Roeck GFP_KERNEL); 18803e9bd8dSGuenter Roeck if (!data) 18903e9bd8dSGuenter Roeck return -ENOMEM; 19003e9bd8dSGuenter Roeck 19103e9bd8dSGuenter Roeck config = i2c_smbus_read_byte_data(client, LM25066_DEVICE_SETUP); 192*8b313ca7SGuenter Roeck if (config < 0) 193*8b313ca7SGuenter Roeck return config; 19403e9bd8dSGuenter Roeck 19503e9bd8dSGuenter Roeck data->id = id->driver_data; 19603e9bd8dSGuenter Roeck info = &data->info; 19703e9bd8dSGuenter Roeck 19803e9bd8dSGuenter Roeck info->pages = 2; 19903e9bd8dSGuenter Roeck info->format[PSC_VOLTAGE_IN] = direct; 20003e9bd8dSGuenter Roeck info->format[PSC_VOLTAGE_OUT] = direct; 20103e9bd8dSGuenter Roeck info->format[PSC_CURRENT_IN] = direct; 20203e9bd8dSGuenter Roeck info->format[PSC_TEMPERATURE] = direct; 20303e9bd8dSGuenter Roeck info->format[PSC_POWER] = direct; 20403e9bd8dSGuenter Roeck 20503e9bd8dSGuenter Roeck info->m[PSC_TEMPERATURE] = 16; 20603e9bd8dSGuenter Roeck info->b[PSC_TEMPERATURE] = 0; 20703e9bd8dSGuenter Roeck info->R[PSC_TEMPERATURE] = 0; 20803e9bd8dSGuenter Roeck 20903e9bd8dSGuenter Roeck info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT 21003e9bd8dSGuenter Roeck | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN 21103e9bd8dSGuenter Roeck | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; 21203e9bd8dSGuenter Roeck info->func[1] = PMBUS_HAVE_VOUT; 21303e9bd8dSGuenter Roeck 21403e9bd8dSGuenter Roeck info->read_word_data = lm25066_read_word_data; 21503e9bd8dSGuenter Roeck info->write_word_data = lm25066_write_word_data; 2163a2805e8SGuenter Roeck info->write_byte = lm25066_write_byte; 21703e9bd8dSGuenter Roeck 21803e9bd8dSGuenter Roeck switch (id->driver_data) { 21903e9bd8dSGuenter Roeck case lm25066: 22003e9bd8dSGuenter Roeck info->m[PSC_VOLTAGE_IN] = 22070; 22103e9bd8dSGuenter Roeck info->b[PSC_VOLTAGE_IN] = 0; 22203e9bd8dSGuenter Roeck info->R[PSC_VOLTAGE_IN] = -2; 22303e9bd8dSGuenter Roeck info->m[PSC_VOLTAGE_OUT] = 22070; 22403e9bd8dSGuenter Roeck info->b[PSC_VOLTAGE_OUT] = 0; 22503e9bd8dSGuenter Roeck info->R[PSC_VOLTAGE_OUT] = -2; 22603e9bd8dSGuenter Roeck 22703e9bd8dSGuenter Roeck if (config & LM25066_DEV_SETUP_CL) { 22803e9bd8dSGuenter Roeck info->m[PSC_CURRENT_IN] = 6852; 22903e9bd8dSGuenter Roeck info->b[PSC_CURRENT_IN] = 0; 23003e9bd8dSGuenter Roeck info->R[PSC_CURRENT_IN] = -2; 23103e9bd8dSGuenter Roeck info->m[PSC_POWER] = 369; 23203e9bd8dSGuenter Roeck info->b[PSC_POWER] = 0; 23303e9bd8dSGuenter Roeck info->R[PSC_POWER] = -2; 23403e9bd8dSGuenter Roeck } else { 23503e9bd8dSGuenter Roeck info->m[PSC_CURRENT_IN] = 13661; 23603e9bd8dSGuenter Roeck info->b[PSC_CURRENT_IN] = 0; 23703e9bd8dSGuenter Roeck info->R[PSC_CURRENT_IN] = -2; 23803e9bd8dSGuenter Roeck info->m[PSC_POWER] = 736; 23903e9bd8dSGuenter Roeck info->b[PSC_POWER] = 0; 24003e9bd8dSGuenter Roeck info->R[PSC_POWER] = -2; 24103e9bd8dSGuenter Roeck } 24203e9bd8dSGuenter Roeck break; 24303e9bd8dSGuenter Roeck case lm5064: 24403e9bd8dSGuenter Roeck info->m[PSC_VOLTAGE_IN] = 22075; 24503e9bd8dSGuenter Roeck info->b[PSC_VOLTAGE_IN] = 0; 24603e9bd8dSGuenter Roeck info->R[PSC_VOLTAGE_IN] = -2; 24703e9bd8dSGuenter Roeck info->m[PSC_VOLTAGE_OUT] = 22075; 24803e9bd8dSGuenter Roeck info->b[PSC_VOLTAGE_OUT] = 0; 24903e9bd8dSGuenter Roeck info->R[PSC_VOLTAGE_OUT] = -2; 25003e9bd8dSGuenter Roeck 25103e9bd8dSGuenter Roeck if (config & LM25066_DEV_SETUP_CL) { 25203e9bd8dSGuenter Roeck info->m[PSC_CURRENT_IN] = 6713; 25303e9bd8dSGuenter Roeck info->b[PSC_CURRENT_IN] = 0; 25403e9bd8dSGuenter Roeck info->R[PSC_CURRENT_IN] = -2; 25503e9bd8dSGuenter Roeck info->m[PSC_POWER] = 3619; 25603e9bd8dSGuenter Roeck info->b[PSC_POWER] = 0; 25703e9bd8dSGuenter Roeck info->R[PSC_POWER] = -3; 25803e9bd8dSGuenter Roeck } else { 25903e9bd8dSGuenter Roeck info->m[PSC_CURRENT_IN] = 13426; 26003e9bd8dSGuenter Roeck info->b[PSC_CURRENT_IN] = 0; 26103e9bd8dSGuenter Roeck info->R[PSC_CURRENT_IN] = -2; 26203e9bd8dSGuenter Roeck info->m[PSC_POWER] = 7238; 26303e9bd8dSGuenter Roeck info->b[PSC_POWER] = 0; 26403e9bd8dSGuenter Roeck info->R[PSC_POWER] = -3; 26503e9bd8dSGuenter Roeck } 26603e9bd8dSGuenter Roeck break; 26703e9bd8dSGuenter Roeck case lm5066: 26803e9bd8dSGuenter Roeck info->m[PSC_VOLTAGE_IN] = 4587; 26903e9bd8dSGuenter Roeck info->b[PSC_VOLTAGE_IN] = 0; 27003e9bd8dSGuenter Roeck info->R[PSC_VOLTAGE_IN] = -2; 27103e9bd8dSGuenter Roeck info->m[PSC_VOLTAGE_OUT] = 4587; 27203e9bd8dSGuenter Roeck info->b[PSC_VOLTAGE_OUT] = 0; 27303e9bd8dSGuenter Roeck info->R[PSC_VOLTAGE_OUT] = -2; 27403e9bd8dSGuenter Roeck 27503e9bd8dSGuenter Roeck if (config & LM25066_DEV_SETUP_CL) { 27603e9bd8dSGuenter Roeck info->m[PSC_CURRENT_IN] = 10753; 27703e9bd8dSGuenter Roeck info->b[PSC_CURRENT_IN] = 0; 27803e9bd8dSGuenter Roeck info->R[PSC_CURRENT_IN] = -2; 27903e9bd8dSGuenter Roeck info->m[PSC_POWER] = 1204; 28003e9bd8dSGuenter Roeck info->b[PSC_POWER] = 0; 28103e9bd8dSGuenter Roeck info->R[PSC_POWER] = -3; 28203e9bd8dSGuenter Roeck } else { 28303e9bd8dSGuenter Roeck info->m[PSC_CURRENT_IN] = 5405; 28403e9bd8dSGuenter Roeck info->b[PSC_CURRENT_IN] = 0; 28503e9bd8dSGuenter Roeck info->R[PSC_CURRENT_IN] = -2; 28603e9bd8dSGuenter Roeck info->m[PSC_POWER] = 605; 28703e9bd8dSGuenter Roeck info->b[PSC_POWER] = 0; 28803e9bd8dSGuenter Roeck info->R[PSC_POWER] = -3; 28903e9bd8dSGuenter Roeck } 29003e9bd8dSGuenter Roeck break; 29103e9bd8dSGuenter Roeck default: 292*8b313ca7SGuenter Roeck return -ENODEV; 29303e9bd8dSGuenter Roeck } 29403e9bd8dSGuenter Roeck 295*8b313ca7SGuenter Roeck return pmbus_do_probe(client, id, info); 29603e9bd8dSGuenter Roeck } 29703e9bd8dSGuenter Roeck 29803e9bd8dSGuenter Roeck static int lm25066_remove(struct i2c_client *client) 29903e9bd8dSGuenter Roeck { 300866cf12aSGuenter Roeck pmbus_do_remove(client); 301866cf12aSGuenter Roeck return 0; 30203e9bd8dSGuenter Roeck } 30303e9bd8dSGuenter Roeck 30403e9bd8dSGuenter Roeck static const struct i2c_device_id lm25066_id[] = { 30503e9bd8dSGuenter Roeck {"lm25066", lm25066}, 30603e9bd8dSGuenter Roeck {"lm5064", lm5064}, 30703e9bd8dSGuenter Roeck {"lm5066", lm5066}, 30803e9bd8dSGuenter Roeck { } 30903e9bd8dSGuenter Roeck }; 31003e9bd8dSGuenter Roeck 31103e9bd8dSGuenter Roeck MODULE_DEVICE_TABLE(i2c, lm25066_id); 31203e9bd8dSGuenter Roeck 31303e9bd8dSGuenter Roeck /* This is the driver that will be inserted */ 31403e9bd8dSGuenter Roeck static struct i2c_driver lm25066_driver = { 31503e9bd8dSGuenter Roeck .driver = { 31603e9bd8dSGuenter Roeck .name = "lm25066", 31703e9bd8dSGuenter Roeck }, 31803e9bd8dSGuenter Roeck .probe = lm25066_probe, 31903e9bd8dSGuenter Roeck .remove = lm25066_remove, 32003e9bd8dSGuenter Roeck .id_table = lm25066_id, 32103e9bd8dSGuenter Roeck }; 32203e9bd8dSGuenter Roeck 323f0967eeaSAxel Lin module_i2c_driver(lm25066_driver); 32403e9bd8dSGuenter Roeck 32503e9bd8dSGuenter Roeck MODULE_AUTHOR("Guenter Roeck"); 32603e9bd8dSGuenter Roeck MODULE_DESCRIPTION("PMBus driver for LM25066/LM5064/LM5066"); 32703e9bd8dSGuenter Roeck MODULE_LICENSE("GPL"); 328