#define CLK_RATE_19_2MHZ 19200000
#define CLK_RATE_25_0MHZ 25000000
+/* X-Powers AXP288 register set */
+#define ALDO1_SEL_REG 0x28
+#define ALDO1_CTRL3_REG 0x13
+#define ALDO1_2P8V 0x16
+#define ALDO1_CTRL3_SHIFT 0x05
+
+#define ELDO_CTRL_REG 0x12
+
#define ELDO1_SEL_REG 0x19
#define ELDO1_1P8V 0x16
#define ELDO1_CTRL_SHIFT 0x00
+
#define ELDO2_SEL_REG 0x1a
#define ELDO2_1P8V 0x16
#define ELDO2_CTRL_SHIFT 0x01
+/* TI SND9039 PMIC register set */
+#define LDO9_REG 0x49
+#define LDO10_REG 0x4a
+#define LDO11_REG 0x4b
+
+#define LDO_2P8V_ON 0x2f /* 0x2e selects 2.85V ... */
+#define LDO_2P8V_OFF 0x2e /* ... bottom bit is "enabled" */
+
+#define LDO_1P8V_ON 0x59 /* 0x58 selects 1.80V ... */
+#define LDO_1P8V_OFF 0x58 /* ... bottom bit is "enabled" */
+
+/* CRYSTAL COVE PMIC register set */
+#define CRYSTAL_1P8V_REG 0x57
+#define CRYSTAL_2P8V_REG 0x5d
+#define CRYSTAL_ON 0x63
+#define CRYSTAL_OFF 0x62
+
struct gmin_subdev {
struct v4l2_subdev *subdev;
int clock_num;
bool v2p8_on;
bool v1p2_on;
bool v2p8_vcm_on;
+
+ u8 pwm_i2c_addr;
+
+ /* For PMIC AXP */
+ int eldo1_sel_reg, eldo1_1p8v, eldo1_ctrl_shift;
+ int eldo2_sel_reg, eldo2_1p8v, eldo2_ctrl_shift;
};
static struct gmin_subdev gmin_subdevs[MAX_SUBDEVS];
#define PMIC_ACPI_TI "INT33F5:00" /* Dollar Cove TI PMIC */
#define PMIC_ACPI_CRYSTALCOVE "INT33FD:00" /* Crystal Cove PMIC */
+#define PMIC_PLATFORM_TI "intel_soc_pmic_chtdc_ti"
+
static enum {
PMIC_UNSET = 0,
PMIC_REGULATOR,
#define GMIN_PMC_CLK_NAME 14 /* "pmc_plt_clk_[0..5]" */
static char gmin_pmc_clk_name[GMIN_PMC_CLK_NAME];
-struct gmin_match_name {
- const char *name;
- struct device *dev;
-};
-
-static int gmin_match_one(struct device *dev, void *data)
+static int gmin_i2c_match_one(struct device *dev, const void *data)
{
- struct gmin_match_name *match = data;
- const char *name = match->name;
+ const char *name = data;
struct i2c_client *client;
if (dev->type != &i2c_client_type)
client = to_i2c_client(dev);
- dev_info(match->dev, "found '%s' at address 0x%02x, adapter %d\n",
- client->name, client->addr, client->adapter->nr);
-
return (!strcmp(name, client->name));
}
-static bool gmin_i2c_dev_exists(struct device *dev, char *name)
+static struct i2c_client *gmin_i2c_dev_exists(struct device *dev, char *name,
+ struct i2c_client **client)
{
- struct gmin_match_name match;
- bool found;
- int ret = 0;
+ struct device *d;
+
+ while ((d = bus_find_device(&i2c_bus_type, NULL, name,
+ gmin_i2c_match_one))) {
+ *client = to_i2c_client(d);
+ dev_dbg(dev, "found '%s' at address 0x%02x, adapter %d\n",
+ (*client)->name, (*client)->addr,
+ (*client)->adapter->nr);
+ return *client;
+ }
- match.dev = dev;
- match.name = name;
+ return NULL;
+}
- ret = i2c_for_each_dev(&match, gmin_match_one);
+static int gmin_i2c_write(struct device *dev, u16 i2c_addr, u8 reg,
+ u32 value, u32 mask)
+{
+ int ret;
- found = !!ret;
+ /*
+ * FIXME: Right now, the intel_pmic driver just write values
+ * directly at the regmap, instead of properly implementing
+ * i2c_transfer() mechanism. Let's use the same interface here,
+ * as otherwise we may face issues.
+ */
- if (found)
- dev_info(dev, "%s found on I2C\n", name);
- else
- dev_info(dev, "%s not found on I2C\n", name);
+ dev_dbg(dev,
+ "I2C write, addr: 0x%02x, reg: 0x%02x, value: 0x%02x, mask: 0x%02x\n",
+ i2c_addr, reg, value, mask);
+
+ ret = intel_soc_pmic_exec_mipi_pmic_seq_element(i2c_addr, reg,
+ value, mask);
+
+ if (ret == -EOPNOTSUPP) {
+ dev_err(dev,
+ "ACPI didn't mapped the OpRegion needed to access I2C address 0x%02x.\n"
+ "Need to compile the Kernel using CONFIG_*_PMIC_OPREGION settings\n",
+ i2c_addr);
+ return ret;
+ }
- return found;
+ return ret;
}
static struct gmin_subdev *gmin_subdev_add(struct v4l2_subdev *subdev)
{
int i, ret;
struct device *dev;
- struct i2c_client *client = v4l2_get_subdevdata(subdev);
+ struct i2c_client *power = NULL, *client = v4l2_get_subdevdata(subdev);
if (!client)
return NULL;
dev = &client->dev;
if (!pmic_id) {
- if (gmin_i2c_dev_exists(dev, PMIC_ACPI_TI))
+ if (gmin_i2c_dev_exists(dev, PMIC_ACPI_TI, &power))
pmic_id = PMIC_TI;
- else if (gmin_i2c_dev_exists(dev, PMIC_ACPI_AXP))
+ else if (gmin_i2c_dev_exists(dev, PMIC_ACPI_AXP, &power))
pmic_id = PMIC_AXP;
- else if (gmin_i2c_dev_exists(dev, PMIC_ACPI_CRYSTALCOVE))
+ else if (gmin_i2c_dev_exists(dev, PMIC_ACPI_CRYSTALCOVE, &power))
pmic_id = PMIC_CRYSTALCOVE;
else
pmic_id = PMIC_REGULATOR;
if (i >= MAX_SUBDEVS)
return NULL;
- dev_info(dev,
- "gmin: power management provided via %s\n",
- pmic_name[pmic_id]);
+
+ if (power) {
+ gmin_subdevs[i].pwm_i2c_addr = power->addr;
+ dev_info(dev,
+ "gmin: power management provided via %s (i2c addr 0x%02x)\n",
+ pmic_name[pmic_id], power->addr);
+ } else {
+ dev_info(dev, "gmin: power management provided via %s\n",
+ pmic_name[pmic_id]);
+ }
gmin_subdevs[i].subdev = subdev;
gmin_subdevs[i].clock_num = gmin_get_var_int(dev, false, "CamClk", 0);
if (IS_ERR(gmin_subdevs[i].gpio1))
gmin_subdevs[i].gpio1 = NULL;
- if (pmic_id == PMIC_REGULATOR) {
- /* Those regulators may have different names depending on the BIOS */
- gmin_subdevs[i].v1p8_reg = regulator_get_optional(dev, "V1P8SX");
- gmin_subdevs[i].v2p8_reg = regulator_get_optional(dev, "V2P8SX");
-
+ switch (pmic_id) {
+ case PMIC_REGULATOR:
+ gmin_subdevs[i].v1p8_reg = regulator_get(dev, "V1P8SX");
+ gmin_subdevs[i].v2p8_reg = regulator_get(dev, "V2P8SX");
gmin_subdevs[i].v1p2_reg = regulator_get(dev, "V1P2A");
gmin_subdevs[i].v2p8_vcm_reg = regulator_get(dev, "VPROG4B");
- /*
- * Based on DTST dumps on newer Atom E3800 devices, it seems that
- * the regulators data now have new names.
- */
- if (IS_ERR(gmin_subdevs[i].v1p8_reg))
- gmin_subdevs[i].v1p8_reg = regulator_get(dev, "Regulator1p8v");
-
- if (IS_ERR(gmin_subdevs[i].v2p8_reg))
- gmin_subdevs[i].v2p8_reg = regulator_get(dev, "Regulator2p8v");
-
-
/* Note: ideally we would initialize v[12]p8_on to the
* output of regulator_is_enabled(), but sadly that
* API is broken with the current drivers, returning
* "unbalanced disable" WARNing if we try to disable
* it.
*/
+ break;
+
+ case PMIC_AXP:
+ gmin_subdevs[i].eldo1_1p8v = gmin_get_var_int(dev, false,
+ "eldo1_1p8v",
+ ELDO1_1P8V);
+ gmin_subdevs[i].eldo1_sel_reg = gmin_get_var_int(dev, false,
+ "eldo1_sel_reg",
+ ELDO1_SEL_REG);
+ gmin_subdevs[i].eldo1_ctrl_shift = gmin_get_var_int(dev, false,
+ "eldo1_ctrl_shift",
+ ELDO1_CTRL_SHIFT);
+ gmin_subdevs[i].eldo2_1p8v = gmin_get_var_int(dev, false,
+ "eldo2_1p8v",
+ ELDO2_1P8V);
+ gmin_subdevs[i].eldo2_sel_reg = gmin_get_var_int(dev, false,
+ "eldo2_sel_reg",
+ ELDO2_SEL_REG);
+ gmin_subdevs[i].eldo2_ctrl_shift = gmin_get_var_int(dev, false,
+ "eldo2_ctrl_shift",
+ ELDO2_CTRL_SHIFT);
+ gmin_subdevs[i].pwm_i2c_addr = power->addr;
+ break;
+
+ default:
+ break;
}
return &gmin_subdevs[i];
return gmin_subdev_add(subdev);
}
+static int axp_regulator_set(struct device *dev, struct gmin_subdev *gs,
+ int sel_reg, u8 setting,
+ int ctrl_reg, int shift, bool on)
+{
+ int ret;
+ int val;
+
+ ret = gmin_i2c_write(dev, gs->pwm_i2c_addr, sel_reg, setting, 0xff);
+ if (ret)
+ return ret;
+
+ val = on ? 1 << shift : 0;
+
+ ret = gmin_i2c_write(dev, gs->pwm_i2c_addr, sel_reg, val, 1 << shift);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int axp_v1p8_on(struct device *dev, struct gmin_subdev *gs)
+{
+ int ret;
+ ret = axp_regulator_set(dev, gs, gs->eldo2_sel_reg, gs->eldo2_1p8v,
+ ELDO_CTRL_REG, gs->eldo2_ctrl_shift, true);
+ if (ret)
+ return ret;
+
+ /*
+ * This sleep comes out of the gc2235 driver, which is the
+ * only one I currently see that wants to set both 1.8v rails.
+ */
+ usleep_range(110, 150);
+
+ ret = axp_regulator_set(dev, gs, gs->eldo1_sel_reg, gs->eldo1_1p8v,
+ ELDO_CTRL_REG, gs->eldo1_ctrl_shift, true);
+ if (ret)
+ return ret;
+
+ ret = axp_regulator_set(dev, gs, gs->eldo2_sel_reg, gs->eldo2_1p8v,
+ ELDO_CTRL_REG, gs->eldo2_ctrl_shift, false);
+ return ret;
+}
+
+static int axp_v1p8_off(struct device *dev, struct gmin_subdev *gs)
+{
+ int ret;
+ ret = axp_regulator_set(dev, gs, gs->eldo1_sel_reg, gs->eldo1_1p8v,
+ ELDO_CTRL_REG, gs->eldo1_ctrl_shift, false);
+ if (ret)
+ return ret;
+
+ ret = axp_regulator_set(dev, gs, gs->eldo2_sel_reg, gs->eldo2_1p8v,
+ ELDO_CTRL_REG, gs->eldo2_ctrl_shift, false);
+ return ret;
+}
+
+
static int gmin_gpio0_ctrl(struct v4l2_subdev *subdev, int on)
{
struct gmin_subdev *gs = find_gmin_subdev(subdev);
return 0;
gs->v1p2_on = on;
+ /* use regulator for PMIC */
if (gs->v1p2_reg) {
if (on)
return regulator_enable(gs->v1p2_reg);
return regulator_disable(gs->v1p2_reg);
}
- /*TODO:v1p2 needs to extend to other PMICs*/
+ /* TODO:v1p2 may need to extend to other PMICs */
return -EINVAL;
}
int ret;
struct device *dev;
struct i2c_client *client = v4l2_get_subdevdata(subdev);
+ int value;
dev = &client->dev;
return regulator_disable(gs->v1p8_reg);
}
+ switch (pmic_id) {
+ case PMIC_AXP:
+ if (on)
+ return axp_v1p8_on(subdev->dev, gs);
+ else
+ return axp_v1p8_off(subdev->dev, gs);
+ case PMIC_TI:
+ value = on ? LDO_1P8V_ON : LDO_1P8V_OFF;
+
+ return gmin_i2c_write(subdev->dev, gs->pwm_i2c_addr,
+ LDO10_REG, value, 0xff);
+ case PMIC_CRYSTALCOVE:
+ value = on ? CRYSTAL_ON : CRYSTAL_OFF;
+
+ return gmin_i2c_write(subdev->dev, gs->pwm_i2c_addr,
+ CRYSTAL_1P8V_REG, value, 0xff);
+ default:
+ dev_err(subdev->dev, "Couldn't set power mode for v1p2\n");
+ }
+
+
return -EINVAL;
}
int ret;
struct device *dev;
struct i2c_client *client = v4l2_get_subdevdata(subdev);
+ int value;
dev = &client->dev;
return regulator_disable(gs->v2p8_reg);
}
+ switch (pmic_id) {
+ case PMIC_AXP:
+ return axp_regulator_set(subdev->dev, gs, ALDO1_SEL_REG,
+ ALDO1_2P8V, ALDO1_CTRL3_REG,
+ ALDO1_CTRL3_SHIFT, on);
+ case PMIC_TI:
+ value = on ? LDO_2P8V_ON : LDO_2P8V_OFF;
+
+ return gmin_i2c_write(subdev->dev, gs->pwm_i2c_addr,
+ LDO9_REG, value, 0xff);
+ case PMIC_CRYSTALCOVE:
+ value = on ? CRYSTAL_ON : CRYSTAL_OFF;
+
+ return gmin_i2c_write(subdev->dev, gs->pwm_i2c_addr,
+ CRYSTAL_2P8V_REG, value, 0xff);
+ default:
+ dev_err(subdev->dev, "Couldn't set power mode for v1p2\n");
+ }
+
return -EINVAL;
}
return 0;
if (on) {
- ret = clk_set_rate(gs->pmc_clk, gs->clock_src ? CLK_RATE_19_2MHZ : CLK_RATE_25_0MHZ);
+ ret = clk_set_rate(gs->pmc_clk,
+ gs->clock_src ? CLK_RATE_19_2MHZ : CLK_RATE_25_0MHZ);
if (ret)
dev_err(&client->dev, "unable to set PMC rate %d\n",
id = dmi_first_match(gmin_vars);
if (id) {
dev_info(maindev, "Found DMI entry for '%s'\n", var8);
- return gmin_get_hardcoded_var(id->driver_data, var8, out, out_len);
+ return gmin_get_hardcoded_var(id->driver_data, var8, out,
+ out_len);
}
/* Our variable names are ASCII by construction, but EFI names