On Tue, 10 Jul 2007 12:23:06 +0530
"Trilok Soni" <[EMAIL PROTECTED]> wrote:

> Add Texas Instruments TWL92330/Menelaus Power Management chip driver.
> This includes voltage regulators, Dual slot memory card tranceivers and
> real-time clock(RTC).
> 
> The support for RTC is integrated with this driver only; it is not separate
> module. Passes 'rtctest' on OMAP H4 EVM, other than lack of
> "periodic" (1/N second) IRQs. System wakeup alarms (from suspend-to-RAM)
> work too.
> 
> The battery keeps the RTC active over power off, so once you set clock
> (rdate/ntpdate/etc, then "hwclock -w") then RTC_HCTOSYS at boot time
> will behave as expected.
> 
> ...
>
> +
> +struct menelaus_chip {
> +     struct mutex            lock;
> +     struct i2c_client       *client;
> +     struct work_struct      work;
> +#ifdef CONFIG_RTC_DRV_TWL92330
> +     struct rtc_device       *rtc;
> +     u8                      rtc_control;
> +     unsigned                uie:1;
> +#endif
> +     unsigned                vcore_hw_mode:1;

uie and vcore_hw_mode will share the same memory word.  Consequently, on
preemptible or SMP kernels, a simple

        p->uie = 1;

can race with a simple

        p->vcore_hw_mode = 1;

in a different thread of control, potentially corrupting the other value. 

The same race can occur even on uniprocessor, non-preempt kernels if one of
these fields gets assigned to from interrupt/softirq/etc contexts.

Does your code have suitable locking to prevent such races?  If not, I'd
suggest that these two fields be converted to int or char or whatever.

> +     u8                      mask1, mask2;
> +     void                    (*handlers[16])(struct menelaus_chip *);

What determined the value of this hard-wired 16?

> +     void                    (*mmc_callback)(void *data, u8 mask);
> +     void                    *mmc_callback_data;
> +};
> +
>
> ...
>
> +/* Adds a handler for an interrupt. Does not run in interrupt context */
> +static int menelaus_add_irq_work(int irq,
> +             void (*handler)(struct menelaus_chip *))
> +{
> +     int ret = 0;

Unneeded initialisation.

> +     mutex_lock(&the_menelaus->lock);
> +     the_menelaus->handlers[irq] = handler;
> +     ret = menelaus_enable_irq(irq);
> +     mutex_unlock(&the_menelaus->lock);
> +
> +     return ret;
> +}
> +
> +/* Removes handler for an interrupt */
> +static int menelaus_remove_irq_work(int irq)
> +{
> +     int ret = 0;

Ditto

> +     mutex_lock(&the_menelaus->lock);
> +     ret = menelaus_disable_irq(irq);
> +     the_menelaus->handlers[irq] = NULL;
> +     mutex_unlock(&the_menelaus->lock);
> +
> +     return ret;
> +}
> +
> +/*
> + * Gets scheduled when a card detect interrupt happens. Note that in some 
> cases
> + * this line is wired to card cover switch rather than the card detect switch
> + * in each slot. In this case the cards are not seen by menelaus.
> + * FIXME: Add handling for D1 too
> + */
> +static void menelaus_mmc_cd_work(struct menelaus_chip *menelaus_hw)
> +{
> +     int reg;
> +     unsigned char card_mask = 0;
> +
> +     reg = menelaus_read_reg(MENELAUS_MCT_PIN_ST);
> +     if (reg < 0)
> +             return;
> +
> +     if (!(reg & 0x1))
> +             card_mask |= (1 << 0);
> +
> +     if (!(reg & 0x2))
> +             card_mask |= (1 << 1);

The above looks like a slow way of doing

        card_mask = (reg & 3) ^ 3;

But perhaps what you have is a little clearer.

> +     if (menelaus_hw->mmc_callback)
> +             menelaus_hw->mmc_callback(menelaus_hw->mmc_callback_data,
> +                                       card_mask);
> +}
> +
> +/*
> + * Toggles the MMC slots between open-drain and push-pull mode.
> + */
> +int menelaus_set_mmc_opendrain(int slot, int enable)
> +{
> +     int ret, val;
> +
> +     if (slot != 1 && slot != 2)
> +             return -EINVAL;

Can this happen?

> +     mutex_lock(&the_menelaus->lock);
> +     ret = menelaus_read_reg(MENELAUS_MCT_CTRL1);
> +     if (ret < 0) {
> +             mutex_unlock(&the_menelaus->lock);
> +             return ret;
> +     }
> +     val = ret;
> +     if (slot == 1) {
> +             if (enable)
> +                     val |= 1 << 2;
> +             else
> +                     val &= ~(1 << 2);
> +     } else {
> +             if (enable)
> +                     val |= 1 << 3;
> +             else
> +                     val &= ~(1 << 3);
> +     }
> +     ret = menelaus_write_reg(MENELAUS_MCT_CTRL1, val);
> +     mutex_unlock(&the_menelaus->lock);
> +
> +     return ret;
> +}
> +EXPORT_SYMBOL(menelaus_set_mmc_opendrain);

Nothing uses this export.  We should remove the export until some code
which requires it is merged.

> +int menelaus_set_slot_sel(int enable)
> +{
> +     int ret;
> +
> +     mutex_lock(&the_menelaus->lock);
> +     ret = menelaus_read_reg(MENELAUS_GPIO_CTRL);
> +     if (ret < 0)
> +             goto out;
> +     ret |= 0x02;
> +     if (enable)
> +             ret |= 1 << 5;
> +     else
> +             ret &= ~(1 << 5);
> +     ret = menelaus_write_reg(MENELAUS_GPIO_CTRL, ret);
> +out:
> +     mutex_unlock(&the_menelaus->lock);
> +     return ret;
> +}
> +EXPORT_SYMBOL(menelaus_set_slot_sel);

Ditto.

> +EXPORT_SYMBOL(menelaus_set_mmc_slot);

Ditto on all ten of these new exports.

> +
> +int menelaus_register_mmc_callback(void (*callback)(void *data, u8 
> card_mask),
> +                                void *data)
> +{
> +     int ret = 0;

Unneeded intialisation.

Probably gcc just fixes this up, but it is poor practice: if someone later
comes along and modifies this code and forgets an initialisation path, your
needless initialisation here will suppress the "might be used
uninitialised" warning which they wanted to see.

> +     the_menelaus->mmc_callback_data = data;
> +     the_menelaus->mmc_callback = callback;
> +     ret = menelaus_add_irq_work(MENELAUS_MMC_S1CD_IRQ,
> +                                 menelaus_mmc_cd_work);
> +     if (ret < 0)
> +             return ret;
> +     ret = menelaus_add_irq_work(MENELAUS_MMC_S2CD_IRQ,
> +                                 menelaus_mmc_cd_work);
> +     if (ret < 0)
> +             return ret;
> +     ret = menelaus_add_irq_work(MENELAUS_MMC_S1D1_IRQ,
> +                                 menelaus_mmc_cd_work);
> +     if (ret < 0)
> +             return ret;
> +     ret = menelaus_add_irq_work(MENELAUS_MMC_S2D1_IRQ,
> +                                 menelaus_mmc_cd_work);
> +
> +     return ret;
> +}
> +EXPORT_SYMBOL(menelaus_register_mmc_callback);
> +
>
> ...
>
> +EXPORT_SYMBOL(menelaus_unregister_mmc_callback);

If we decide to remove all these exports for now, we really should make the
symbols static as well.  Until they are really used externally.

> +struct menelaus_vtg {
> +     const char *name;
> +     u8 vtg_reg;
> +     u8 vtg_shift;
> +     u8 vtg_bits;
> +     u8 mode_reg;
> +};
> +
> +struct menelaus_vtg_value {
> +     u16 vtg;
> +     u16 val;
> +};

Please try to document each field of each struct with inlined comments.

> +static int menelaus_set_voltage(const struct menelaus_vtg *vtg, int mV,
> +                             int vtg_val, int mode)
> +{
> +     int val, ret;
> +     struct i2c_client *c = the_menelaus->client;
> +
> +     mutex_lock(&the_menelaus->lock);
> +     if (vtg == 0)
> +             goto set_voltage;

Please use NULL for zero-valued pointers.  The code as you have it here
will generate a sparse warning (Documentation/SubmitChecklist, point 9,
guys) so I end up getting sent a dopey fixup patch a few weeks later.

> +     ret = menelaus_read_reg(vtg->vtg_reg);
> +     if (ret < 0)
> +             goto out;
> +     val = ret & ~(((1 << vtg->vtg_bits) - 1) << vtg->vtg_shift);
> +     val |= vtg_val << vtg->vtg_shift;
> +
> +     dev_dbg(&c->dev, "Setting voltage '%s'"
> +                      "to %d mV (reg 0x%02x, val 0x%02x)\n",
> +                     vtg->name, mV, vtg->vtg_reg, val);
> +
> +     ret = menelaus_write_reg(vtg->vtg_reg, val);
> +     if (ret < 0)
> +             goto out;
> +set_voltage:
> +     ret = menelaus_write_reg(vtg->mode_reg, mode);
> +out:
> +     mutex_unlock(&the_menelaus->lock);
> +     if (ret == 0) {
> +             /* Wait for voltage to stabilize */
> +             msleep(1);
> +     }
> +     return ret;
> +}
>
> ...
>
> +
> +int menelaus_set_vcore_sw(unsigned int mV)

Please check whether all the newly-added global symbols in fact need to be
global.

> +{
> +     int val, ret;
> +     struct i2c_client *c = the_menelaus->client;
> +
> +     val = menelaus_get_vtg_value(mV, vcore_values,
> +                                  ARRAY_SIZE(vcore_values));
> +     if (val < 0)
> +             return -EINVAL;
> +
> +     dev_dbg(&c->dev, "Setting VCORE to %d mV (val 0x%02x)\n", mV, val);
> +
> +     /* Set SW mode and the voltage in one go. */
> +     mutex_lock(&the_menelaus->lock);
> +     ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val);
> +     if (ret == 0)
> +             the_menelaus->vcore_hw_mode = 0;
> +     mutex_unlock(&the_menelaus->lock);
> +     msleep(1);

It is not possible for the reader to determine why this msleep() is here
just by reading the code.  Hence an explanatory comment is needed.

> +     return ret;
> +}
> +
> +int menelaus_set_vcore_hw(unsigned int roof_mV, unsigned int floor_mV)
> +{
> +     int fval, rval, val, ret;
> +     struct i2c_client *c = the_menelaus->client;
> +
> +     rval = menelaus_get_vtg_value(roof_mV, vcore_values,
> +                                   ARRAY_SIZE(vcore_values));
> +     if (rval < 0)
> +             return -EINVAL;
> +     fval = menelaus_get_vtg_value(floor_mV, vcore_values,
> +                                   ARRAY_SIZE(vcore_values));
> +     if (fval < 0)
> +             return -EINVAL;
> +
> +     dev_dbg(&c->dev, "Setting VCORE FLOOR to %d mV and ROOF to %d mV\n",
> +            floor_mV, roof_mV);
> +
> +     mutex_lock(&the_menelaus->lock);
> +     ret = menelaus_write_reg(MENELAUS_VCORE_CTRL3, fval);
> +     if (ret < 0)
> +             goto out;
> +     ret = menelaus_write_reg(MENELAUS_VCORE_CTRL4, rval);
> +     if (ret < 0)
> +             goto out;
> +     if (!the_menelaus->vcore_hw_mode) {
> +             val = menelaus_read_reg(MENELAUS_VCORE_CTRL1);
> +             /* HW mode, turn OFF byte comparator */
> +             val |= ((1 << 7) | (1 << 5));
> +             ret = menelaus_write_reg(MENELAUS_VCORE_CTRL1, val);
> +             the_menelaus->vcore_hw_mode = 1;
> +     }
> +     msleep(1);

Ditto, for all msleep()s, please.

> +out:
> +     mutex_unlock(&the_menelaus->lock);
> +     return ret;
> +}
> +
>
> ...
>
> +/*-----------------------------------------------------------------------*/
> +
> +/* Handles Menelaus interrupts. Does not run in interrupt context */
> +static void menelaus_work(struct work_struct *_menelaus)
> +{
> +     struct menelaus_chip *menelaus =
> +                     container_of(_menelaus, struct menelaus_chip, work);
> +     void (*handler)(struct menelaus_chip *menelaus);
> +
> +     while (1) {
> +             unsigned isr;
> +
> +             isr = (menelaus_read_reg(MENELAUS_INT_STATUS2)
> +                             & ~menelaus->mask2) << 8;
> +             isr |= menelaus_read_reg(MENELAUS_INT_STATUS1)
> +                             & ~menelaus->mask1;
> +             if (!isr)
> +                     break;
> +
> +             while (isr) {
> +                     int irq = fls(isr) - 1;
> +                     isr &= ~(1 << irq);
> +
> +                     mutex_lock(&menelaus->lock);
> +                     menelaus_disable_irq(irq);
> +                     menelaus_ack_irq(irq);
> +                     handler = menelaus->handlers[irq];
> +                     if (handler)
> +                             handler(menelaus);
> +                     menelaus_enable_irq(irq);
> +                     mutex_unlock(&menelaus->lock);
> +             }
> +     }
> +     enable_irq(menelaus->client->irq);

This can do an enable_irq() of an already-enabled IRQ.  I don't know if
that will cause runtime problems, but it doesn't seem desirable.

> +}
> +
> +/*
> + * We cannot use I2C in interrupt context, so we just schedule work.
> + */
> +static irqreturn_t menelaus_irq(int irq, void *_menelaus)
> +{
> +     struct menelaus_chip *menelaus = _menelaus;
> +
> +     disable_irq_nosync(irq);

I guess the reader of this code will wonder why the nosync variant was
used.  A comment would explain that.

In fact the reader might wonder why the heck we use a workqueue at all,
rather than just doing the work in interrupt context as drivers normally
do.

Perhaps that was all explained somewhere and I missed it.  If not, I'd
suggest that this design decision should be described in a code comment
somewhere.

> +     (void)schedule_work(&menelaus->work);

Please remove the (void).

> +     return IRQ_HANDLED;
> +}
>
> ...
>
> +#ifdef CONFIG_RTC_INTF_DEV
> +
> +static void menelaus_rtc_update_work(struct menelaus_chip *m)
> +{
> +     /* report 1/sec update */
> +     local_irq_disable();
> +     rtc_update_irq(m->rtc, 1, RTC_IRQF | RTC_UF);
> +     local_irq_enable();
> +}

ow.  Is that local_irq_disable() assuming that this code will only ever run
on a uniprocessor machine?

If so, that's pretty poor, IMO.  It would be better to design the code to
be SMP-capable from day one.

> +static int menelaus_ioctl(struct device *dev, unsigned cmd, unsigned long 
> arg)
> +{
> +     int     status;
> +
> +     if (the_menelaus->client->irq <= 0)
> +             return -ENOIOCTLCMD;
> +
> +     switch (cmd) {
> +     /* alarm IRQ */
> +     case RTC_AIE_ON:
> +             if (the_menelaus->rtc_control & RTC_CTRL_AL_EN)
> +                     return 0;
> +             the_menelaus->rtc_control |= RTC_CTRL_AL_EN;
> +             break;
> +     case RTC_AIE_OFF:
> +             if (!(the_menelaus->rtc_control & RTC_CTRL_AL_EN))
> +                     return 0;
> +             the_menelaus->rtc_control &= ~RTC_CTRL_AL_EN;
> +             break;
> +     /* 1/second "update" IRQ */
> +     case RTC_UIE_ON:
> +             if (the_menelaus->uie)
> +                     return 0;
> +             status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ);
> +             status = menelaus_add_irq_work(MENELAUS_RTCTMR_IRQ,
> +                             menelaus_rtc_update_work);
> +             if (status == 0)
> +                     the_menelaus->uie = 1;
> +             return status;
> +     case RTC_UIE_OFF:
> +             if (!the_menelaus->uie)
> +                     return 0;
> +             status = menelaus_remove_irq_work(MENELAUS_RTCTMR_IRQ);
> +             if (status == 0)
> +                     the_menelaus->uie = 0;
> +             return status;
> +     default:
> +             return -ENOIOCTLCMD;
> +     }
> +     return menelaus_write_reg(MENELAUS_RTC_CTRL, the_menelaus->rtc_control);
> +}
> +
> +#else
> +#define menelaus_ioctl       NULL
> +#endif
> +
> +/* REVISIT no compensation register support ... */
> +
> +static const struct rtc_class_ops menelaus_rtc_ops = {
> +     .ioctl                  = menelaus_ioctl,
> +     .read_time              = menelaus_read_time,
> +     .set_time               = menelaus_set_time,
> +     .read_alarm             = menelaus_read_alarm,
> +     .set_alarm              = menelaus_set_alarm,
> +};
> +
>
> ...
>
> +static inline void menelaus_rtc_init(struct menelaus_chip *m)
> +{

The `inline' isn't needed here.  If it has one callsite the compiler will
inline it for you.  If we later gain a second callsite, your inline becomes
wrong: the function is far too large to be worth inlining at both callsites.

> +     int     alarm = (m->client->irq > 0);
> +
> +     /* assume 32KDETEN pin is pulled high */
> +     if (!(menelaus_read_reg(MENELAUS_OSC_CTRL) & 0x80)) {
> +             dev_dbg(&m->client->dev, "no 32k oscillator\n");
> +             return;
> +     }
> +
> +     /* support RTC alarm; it can issue wakeups */
> +     if (alarm) {
> +             if (menelaus_add_irq_work(MENELAUS_RTCALM_IRQ,
> +                             menelaus_rtc_alarm_work) < 0) {
> +                     dev_err(&m->client->dev, "can't handle RTC alarm\n");
> +                     return;
> +             }
> +             device_init_wakeup(&m->client->dev, 1);
> +     }
> +
> +     /* be sure RTC is enabled; allow 1/sec irqs; leave 12hr mode alone */
> +     m->rtc_control = menelaus_read_reg(MENELAUS_RTC_CTRL);
> +     if (!(m->rtc_control & RTC_CTRL_RTC_EN)
> +                     || (m->rtc_control & RTC_CTRL_AL_EN)
> +                     || (m->rtc_control & RTC_CTRL_EVERY_MASK)) {
> +             if (!(m->rtc_control & RTC_CTRL_RTC_EN)) {
> +                     dev_warn(&m->client->dev, "rtc clock needs setting\n");
> +                     m->rtc_control |= RTC_CTRL_RTC_EN;
> +             }
> +             m->rtc_control &= ~RTC_CTRL_EVERY_MASK;
> +             m->rtc_control &= ~RTC_CTRL_AL_EN;
> +             menelaus_write_reg(MENELAUS_RTC_CTRL, m->rtc_control);
> +     }
> +
> +     m->rtc = rtc_device_register(DRIVER_NAME,
> +                     &m->client->dev,
> +                     &menelaus_rtc_ops, THIS_MODULE);
> +     if (IS_ERR(m->rtc)) {
> +             if (alarm) {
> +                     menelaus_remove_irq_work(MENELAUS_RTCALM_IRQ);
> +                     device_init_wakeup(&m->client->dev, 0);
> +             }
> +             dev_err(&m->client->dev, "can't register RTC: %d\n",
> +                             (int) PTR_ERR(m->rtc));
> +             the_menelaus->rtc = NULL;
> +     }
> +}
> +
> +#else
> +
> +static inline void menelaus_rtc_init(struct menelaus_chip *m)
> +{
> +     /* nothing */
> +}

The inline here is OK.  Strictly it isn't needed either, but what you have
here is a very typical kernel pattern.

> +#endif
> +
> +/*-----------------------------------------------------------------------*/
> +
> +static struct i2c_driver menelaus_i2c_driver;
> +
> +static int menelaus_probe(struct i2c_client *client)
> +{
> +     struct menelaus_chip    *menelaus;
> +     int                     rev = 0, val;

unneeded initialisation.

> +     int                     err = 0;
> +     struct menelaus_platform_data *menelaus_pdata =
> +                                     client->dev.platform_data;
> +
> +     if (the_menelaus) {
> +             dev_dbg(&client->dev, "only one %s for now\n",
> +                             DRIVER_NAME);
> +             return -ENODEV;

I doubt if this can happen?

> +     }
> +
> +     menelaus = kzalloc(sizeof *menelaus, GFP_KERNEL);
> +     if (!menelaus)
> +             return -ENOMEM;
> +
> +     i2c_set_clientdata(client, menelaus);
> +
> +     the_menelaus = menelaus;
> +     menelaus->client = client;
> +
> +     /* If a true probe check the device */
> +     rev = menelaus_read_reg(MENELAUS_REV);
> +     if (rev < 0) {
> +             pr_err("device not found");
> +             err = -ENODEV;
> +             goto fail1;
> +     }
> +
> +     /* Ack and disable all Menelaus interrupts */
> +     menelaus_write_reg(MENELAUS_INT_ACK1, 0xff);
> +     menelaus_write_reg(MENELAUS_INT_ACK2, 0xff);
> +     menelaus_write_reg(MENELAUS_INT_MASK1, 0xff);
> +     menelaus_write_reg(MENELAUS_INT_MASK2, 0xff);
> +     menelaus->mask1 = 0xff;
> +     menelaus->mask2 = 0xff;
> +
> +     /* Set output buffer strengths */
> +     menelaus_write_reg(MENELAUS_MCT_CTRL1, 0x73);
> +
> +     if (client->irq > 0) {
> +             err = request_irq(client->irq, menelaus_irq, IRQF_DISABLED,
> +                               DRIVER_NAME, menelaus);
> +             if (err) {
> +                     dev_dbg(&client->dev,  "can't get IRQ %d, err %d",
> +                                     client->irq, err);
> +                     goto fail1;
> +             }
> +     }
> +
> +     mutex_init(&menelaus->lock);
> +     INIT_WORK(&menelaus->work, menelaus_work);
> +
> +     pr_info("Menelaus rev %d.%d\n", rev >> 4, rev & 0x0f);
> +
> +     val = menelaus_read_reg(MENELAUS_VCORE_CTRL1);
> +     if (val < 0)
> +             goto fail2;
> +     if (val & (1 << 7))
> +             menelaus->vcore_hw_mode = 1;
> +     else
> +             menelaus->vcore_hw_mode = 0;
> +
> +     if (menelaus_pdata != NULL && menelaus_pdata->late_init != NULL) {
> +             err = menelaus_pdata->late_init(&client->dev);
> +             if (err < 0)
> +                     goto fail2;
> +     }
> +
> +     menelaus_rtc_init(menelaus);
> +
> +     return 0;
> +fail2:
> +     free_irq(client->irq, menelaus);
> +     flush_scheduled_work();
> +fail1:
> +     kfree(menelaus);
> +     return err;
> +}
> +

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to