diff options
Diffstat (limited to 'drivers/mfd/twl6040.c')
| -rw-r--r-- | drivers/mfd/twl6040.c | 150 |
1 files changed, 122 insertions, 28 deletions
diff --git a/drivers/mfd/twl6040.c b/drivers/mfd/twl6040.c index daf66942071..ae26d84b3a5 100644 --- a/drivers/mfd/twl6040.c +++ b/drivers/mfd/twl6040.c @@ -44,6 +44,59 @@ #define VIBRACTRL_MEMBER(reg) ((reg == TWL6040_REG_VIBCTLL) ? 0 : 1) #define TWL6040_NUM_SUPPLIES (2) +static struct reg_default twl6040_defaults[] = { + { 0x01, 0x4B }, /* REG_ASICID (ro) */ + { 0x02, 0x00 }, /* REG_ASICREV (ro) */ + { 0x03, 0x00 }, /* REG_INTID */ + { 0x04, 0x00 }, /* REG_INTMR */ + { 0x05, 0x00 }, /* REG_NCPCTRL */ + { 0x06, 0x00 }, /* REG_LDOCTL */ + { 0x07, 0x60 }, /* REG_HPPLLCTL */ + { 0x08, 0x00 }, /* REG_LPPLLCTL */ + { 0x09, 0x4A }, /* REG_LPPLLDIV */ + { 0x0A, 0x00 }, /* REG_AMICBCTL */ + { 0x0B, 0x00 }, /* REG_DMICBCTL */ + { 0x0C, 0x00 }, /* REG_MICLCTL */ + { 0x0D, 0x00 }, /* REG_MICRCTL */ + { 0x0E, 0x00 }, /* REG_MICGAIN */ + { 0x0F, 0x1B }, /* REG_LINEGAIN */ + { 0x10, 0x00 }, /* REG_HSLCTL */ + { 0x11, 0x00 }, /* REG_HSRCTL */ + { 0x12, 0x00 }, /* REG_HSGAIN */ + { 0x13, 0x00 }, /* REG_EARCTL */ + { 0x14, 0x00 }, /* REG_HFLCTL */ + { 0x15, 0x00 }, /* REG_HFLGAIN */ + { 0x16, 0x00 }, /* REG_HFRCTL */ + { 0x17, 0x00 }, /* REG_HFRGAIN */ + { 0x18, 0x00 }, /* REG_VIBCTLL */ + { 0x19, 0x00 }, /* REG_VIBDATL */ + { 0x1A, 0x00 }, /* REG_VIBCTLR */ + { 0x1B, 0x00 }, /* REG_VIBDATR */ + { 0x1C, 0x00 }, /* REG_HKCTL1 */ + { 0x1D, 0x00 }, /* REG_HKCTL2 */ + { 0x1E, 0x00 }, /* REG_GPOCTL */ + { 0x1F, 0x00 }, /* REG_ALB */ + { 0x20, 0x00 }, /* REG_DLB */ + /* 0x28, REG_TRIM1 */ + /* 0x29, REG_TRIM2 */ + /* 0x2A, REG_TRIM3 */ + /* 0x2B, REG_HSOTRIM */ + /* 0x2C, REG_HFOTRIM */ + { 0x2D, 0x08 }, /* REG_ACCCTL */ + { 0x2E, 0x00 }, /* REG_STATUS (ro) */ +}; + +static struct reg_default twl6040_patch[] = { + /* + * Select I2C bus access to dual access registers + * Interrupt register is cleared on read + * Select fast mode for i2c (400KHz) + */ + { TWL6040_REG_ACCCTL, + TWL6040_I2CSEL | TWL6040_INTCLRMODE | TWL6040_I2CMODE(1) }, +}; + + static bool twl6040_has_vibra(struct device_node *node) { #ifdef CONFIG_OF @@ -238,6 +291,11 @@ int twl6040_power(struct twl6040 *twl6040, int on) if (twl6040->power_count++) goto out; + clk_prepare_enable(twl6040->clk32k); + + /* Allow writes to the chip */ + regcache_cache_only(twl6040->regmap, false); + if (gpio_is_valid(twl6040->audpwron)) { /* use automatic power-up sequence */ ret = twl6040_power_up_automatic(twl6040); @@ -253,6 +311,10 @@ int twl6040_power(struct twl6040 *twl6040, int on) goto out; } } + + /* Sync with the HW */ + regcache_sync(twl6040->regmap); + /* Default PLL configuration after power up */ twl6040->pll = TWL6040_SYSCLK_SEL_LPPLL; twl6040->sysclk = 19200000; @@ -279,8 +341,15 @@ int twl6040_power(struct twl6040 *twl6040, int on) /* use manual power-down sequence */ twl6040_power_down_manual(twl6040); } + + /* Set regmap to cache only and mark it as dirty */ + regcache_cache_only(twl6040->regmap, true); + regcache_mark_dirty(twl6040->regmap); + twl6040->sysclk = 0; twl6040->mclk = 0; + + clk_disable_unprepare(twl6040->clk32k); } out: @@ -372,12 +441,9 @@ int twl6040_set_pll(struct twl6040 *twl6040, int pll_id, TWL6040_HPLLENA; break; case 19200000: - /* - * PLL disabled - * (enable PLL if MCLK jitter quality - * doesn't meet specification) - */ - hppllctl |= TWL6040_MCLK_19200KHZ; + /* PLL enabled, bypass mode */ + hppllctl |= TWL6040_MCLK_19200KHZ | + TWL6040_HPLLBP | TWL6040_HPLLENA; break; case 26000000: /* PLL enabled, active mode */ @@ -385,9 +451,9 @@ int twl6040_set_pll(struct twl6040 *twl6040, int pll_id, TWL6040_HPLLENA; break; case 38400000: - /* PLL enabled, active mode */ + /* PLL enabled, bypass mode */ hppllctl |= TWL6040_MCLK_38400KHZ | - TWL6040_HPLLENA; + TWL6040_HPLLBP | TWL6040_HPLLENA; break; default: dev_err(twl6040->dev, @@ -490,9 +556,24 @@ static bool twl6040_readable_reg(struct device *dev, unsigned int reg) static bool twl6040_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { - case TWL6040_REG_VIBCTLL: - case TWL6040_REG_VIBCTLR: - case TWL6040_REG_INTMR: + case TWL6040_REG_ASICID: + case TWL6040_REG_ASICREV: + case TWL6040_REG_INTID: + case TWL6040_REG_LPPLLCTL: + case TWL6040_REG_HPPLLCTL: + case TWL6040_REG_STATUS: + return true; + default: + return false; + } +} + +static bool twl6040_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TWL6040_REG_ASICID: + case TWL6040_REG_ASICREV: + case TWL6040_REG_STATUS: return false; default: return true; @@ -502,10 +583,15 @@ static bool twl6040_volatile_reg(struct device *dev, unsigned int reg) static struct regmap_config twl6040_regmap_config = { .reg_bits = 8, .val_bits = 8, + + .reg_defaults = twl6040_defaults, + .num_reg_defaults = ARRAY_SIZE(twl6040_defaults), + .max_register = TWL6040_REG_STATUS, /* 0x2e */ .readable_reg = twl6040_readable_reg, .volatile_reg = twl6040_volatile_reg, + .writeable_reg = twl6040_writeable_reg, .cache_type = REGCACHE_RBTREE, }; @@ -559,19 +645,25 @@ static int twl6040_probe(struct i2c_client *client, i2c_set_clientdata(client, twl6040); + twl6040->clk32k = devm_clk_get(&client->dev, "clk32k"); + if (IS_ERR(twl6040->clk32k)) { + dev_info(&client->dev, "clk32k is not handled\n"); + twl6040->clk32k = NULL; + } + twl6040->supplies[0].supply = "vio"; twl6040->supplies[1].supply = "v2v1"; ret = devm_regulator_bulk_get(&client->dev, TWL6040_NUM_SUPPLIES, twl6040->supplies); if (ret != 0) { dev_err(&client->dev, "Failed to get supplies: %d\n", ret); - goto regulator_get_err; + return ret; } ret = regulator_bulk_enable(TWL6040_NUM_SUPPLIES, twl6040->supplies); if (ret != 0) { dev_err(&client->dev, "Failed to enable supplies: %d\n", ret); - goto regulator_get_err; + return ret; } twl6040->dev = &client->dev; @@ -580,7 +672,15 @@ static int twl6040_probe(struct i2c_client *client, mutex_init(&twl6040->mutex); init_completion(&twl6040->ready); + regmap_register_patch(twl6040->regmap, twl6040_patch, + ARRAY_SIZE(twl6040_patch)); + twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV); + if (twl6040->rev < 0) { + dev_err(&client->dev, "Failed to read revision register: %d\n", + twl6040->rev); + goto gpio_err; + } /* ERRATA: Automatic power-up is not possible in ES1.0 */ if (twl6040_get_revid(twl6040) > TWL6040_REV_ES1_0) @@ -594,6 +694,9 @@ static int twl6040_probe(struct i2c_client *client, GPIOF_OUT_INIT_LOW, "audpwron"); if (ret) goto gpio_err; + + /* Clear any pending interrupt */ + twl6040_reg_read(twl6040, TWL6040_REG_INTID); } ret = regmap_add_irq_chip(twl6040->regmap, twl6040->irq, IRQF_ONESHOT, @@ -619,12 +722,9 @@ static int twl6040_probe(struct i2c_client *client, "twl6040_irq_th", twl6040); if (ret) { dev_err(twl6040->dev, "Thermal IRQ request failed: %d\n", ret); - goto thirq_err; + goto readyirq_err; } - /* dual-access registers controlled by I2C only */ - twl6040_set_bits(twl6040, TWL6040_REG_ACCCTL, TWL6040_I2CSEL); - /* * The main functionality of twl6040 to provide audio on OMAP4+ systems. * We can add the ASoC codec child whenever this driver has been loaded. @@ -656,24 +756,21 @@ static int twl6040_probe(struct i2c_client *client, cell->name = "twl6040-gpo"; children++; + /* The chip is powered down so mark regmap to cache only and dirty */ + regcache_cache_only(twl6040->regmap, true); + regcache_mark_dirty(twl6040->regmap); + ret = mfd_add_devices(&client->dev, -1, twl6040->cells, children, NULL, 0, NULL); if (ret) - goto mfd_err; + goto readyirq_err; return 0; -mfd_err: - devm_free_irq(&client->dev, twl6040->irq_th, twl6040); -thirq_err: - devm_free_irq(&client->dev, twl6040->irq_ready, twl6040); readyirq_err: regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); gpio_err: regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); -regulator_get_err: - i2c_set_clientdata(client, NULL); - return ret; } @@ -684,12 +781,9 @@ static int twl6040_remove(struct i2c_client *client) if (twl6040->power_count) twl6040_power(twl6040, 0); - devm_free_irq(&client->dev, twl6040->irq_ready, twl6040); - devm_free_irq(&client->dev, twl6040->irq_th, twl6040); regmap_del_irq_chip(twl6040->irq, twl6040->irq_data); mfd_remove_devices(&client->dev); - i2c_set_clientdata(client, NULL); regulator_bulk_disable(TWL6040_NUM_SUPPLIES, twl6040->supplies); |
