#include <linux/libps2.h>
 #include <linux/mutex.h>
 #include <linux/dmi.h>
+#include <linux/property.h>
 
 #define DRIVER_DESC    "AT and PS/2 keyboard driver"
 
 module_param_named(terminal, atkbd_terminal, bool, 0);
 MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard connected via AT/PS2");
 
+#define MAX_FUNCTION_ROW_KEYS  24
+
 /*
  * Scancode to keycode tables. These are just the default setting, and
  * are loadable via a userland utility.
 
        /* Serializes reconnect(), attr->set() and event work */
        struct mutex mutex;
+
+       u32 function_row_physmap[MAX_FUNCTION_ROW_KEYS];
+       int num_function_row_keys;
 };
 
 /*
        __ATTR(_name, S_IRUGO, atkbd_do_show_##_name, NULL);
 
 ATKBD_DEFINE_RO_ATTR(err_count);
+ATKBD_DEFINE_RO_ATTR(function_row_physmap);
 
 static struct attribute *atkbd_attributes[] = {
        &atkbd_attr_extra.attr,
        &atkbd_attr_softrepeat.attr,
        &atkbd_attr_softraw.attr,
        &atkbd_attr_err_count.attr,
+       &atkbd_attr_function_row_physmap.attr,
        NULL
 };
 
+static ssize_t atkbd_show_function_row_physmap(struct atkbd *atkbd, char *buf)
+{
+       ssize_t size = 0;
+       int i;
+
+       if (!atkbd->num_function_row_keys)
+               return 0;
+
+       for (i = 0; i < atkbd->num_function_row_keys; i++)
+               size += scnprintf(buf + size, PAGE_SIZE - size, "%02X ",
+                                 atkbd->function_row_physmap[i]);
+       size += scnprintf(buf + size, PAGE_SIZE - size, "\n");
+       return size;
+}
+
+static umode_t atkbd_attr_is_visible(struct kobject *kobj,
+                               struct attribute *attr, int i)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct serio *serio = to_serio_port(dev);
+       struct atkbd *atkbd = serio_get_drvdata(serio);
+
+       if (attr == &atkbd_attr_function_row_physmap.attr &&
+           !atkbd->num_function_row_keys)
+               return 0;
+
+       return attr->mode;
+}
+
 static struct attribute_group atkbd_attribute_group = {
        .attrs  = atkbd_attributes,
+       .is_visible = atkbd_attr_is_visible,
 };
 
 static const unsigned int xl_table[] = {
        }
 }
 
+static void atkbd_parse_fwnode_data(struct serio *serio)
+{
+       struct atkbd *atkbd = serio_get_drvdata(serio);
+       struct device *dev = &serio->dev;
+       int n;
+
+       /* Parse "function-row-physmap" property */
+       n = device_property_count_u32(dev, "function-row-physmap");
+       if (n > 0 && n <= MAX_FUNCTION_ROW_KEYS &&
+           !device_property_read_u32_array(dev, "function-row-physmap",
+                                           atkbd->function_row_physmap, n)) {
+               atkbd->num_function_row_keys = n;
+               dev_dbg(dev, "FW reported %d function-row key locations\n", n);
+       }
+}
+
 /*
  * atkbd_connect() is called when the serio module finds an interface
  * that isn't handled yet by an appropriate device driver. We check if
                atkbd->id = 0xab00;
        }
 
+       atkbd_parse_fwnode_data(serio);
+
        atkbd_set_keycode_table(atkbd);
        atkbd_set_device_attrs(atkbd);