/* ts81001.c - basic driver for Triune's 81001 */ /* * Triune ts81001 driver * * Copyright 2015 Olio Devices * * Mattis Fjallstrom (mattis@oliodevices.com) */ /* * DESCRIPTION * =========== * This driver isn't strictly necessary. It provides access to some * of the basic functionality of the ts81001 from the linux kernel in * an easier way than otherwise. (The standard way would just be to * query the i2c bus). * * The plan is to also simplify updating the charger firmware ... but * that's not there yet. For now I'll also hardcode all the settings * (like i2c bus address) but that may change later. */ /* * Modification History * ==================== * 01a, 20150921, mfj Created */ /* Includes */ #include #include #include #include #include #include #include "ts81001.h" /* Defines */ #ifdef OLIODEBUG #define olio_debug(format, ...) printk ("OLIO %s: ", __FUNCTION__); printk (format, ##__VA_ARGS__) #else #define olio_debug(format, ...) #endif /* Globals */ const int TS81001_ADDR = 0x49; const u8 TS81001_STATUS_REG = 0x10; const u8 TS81001_CURRENT_REG_LSB = 0x20; const u8 TS81001_CURRENT_REG_MSB = 0x21; /* Trying 400mA since we can't actually set 390mA. */ const u16 TS81001_CHARGE_CURRENT = 400; const u8 TS81001_CHARGE_CURRENT_LSB = 0x90; /* 0x190 == 400 (0x186 == 390)*/ const u8 TS81001_CHARGE_CURRENT_MSB = 0x01; const u8 TS81001_CHARGE_CURR_100_LSB = 0x64; /* 0x64 == 100 */ const u8 TS81001_CHARGE_CURR_100_MSB = 0x00; /*************************************************************************** * Operations */ static int ts81001_i2c_read (struct i2c_client * client, u8 reg_addr, int len, u8 * data, bool b_lock) { int err = 0; struct i2c_msg msg[2]; olio_debug ("entered\n"); msg[0].addr = client->addr; msg[0].flags = client->flags; msg[0].len = 1; msg[0].buf = ®_addr; msg[1].addr = client->addr; msg[1].flags = client->flags | I2C_M_RD; msg[1].len = len; msg[1].buf = data; if (b_lock) /* TODO: Add locking! */ err = i2c_transfer(client->adapter, msg, 2); else err = i2c_transfer(client->adapter, msg, 2); olio_debug ("exiting\n"); return err; } static int ts81001_write_i2c_blk(struct i2c_client * client, u8 reg_addr, u8 * data, u16 sz) { struct i2c_msg msg; int ret; u8 buf[3]; olio_debug ("entered\n"); if (!client->adapter) return -ENODEV; buf[0] = reg_addr; memcpy(&buf[1], data, sz); olio_debug ("writing 0x%x,0x%x to 0x%x device 0x%x\n", buf[1], buf[2], buf[0], client->addr); msg.addr = client->addr; msg.flags = 0; //client->flags; msg.len = sz + 1; msg.buf = buf; ret = i2c_transfer(client->adapter, &msg, 1); olio_debug ("exiting, ret = %d\n", ret); return ret; } /*************************************************************************** * ts81001_get_status - get current charger state. * * This function reads the status from the device on the i2c bus. The * ts81001 can be really slow to react, so we'll try again a few times * before accepting failure. * * If we can't read a value, we return -1. This let's other parts of the * system know that we failed to read status, and not change anything. */ static int ts81001_get_status (struct ts81001 * ts) { u8 data; ts81001_state_t state; int len = 1; int err = 0; int retries = 2; int i; olio_debug ("entered\n"); for (i = 0; i < retries; i++) { err = ts81001_i2c_read (ts->client, TS81001_STATUS_REG, len, &data, false); if (err <= 0) { olio_debug ("Error reading status from ts81001, err = %d\n", err); state = -1; } else { state = (ts81001_state_t) data; break; } } olio_debug ("exiting\n"); return state; } /*************************************************************************** * ts81001_request_charge - ask for current from charger * * This function allows us to set a new current for the charger. It depends * on the firmware being recent enough, though. Returns the number of bytes * written, or error (negative values) if needed. */ static int ts81001_request_charge (struct ts81001 * ts, u16 mA) { int retries = 2; int err = 0; int i; /* u16 request_current = TS81001_CHARGE_CURRENT; */ u8 data[2]; data[0] = mA & 0x00ff; data[1] = (mA & 0xff00) >> 8; printk ("OLIO %s entered, requested current = lb:0x%x, hb:0x%x\n", __FUNCTION__, data[0], data[1]); for (i = 0; i < retries; i++) { err = ts81001_write_i2c_blk(ts->client, TS81001_CURRENT_REG_LSB, &data[0], 2); if (err <= 0) olio_debug ("error (%d) writing data to " "register (try = %d)\n", err, i); } return err; } /*************************************************************************** * ts81001_charging_stop - send charge complete to power provider * * This function allows to stop charging - stopping and restarting charging * is intended to simulate the device taken off the charger and put back on. */ #define TS81001_CHARGE_COMPLETE 0x01 #define TS81001_STATE_REPORT_REG 0x12 static int ts81001_charging_stop (struct ts81001 * ts) { u8 data; int len = 1; int err = 0; int i, retries = 2; olio_debug ("entered\n"); data = TS81001_CHARGE_COMPLETE; for (i = 0; i < retries; i++) { err = ts81001_write_i2c_blk(ts->client, TS81001_STATE_REPORT_REG, (u8*) &data, len); } if (err <= 0) { olio_debug ("Error writing charging stop to ts81001, " "err = %d\n", err); } olio_debug ("exiting\n"); return err; } /*************************************************************************** * ts81001_charging_stop - send charge complete to power provider * * This function allows to stop charging - stopping and restarting charging * is intended to simulate the device taken off the charger and put back on. */ #define TS81001_CHARGE_RESTART 0xFF static int ts81001_charging_restart (struct ts81001 * ts) { u8 data; int len = 1; int err = 0; int i, retries = 2; olio_debug ("entered\n"); data = TS81001_CHARGE_RESTART; for (i = 0; i < retries; i++) { err = ts81001_write_i2c_blk(ts->client, TS81001_STATE_REPORT_REG, (u8*) &data, len); if (err <= 0) { olio_debug ("Error writing state to ts81001, " "err = %d\n", err); } } olio_debug ("exiting\n"); return err; } #if 0 /* NOT FUNCTIONAL, NOT SURE HOW TO MAKE IT FUNCTIONAL! */ /*************************************************************************** * ts81001_disconnect - stop talking to power TX * * If we send a 'signal strength' package, the TX unit should forget that * we've reached full charge. */ static int ts81001_disconnect (struct ts81001 * ts) { u8 data; int len = 1; int err = 0; int i, retries = 2; olio_debug ("entered\n"); data = TS81001_CHARGE_RESTART; for (i = 0; i < retries; i++) { err = ts81001_write_i2c_blk(ts->client, TS81001_STATE_REPORT_REG, (u8*) &data, len); if (err <= 0) { olio_debug ("Error writing state to ts81001, " "err = %d\n", err); } } olio_debug ("exiting\n"); return err; } #endif static struct ts81001_ops ts_ops = { .get_status = ts81001_get_status, .request_charge = ts81001_request_charge, .charge_stop = ts81001_charging_stop, .charge_start = ts81001_charging_restart, }; /*************************************************************************** * sysfs operations ***************************************************************************/ /* sysfs entry for requesting power, mA's. */ /*************************************************************************** * ts81001_sysfs_set_power_request - request power level from TX * * This entry allows us to request a certain power level from the TX unit. * Mainly used for testing. */ static ssize_t ts81001_sysfs_set_power_request ( struct device * dev, struct device_attribute * attr, const char * buf, size_t count) { int mA_req; int s; u16 mA; ssize_t retval = 0; struct ts81001 * ts81001 = i2c_get_clientdata (to_i2c_client (dev)); if ((s = sscanf (buf, "%d\n", &mA_req)) != 1) { printk ("%s OLIO wrong number of args (%d, expected 1) " "for power request.\n", __FUNCTION__, s); retval = -EINVAL; goto done; } else { printk ("%s OLIO read %d for new power level\n", __FUNCTION__, mA_req); } if (mA_req > 0xffff) { printk ("%s OLIO warning, max request is %d.\n", __FUNCTION__, 0xffff); mA_req = 0xffff; } mA = (u16) mA_req; retval = ts81001_request_charge (ts81001, mA); done: return count; } /*************************************************************************** * ts81001_sysfs_show_power_request - show the power level last requested * * This sysfs entry will read the value of the power request registers in * the ts81001 and return it. */ static ssize_t ts81001_sysfs_show_power_request (struct device * dev, struct device_attribute * attr, char * buf) { return 0; } static struct device_attribute ts81001_device_attr = __ATTR(ts81001_power_request, S_IWUGO | S_IRUGO, ts81001_sysfs_show_power_request, ts81001_sysfs_set_power_request); /*************************************************************************** * probe */ static int ts81001_i2c_probe(struct i2c_client * client, const struct i2c_device_id * id) { struct ts81001 *ts81001; int ret = 0; int chip_id = id->driver_data; olio_debug("OLIO entered\n"); ts81001 = kmalloc(sizeof(*ts81001), GFP_KERNEL); if (!ts81001) return -ENOMEM; ts81001->dev = &client->dev; ts81001->name = client->name; ts81001->client = client; ts81001->id = chip_id; ts81001->ops = &ts_ops; /* Fill out i2c_client struct */ i2c_set_clientdata(client, ts81001); device_create_file (ts81001->dev, &ts81001_device_attr); olio_debug ("exiting\n"); return ret; } static int ts81001_i2c_remove(struct i2c_client * client) { struct ts81001 * ts = i2c_get_clientdata(client); kfree (ts); return 0; } static const struct i2c_device_id ts81001_id_table[] = { { TS81001_DEV_NAME }, { }, }; MODULE_DEVICE_TABLE(i2c, ts81001_id_table); static struct i2c_driver ts81001_i2c_driver = { .probe = ts81001_i2c_probe, .remove = ts81001_i2c_remove, .id_table = ts81001_id_table, .driver = { .name = "ts81001", .owner = THIS_MODULE, }, }; module_i2c_driver(ts81001_i2c_driver); MODULE_AUTHOR("Mattis Fjallstrom