diff options
Diffstat (limited to 'drivers/power/supply/sbs-battery.c')
| -rw-r--r-- | drivers/power/supply/sbs-battery.c | 440 |
1 files changed, 277 insertions, 163 deletions
diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 83b9924033bd..943c82ee978f 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -7,6 +7,7 @@ #include <linux/bits.h> #include <linux/delay.h> +#include <linux/devm-helpers.h> #include <linux/err.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> @@ -15,11 +16,12 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/property.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/power/sbs-battery.h> #include <linux/power_supply.h> #include <linux/slab.h> #include <linux/stat.h> +#include <linux/string_choices.h> enum { REG_MANUFACTURER_DATA, @@ -30,8 +32,9 @@ enum { REG_CURRENT_AVG, REG_MAX_ERR, REG_CAPACITY, - REG_TIME_TO_EMPTY, - REG_TIME_TO_FULL, + REG_TIME_TO_EMPTY_NOW, + REG_TIME_TO_EMPTY_AVG, + REG_TIME_TO_FULL_AVG, REG_STATUS, REG_CAPACITY_LEVEL, REG_CYCLE_COUNT, @@ -51,6 +54,14 @@ enum { REG_CHARGE_VOLTAGE, }; +#define REG_ADDR_SPEC_INFO 0x1A +#define SPEC_INFO_VERSION_MASK GENMASK(7, 4) +#define SPEC_INFO_VERSION_SHIFT 4 + +#define SBS_VERSION_1_0 1 +#define SBS_VERSION_1_1 2 +#define SBS_VERSION_1_1_WITH_PEC 3 + #define REG_ADDR_MANUFACTURE_DATE 0x1B /* Battery Mode defines */ @@ -93,7 +104,7 @@ static const struct chip_data { [REG_TEMPERATURE] = SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), [REG_VOLTAGE] = - SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 65535), [REG_CURRENT_NOW] = SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), [REG_CURRENT_AVG] = @@ -110,9 +121,11 @@ static const struct chip_data { SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535), [REG_FULL_CHARGE_CAPACITY_CHARGE] = SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535), - [REG_TIME_TO_EMPTY] = + [REG_TIME_TO_EMPTY_NOW] = + SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, 0x11, 0, 65535), + [REG_TIME_TO_EMPTY_AVG] = SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), - [REG_TIME_TO_FULL] = + [REG_TIME_TO_FULL_AVG] = SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535), [REG_CHARGE_CURRENT] = SBS_DATA(POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, 0x14, 0, 65535), @@ -156,6 +169,7 @@ static const enum power_supply_property sbs_properties[] = { POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN, POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_SERIAL_NUMBER, @@ -180,12 +194,19 @@ static const enum power_supply_property sbs_properties[] = { /* Supports special manufacturer commands from TI BQ20Z65 and BQ20Z75 IC. */ #define SBS_FLAGS_TI_BQ20ZX5 BIT(0) +static const enum power_supply_property string_properties[] = { + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +#define NR_STRING_BUFFERS ARRAY_SIZE(string_properties) + struct sbs_info { struct i2c_client *client; struct power_supply *power_supply; bool is_present; struct gpio_desc *gpio_detect; - bool enable_detection; bool charger_broadcasts; int last_state; int poll_time; @@ -194,11 +215,32 @@ struct sbs_info { struct delayed_work work; struct mutex mode_lock; u32 flags; + int technology; + char strings[NR_STRING_BUFFERS][I2C_SMBUS_BLOCK_MAX + 1]; }; -static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; -static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; -static char chemistry[I2C_SMBUS_BLOCK_MAX + 1]; +static char *sbs_get_string_buf(struct sbs_info *chip, + enum power_supply_property psp) +{ + int i = 0; + + for (i = 0; i < NR_STRING_BUFFERS; i++) + if (string_properties[i] == psp) + return chip->strings[i]; + + return ERR_PTR(-EINVAL); +} + +static void sbs_invalidate_cached_props(struct sbs_info *chip) +{ + int i = 0; + + chip->technology = -1; + + for (i = 0; i < NR_STRING_BUFFERS; i++) + chip->strings[i][0] = 0; +} + static bool force_load; static int sbs_read_word_data(struct i2c_client *client, u8 address); @@ -224,14 +266,64 @@ exit: static int sbs_update_presence(struct sbs_info *chip, bool is_present) { + struct i2c_client *client = chip->client; + int retries = chip->i2c_retry_count; + s32 ret = 0; + u8 version; + if (chip->is_present == is_present) return 0; if (!is_present) { chip->is_present = false; + /* Disable PEC when no device is present */ + client->flags &= ~I2C_CLIENT_PEC; + sbs_invalidate_cached_props(chip); return 0; } + /* Check if device supports packet error checking and use it */ + while (retries > 0) { + ret = i2c_smbus_read_word_data(client, REG_ADDR_SPEC_INFO); + if (ret >= 0) + break; + + /* + * Some batteries trigger the detection pin before the + * I2C bus is properly connected. This works around the + * issue. + */ + msleep(100); + + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, "failed to read spec info: %d\n", ret); + + /* fallback to old behaviour */ + client->flags &= ~I2C_CLIENT_PEC; + chip->is_present = true; + + return ret; + } + + version = (ret & SPEC_INFO_VERSION_MASK) >> SPEC_INFO_VERSION_SHIFT; + + if (version == SBS_VERSION_1_1_WITH_PEC) + client->flags |= I2C_CLIENT_PEC; + else + client->flags &= ~I2C_CLIENT_PEC; + + if (of_device_is_compatible(client->dev.parent->of_node, "google,cros-ec-i2c-tunnel") + && client->flags & I2C_CLIENT_PEC) { + dev_info(&client->dev, "Disabling PEC because of broken Cros-EC implementation\n"); + client->flags &= ~I2C_CLIENT_PEC; + } + + dev_dbg(&client->dev, "PEC: %s\n", + str_enabled_disabled(client->flags & I2C_CLIENT_PEC)); + if (!chip->is_present && is_present && !chip->charger_broadcasts) sbs_disable_charger_broadcasts(chip); @@ -263,8 +355,7 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address) return ret; } -static int sbs_read_string_data(struct i2c_client *client, u8 address, - char *values) +static int sbs_read_string_data_fallback(struct i2c_client *client, u8 address, char *values) { struct sbs_info *chip = i2c_get_clientdata(client); s32 ret = 0, block_length = 0; @@ -274,6 +365,9 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address, retries_length = chip->i2c_retry_count; retries_block = chip->i2c_retry_count; + dev_warn_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_BLOCK_DATA.\n" + "Fallback method does not support PEC.\n"); + /* Adapter needs to support these two functions */ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | @@ -329,6 +423,38 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address, return ret; } +static int sbs_read_string_data(struct i2c_client *client, u8 address, char *values) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + int retries = chip->i2c_retry_count; + int ret = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { + bool pec = client->flags & I2C_CLIENT_PEC; + client->flags &= ~I2C_CLIENT_PEC; + ret = sbs_read_string_data_fallback(client, address, values); + if (pec) + client->flags |= I2C_CLIENT_PEC; + return ret; + } + + while (retries > 0) { + ret = i2c_smbus_read_block_data(client, address, values); + if (ret >= 0) + break; + retries--; + } + + if (ret < 0) { + dev_dbg(&client->dev, "failed to read block 0x%x: %d\n", address, ret); + return ret; + } + + /* add string termination */ + values[ret] = '\0'; + return ret; +} + static int sbs_write_word_data(struct i2c_client *client, u8 address, u16 value) { @@ -389,37 +515,6 @@ static bool sbs_bat_needs_calibration(struct i2c_client *client) return !!(ret & BIT(7)); } -static int sbs_get_battery_presence_and_health( - struct i2c_client *client, enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret; - - /* Dummy command; if it succeeds, battery is present. */ - ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); - - if (ret < 0) { /* battery not present*/ - if (psp == POWER_SUPPLY_PROP_PRESENT) { - val->intval = 0; - return 0; - } - return ret; - } - - if (psp == POWER_SUPPLY_PROP_PRESENT) - val->intval = 1; /* battery present */ - else { /* POWER_SUPPLY_PROP_HEALTH */ - if (sbs_bat_needs_calibration(client)) { - val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED; - } else { - /* SBS spec doesn't have a general health command. */ - val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; - } - } - - return 0; -} - static int sbs_get_ti_battery_presence_and_health( struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val) @@ -478,6 +573,41 @@ static int sbs_get_ti_battery_presence_and_health( return 0; } +static int sbs_get_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + int ret; + + if (chip->flags & SBS_FLAGS_TI_BQ20ZX5) + return sbs_get_ti_battery_presence_and_health(client, psp, val); + + /* Dummy command; if it succeeds, battery is present. */ + ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); + + if (ret < 0) { /* battery not present*/ + if (psp == POWER_SUPPLY_PROP_PRESENT) { + val->intval = 0; + return 0; + } + return ret; + } + + if (psp == POWER_SUPPLY_PROP_PRESENT) + val->intval = 1; /* battery present */ + else { /* POWER_SUPPLY_PROP_HEALTH */ + if (sbs_bat_needs_calibration(client)) { + val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED; + } else { + /* SBS spec doesn't have a general health command. */ + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } + } + + return 0; +} + static int sbs_get_battery_property(struct i2c_client *client, int reg_offset, enum power_supply_property psp, union power_supply_propval *val) @@ -545,17 +675,45 @@ static int sbs_get_battery_property(struct i2c_client *client, return 0; } -static int sbs_get_battery_string_property(struct i2c_client *client, - int reg_offset, enum power_supply_property psp, char *val) +static int sbs_get_property_index(struct i2c_client *client, + enum power_supply_property psp) { - s32 ret; + int count; - ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val); + for (count = 0; count < ARRAY_SIZE(sbs_data); count++) + if (psp == sbs_data[count].psp) + return count; - if (ret < 0) - return ret; + dev_warn(&client->dev, + "%s: Invalid Property - %d\n", __func__, psp); - return 0; + return -EINVAL; +} + +static const char *sbs_get_constant_string(struct sbs_info *chip, + enum power_supply_property psp) +{ + int ret; + char *buf; + u8 addr; + + buf = sbs_get_string_buf(chip, psp); + if (IS_ERR(buf)) + return buf; + + if (!buf[0]) { + ret = sbs_get_property_index(chip->client, psp); + if (ret < 0) + return ERR_PTR(ret); + + addr = sbs_data[ret].addr; + + ret = sbs_read_string_data(chip->client, addr, buf); + if (ret < 0) + return ERR_PTR(ret); + } + + return buf; } static void sbs_unit_adjustment(struct i2c_client *client, @@ -595,6 +753,7 @@ static void sbs_unit_adjustment(struct i2c_client *client, val->intval -= TEMP_KELVIN_TO_CELSIUS; break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: /* sbs provides time to empty and time to full in minutes. @@ -678,48 +837,36 @@ static int sbs_get_battery_serial_number(struct i2c_client *client, return 0; } -static int sbs_get_property_index(struct i2c_client *client, - enum power_supply_property psp) -{ - int count; - for (count = 0; count < ARRAY_SIZE(sbs_data); count++) - if (psp == sbs_data[count].psp) - return count; - - dev_warn(&client->dev, - "%s: Invalid Property - %d\n", __func__, psp); - - return -EINVAL; -} - -static int sbs_get_chemistry(struct i2c_client *client, +static int sbs_get_chemistry(struct sbs_info *chip, union power_supply_propval *val) { - enum power_supply_property psp = POWER_SUPPLY_PROP_TECHNOLOGY; - int ret; + const char *chemistry; - ret = sbs_get_property_index(client, psp); - if (ret < 0) - return ret; + if (chip->technology != -1) { + val->intval = chip->technology; + return 0; + } - ret = sbs_get_battery_string_property(client, ret, psp, - chemistry); - if (ret < 0) - return ret; + chemistry = sbs_get_constant_string(chip, POWER_SUPPLY_PROP_TECHNOLOGY); + + if (IS_ERR(chemistry)) + return PTR_ERR(chemistry); if (!strncasecmp(chemistry, "LION", 4)) - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + chip->technology = POWER_SUPPLY_TECHNOLOGY_LION; else if (!strncasecmp(chemistry, "LiP", 3)) - val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO; + chip->technology = POWER_SUPPLY_TECHNOLOGY_LIPO; else if (!strncasecmp(chemistry, "NiCd", 4)) - val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd; + chip->technology = POWER_SUPPLY_TECHNOLOGY_NiCd; else if (!strncasecmp(chemistry, "NiMH", 4)) - val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; + chip->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; else - val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + + if (chip->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN) + dev_warn(&chip->client->dev, "Unknown chemistry: %s\n", chemistry); - if (val->intval == POWER_SUPPLY_TECHNOLOGY_UNKNOWN) - dev_warn(&client->dev, "Unknown chemistry: %s\n", chemistry); + val->intval = chip->technology; return 0; } @@ -763,6 +910,7 @@ static int sbs_get_property(struct power_supply *psy, int ret = 0; struct sbs_info *chip = power_supply_get_drvdata(psy); struct i2c_client *client = chip->client; + const char *str; if (chip->gpio_detect) { ret = gpiod_get_value_cansleep(chip->gpio_detect); @@ -780,12 +928,7 @@ static int sbs_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_HEALTH: - if (chip->flags & SBS_FLAGS_TI_BQ20ZX5) - ret = sbs_get_ti_battery_presence_and_health(client, - psp, val); - else - ret = sbs_get_battery_presence_and_health(client, psp, - val); + ret = sbs_get_battery_presence_and_health(client, psp, val); /* this can only be true if no gpio is used */ if (psp == POWER_SUPPLY_PROP_PRESENT) @@ -793,7 +936,7 @@ static int sbs_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_TECHNOLOGY: - ret = sbs_get_chemistry(client, val); + ret = sbs_get_chemistry(chip, val); if (ret < 0) break; @@ -829,6 +972,7 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_TEMP: + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: @@ -845,23 +989,12 @@ static int sbs_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_MODEL_NAME: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_string_property(client, ret, psp, - model_name); - val->strval = model_name; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - ret = sbs_get_property_index(client, psp); - if (ret < 0) - break; - - ret = sbs_get_battery_string_property(client, ret, psp, - manufacturer); - val->strval = manufacturer; + str = sbs_get_constant_string(chip, psp); + if (IS_ERR(str)) + ret = PTR_ERR(str); + else + val->strval = str; break; case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: @@ -876,32 +1009,30 @@ static int sbs_get_property(struct power_supply *psy, return -EINVAL; } - if (!chip->enable_detection) - goto done; + if (!chip->gpio_detect && chip->is_present != (ret >= 0)) { + bool old_present = chip->is_present; + union power_supply_propval val; + int err = sbs_get_battery_presence_and_health( + client, POWER_SUPPLY_PROP_PRESENT, &val); - if (!chip->gpio_detect && - chip->is_present != (ret >= 0)) { - sbs_update_presence(chip, (ret >= 0)); - power_supply_changed(chip->power_supply); + sbs_update_presence(chip, !err && val.intval); + + if (old_present != chip->is_present) + power_supply_changed(chip->power_supply); } done: if (!ret) { /* Convert units to match requirements for power supply class */ sbs_unit_adjustment(client, psp, val); + dev_dbg(&client->dev, + "%s: property = %d, value = %x\n", __func__, + psp, val->intval); + } else if (!chip->is_present) { + /* battery not present, so return NODATA for properties */ + ret = -ENODATA; } - - dev_dbg(&client->dev, - "%s: property = %d, value = %x\n", __func__, psp, val->intval); - - if (ret && chip->is_present) - return ret; - - /* battery not present, so return NODATA for properties */ - if (ret) - return -ENODATA; - - return 0; + return ret; } static void sbs_supply_changed(struct sbs_info *chip) @@ -1005,12 +1136,12 @@ static int sbs_probe(struct i2c_client *client) if (!chip) return -ENOMEM; - chip->flags = (u32)(uintptr_t)device_get_match_data(&client->dev); + chip->flags = (uintptr_t)i2c_get_match_data(client); chip->client = client; - chip->enable_detection = false; - psy_cfg.of_node = client->dev.of_node; + psy_cfg.fwnode = dev_fwnode(&client->dev); psy_cfg.drv_data = chip; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + sbs_invalidate_cached_props(chip); mutex_init(&chip->mode_lock); /* use pdata if available, fall back to DT properties, @@ -1037,11 +1168,9 @@ static int sbs_probe(struct i2c_client *client) chip->gpio_detect = devm_gpiod_get_optional(&client->dev, "sbs,battery-detect", GPIOD_IN); - if (IS_ERR(chip->gpio_detect)) { - dev_err(&client->dev, "Failed to get gpio: %ld\n", - PTR_ERR(chip->gpio_detect)); - return PTR_ERR(chip->gpio_detect); - } + if (IS_ERR(chip->gpio_detect)) + return dev_err_probe(&client->dev, PTR_ERR(chip->gpio_detect), + "Failed to get gpio\n"); i2c_set_clientdata(client, chip); @@ -1068,43 +1197,29 @@ skip_gpio: * to the battery. */ if (!(force_load || chip->gpio_detect)) { - rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); + union power_supply_propval val; - if (rc < 0) { - dev_err(&client->dev, "%s: Failed to get device status\n", - __func__); - goto exit_psupply; - } + rc = sbs_get_battery_presence_and_health( + client, POWER_SUPPLY_PROP_PRESENT, &val); + if (rc < 0 || !val.intval) + return dev_err_probe(&client->dev, -ENODEV, + "Failed to get present status\n"); } + rc = devm_delayed_work_autocancel(&client->dev, &chip->work, + sbs_delayed_work); + if (rc) + return rc; + chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc, &psy_cfg); - if (IS_ERR(chip->power_supply)) { - dev_err(&client->dev, - "%s: Failed to register power supply\n", __func__); - rc = PTR_ERR(chip->power_supply); - goto exit_psupply; - } + if (IS_ERR(chip->power_supply)) + return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), + "Failed to register power supply\n"); dev_info(&client->dev, "%s: battery gas gauge device registered\n", client->name); - INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); - - chip->enable_detection = true; - - return 0; - -exit_psupply: - return rc; -} - -static int sbs_remove(struct i2c_client *client) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - - cancel_delayed_work_sync(&chip->work); - return 0; } @@ -1139,9 +1254,9 @@ static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); #endif static const struct i2c_device_id sbs_id[] = { - { "bq20z65", 0 }, - { "bq20z75", 0 }, - { "sbs-battery", 1 }, + { "bq20z65", SBS_FLAGS_TI_BQ20ZX5 }, + { "bq20z75", SBS_FLAGS_TI_BQ20ZX5 }, + { "sbs-battery", 0 }, {} }; MODULE_DEVICE_TABLE(i2c, sbs_id); @@ -1161,8 +1276,7 @@ static const struct of_device_id sbs_dt_ids[] = { MODULE_DEVICE_TABLE(of, sbs_dt_ids); static struct i2c_driver sbs_battery_driver = { - .probe_new = sbs_probe, - .remove = sbs_remove, + .probe = sbs_probe, .alert = sbs_alert, .id_table = sbs_id, .driver = { |
