/* * tfp410.c - DVI output chip * * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TESTING /* register definitions according to the TFP410 data sheet */ #define TFP410_VID 0x014C #define TFP410_DID 0x0410 #define TFP410_VID_DID (TFP410_VID | (TFP410_DID << 16)) #define TFP410_VID_LO 0x00 #define TFP410_VID_HI 0x01 #define TFP410_DID_LO 0x02 #define TFP410_DID_HI 0x03 #define TFP410_REV 0x04 #define TFP410_CTL_1 0x08 #define TFP410_CTL_1_RSVD (1<<7) #define TFP410_CTL_1_TDIS (1<<6) #define TFP410_CTL_1_VEN (1<<5) #define TFP410_CTL_1_HEN (1<<4) #define TFP410_CTL_1_DSEL (1<<3) #define TFP410_CTL_1_BSEL (1<<2) #define TFP410_CTL_1_EDGE (1<<1) #define TFP410_CTL_1_PD (1<<0) #define TFP410_CTL_2 0x09 #define TFP410_CTL_2_VLOW (1<<7) #define TFP410_CTL_2_MSEL_MASK (0x7<<4) #define TFP410_CTL_2_MSEL_INT (1<<4) #define TFP410_CTL_2_MSEL_RSEN (2<<4) #define TFP410_CTL_2_MSEL_HTPLG (3<<4) #define TFP410_CTL_2_TSEL_RSEN (0<<3) #define TFP410_CTL_2_TSEL_HTPLG (1<<3) #define TFP410_CTL_2_RSEN (1<<2) #define TFP410_CTL_2_HTPLG (1<<1) #define TFP410_CTL_2_MDI (1<<0) #define TFP410_CTL_3 0x0A #define TFP410_CTL_3_DK_MASK (0x7<<5) #define TFP410_CTL_3_DK (1<<5) #define TFP410_CTL_3_DKEN (1<<4) #define TFP410_CTL_3_CTL_MASK (0x7<<1) #define TFP410_CTL_3_CTL (1<<1) #define TFP410_USERCFG 0x0B #define DRV_NAME "tfp410" static const char *client_name = DRV_NAME; struct tfp410_priv { struct i2c_client *client; struct gpio_desc *gp_i2c_sel; wait_queue_head_t sample_waitq; struct completion init_exit; //thread exit notification struct task_struct *rtask; int bReady; int interruptCnt; int irq; unsigned gp; #define TFP_ENABLE_HOTPLUG 1 #define TFP_ENABLE_RSEN 2 int enabled; int default_mode; int displayoff; char display_id[16]; #ifdef TESTING struct timeval lastInterruptTime; #endif }; static unsigned char cmd_off_rsen[] = { TFP410_CTL_1, TFP410_CTL_1_RSVD | TFP410_CTL_1_VEN | TFP410_CTL_1_HEN | TFP410_CTL_1_DSEL | TFP410_CTL_1_BSEL, TFP410_CTL_2, TFP410_CTL_2_MDI | TFP410_CTL_2_MSEL_INT | TFP410_CTL_2_TSEL_RSEN, 0 }; static unsigned char cmd_off_hotplug[] = { TFP410_CTL_1, TFP410_CTL_1_RSVD | TFP410_CTL_1_VEN | TFP410_CTL_1_HEN | TFP410_CTL_1_DSEL | TFP410_CTL_1_BSEL, TFP410_CTL_2, TFP410_CTL_2_MDI | TFP410_CTL_2_MSEL_INT | TFP410_CTL_2_TSEL_HTPLG, 0 }; static unsigned char cmd_on_rsen[] = { TFP410_CTL_1, TFP410_CTL_1_RSVD | TFP410_CTL_1_VEN | TFP410_CTL_1_HEN | TFP410_CTL_1_DSEL | TFP410_CTL_1_BSEL | TFP410_CTL_1_PD, TFP410_CTL_2, TFP410_CTL_2_MDI | TFP410_CTL_2_MSEL_INT | TFP410_CTL_2_TSEL_RSEN, 0 }; static unsigned char cmd_on_hotplug[] = { TFP410_CTL_1, TFP410_CTL_1_RSVD | TFP410_CTL_1_VEN | TFP410_CTL_1_HEN | TFP410_CTL_1_DSEL | TFP410_CTL_1_BSEL | TFP410_CTL_1_PD, TFP410_CTL_2, TFP410_CTL_2_MDI | TFP410_CTL_2_MSEL_INT | TFP410_CTL_2_TSEL_HTPLG, 0 }; /* * Initialization function */ static int tfp410_send_cmds(struct i2c_client *client, unsigned char *p) { int result = 0; while (*p) { result = i2c_smbus_write_byte_data(client, p[0], p[1]); if (result) { pr_err("%s: failed(%i) %x=%x\n", __func__, result, p[0], p[1]); return result; } p += 2; } return result; } static int tfp410_on(struct tfp410_priv *tfp, int mode) { int result; int hotplug = (mode == TFP_ENABLE_HOTPLUG); pr_info("tfp410: power up %s\n", hotplug ? "hotplug" : "rsen"); result = tfp410_send_cmds(tfp->client, hotplug ? cmd_on_hotplug : cmd_on_rsen); if (!result) tfp->enabled = mode; return result; } static int tfp410_off(struct tfp410_priv *tfp) { int result; int hotplug = (tfp->default_mode == TFP_ENABLE_HOTPLUG); pr_info("tfp410: power down %s\n", hotplug ? "hotplug" : "rsen"); result = tfp410_send_cmds(tfp->client, hotplug ? cmd_off_hotplug : cmd_off_rsen); if (!result) tfp->enabled = 0; return result; } void tfp410_check_hotplug_rsen(struct tfp410_priv *tfp) { int retry; int mode; for (retry = 0; retry < 10; retry++) { int ret = i2c_smbus_read_byte_data(tfp->client, TFP410_CTL_2); if (ret < 0) { struct device *dev = &tfp->client->dev; dev_err(dev, "i2c_smbus_read_byte_data failed(%i)\n", ret); mode = tfp->default_mode; if (retry < 8) continue; } else if (ret & TFP410_CTL_2_HTPLG) { mode = TFP_ENABLE_HOTPLUG; tfp->default_mode = mode; } else if (ret & TFP410_CTL_2_RSEN) { mode = TFP_ENABLE_RSEN; tfp->default_mode = mode; } else { mode = 0; } if ((mode != tfp->enabled) || !(ret & TFP410_CTL_2_MDI)) { if (mode) tfp410_on(tfp, mode); else tfp410_off(tfp); } else { break; } } } struct tfp410_priv *g_tfp; static int lcd_fb_event(struct notifier_block *nb, unsigned long val, void *v) { struct fb_event *event = v; struct tfp410_priv *tfp = g_tfp; struct device *dev; if (!tfp) return 0; dev = &tfp->client->dev; dev_info(dev, "%s: event %lx, display %s\n", __func__, val, event->info->fix.id); if (strncmp(event->info->fix.id, tfp->display_id, sizeof(tfp->display_id))) { return 0; } switch (val) { case FB_EVENT_BLANK: { int blankType = *((int *)event->data); dev_info(dev, "%s: blank type 0x%x\n", __func__, blankType ); tfp->displayoff = (blankType == FB_BLANK_UNBLANK) ? 0 : 1; break; } case FB_EVENT_SUSPEND : { dev_info(dev, "%s: suspend\n", __func__ ); tfp->displayoff = 1; break; } case FB_EVENT_RESUME : { dev_info(dev, "%s: resume\n", __func__ ); tfp->displayoff = 0; break; } default: dev_info(dev, "%s: unknown event %lx\n", __func__, val); } tfp->bReady=1; wmb(); wake_up(&tfp->sample_waitq); return 0; } static struct notifier_block nb = { .notifier_call = lcd_fb_event, }; /* * This is a RT kernel thread that handles the I2c accesses * The I2c access functions are expected to be able to sleep. */ static int tfp_thread(void *_tfp) { struct tfp410_priv *tfp = _tfp; struct task_struct *tsk = current; /* only want to receive SIGKILL, SIGTERM */ allow_signal(SIGKILL); allow_signal(SIGTERM); /* * We could run as a real-time thread. However, thus far * this doesn't seem to be necessary. */ // tsk->policy = SCHED_FIFO; // tsk->rt_priority = 1; complete(&tfp->init_exit); fb_register_client(&nb); g_tfp = tfp; tfp->interruptCnt=0; do { int bit = -1; do { unsigned gp = tfp->gp; int b; int ret; tfp->bReady = 0; b = gpio_get_value(gp); #ifdef TESTING pr_info("tfp410: int bit=%d, displayoff=%d\n", b, tfp->displayoff); #endif if (bit == b) break; if (signal_pending(tsk)) goto exit1; /* wait 1/2 second for power/bit to stabilize */ ret = wait_event_interruptible_timeout(tfp->sample_waitq, tfp->bReady, msecs_to_jiffies(500)); bit = (!ret) ? b : -1; } while (1); if (tfp->displayoff) tfp410_off(tfp); else tfp410_check_hotplug_rsen(tfp); if (signal_pending(tsk)) break; wait_event_interruptible(tfp->sample_waitq, tfp->bReady); if (signal_pending(tsk)) break; } while (1); exit1: fb_unregister_client(&nb); g_tfp = NULL; tfp410_off(tfp); tfp->rtask = NULL; // pr_err("%s: ts_thread exiting\n",client_name); complete_and_exit(&tfp->init_exit, 0); } /* * We only detect samples ready with this interrupt * handler, and even then we just schedule our task. */ static irqreturn_t tfp_interrupt(int irq, void *id) { struct tfp410_priv *tfp = id; // pr_err("%s\n", __func__); tfp->interruptCnt++; tfp->bReady=1; wmb(); wake_up(&tfp->sample_waitq); #ifdef TESTING { suseconds_t tv_usec = tfp->lastInterruptTime.tv_usec; int delta; do_gettimeofday(&tfp->lastInterruptTime); delta = tfp->lastInterruptTime.tv_usec - tv_usec; if (delta<0) delta += 1000000; pr_info("(delta=%ius gp%i)\n",delta, tfp->gp); } #endif return IRQ_HANDLED; } /* * Release accelerometer resources. Disable IRQs. */ static void tfp_deinit(struct tfp410_priv *tfp) { if (tfp) { if (tfp->rtask) { send_sig(SIGKILL, tfp->rtask, 1); wait_for_completion(&tfp->init_exit); } } } static int tfp_init(struct tfp410_priv *tfp, struct i2c_client *client) { struct task_struct *t; /* Initialize the tfp410 chip */ int result = tfp410_on(tfp, tfp->default_mode); if (result) dev_err(&client->dev, "init failed\n"); if (tfp->irq < 0) return 0; if (tfp->rtask) panic("tfp410d: rtask running?"); init_completion(&tfp->init_exit); t = kthread_create(tfp_thread, tfp, DRV_NAME); if (IS_ERR(t)) { wait_for_completion(&tfp->init_exit); //wait for thread to Start result = 0; } tfp->rtask = t; wake_up_process(t); return result; } static ssize_t tfp410_reg_show(struct device *dev, struct device_attribute *attr, char *buf) { u8 tmp[16]; struct tfp410_priv *tfp = dev_get_drvdata(dev); if (i2c_smbus_read_i2c_block_data(tfp->client, 0, 11, tmp) < 11) { dev_err(dev, "i2c block read failed\n"); return -EIO; } return sprintf(buf, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10]); } static ssize_t tfp410_reg_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned val; int result; struct tfp410_priv *tfp = dev_get_drvdata(dev); char *endp; unsigned reg = simple_strtol(buf, &endp, 16); if (reg >= 0x3e) return count; if (endp) if (*endp == 0x20) endp++; val = simple_strtol(endp, &endp, 16); if (val >= 0x100) return count; dev_err(dev, "%s:reg=0x%x, val=0x%x\n", __func__, reg, val); result = i2c_smbus_write_byte_data(tfp->client, reg, val); if (result < 0) { dev_err(dev, "i2c_smbus_write_byte_data failed(%i)\n", result); return -EIO; } return count; } static DEVICE_ATTR(tfp410_reg, 0644, tfp410_reg_show, tfp410_reg_store); static ssize_t tfp410_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { u8 tmp[4]; int result; struct tfp410_priv *tfp = dev_get_drvdata(dev); result = i2c_smbus_read_i2c_block_data(tfp->client, TFP410_CTL_1, 1, tmp); if (result < 1) { dev_err(dev, "i2c block read failed(%i)\n", result); return -EIO; } return sprintf(buf, "%i\n", (tmp[0] & TFP410_CTL_1_PD) ? 1 : 0); } static ssize_t tfp410_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; int result; struct tfp410_priv *tfp = dev_get_drvdata(dev); val = simple_strtol(buf, NULL, 10); if (val < 0) return count; result = i2c_smbus_read_byte_data(tfp->client, TFP410_CTL_1); if (result < 0) { dev_err(dev, "i2c_smbus_read_byte_data failed(%i)\n", result); return -EIO; } result &= ~TFP410_CTL_1_PD; if (val & 1) result |= TFP410_CTL_1_PD; result = i2c_smbus_write_byte_data(tfp->client, TFP410_CTL_1, result); if (result < 0) { dev_err(dev, "i2c_smbus_write_byte_data failed(%i)\n", result); return -EIO; } tfp->enabled = (val & 1) ? tfp->default_mode : 0; return count; } static DEVICE_ATTR(tfp410_enable, 0644, tfp410_enable_show, tfp410_enable_store); /* * I2C init/probing/exit functions */ static int tfp410_probe(struct i2c_client *client, const struct i2c_device_id *id) { unsigned vid_did; int result; int gp; int ret; unsigned retry = 0; struct tfp410_priv *tfp; struct i2c_adapter *adapter; struct device_node *np = client->dev.of_node; struct gpio_desc *gp_i2c_sel; const char *display_id = NULL; adapter = to_i2c_adapter(client->dev.parent); result = i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA); if (!result) { dev_err(&client->dev, "i2c_check_functionality failed\n"); return -ENODEV; } gp = of_get_named_gpio(np, "interrupts-extended", 0); if (!gpio_is_valid(gp)) return -ENODEV; gp_i2c_sel = devm_gpiod_get_index(&client->dev, "i2c_sel", 0); if (!IS_ERR(gp_i2c_sel)) { gpiod_direction_output(gp_i2c_sel, 0); /* reset */ ret = gpiod_direction_output(gp_i2c_sel, 1); /* enable i2c mode */ if (ret) return ret; } else { dev_warn(&client->dev, "no i2c_sel pin available"); } do { msleep(2); if (i2c_smbus_read_i2c_block_data(client, TFP410_VID_LO, 4, (unsigned char *)&vid_did) == 4) break; dev_err(&client->dev, "i2c block read failed, retry=%d\n", retry); if (retry++ > 6) { result = -EIO; goto release_gpio; } } while (1); if (vid_did != TFP410_VID_DID) { dev_err(&client->dev, "id match failed %x != %x\n", vid_did, TFP410_VID_DID); result = -EIO; goto release_gpio; } tfp = kzalloc(sizeof(struct tfp410_priv),GFP_KERNEL); if (!tfp) { dev_err(&client->dev, "Couldn't allocate memory\n"); return -ENOMEM; } init_waitqueue_head(&tfp->sample_waitq); tfp->client = client; tfp->gp_i2c_sel = gp_i2c_sel; tfp->irq = client->irq; tfp->gp = gp; of_property_read_string(np, "display_id", &display_id); if (display_id) strncpy(&tfp->display_id[0], display_id, sizeof(tfp->display_id)); tfp->default_mode = TFP_ENABLE_HOTPLUG; pr_info("%s: tfp410 irq=%i gp=%i\n", __func__, tfp->irq, tfp->gp); if (tfp->irq > 0) { result = request_irq(tfp->irq, &tfp_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, client_name, tfp); if (result) { pr_err("%s: request_irq failed, irq:%i\n", client_name,tfp->irq); goto free_tfp; } } i2c_set_clientdata(client, tfp); result = tfp_init(tfp, client); if (result) goto free_irq; ret = device_create_file(&client->dev, &dev_attr_tfp410_reg); if (ret < 0) pr_warn("failed to add tfp410 sysfs files\n"); ret = device_create_file(&client->dev, &dev_attr_tfp410_enable); if (ret < 0) pr_warn("failed to add tfp410 sysfs files\n"); return result; free_irq: if (tfp->irq >= 0) free_irq(tfp->irq, tfp); free_tfp: tfp_deinit(tfp); kfree(tfp); release_gpio: if (!IS_ERR(gp_i2c_sel)) gpiod_direction_output(gp_i2c_sel, 0); /* disable i2c mode */ return result; } static int tfp410_remove(struct i2c_client *client) { struct tfp410_priv *tfp = i2c_get_clientdata(client); int result = tfp410_off(tfp); device_remove_file(&client->dev, &dev_attr_tfp410_reg); if (tfp) { if (tfp->irq >= 0) free_irq(tfp->irq, tfp); tfp_deinit(tfp); kfree(tfp); } return result; } static int tfp410_suspend(struct i2c_client *client, pm_message_t mesg) { struct tfp410_priv *tfp = i2c_get_clientdata(client); int result = tfp410_off(tfp); return result; } static int tfp410_resume(struct i2c_client *client) { struct tfp410_priv *tfp = i2c_get_clientdata(client); int result = tfp410_on(tfp, tfp->default_mode); return result; } static const struct i2c_device_id tfp410_id[] = { {DRV_NAME, 0}, {}, }; MODULE_DEVICE_TABLE(i2c, tfp410_id); static struct i2c_driver tfp410_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, }, .suspend = tfp410_suspend, .resume = tfp410_resume, .probe = tfp410_probe, .remove = tfp410_remove, .id_table = tfp410_id, }; static int __init tfp410_init(void) { /* register driver */ int res = i2c_add_driver(&tfp410_driver); if (res < 0) { pr_info("add tfp410 i2c driver failed\n"); return -ENODEV; } pr_info("add tfp410 i2c driver\n"); return res; } static void __exit tfp410_exit(void) { pr_info("remove tfp410 i2c driver.\n"); i2c_del_driver(&tfp410_driver); } MODULE_AUTHOR("Boundary Devices, Inc."); MODULE_DESCRIPTION("tfp410 DVI output driver"); MODULE_LICENSE("GPL"); module_init(tfp410_init); module_exit(tfp410_exit);