RE: [RFC 1/5] media: i2c: max2175: Add MAX2175 support
From: Ramesh Shanmugasundaram <hidden>
Date: 2016-10-18 15:04:27
Also in:
linux-media, linux-renesas-soc
Hi Geert, Thank you for the review.
Subject: Re: [RFC 1/5] media: i2c: max2175: Add MAX2175 support
[...]
quoted
--- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/max2175.txt@@ -0,0 +1,60 @@ +Maxim Integrated MAX2175 RF to Bits tuner +----------------------------------------- + +The MAX2175 IC is an advanced analog/digital hybrid-radio receiver +with RF to Bits® front-end designed for software-defined radiosolutions.quoted
+ +Required properties: +-------------------- +- compatible: "maxim,max2175" for MAX2175 RF-to-bits tuner. +- clocks: phandle to the fixed xtal clock. +- clock-names: name of the fixed xtal clock. +- port: video interface child port node of a tuner that defines the +local + and remote endpoints. The remote endpoint is assumed to be an SDR +device + that is capable of receiving the digital samples from the tuner. + +Optional properties: +-------------------- +- maxim,slave : empty property indicates this is a slave of another + master tuner. This is used to define two tuners in + diversity mode (1 master, 1 slave). By default each + tuner is an individual master. +- maxim,refout-load: load capacitance value (in pF) on reference output + drive level. The mapping of these load values to + respective bit values are given below. + 0 - Reference output disabled + 1 - 10pF load + 2 - 20pF load + 3 - 30pF load + 4 - 40pF load + 5 - 60pF load + 6 - 70pF loadFor properties involving units, usually the unit is made part of the property name, e.g. maxim,refout-load-pF = 40. This avoids confusion, and allows for extension later.
Agreed. I have modified it as
- maxim,refout-load-pF: load capacitance value (in pF) on reference
output drive level. The default is refout disabled
or no load. The possible load values are
10pF
20pF
30pF
40pF
60pF
70pF
quoted
+/* A tuner device instance under i2c bus */ +max2175_0: tuner@60 { + #clock-cells = <0>; + compatible = "maxim,max2175"; + reg = <0x60>; + clocks = <&maxim_xtal>; + clock-names = "xtal"; + maxim,refout-load = <10>;10 is not listed above. Perhaps you meant 10 pF?
Yes.
quoted
--- /dev/null +++ b/drivers/media/i2c/max2175/max2175.c@@ -0,0 +1,1624 @@quoted
+/* NOTE: Any addition/deletion in the below list should be reflected +in + * max2175_modetag enum + */You can drop the above comment if you make this explicit using C99 designated initializers, cfr. below.quoted
+static const struct max2175_rxmode eu_rx_modes[] = { /* Indexed by EUmodetag */quoted
+ /* EU modes */ + { MAX2175_BAND_VHF, 182640000, 0, { 0, 0, 0, 0 } },[MAX2175_DAB_1_2] = { MAX2175_BAND_VHF, 182640000, 0, { 0, 0, 0, 0 } },quoted
+}; + +static const struct max2175_rxmode na_rx_modes[] = { /* Indexed by NAmodetag */quoted
+ /* NA modes */ + { MAX2175_BAND_FM, 98255520, 1, { 0, 0, 0, 0 } },[MAX2175_NA_FM_1_0] = { MAX2175_BAND_FM, 98255520, 1, { 0, 0, 0, 0 } },
Thank you. Using designated initializers now.
quoted
+struct max2175_ctx { + struct v4l2_subdev sd; + struct i2c_client *client; + struct device *dev; + + /* Cached configuration */ + u8 regs[256]; + enum max2175_modetag mode; /* Receive mode tag */ + u32 freq; /* In Hz */ + struct max2175_rxmode *rx_modes; + + /* Device settings */ + bool master; + u32 decim_ratio; + u64 xtal_freq; + + /* ROM values */ + u8 rom_bbf_bw_am; + u8 rom_bbf_bw_fm; + u8 rom_bbf_bw_dab; + + /* Local copy of old settings */ + u8 i2s_test; + + u8 nbd_gain; + u8 nbd_threshold; + u8 wbd_gain; + u8 wbd_threshold; + u8 bbd_threshold; + u8 bbdclip_threshold; + u8 lt_wbd_threshold; + u8 lt_wbd_gain; + + /* Controls */ + struct v4l2_ctrl_handler ctrl_hdl; + struct v4l2_ctrl *lna_gain; /* LNA gain value */ + struct v4l2_ctrl *if_gain; /* I/F gain value */ + struct v4l2_ctrl *pll_lock; /* PLL lock */ + struct v4l2_ctrl *i2s_en; /* I2S output enable */ + struct v4l2_ctrl *i2s_mode; /* I2S mode value */ + struct v4l2_ctrl *am_hiz; /* AM High impledance input */ + struct v4l2_ctrl *hsls; /* High-side/Low-side polarity*/quoted
+ struct v4l2_ctrl *rx_mode; /* Receive mode */ + + /* Driver private variables */ + bool mode_resolved; /* Flag to sanity check settings*/quoted
+};Sorting the struct members by decreasing size helps to avoid gaps due to alignment restrictions, and may reduce memory consumption.
OK. I have corrected it to certain extent but still having groups (not strict decreasing order - for better readability). I have pushed the u8 group members to the end though.
quoted
+/* Flush local copy to device from idx to idx+len (inclusive) */ +static void max2175_flush_regstore(struct max2175_ctx *ctx, u8 idx, +u8 len) { + u8 i;I'd just use unsigned int for loop counters.
Agreed.
quoted
+ + for (i = idx; i <= len; i++) + max2175_reg_write(ctx, i, ctx->regs[i]); }quoted
+static int max2175_update_i2s_mode(struct max2175_ctx *ctx, u32 +i2s_mode) { + /* Only change if it's new */ + if (max2175_read_bits(ctx, 29, 2, 0) == i2s_mode)Many magic numbers, not only here (29, 2), but everywhere. Can you please add #defines for these?
The Tuner provider is unwilling to disclose all register details. I agree on the readability issue with this restriction but this is somewhat true for some sensitive IPs in the media subsystem.
quoted
+ return 0; + + max2175_write_bits(ctx, 29, 2, 0, i2s_mode); + + /* Based on I2S mode value I2S_WORD_CNT values change */ + if (i2s_mode == MAX2175_I2S_MODE3) { + max2175_write_bits(ctx, 30, 6, 0, 1); + } else if (i2s_mode == MAX2175_I2S_MODE2 || + i2s_mode == MAX2175_I2S_MODE4) { + max2175_write_bits(ctx, 30, 6, 0, 0); + } else if (i2s_mode == MAX2175_I2S_MODE0) { + max2175_write_bits(ctx, 30, 6, 0, + ctx->rx_modes[ctx- mode].i2s_word_size); + } else { + v4l2_err(ctx->client, + "failed: i2s_mode %u unsupported\n", i2s_mode); + return 1; + }switch (i2s_mode) { ... }
Agreed.
quoted
+ mxm_dbg(ctx, "updated i2s_mode %u\n", i2s_mode); + return 0; +}quoted
+static void max2175_set_filter_coeffs(struct max2175_ctx *ctx, u8m_sel,quoted
+ u8 bank, const u16 *coeffs) { + u8 i, coeff_addr, upper_address;I'd just use unsigned int for these.
Agreed.
quoted
+ + mxm_dbg(ctx, "start: m_sel %d bank %d\n", m_sel, bank); + max2175_write_bits(ctx, 114, 5, 4, m_sel); + + if (m_sel == 2) + upper_address = 12; + else + upper_address = 24; + + max2175_set_bit(ctx, 117, 7, 1); + for (i = 0; i < upper_address; i++) { + coeff_addr = i + (bank * 24); + max2175_set_bits(ctx, 115, 7, 0, + (u8)((coeffs[i] >> 8) & 0xff)); + max2175_set_bits(ctx, 116, 7, 0, (u8)(coeffs[i] & + 0xff));I don't think you need the casts to u8, or the masking with 0xff.
Yes. Corrected.
quoted
+ max2175_set_bits(ctx, 117, 6, 0, coeff_addr); + max2175_flush_regstore(ctx, 115, 3); + } + max2175_write_bit(ctx, 117, 7, 0); } + +static void max2175_load_dab_1p2(struct max2175_ctx *ctx) { + u32 i;unsigned int?
Agreed.
quoted
+static int max2175_set_lo_freq(struct max2175_ctx *ctx, u64 lo_freq) +{ + int ret; + u32 lo_mult; + u64 scaled_lo_freq; + const u64 scale_factor = 1000000ULL; + u64 scaled_npf, scaled_integer, scaled_fraction; + u32 frac_desired, int_desired; + u8 loband_bits, vcodiv_bits; + + scaled_lo_freq = lo_freq; + /* Scale to larger number for precision */ + scaled_lo_freq = scaled_lo_freq * scale_factor * 100; + + mxm_dbg(ctx, "scaled lo_freq %llu lo_freq %llu\n", + scaled_lo_freq, lo_freq); + + if (MAX2175_IS_BAND_AM(ctx)) { + if (max2175_get_bit(ctx, 5, 7) == 0) + loband_bits = 0; + vcodiv_bits = 0; + lo_mult = 16; + } else if (max2175_get_bits(ctx, 5, 1, 0) == MAX2175_BAND_FM) { + if (lo_freq <= 74700000) { + loband_bits = 0; + vcodiv_bits = 0; + lo_mult = 16; + } else if ((lo_freq > 74700000) && (lo_freq <=110000000)) {quoted
+ loband_bits = 1; + vcodiv_bits = 0; + } else { + loband_bits = 1; + vcodiv_bits = 3; + } + lo_mult = 8; + } else if (max2175_get_bits(ctx, 5, 1, 0) == MAX2175_BAND_VHF) { + if (lo_freq <= 210000000) { + loband_bits = 2; + vcodiv_bits = 2; + } else { + loband_bits = 2; + vcodiv_bits = 1; + } + lo_mult = 4; + } else { + loband_bits = 3; + vcodiv_bits = 2; + lo_mult = 2; + } + + if (max2175_get_bits(ctx, 5, 1, 0) == MAX2175_BAND_L) + scaled_npf = (scaled_lo_freq / ctx->xtal_freq / lo_mult)/ 100;quoted
+ else + scaled_npf = (scaled_lo_freq / ctx->xtal_freq * + lo_mult) / 100;Please use one of the div64*() functions for divisions involving 64-bit quantities (try to build for 32-bit and see). More of these below...
Thanks. All 64bit divisions are corrected to div_x64 variants. You are right - kbuild reported MIPS build failure because of this. I tried only x86 for 32-bit.
quoted
+ scaled_integer = scaled_npf / scale_factor * scale_factor; + int_desired = (u32)(scaled_npf / scale_factor); + scaled_fraction = scaled_npf - scaled_integer; + frac_desired = (u32)(scaled_fraction * 1048576 / + scale_factor);quoted
+ /* Write the calculated values to the appropriate registers */ + max2175_set_bits(ctx, 5, 3, 2, loband_bits); + max2175_set_bits(ctx, 6, 7, 6, vcodiv_bits); + max2175_set_bits(ctx, 1, 7, 0, (u8)(int_desired & 0xff)); + max2175_set_bits(ctx, 2, 3, 0, (u8)((frac_desired >> 16) &0x1f));quoted
+ max2175_set_bits(ctx, 3, 7, 0, (u8)((frac_desired >> 8) &0xff));quoted
+ max2175_set_bits(ctx, 4, 7, 0, (u8)(frac_desired & 0xff));No need for casts etc.
Yes, corrected.
quoted
+ /* Flush the above registers to device */ + max2175_flush_regstore(ctx, 1, 6); + return ret; +} + +static int max2175_set_nco_freq(struct max2175_ctx *ctx, s64 +nco_freq_desired) { + int ret; + u64 clock_rate, abs_nco_freq; + s64 nco_freq, nco_val_desired; + u32 nco_reg; + const u64 scale_factor = 1000000ULL; + + mxm_dbg(ctx, "nco_freq: freq = %lld\n", nco_freq_desired); + clock_rate = ctx->xtal_freq / ctx->decim_ratio; + nco_freq = -nco_freq_desired; + + if (nco_freq < 0) + abs_nco_freq = -nco_freq; + else + abs_nco_freq = nco_freq; + + /* Scale up the values for precision */ + if (abs_nco_freq < (clock_rate / 2)) { + nco_val_desired = (2 * nco_freq * scale_factor) /clock_rate;quoted
+ } else { + if (nco_freq < 0) + nco_val_desired = (-2 * (clock_rate -abs_nco_freq) *quoted
+ scale_factor) / clock_rate; + else + nco_val_desired = (2 * (clock_rate -abs_nco_freq) *quoted
+ scale_factor) / clock_rate; + } + + /* Scale down to get the fraction */ + if (nco_freq < 0) + nco_reg = 0x200000 + ((nco_val_desired * 1048576) / + scale_factor); + else + nco_reg = (nco_val_desired * 1048576) / scale_factor;More 64-bit divisions. In addition, the dividers are 64-bit too. Can't they be 32-bit?
Agreed. The dividers need not be 32-bit. Corrected everywhere.
quoted
+static int max2175_probe(struct i2c_client *client, + const struct i2c_device_id *id) { + struct max2175_ctx *ctx; + struct device *dev = &client->dev; + struct v4l2_subdev *sd; + struct v4l2_ctrl_handler *hdl; + struct clk *clk; + bool master = true; + u32 refout_load, refout_bits = 0; /* REFOUT disabled */ + int ret; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "i2c check failed\n"); + return -EIO; + } + + if (of_find_property(client->dev.of_node, "maxim,slave", NULL)) + master = false; + + if (!of_property_read_u32(client->dev.of_node, "maxim,refout-load",quoted
+ &refout_load)) + refout_bits = max2175_refout_load_to_bits(client, + refout_load); + + clk = devm_clk_get(&client->dev, "xtal"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&client->dev, "cannot get xtal clock %d\n",ret);quoted
+ return -ENODEV; + } + + ctx = kzalloc(sizeof(struct max2175_ctx), + GFP_KERNEL);devm_kzalloc()?
Agreed & corrected.
quoted
+ if (ctx == NULL) + return -ENOMEM; + + sd = &ctx->sd; + ctx->master = master; + ctx->mode_resolved = false; + + /* Set the defaults */ + ctx->freq = bands_rf.rangelow; + + ctx->xtal_freq = clk_get_rate(clk); + dev_info(&client->dev, "xtal freq %lluHz\n", ctx->xtal_freq); + + v4l2_i2c_subdev_init(sd, client, &max2175_ops); + ctx->client = client; + + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + ctx->dev = dev; + + /* Controls */ + hdl = &ctx->ctrl_hdl; + ret = v4l2_ctrl_handler_init(hdl, 8); + if (ret) { + dev_err(&client->dev, "ctrl handler init failed\n"); + goto err; + } + + ctx->lna_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_LNA_GAIN, + 0, 15, 1, 2); + ctx->lna_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->if_gain = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_IF_GAIN, + 0, 31, 1, 0); + ctx->if_gain->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->pll_lock = v4l2_ctrl_new_std(hdl, &max2175_ctrl_ops, + V4L2_CID_RF_TUNER_PLL_LOCK, + 0, 1, 1, 0); + ctx->pll_lock->flags |= (V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY); + ctx->i2s_en = v4l2_ctrl_new_custom(hdl, &max2175_i2s_en, NULL); + ctx->i2s_mode = v4l2_ctrl_new_custom(hdl, &max2175_i2s_mode,NULL);quoted
+ ctx->am_hiz = v4l2_ctrl_new_custom(hdl, &max2175_am_hiz, NULL); + ctx->hsls = v4l2_ctrl_new_custom(hdl, &max2175_hsls, NULL); + + if (ctx->xtal_freq == MAX2175_EU_XTAL_FREQ) { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_eu_rx_mode,NULL);quoted
+ ctx->rx_modes = (struct max2175_rxmode *)eu_rx_modes; + } else { + ctx->rx_mode = v4l2_ctrl_new_custom(hdl, + &max2175_na_rx_mode,NULL);quoted
+ ctx->rx_modes = (struct max2175_rxmode *)na_rx_modes; + }The casts are meant to cast away constness. Can that be avoided?
Agreed. Made ctx->rx_modes member as const and avoided the cast here.
quoted
--- /dev/null +++ b/drivers/media/i2c/max2175/max2175.hquoted
+/* NOTE: Any addition/deletion in the below enum should be reflected +in + * V4L2_CID_MAX2175_RX_MODE ctrl stringsWhich strings exactly?
It is supposed to be these two
+ */
+static const char * const max2175_ctrl_eu_rx_mode_strings[] = {
+ "DAB 1.2",
+ "NULL",
+};
+
+static const char * const max2175_ctrl_na_rx_mode_strings[] = {
+ "NA FM 1.0",
+ "NULL",
+};
+
I have corrected them now to use designated initializers. Thanks.
quoted
+ */ +enum max2175_modetag { + /* EU modes */ + MAX2175_DAB_1_2 = 0, + + /* Other possible modes to add in future + * MAX2175_DAB_1_0, + * MAX2175_DAB_1_3, + * MAX2175_EU_FM_2_2, + * MAX2175_EU_FM_1_0, + * MAX2175_EU_FMHD_4_0, + * MAX2175_EU_AM_1_0, + * MAX2175_EU_AM_2_2, + */ + + /* NA modes */ + MAX2175_NA_FM_1_0 = 0, + + /* Other possible modes to add in future + * MAX2175_NA_FM_1_2, + * MAX2175_NA_FMHD_1_0, + * MAX2175_NA_FMHD_1_2, + * MAX2175_NA_AM_1_0, + * MAX2175_NA_AM_1_2, + */The MAX2175_NA_* definitions share their values with the MAX2175_DAB_* and future MAX2175_EU_* values. Do you have to use a single enum for both?
I have made them separate enums now. NA_ modes were not tested yet.
I have also fixed few bugs in the meantime. I'll wait for this week for other media maintainers & experts to comment on the driver model & the new SDR format open item. Based on the response, I will post a new [PATCH] or [RFC v2] with these comments updated.
Thanks,
Ramesh
��칻
�&�~�&���+-��ݶ��w��˛���m�^�'
����{ay�
ʇڙ�,j��f���h�����/oSc��ڳ9�u�����&jw��(�階�ݢj"���m�����z�ޖ���f���h���~�m�