The tda988x i2c chip registers are accessed through a paged register scheme. The kernel regmap abstraction supports paged accesses. Replace this driver's dedicated i2c access functions with a standard i2c regmap. Pros: - dedicated i2c access code disappears: accesses now go through well-maintained regmap code - page atomicity requirements now handled by regmap - ro/wo/rw access modes are now explicitly defined: any inappropriate register accesses (e.g. write to a read-only register) will now be explicitly rejected by the regmap core - all tda988x registers are now visible via debugfs Cons: - this will probably require extensive testing Tested on a TDA19988 using a Freescale/NXP imx6q. Signed-off-by: Sven Van Asbroeck <TheSven73@xxxxxxxxx> --- I originally hacked this together while debugging an incompatibility between the tda988x's audio input and the audio codec I was driving it with. That required me to have debug access to the chip's register values. A regmap did the trick, it has good debugfs support. drivers/gpu/drm/i2c/tda998x_drv.c | 350 ++++++++++++++++++++---------- 1 file changed, 234 insertions(+), 116 deletions(-) diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 7f34601bb515..8153e2e19e18 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -21,6 +21,7 @@ #include <linux/module.h> #include <linux/platform_data/tda9950.h> #include <linux/irq.h> +#include <linux/regmap.h> #include <sound/asoundef.h> #include <sound/hdmi-codec.h> @@ -43,10 +44,9 @@ struct tda998x_audio_port { struct tda998x_priv { struct i2c_client *cec; struct i2c_client *hdmi; - struct mutex mutex; + struct regmap *regmap; u16 rev; u8 cec_addr; - u8 current_page; bool is_on; bool supports_infoframes; bool sink_has_audio; @@ -88,12 +88,10 @@ struct tda998x_priv { /* The TDA9988 series of devices use a paged register scheme.. to simplify * things we encode the page # in upper bits of the register #. To read/ * write a given register, we need to make sure CURPAGE register is set - * appropriately. Which implies reads/writes are not atomic. Fun! + * appropriately. */ #define REG(page, addr) (((page) << 8) | (addr)) -#define REG2ADDR(reg) ((reg) & 0xff) -#define REG2PAGE(reg) (((reg) >> 8) & 0xff) #define REG_CURPAGE 0xff /* write */ @@ -285,8 +283,9 @@ struct tda998x_priv { /* Page 09h: EDID Control */ +/* EDID_DATA consists of 128 successive registers read */ #define REG_EDID_DATA_0 REG(0x09, 0x00) /* read */ -/* next 127 successive registers are the EDID block */ +#define REG_EDID_DATA_127 REG(0x09, 0x7f) /* read */ #define REG_EDID_CTRL REG(0x09, 0xfa) /* read/write */ #define REG_DDC_ADDR REG(0x09, 0xfb) /* read/write */ #define REG_DDC_OFFS REG(0x09, 0xfc) /* read/write */ @@ -295,11 +294,17 @@ struct tda998x_priv { /* Page 10h: information frames and packets */ +/* REG_IF1_HB consists of 32 successive registers read/write */ #define REG_IF1_HB0 REG(0x10, 0x20) /* read/write */ +/* REG_IF2_HB consists of 32 successive registers read/write */ #define REG_IF2_HB0 REG(0x10, 0x40) /* read/write */ +/* REG_IF3_HB consists of 32 successive registers read/write */ #define REG_IF3_HB0 REG(0x10, 0x60) /* read/write */ +/* REG_IF4_HB consists of 32 successive registers read/write */ #define REG_IF4_HB0 REG(0x10, 0x80) /* read/write */ +/* REG_IF5_HB consists of 32 successive registers read/write */ #define REG_IF5_HB0 REG(0x10, 0xa0) /* read/write */ +#define REG_IF5_HB31 REG(0x10, 0xbf) /* read/write */ /* Page 11h: audio settings and content info packets */ @@ -542,93 +547,29 @@ static void tda998x_cec_hook_release(void *data) cec_enamods(priv, CEC_ENAMODS_EN_CEC_CLK | CEC_ENAMODS_EN_CEC, false); } -static int -set_page(struct tda998x_priv *priv, u16 reg) -{ - if (REG2PAGE(reg) != priv->current_page) { - struct i2c_client *client = priv->hdmi; - u8 buf[] = { - REG_CURPAGE, REG2PAGE(reg) - }; - int ret = i2c_master_send(client, buf, sizeof(buf)); - if (ret < 0) { - dev_err(&client->dev, "%s %04x err %d\n", __func__, - reg, ret); - return ret; - } - - priv->current_page = REG2PAGE(reg); - } - return 0; -} - static int reg_read_range(struct tda998x_priv *priv, u16 reg, char *buf, int cnt) { - struct i2c_client *client = priv->hdmi; - u8 addr = REG2ADDR(reg); int ret; - mutex_lock(&priv->mutex); - ret = set_page(priv, reg); - if (ret < 0) - goto out; - - ret = i2c_master_send(client, &addr, sizeof(addr)); + ret = regmap_bulk_read(priv->regmap, reg, buf, cnt); if (ret < 0) - goto fail; - - ret = i2c_master_recv(client, buf, cnt); - if (ret < 0) - goto fail; - - goto out; - -fail: - dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg); -out: - mutex_unlock(&priv->mutex); - return ret; + return ret; + return cnt; } -#define MAX_WRITE_RANGE_BUF 32 - static void reg_write_range(struct tda998x_priv *priv, u16 reg, u8 *p, int cnt) { - struct i2c_client *client = priv->hdmi; - /* This is the maximum size of the buffer passed in */ - u8 buf[MAX_WRITE_RANGE_BUF + 1]; - int ret; - - if (cnt > MAX_WRITE_RANGE_BUF) { - dev_err(&client->dev, "Fixed write buffer too small (%d)\n", - MAX_WRITE_RANGE_BUF); - return; - } - - buf[0] = REG2ADDR(reg); - memcpy(&buf[1], p, cnt); - - mutex_lock(&priv->mutex); - ret = set_page(priv, reg); - if (ret < 0) - goto out; - - ret = i2c_master_send(client, buf, cnt + 1); - if (ret < 0) - dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); -out: - mutex_unlock(&priv->mutex); + regmap_bulk_write(priv->regmap, reg, p, cnt); } static int reg_read(struct tda998x_priv *priv, u16 reg) { - u8 val = 0; - int ret; + int ret, val; - ret = reg_read_range(priv, reg, &val, sizeof(val)); + ret = regmap_read(priv->regmap, reg, &val); if (ret < 0) return ret; return val; @@ -637,59 +578,27 @@ reg_read(struct tda998x_priv *priv, u16 reg) static void reg_write(struct tda998x_priv *priv, u16 reg, u8 val) { - struct i2c_client *client = priv->hdmi; - u8 buf[] = {REG2ADDR(reg), val}; - int ret; - - mutex_lock(&priv->mutex); - ret = set_page(priv, reg); - if (ret < 0) - goto out; - - ret = i2c_master_send(client, buf, sizeof(buf)); - if (ret < 0) - dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); -out: - mutex_unlock(&priv->mutex); + regmap_write(priv->regmap, reg, val); } static void reg_write16(struct tda998x_priv *priv, u16 reg, u16 val) { - struct i2c_client *client = priv->hdmi; - u8 buf[] = {REG2ADDR(reg), val >> 8, val}; - int ret; - - mutex_lock(&priv->mutex); - ret = set_page(priv, reg); - if (ret < 0) - goto out; + u8 buf[] = {val >> 8, val}; - ret = i2c_master_send(client, buf, sizeof(buf)); - if (ret < 0) - dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); -out: - mutex_unlock(&priv->mutex); + regmap_bulk_write(priv->regmap, reg, buf, ARRAY_SIZE(buf)); } static void reg_set(struct tda998x_priv *priv, u16 reg, u8 val) { - int old_val; - - old_val = reg_read(priv, reg); - if (old_val >= 0) - reg_write(priv, reg, old_val | val); + regmap_update_bits(priv->regmap, reg, val, val); } static void reg_clear(struct tda998x_priv *priv, u16 reg, u8 val) { - int old_val; - - old_val = reg_read(priv, reg); - if (old_val >= 0) - reg_write(priv, reg, old_val & ~val); + regmap_update_bits(priv->regmap, reg, val, 0); } static void @@ -816,7 +725,7 @@ static void tda998x_write_if(struct tda998x_priv *priv, u8 bit, u16 addr, union hdmi_infoframe *frame) { - u8 buf[MAX_WRITE_RANGE_BUF]; + u8 buf[32]; ssize_t len; len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); @@ -1654,6 +1563,211 @@ static void tda998x_destroy(struct device *dev) cec_notifier_put(priv->cec_notify); } +static bool tda_is_edid_data_reg(unsigned int reg) +{ + return (reg >= REG_EDID_DATA_0) && + (reg <= REG_EDID_DATA_127); +} + +static bool tda_is_precious_volatile_reg(struct device *dev, unsigned int reg) +{ + if (tda_is_edid_data_reg(reg)) + return true; + switch (reg) { + case REG_INT_FLAGS_0: + case REG_INT_FLAGS_1: + case REG_INT_FLAGS_2: + return true; + default: + return false; + } +} + +static bool tda_is_rw_reg(unsigned int reg) +{ + if ((reg >= REG_IF1_HB0) && (reg <= REG_IF5_HB31)) + return true; + switch (reg) { + case REG_MAIN_CNTRL0: + case REG_DDC_DISABLE: + case REG_CCLK_ON: + case REG_I2C_MASTER: + case REG_FEAT_POWERDOWN: + case REG_INT_FLAGS_0: + case REG_INT_FLAGS_1: + case REG_INT_FLAGS_2: + case REG_ENA_ACLK: + case REG_ENA_VP_0: + case REG_ENA_VP_1: + case REG_ENA_VP_2: + case REG_ENA_AP: + case REG_MUX_AP: + case REG_MUX_VP_VIP_OUT: + case REG_I2S_FORMAT: + case REG_PLL_SERIAL_1: + case REG_PLL_SERIAL_2: + case REG_PLL_SERIAL_3: + case REG_SERIALIZER: + case REG_BUFFER_OUT: + case REG_PLL_SCG1: + case REG_PLL_SCG2: + case REG_PLL_SCGN1: + case REG_PLL_SCGN2: + case REG_PLL_SCGR1: + case REG_PLL_SCGR2: + case REG_AUDIO_DIV: + case REG_SEL_CLK: + case REG_ANA_GENERAL: + case REG_EDID_CTRL: + case REG_DDC_ADDR: + case REG_DDC_OFFS: + case REG_DDC_SEGM_ADDR: + case REG_DDC_SEGM: + case REG_AIP_CNTRL_0: + case REG_CA_I2S: + case REG_LATENCY_RD: + case REG_ACR_CTS_0: + case REG_ACR_CTS_1: + case REG_ACR_CTS_2: + case REG_ACR_N_0: + case REG_ACR_N_1: + case REG_ACR_N_2: + case REG_CTS_N: + case REG_ENC_CNTRL: + case REG_DIP_FLAGS: + case REG_DIP_IF_FLAGS: + case REG_TX3: + case REG_TX4: + case REG_TX33: + case REG_CH_STAT_B(0): + case REG_CH_STAT_B(1): + case REG_CH_STAT_B(2): + case REG_CH_STAT_B(3): + return true; + default: + return false; + } +} + +static bool tda_is_readable_reg(struct device *dev, unsigned int reg) +{ + if (tda_is_rw_reg(reg) || tda_is_edid_data_reg(reg)) + return true; + switch (reg) { + case REG_VERSION_LSB: + case REG_VERSION_MSB: + return true; + default: + return false; + } +} + +static bool tda_is_writeable_reg(struct device *dev, unsigned int reg) +{ + if (tda_is_rw_reg(reg)) + return true; + switch (reg) { + case REG_CURPAGE: + case REG_SOFTRESET: + case REG_VIP_CNTRL_0: + case REG_VIP_CNTRL_1: + case REG_VIP_CNTRL_2: + case REG_VIP_CNTRL_3: + case REG_VIP_CNTRL_4: + case REG_VIP_CNTRL_5: + case REG_MAT_CONTRL: + case REG_VIDFORMAT: + case REG_REFPIX_MSB: + case REG_REFPIX_LSB: + case REG_REFLINE_MSB: + case REG_REFLINE_LSB: + case REG_NPIX_MSB: + case REG_NPIX_LSB: + case REG_NLINE_MSB: + case REG_NLINE_LSB: + case REG_VS_LINE_STRT_1_MSB: + case REG_VS_LINE_STRT_1_LSB: + case REG_VS_PIX_STRT_1_MSB: + case REG_VS_PIX_STRT_1_LSB: + case REG_VS_LINE_END_1_MSB: + case REG_VS_LINE_END_1_LSB: + case REG_VS_PIX_END_1_MSB: + case REG_VS_PIX_END_1_LSB: + case REG_VS_LINE_STRT_2_MSB: + case REG_VS_LINE_STRT_2_LSB: + case REG_VS_PIX_STRT_2_MSB: + case REG_VS_PIX_STRT_2_LSB: + case REG_VS_LINE_END_2_MSB: + case REG_VS_LINE_END_2_LSB: + case REG_VS_PIX_END_2_MSB: + case REG_VS_PIX_END_2_LSB: + case REG_HS_PIX_START_MSB: + case REG_HS_PIX_START_LSB: + case REG_HS_PIX_STOP_MSB: + case REG_HS_PIX_STOP_LSB: + case REG_VWIN_START_1_MSB: + case REG_VWIN_START_1_LSB: + case REG_VWIN_END_1_MSB: + case REG_VWIN_END_1_LSB: + case REG_VWIN_START_2_MSB: + case REG_VWIN_START_2_LSB: + case REG_VWIN_END_2_MSB: + case REG_VWIN_END_2_LSB: + case REG_DE_START_MSB: + case REG_DE_START_LSB: + case REG_DE_STOP_MSB: + case REG_DE_STOP_LSB: + case REG_TBG_CNTRL_0: + case REG_TBG_CNTRL_1: + case REG_ENABLE_SPACE: + case REG_HVF_CNTRL_0: + case REG_HVF_CNTRL_1: + case REG_RPT_CNTRL: + case REG_AIP_CLKSEL: + return true; + default: + return false; + } +} + +static const struct reg_default tda_reg_default[] = { + { REG_CURPAGE, 0xff }, +}; + +static const struct regmap_range_cfg hdmi_range_cfg[] = { + { + .range_min = 0x00, + .range_max = REG_TX33, + .selector_reg = REG_CURPAGE, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +/* Paged register scheme, with a write-only page register (CURPAGE). + * Make this work by marking CURPAGE write-only and cacheable. Give it + * an invalid page default value, which guarantees that it will get written to + * the first time we read/write the registers. + */ + +static const struct regmap_config hdmi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .ranges = hdmi_range_cfg, + .num_ranges = ARRAY_SIZE(hdmi_range_cfg), + .max_register = REG_TX33, + + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tda_is_precious_volatile_reg, + .precious_reg = tda_is_precious_volatile_reg, + .readable_reg = tda_is_readable_reg, + .writeable_reg = tda_is_writeable_reg, + .reg_defaults = tda_reg_default, + .num_reg_defaults = ARRAY_SIZE(tda_reg_default), +}; + static int tda998x_create(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -1666,10 +1780,15 @@ static int tda998x_create(struct device *dev) priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + priv->regmap = devm_regmap_init_i2c(client, &hdmi_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(dev, "Failed to allocate register map: %d\n", ret); + return ret; + } dev_set_drvdata(dev, priv); - mutex_init(&priv->mutex); /* protect the page access */ mutex_init(&priv->audio_mutex); /* protect access from audio thread */ mutex_init(&priv->edid_mutex); INIT_LIST_HEAD(&priv->bridge.list); @@ -1683,7 +1802,6 @@ static int tda998x_create(struct device *dev) /* CEC I2C address bound to TDA998x I2C addr by configuration pins */ priv->cec_addr = 0x34 + (client->addr & 0x03); - priv->current_page = 0xff; priv->hdmi = client; /* wake up the device: */ -- 2.17.1 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel